Browse Source

feat(Percussion): render percussion with few voices on single staffline, add options (merge #756)

Merge pull request #756 from opensheetmusicdisplay/feat/percussion_stafflines

Different Staffline Count/Percussion on Single Line
Simon 5 years ago
parent
commit
3062b7f014

+ 1 - 1
demo/index.js

@@ -29,6 +29,7 @@ import { OpenSheetMusicDisplay } from '../src/OpenSheetMusicDisplay/OpenSheetMus
             "OSMD Function Test - Bar lines": "OSMD_function_test_bar_lines.musicxml",
             "OSMD Function Test - Bar lines": "OSMD_function_test_bar_lines.musicxml",
             "OSMD Function Test - Color (from XML)": "OSMD_function_test_color.musicxml",
             "OSMD Function Test - Color (from XML)": "OSMD_function_test_color.musicxml",
             "OSMD Function Test - Drumset": "OSMD_function_test_drumset.musicxml",
             "OSMD Function Test - Drumset": "OSMD_function_test_drumset.musicxml",
+            "OSMD Function Test - Drums on one Line": "OSMD_Function_Test_Drums_one_line_snare_plus_piano.musicxml", 
             "OSMD Function Test - Expressions": "OSMD_function_test_expressions.musicxml",
             "OSMD Function Test - Expressions": "OSMD_function_test_expressions.musicxml",
             "OSMD Function Test - Expressions Overlap": "OSMD_function_test_expressions_overlap.musicxml",
             "OSMD Function Test - Expressions Overlap": "OSMD_function_test_expressions_overlap.musicxml",
             "OSMD Function Test - Grace Notes": "OSMD_function_test_GraceNotes.xml",
             "OSMD Function Test - Grace Notes": "OSMD_function_test_GraceNotes.xml",
@@ -436,7 +437,6 @@ import { OpenSheetMusicDisplay } from '../src/OpenSheetMusicDisplay/OpenSheetMus
                 //groups: [[3,4], [1,1]],
                 //groups: [[3,4], [1,1]],
                 maintain_stem_directions: false
                 maintain_stem_directions: false
             },
             },
-
             pageFormat: pageFormat,
             pageFormat: pageFormat,
             pageBackgroundColor: pageBackgroundColor,
             pageBackgroundColor: pageBackgroundColor,
             renderSingleHorizontalStaffline: singleHorizontalStaffline
             renderSingleHorizontalStaffline: singleHorizontalStaffline

+ 22 - 6
src/MusicalScore/Graphical/EngravingRules.ts

@@ -41,7 +41,7 @@ export class EngravingRules {
     private staffDistance: number;
     private staffDistance: number;
     private betweenStaffDistance: number;
     private betweenStaffDistance: number;
     private staffHeight: number;
     private staffHeight: number;
-    private tabStaffHeight: number;
+    private tabStaffInterlineHeight: number;
     private betweenStaffLinesDistance: number;
     private betweenStaffLinesDistance: number;
     /** Whether to automatically beam notes that don't already have beams in XML. */
     /** Whether to automatically beam notes that don't already have beams in XML. */
     private autoBeamNotes: boolean;
     private autoBeamNotes: boolean;
@@ -52,6 +52,8 @@ export class EngravingRules {
     private beamForwardLength: number;
     private beamForwardLength: number;
     private clefLeftMargin: number;
     private clefLeftMargin: number;
     private clefRightMargin: number;
     private clefRightMargin: number;
+    private percussionOneLineCutoff: number;
+    private percussionForceVoicesOneLineCutoff: number;
     private betweenKeySymbolsDistance: number;
     private betweenKeySymbolsDistance: number;
     private keyRightMargin: number;
     private keyRightMargin: number;
     private rhythmRightMargin: number;
     private rhythmRightMargin: number;
@@ -255,7 +257,7 @@ export class EngravingRules {
 
 
         // System Sizing and Label Variables
         // System Sizing and Label Variables
         this.staffHeight = 4.0;
         this.staffHeight = 4.0;
-        this.tabStaffHeight = 6.67;
+        this.tabStaffInterlineHeight = 1.1111;
         this.betweenStaffLinesDistance = EngravingRules.unit;
         this.betweenStaffLinesDistance = EngravingRules.unit;
         this.systemLeftMargin = 0.0;
         this.systemLeftMargin = 0.0;
         this.systemRightMargin = 0.0;
         this.systemRightMargin = 0.0;
@@ -282,6 +284,8 @@ export class EngravingRules {
         // Beam Sizing Variables
         // Beam Sizing Variables
         this.clefLeftMargin = 0.5;
         this.clefLeftMargin = 0.5;
         this.clefRightMargin = 0.75;
         this.clefRightMargin = 0.75;
+        this.percussionOneLineCutoff = 4;
+        this.percussionForceVoicesOneLineCutoff = 3;
         this.betweenKeySymbolsDistance = 0.2;
         this.betweenKeySymbolsDistance = 0.2;
         this.keyRightMargin = 0.75;
         this.keyRightMargin = 0.75;
         this.rhythmRightMargin = 1.25;
         this.rhythmRightMargin = 1.25;
@@ -644,11 +648,11 @@ export class EngravingRules {
     public set StaffHeight(value: number) {
     public set StaffHeight(value: number) {
         this.staffHeight = value;
         this.staffHeight = value;
     }
     }
-    public get TabStaffHeight(): number {
-        return this.tabStaffHeight;
+    public get TabStaffInterlineHeight(): number {
+        return this.tabStaffInterlineHeight;
     }
     }
-    public set TabStaffHeight(value: number) {
-        this.tabStaffHeight = value;
+    public set TabStaffInterlineHeight(value: number) {
+        this.tabStaffInterlineHeight = value;
     }
     }
     public get BetweenStaffLinesDistance(): number {
     public get BetweenStaffLinesDistance(): number {
         return this.betweenStaffLinesDistance;
         return this.betweenStaffLinesDistance;
@@ -704,6 +708,18 @@ export class EngravingRules {
     public set ClefRightMargin(value: number) {
     public set ClefRightMargin(value: number) {
         this.clefRightMargin = value;
         this.clefRightMargin = value;
     }
     }
+    public get PercussionOneLineCutoff(): number {
+        return this.percussionOneLineCutoff;
+    }
+    public set PercussionOneLineCutoff(value: number) {
+        this.percussionOneLineCutoff = value;
+    }
+    public get PercussionForceVoicesOneLineCutoff(): number {
+        return this.percussionForceVoicesOneLineCutoff;
+    }
+    public set PercussionForceVoicesOneLineCutoff(value: number) {
+        this.percussionForceVoicesOneLineCutoff = value;
+    }
     public get KeyRightMargin(): number {
     public get KeyRightMargin(): number {
         return this.keyRightMargin;
         return this.keyRightMargin;
     }
     }

+ 21 - 4
src/MusicalScore/Graphical/MusicSheetCalculator.ts

@@ -67,6 +67,7 @@ import { GraphicalInstantaneousDynamicExpression } from "./GraphicalInstantaneou
 import { ContDynamicEnum } from "../VoiceData/Expressions/ContinuousExpressions/ContinuousDynamicExpression";
 import { ContDynamicEnum } from "../VoiceData/Expressions/ContinuousExpressions/ContinuousDynamicExpression";
 import { GraphicalContinuousDynamicExpression } from "./GraphicalContinuousDynamicExpression";
 import { GraphicalContinuousDynamicExpression } from "./GraphicalContinuousDynamicExpression";
 import { FillEmptyMeasuresWithWholeRests } from "../../OpenSheetMusicDisplay/OSMDOptions";
 import { FillEmptyMeasuresWithWholeRests } from "../../OpenSheetMusicDisplay/OSMDOptions";
+import { IStafflineNoteCalculator } from "../Interfaces/IStafflineNoteCalculator";
 import { GraphicalUnknownExpression } from "./GraphicalUnknownExpression";
 import { GraphicalUnknownExpression } from "./GraphicalUnknownExpression";
 
 
 /**
 /**
@@ -75,6 +76,7 @@ import { GraphicalUnknownExpression } from "./GraphicalUnknownExpression";
 export abstract class MusicSheetCalculator {
 export abstract class MusicSheetCalculator {
     public static symbolFactory: IGraphicalSymbolFactory;
     public static symbolFactory: IGraphicalSymbolFactory;
     public static transposeCalculator: ITransposeCalculator;
     public static transposeCalculator: ITransposeCalculator;
+    public static stafflineNoteCalculator: IStafflineNoteCalculator;
     protected static textMeasurer: ITextMeasurer;
     protected static textMeasurer: ITextMeasurer;
 
 
     protected staffEntriesWithGraphicalTies: GraphicalStaffEntry[] = [];
     protected staffEntriesWithGraphicalTies: GraphicalStaffEntry[] = [];
@@ -1525,6 +1527,8 @@ export abstract class MusicSheetCalculator {
                 graphicalNote = MusicSheetCalculator.symbolFactory.createGraceNote(note, gve, activeClef, octaveShiftValue);
                 graphicalNote = MusicSheetCalculator.symbolFactory.createGraceNote(note, gve, activeClef, octaveShiftValue);
             } else {
             } else {
                 graphicalNote = MusicSheetCalculator.symbolFactory.createNote(note, gve, activeClef, octaveShiftValue, undefined);
                 graphicalNote = MusicSheetCalculator.symbolFactory.createNote(note, gve, activeClef, octaveShiftValue, undefined);
+                const staffLineCount: number = voiceEntry.ParentSourceStaffEntry.ParentStaff.StafflineCount;
+                graphicalNote = MusicSheetCalculator.stafflineNoteCalculator.positionNote(graphicalNote, activeClef, staffLineCount);
             }
             }
             if (note.Pitch !== undefined) {
             if (note.Pitch !== undefined) {
                 this.checkNoteForAccidental(graphicalNote, accidentalCalculator, activeClef, octaveShiftValue);
                 this.checkNoteForAccidental(graphicalNote, accidentalCalculator, activeClef, octaveShiftValue);
@@ -1968,6 +1972,17 @@ export abstract class MusicSheetCalculator {
                                    staffEntryLinks: StaffEntryLink[]): GraphicalMeasure {
                                    staffEntryLinks: StaffEntryLink[]): GraphicalMeasure {
         const staff: Staff = this.graphicalMusicSheet.ParentMusicSheet.getStaffFromIndex(staffIndex);
         const staff: Staff = this.graphicalMusicSheet.ParentMusicSheet.getStaffFromIndex(staffIndex);
         let measure: GraphicalMeasure = undefined;
         let measure: GraphicalMeasure = undefined;
+        //This property is active...
+        if (this.rules.PercussionOneLineCutoff !== undefined && this.rules.PercussionOneLineCutoff !== 0) {
+            //We have a percussion clef, check to see if this property applies...
+            if (activeClefs[staffIndex].ClefType === ClefEnum.percussion) {
+                //-1 means always trigger, or we are under the cutoff number specified
+                if (this.rules.PercussionOneLineCutoff === -1 ||
+                    staff.ParentInstrument.SubInstruments.length < this.rules.PercussionOneLineCutoff) {
+                    staff.StafflineCount = 1;
+                }
+            }
+        }
         if (activeClefs[staffIndex].ClefType === ClefEnum.TAB) {
         if (activeClefs[staffIndex].ClefType === ClefEnum.TAB) {
             staff.isTab = true;
             staff.isTab = true;
             measure = MusicSheetCalculator.symbolFactory.createTabStaffMeasure(sourceMeasure, staff);
             measure = MusicSheetCalculator.symbolFactory.createTabStaffMeasure(sourceMeasure, staff);
@@ -2110,10 +2125,12 @@ export abstract class MusicSheetCalculator {
                 graphicalStaffEntry.relInMeasureTimestamp = voiceEntry.Timestamp;
                 graphicalStaffEntry.relInMeasureTimestamp = voiceEntry.Timestamp;
                 const gve: GraphicalVoiceEntry = MusicSheetCalculator.symbolFactory.createVoiceEntry(voiceEntry, graphicalStaffEntry);
                 const gve: GraphicalVoiceEntry = MusicSheetCalculator.symbolFactory.createVoiceEntry(voiceEntry, graphicalStaffEntry);
                 graphicalStaffEntry.graphicalVoiceEntries.push(gve);
                 graphicalStaffEntry.graphicalVoiceEntries.push(gve);
-                const graphicalNote: GraphicalNote = MusicSheetCalculator.symbolFactory.createNote(note,
-                                                                                                   gve,
-                                                                                                   new ClefInstruction(),
-                                                                                                   OctaveEnum.NONE, undefined);
+                let graphicalNote: GraphicalNote = MusicSheetCalculator.symbolFactory.createNote(note,
+                                                                                                 gve,
+                                                                                                 new ClefInstruction(),
+                                                                                                 OctaveEnum.NONE, undefined);
+                const staffLineCount: number = voiceEntry.ParentSourceStaffEntry.ParentStaff.StafflineCount;
+                graphicalNote = MusicSheetCalculator.stafflineNoteCalculator.positionNote(graphicalNote, activeClefs[staffIndex], staffLineCount);
                 gve.notes.push(graphicalNote);
                 gve.notes.push(graphicalNote);
             }
             }
         }
         }

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

@@ -442,7 +442,7 @@ export class MusicSystemBuilder {
                 if (abstractNotationInstruction instanceof ClefInstruction) {
                 if (abstractNotationInstruction instanceof ClefInstruction) {
                     currentClef = <ClefInstruction>abstractNotationInstruction;
                     currentClef = <ClefInstruction>abstractNotationInstruction;
                 } else if (abstractNotationInstruction instanceof KeyInstruction) {
                 } else if (abstractNotationInstruction instanceof KeyInstruction) {
-                    currentKey = KeyInstruction.copy(<KeyInstruction>abstractNotationInstruction);
+                    currentKey = <KeyInstruction>abstractNotationInstruction;
                 } else if (abstractNotationInstruction instanceof RhythmInstruction) {
                 } else if (abstractNotationInstruction instanceof RhythmInstruction) {
                     currentRhythm = <RhythmInstruction>abstractNotationInstruction;
                     currentRhythm = <RhythmInstruction>abstractNotationInstruction;
                 }
                 }
@@ -453,7 +453,7 @@ export class MusicSystemBuilder {
                 currentClef = this.activeClefs[visibleStaffIdx];
                 currentClef = this.activeClefs[visibleStaffIdx];
             }
             }
             if (currentKey === undefined) {
             if (currentKey === undefined) {
-                currentKey = KeyInstruction.copy(this.activeKeys[visibleStaffIdx]);
+                currentKey = this.activeKeys[visibleStaffIdx];
             }
             }
             if (isFirstSourceMeasure && currentRhythm === undefined) {
             if (isFirstSourceMeasure && currentRhythm === undefined) {
                 currentRhythm = this.activeRhythm[visibleStaffIdx];
                 currentRhythm = this.activeRhythm[visibleStaffIdx];

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

@@ -146,8 +146,8 @@ export class SkyBottomLineCalculator {
             log.debug(`SkyLine calculation was not correct (${this.mSkyLine.length} instead of ${arrayLength})`);
             log.debug(`SkyLine calculation was not correct (${this.mSkyLine.length} instead of ${arrayLength})`);
         }
         }
         // Remap the values from 0 to +/- height in units
         // Remap the values from 0 to +/- height in units
-        this.mSkyLine = this.mSkyLine.map(v => (v - Math.max(...this.mSkyLine)) / unitInPixels);
-        this.mBottomLine = this.mBottomLine.map(v => (v - Math.min(...this.mBottomLine)) / unitInPixels + this.StaffLineParent.StaffHeight);
+        this.mSkyLine = this.mSkyLine.map(v => (v - Math.max(...this.mSkyLine)) / unitInPixels + this.StaffLineParent.TopLineOffset);
+        this.mBottomLine = this.mBottomLine.map(v => (v - Math.min(...this.mBottomLine)) / unitInPixels + this.StaffLineParent.BottomLineOffset);
     }
     }
 
 
     /**
     /**

+ 51 - 2
src/MusicalScore/Graphical/StaffLine.ts

@@ -30,6 +30,8 @@ export abstract class StaffLine extends GraphicalObject {
     protected abstractExpressions: AbstractGraphicalExpression[] = [];
     protected abstractExpressions: AbstractGraphicalExpression[] = [];
     /** The staff height in units */
     /** The staff height in units */
     private staffHeight: number;
     private staffHeight: number;
+    private topLineOffset: number;
+    private bottomLineOffset: number;
 
 
     // For displaying Slurs
     // For displaying Slurs
     protected graphicalSlurs: GraphicalSlur[] = [];
     protected graphicalSlurs: GraphicalSlur[] = [];
@@ -41,8 +43,48 @@ export abstract class StaffLine extends GraphicalObject {
         this.boundingBox = new BoundingBox(this, parentSystem.PositionAndShape);
         this.boundingBox = new BoundingBox(this, parentSystem.PositionAndShape);
         this.skyBottomLine = new SkyBottomLineCalculator(this);
         this.skyBottomLine = new SkyBottomLineCalculator(this);
         this.staffHeight = this.parentMusicSystem.rules.StaffHeight;
         this.staffHeight = this.parentMusicSystem.rules.StaffHeight;
-        if (this.parentStaff.isTab) {
-            this.staffHeight = this.parentMusicSystem.rules.TabStaffHeight;
+        this.topLineOffset = 0;
+        this.bottomLineOffset = 4;
+
+        this.calculateStaffLineOffsets();
+    }
+
+    /**
+     * If the musicXML sets different numbers of stafflines, we need to have different offsets
+     * to accomodate this - primarily for the sky and bottom lines and cursor.
+     */
+    private calculateStaffLineOffsets(): void {
+        if (this.ParentStaff.isTab) {
+            switch (this.ParentStaff.StafflineCount) {
+                case 5:
+                    this.staffHeight = this.bottomLineOffset =
+                        this.ParentStaff.ParentInstrument.GetMusicSheet.Rules.TabStaffInterlineHeight * 6;
+                    break;
+                default:
+                    this.staffHeight = this.bottomLineOffset =
+                        this.ParentStaff.ParentInstrument.GetMusicSheet.Rules.TabStaffInterlineHeight * this.ParentStaff.StafflineCount;
+                    break;
+            }
+        } else {
+            switch (this.ParentStaff.StafflineCount) {
+                case 4:
+                    this.bottomLineOffset = 1;
+                    break;
+                case 3:
+                    this.topLineOffset = 1;
+                    this.bottomLineOffset = 1;
+                    break;
+                case 2:
+                    this.topLineOffset = 2;
+                    this.bottomLineOffset = 1;
+                    break;
+                case 1:
+                    this.topLineOffset = 2;
+                    this.bottomLineOffset = 2;
+                    break;
+                default:
+                    break;
+            }
         }
         }
     }
     }
 
 
@@ -131,6 +173,13 @@ export abstract class StaffLine extends GraphicalObject {
         return this.staffHeight;
         return this.staffHeight;
     }
     }
 
 
+    public get TopLineOffset(): number {
+        return this.topLineOffset;
+    }
+    public get BottomLineOffset(): number {
+        return this.bottomLineOffset;
+    }
+
     // get all Graphical Slurs of a staffline
     // get all Graphical Slurs of a staffline
     public get GraphicalSlurs(): GraphicalSlur[] {
     public get GraphicalSlurs(): GraphicalSlur[] {
         return this.graphicalSlurs;
         return this.graphicalSlurs;

+ 22 - 0
src/MusicalScore/Graphical/VexFlow/VexFlowConverter.ts

@@ -207,6 +207,28 @@ export class VexFlowConverter {
                     xShift = rules.WholeRestXShiftVexflow * unitInPixels; // TODO find way to make dependent on the modifiers
                     xShift = rules.WholeRestXShiftVexflow * unitInPixels; // TODO find way to make dependent on the modifiers
                     // affects VexFlowStaffEntry.calculateXPosition()
                     // affects VexFlowStaffEntry.calculateXPosition()
                 }
                 }
+                if (note.sourceNote.ParentStaff.Voices.length > 1) {
+                    let visibleVoiceEntries: number = 0;
+                    //Find all visible voice entries (don't want invisible rests/notes causing visible shift)
+                    for (let idx: number = 0; idx < note.sourceNote.ParentStaffEntry.VoiceEntries.length ; idx++) {
+                        if (note.sourceNote.ParentStaffEntry.VoiceEntries[idx].Notes[0].PrintObject) {
+                            visibleVoiceEntries++;
+                        }
+                    }
+                    //If we have more than one visible voice entry, shift the rests so no collision occurs
+                    if (visibleVoiceEntries > 1) {
+                        switch (note.sourceNote.ParentVoiceEntry?.ParentVoice?.VoiceId) {
+                            case 1:
+                                keys = ["e/5"];
+                                break;
+                            case 2:
+                                keys = ["f/4"];
+                                break;
+                            default:
+                                break;
+                        }
+                    }
+                }
                 break;
                 break;
             }
             }
 
 

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

@@ -107,6 +107,10 @@ export class VexFlowMeasure extends GraphicalMeasure {
             space_above_staff_ln: 0,
             space_above_staff_ln: 0,
             space_below_staff_ln: 0,
             space_below_staff_ln: 0,
         });
         });
+
+        if (this.ParentStaff !== undefined) {
+            this.setLineNumber(this.ParentStaff.StafflineCount);
+        }
         // constructor sets beginning and end bar type to standard
         // constructor sets beginning and end bar type to standard
 
 
         this.stave.setBegBarType(Vex.Flow.Barline.type.NONE); // technically not correct, but we'd need to set the next measure's beginning bar type
         this.stave.setBegBarType(Vex.Flow.Barline.type.NONE); // technically not correct, but we'd need to set the next measure's beginning bar type
@@ -168,6 +172,64 @@ export class VexFlowMeasure extends GraphicalMeasure {
     }
     }
 
 
     /**
     /**
+     * Sets the number of stafflines that are rendered, so that they are centered properly
+     * @param lineNumber
+     */
+    public setLineNumber(lineNumber: number): void {
+        if (lineNumber !== 5) {
+            if (lineNumber === 0) {
+                (this.stave as any).setNumLines(0);
+                this.stave.getBottomLineY = function(): number {
+                    return this.getYForLine(this.options.num_lines);
+                };
+            } else if (lineNumber === 1) {
+                // Vex.Flow.Stave.setNumLines hides all but the top line.
+                // this is better
+                (this.stave.options as any).line_config = [
+                    { visible: false },
+                    { visible: false },
+                    { visible: true }, // show middle
+                    { visible: false },
+                    { visible: false },
+                ];
+                //quick fix to see if this matters for calculation. Doesn't seem to
+                this.stave.getBottomLineY = function(): number {
+                    return this.getYForLine(2);
+                };
+                //lines (which isn't this case here)
+                //this.stave.options.num_lines = parseInt(lines, 10);
+            } else if (lineNumber === 2) {
+                (this.stave.options as any).line_config = [
+                    { visible: false },
+                    { visible: false },
+                    { visible: true }, // show middle
+                    { visible: true },
+                    { visible: false },
+                ];
+                this.stave.getBottomLineY = function(): number {
+                    return this.getYForLine(3);
+                };
+            } else if (lineNumber === 3) {
+                (this.stave.options as any).line_config = [
+                    { visible: false },
+                    { visible: true },
+                    { visible: true }, // show middle
+                    { visible: true },
+                    { visible: false },
+                ];
+                this.stave.getBottomLineY = function(): number {
+                    return this.getYForLine(2);
+                };
+            } else {
+                (this.stave as any).setNumLines(lineNumber);
+                this.stave.getBottomLineY = function(): number {
+                    return this.getYForLine(this.options.num_lines);
+                };
+            }
+        }
+    }
+
+    /**
      * adds the given key to the begin of the measure.
      * adds the given key to the begin of the measure.
      * This has to update/increase BeginInstructionsWidth.
      * This has to update/increase BeginInstructionsWidth.
      * @param currentKey the new valid key.
      * @param currentKey the new valid key.

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

@@ -48,6 +48,7 @@ import { InstantaneousTempoExpression } from "../../VoiceData/Expressions";
 import { AlignRestOption } from "../../../OpenSheetMusicDisplay";
 import { AlignRestOption } from "../../../OpenSheetMusicDisplay";
 import { VexFlowStaffLine } from "./VexFlowStaffLine";
 import { VexFlowStaffLine } from "./VexFlowStaffLine";
 import { EngravingRules } from "..";
 import { EngravingRules } from "..";
+import { VexflowStafflineNoteCalculator } from "./VexflowStafflineNoteCalculator";
 
 
 export class VexFlowMusicSheetCalculator extends MusicSheetCalculator {
 export class VexFlowMusicSheetCalculator extends MusicSheetCalculator {
   /** space needed for a dash for lyrics spacing, calculated once */
   /** space needed for a dash for lyrics spacing, calculated once */
@@ -59,10 +60,12 @@ export class VexFlowMusicSheetCalculator extends MusicSheetCalculator {
     this.rules = rules;
     this.rules = rules;
     MusicSheetCalculator.symbolFactory = new VexFlowGraphicalSymbolFactory();
     MusicSheetCalculator.symbolFactory = new VexFlowGraphicalSymbolFactory();
     MusicSheetCalculator.TextMeasurer = new VexFlowTextMeasurer(this.rules);
     MusicSheetCalculator.TextMeasurer = new VexFlowTextMeasurer(this.rules);
+    MusicSheetCalculator.stafflineNoteCalculator = new VexflowStafflineNoteCalculator(this.rules);
   }
   }
 
 
   protected clearRecreatedObjects(): void {
   protected clearRecreatedObjects(): void {
     super.clearRecreatedObjects();
     super.clearRecreatedObjects();
+    MusicSheetCalculator.stafflineNoteCalculator = new VexflowStafflineNoteCalculator(this.rules);
     for (const graphicalMeasures of this.graphicalMusicSheet.MeasureList) {
     for (const graphicalMeasures of this.graphicalMusicSheet.MeasureList) {
       for (const graphicalMeasure of graphicalMeasures) {
       for (const graphicalMeasure of graphicalMeasures) {
         (<VexFlowMeasure>graphicalMeasure).clean();
         (<VexFlowMeasure>graphicalMeasure).clean();

+ 89 - 0
src/MusicalScore/Graphical/VexFlow/VexflowStafflineNoteCalculator.ts

@@ -0,0 +1,89 @@
+import { IStafflineNoteCalculator } from "../../Interfaces/IStafflineNoteCalculator";
+import { GraphicalNote } from "../GraphicalNote";
+import { ClefInstruction, ClefEnum } from "../../VoiceData";
+import { Pitch, NoteEnum, AccidentalEnum } from "../../../Common";
+import { VexFlowGraphicalNote } from "./VexFlowGraphicalNote";
+import { Dictionary } from "typescript-collections";
+import { EngravingRules } from "../EngravingRules";
+
+export class VexflowStafflineNoteCalculator implements IStafflineNoteCalculator {
+    private instrumentVoiceMapping: Dictionary<string, Dictionary<number, {note: NoteEnum, octave: number}>> =
+                                                new Dictionary<string, Dictionary<number, {note: NoteEnum, octave: number}>>();
+    private rules: EngravingRules;
+    private voiceIdx: number = 0;
+
+    constructor(rules: EngravingRules) {
+        this.rules = rules;
+    }
+  /**
+   * This method is called for each note, and should make any necessary position changes based on the number of stafflines, clef, etc.
+   * Right now this just directly maps a voice number to a position above or below a staffline
+   * @param graphicalNote The note to be checked/positioned
+   * @param currentClef The clef that is active for this note
+   * @param stafflineCount The number of stafflines we are rendering on
+   * @returns the minimum required x width of the source measure (=list of staff measures)
+   */
+    public positionNote(graphicalNote: GraphicalNote, currentClef: ClefInstruction, stafflineCount: number): GraphicalNote {
+        if (!(graphicalNote instanceof VexFlowGraphicalNote) || currentClef.ClefType !== ClefEnum.percussion ||
+        graphicalNote.sourceNote.isRest() || stafflineCount > 1 || this.rules.PercussionOneLineCutoff === 0 ) {
+            return graphicalNote;
+        }
+
+        const forceOneLineCutoff: number = this.rules.PercussionForceVoicesOneLineCutoff;
+        const forceOneLine: boolean = (forceOneLineCutoff !== undefined && forceOneLineCutoff !== 0) &&
+                                       (forceOneLineCutoff === -1 ||
+                                        graphicalNote.sourceNote.ParentStaff.ParentInstrument.SubInstruments.length < forceOneLineCutoff);
+
+        const instrumentId: string = graphicalNote.sourceNote.PlaybackInstrumentId;
+        const voiceNumber: number = graphicalNote.parentVoiceEntry.parentVoiceEntry.ParentVoice.VoiceId;
+        let currentInstrumentMapping: Dictionary<number, {note: NoteEnum, octave: number}> = undefined;
+
+        if (!this.instrumentVoiceMapping.containsKey(instrumentId)) {
+            currentInstrumentMapping = new Dictionary<number, {note: NoteEnum, octave: number}>();
+            this.instrumentVoiceMapping.setValue(instrumentId, currentInstrumentMapping);
+        } else {
+            currentInstrumentMapping = this.instrumentVoiceMapping.getValue(instrumentId);
+        }
+
+        let fundamental: NoteEnum = NoteEnum.B;
+        let octave: number = 1;
+        const vfGraphicalNote: VexFlowGraphicalNote = graphicalNote as VexFlowGraphicalNote;
+
+        //if we are forcing to one line, just set to B
+        if (!forceOneLine) {
+            if (!currentInstrumentMapping.containsKey(voiceNumber)) {
+                //Direct mapping for more than one voice, position voices
+                switch (this.voiceIdx % 5) {
+                    case 1:
+                        fundamental = NoteEnum.A;
+                        break;
+                    case 2:
+                        fundamental = NoteEnum.F;
+                        break;
+                    case 3:
+                        fundamental = NoteEnum.D;
+                        break;
+                    case 4:
+                        fundamental = NoteEnum.B;
+                        octave = 0;
+                        break;
+                    default:
+                        fundamental = NoteEnum.C;
+                        octave = 2;
+                        break;
+                }
+                //For every new instrument/voice for a instrument, render on diff line
+                this.voiceIdx++;
+                currentInstrumentMapping.setValue(voiceNumber, {note: fundamental, octave: octave});
+            } else {
+                const storageObj: {note: NoteEnum, octave: number} = currentInstrumentMapping.getValue(voiceNumber);
+                fundamental = storageObj.note;
+                octave = storageObj.octave;
+            }
+        }
+
+        //TODO: Check for playback side effects
+        vfGraphicalNote.setAccidental(new Pitch(fundamental, octave, AccidentalEnum.NONE));
+        return graphicalNote;
+    }
+}

+ 6 - 0
src/MusicalScore/Interfaces/IStafflineNoteCalculator.ts

@@ -0,0 +1,6 @@
+import { GraphicalNote } from "../Graphical/GraphicalNote";
+import { ClefInstruction } from "../VoiceData";
+
+export interface IStafflineNoteCalculator {
+    positionNote(graphicalNote: GraphicalNote, currentClef: ClefInstruction, stafflineCount: number): GraphicalNote;
+}

+ 12 - 0
src/MusicalScore/ScoreIO/InstrumentReader.ts

@@ -435,6 +435,18 @@ export class InstrumentReader {
           if (this.isAttributesNodeAtEndOfMeasure(this.xmlMeasureList[this.currentXmlMeasureIndex], xmlNode)) {
           if (this.isAttributesNodeAtEndOfMeasure(this.xmlMeasureList[this.currentXmlMeasureIndex], xmlNode)) {
             this.saveClefInstructionAtEndOfMeasure();
             this.saveClefInstructionAtEndOfMeasure();
           }
           }
+          const staffDetailsNode: IXmlElement = xmlNode.element("staff-details");
+          if (staffDetailsNode) {
+            const staffLinesNode: IXmlElement = staffDetailsNode.element("staff-lines");
+            if (staffLinesNode) {
+              let staffNumber: number = 1;
+              const staffNumberAttr: Attr = staffDetailsNode.attribute("number");
+              if (staffNumberAttr) {
+                staffNumber = parseInt(staffNumberAttr.value, 10);
+              }
+              this.instrument.Staves[staffNumber - 1].StafflineCount = parseInt(staffLinesNode.value, 10);
+            }
+          }
         } else if (xmlNode.name === "forward") {
         } else if (xmlNode.name === "forward") {
           const forFraction: number = parseInt(xmlNode.element("duration").value, 10);
           const forFraction: number = parseInt(xmlNode.element("duration").value, 10);
           currentFraction.Add(new Fraction(forFraction, 4 * this.divisions));
           currentFraction.Add(new Fraction(forFraction, 4 * this.divisions));

+ 7 - 1
src/MusicalScore/VoiceData/Staff.ts

@@ -19,6 +19,7 @@ export class Staff {
     private voices: Voice[] = [];
     private voices: Voice[] = [];
     private volume: number = 1;
     private volume: number = 1;
     private id: number;
     private id: number;
+    private stafflineCount: number = 5;
 
 
     public get ParentInstrument(): Instrument {
     public get ParentInstrument(): Instrument {
         return this.parentInstrument;
         return this.parentInstrument;
@@ -38,5 +39,10 @@ export class Staff {
     public set Volume(value: number) {
     public set Volume(value: number) {
         this.volume = value;
         this.volume = value;
     }
     }
-
+    public get StafflineCount(): number {
+        return this.stafflineCount;
+    }
+    public set StafflineCount(value: number) {
+        this.stafflineCount = value;
+    }
 }
 }

+ 45 - 0
src/OpenSheetMusicDisplay/OSMDOptions.ts

@@ -143,6 +143,51 @@ export interface IOSMDOptions {
      *  at different measures. So this option may result in a page break after a single measure on a page.
      *  at different measures. So this option may result in a page break after a single measure on a page.
      */
      */
     newPageFromXML?: boolean;
     newPageFromXML?: boolean;
+    /** The cutoff number for rendering percussion clef stafflines as a single line. Default is 4.
+     *  This is number of instruments specified, e.g. a drumset:
+     *     <score-part id="P1">
+     *       <part-name>Drumset</part-name>
+     *       <part-abbreviation>D. Set</part-abbreviation>
+     *       <score-instrument id="P1-I36">
+     *           <instrument-name>Acoustic Bass Drum</instrument-name>
+     *           </score-instrument>
+     *       <score-instrument id="P1-I37">
+     *           <instrument-name>Bass Drum 1</instrument-name>
+     *           </score-instrument>
+     *       <score-instrument id="P1-I38">
+     *           <instrument-name>Side Stick</instrument-name>
+     *           </score-instrument>
+     *       <score-instrument id="P1-I39">
+     *           <instrument-name>Acoustic Snare</instrument-name>
+     *           </score-instrument>
+     *           ...
+     *   Would still render as 5 stafflines by default, since we have 4 (or greater) instruments in this part.
+     *   While a snare:
+     *   <score-part id="P2">
+     *   <part-name>Concert Snare Drum</part-name>
+     *   <part-abbreviation>Con. Sn.</part-abbreviation>
+     *   <score-instrument id="P2-I38">
+     *       <instrument-name>Side Stick</instrument-name>
+     *       </score-instrument>
+     *   <score-instrument id="P2-I39">
+     *       <instrument-name>Acoustic Snare</instrument-name>
+     *       </score-instrument>
+     *       ...
+     *   Would render with 1 line on the staff, since we only have 2 voices.
+     *   If this value is 0, the feature is turned off.
+     *   If this value is -1, it will render all percussion clefs as a single line.
+     */
+    percussionOneLineCutoff?: number;
+    /** This property is only active if the above property is active (percussionOneLineCutoff)
+     *  This is the cutoff for forcing all voices to the single line, instead of rendering them at different
+     *  positions above/below the line.
+     *  The default is 3, so if a part has less than voices, all of them will be rendered on the line.
+     *  This is for cases like a Concert snare, which has multiple 'instruments' available (snare, side stick)
+     *  should still render only on the line since there is no ambiguity.
+     *  If this value is 0, the feature is turned off.
+     *  IF this value is -1, it will render all percussion clef voices on the single line.
+     */
+    percussionForceVoicesOneLineCutoff?: number;
 }
 }
 
 
 export enum AlignRestOption {
 export enum AlignRestOption {

+ 7 - 1
src/OpenSheetMusicDisplay/OpenSheetMusicDisplay.ts

@@ -366,7 +366,13 @@ export class OpenSheetMusicDisplay {
                 }
                 }
             }
             }
         }
         }
-
+        if (options.percussionOneLineCutoff !== undefined) {
+            this.rules.PercussionOneLineCutoff = options.percussionOneLineCutoff;
+        }
+        if (this.rules.PercussionOneLineCutoff !== 0 &&
+            options.percussionForceVoicesOneLineCutoff !== undefined) {
+            this.rules.PercussionForceVoicesOneLineCutoff = options.percussionForceVoicesOneLineCutoff;
+        }
         if (options.alignRests !== undefined) {
         if (options.alignRests !== undefined) {
             this.rules.AlignRests = options.alignRests;
             this.rules.AlignRests = options.alignRests;
         }
         }

+ 516 - 0
test/data/OSMD_Function_Test_Drums_one_line_snare_plus_piano.musicxml

@@ -0,0 +1,516 @@
+<?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>OSMD Function Test - Drums on one Line</work-title>
+    </work>
+  <identification>
+    <encoding>
+      <software>MuseScore 3.4.2</software>
+      <encoding-date>2020-05-21</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.78</page-height>
+      <page-width>1190.55</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.275" default-y="1627.09" justify="center" valign="top" font-size="24">Test Drumline</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>
+    <score-part id="P2">
+      <part-name>Concert Snare Drum</part-name>
+      <part-abbreviation>Con. Sn.</part-abbreviation>
+      <score-instrument id="P2-I38">
+        <instrument-name>Side Stick</instrument-name>
+        </score-instrument>
+      <score-instrument id="P2-I39">
+        <instrument-name>Acoustic Snare</instrument-name>
+        </score-instrument>
+      <midi-device port="1"></midi-device>
+      <midi-instrument id="P2-I38">
+        <midi-channel>10</midi-channel>
+        <midi-program>49</midi-program>
+        <midi-unpitched>38</midi-unpitched>
+        <volume>78.7402</volume>
+        <pan>0</pan>
+        </midi-instrument>
+      <midi-instrument id="P2-I39">
+        <midi-channel>10</midi-channel>
+        <midi-program>49</midi-program>
+        <midi-unpitched>39</midi-unpitched>
+        <volume>78.7402</volume>
+        <pan>0</pan>
+        </midi-instrument>
+      </score-part>
+    </part-list>
+  <part id="P1">
+    <measure number="1" width="344.86">
+      <print>
+        <system-layout>
+          <system-margins>
+            <left-margin>228.20</left-margin>
+            <right-margin>0.00</right-margin>
+            </system-margins>
+          <top-system-distance>170.00</top-system-distance>
+          </system-layout>
+        <staff-layout number="2">
+          <staff-distance>65.00</staff-distance>
+          </staff-layout>
+        </print>
+      <attributes>
+        <divisions>2</divisions>
+        <key>
+          <fifths>0</fifths>
+          </key>
+        <time symbol="common">
+          <beats>4</beats>
+          <beat-type>4</beat-type>
+          </time>
+        <staves>2</staves>
+        <clef number="1">
+          <sign>G</sign>
+          <line>2</line>
+          </clef>
+        <clef number="2">
+          <sign>F</sign>
+          <line>4</line>
+          </clef>
+        </attributes>
+      <note default-x="85.50" default-y="-40.00">
+        <pitch>
+          <step>E</step>
+          <octave>4</octave>
+          </pitch>
+        <duration>2</duration>
+        <voice>1</voice>
+        <type>quarter</type>
+        <stem>up</stem>
+        <staff>1</staff>
+        </note>
+      <note default-x="139.76" default-y="-35.00">
+        <pitch>
+          <step>F</step>
+          <octave>4</octave>
+          </pitch>
+        <duration>2</duration>
+        <voice>1</voice>
+        <type>quarter</type>
+        <stem>up</stem>
+        <staff>1</staff>
+        </note>
+      <note default-x="207.60" default-y="-35.00">
+        <pitch>
+          <step>F</step>
+          <octave>4</octave>
+          </pitch>
+        <duration>2</duration>
+        <voice>1</voice>
+        <type>quarter</type>
+        <stem>up</stem>
+        <staff>1</staff>
+        </note>
+      <note>
+        <rest/>
+        <duration>2</duration>
+        <voice>1</voice>
+        <type>quarter</type>
+        <staff>1</staff>
+        </note>
+      <backup>
+        <duration>8</duration>
+        </backup>
+      <note default-x="85.50" default-y="-130.00">
+        <pitch>
+          <step>C</step>
+          <octave>3</octave>
+          </pitch>
+        <duration>2</duration>
+        <voice>5</voice>
+        <type>quarter</type>
+        <stem>up</stem>
+        <staff>2</staff>
+        </note>
+      <note>
+        <rest/>
+        <duration>2</duration>
+        <voice>5</voice>
+        <type>quarter</type>
+        <staff>2</staff>
+        </note>
+      <note>
+        <rest/>
+        <duration>4</duration>
+        <voice>5</voice>
+        <type>half</type>
+        <staff>2</staff>
+        </note>
+      </measure>
+    <measure number="2" width="249.92">
+      <note default-x="10.00" default-y="-35.00">
+        <pitch>
+          <step>F</step>
+          <octave>4</octave>
+          </pitch>
+        <duration>2</duration>
+        <voice>1</voice>
+        <type>quarter</type>
+        <stem>up</stem>
+        <staff>1</staff>
+        </note>
+      <note>
+        <rest/>
+        <duration>2</duration>
+        <voice>1</voice>
+        <type>quarter</type>
+        <staff>1</staff>
+        </note>
+      <note default-x="129.16" default-y="-50.00">
+        <pitch>
+          <step>C</step>
+          <octave>4</octave>
+          </pitch>
+        <duration>2</duration>
+        <voice>1</voice>
+        <type>quarter</type>
+        <stem>up</stem>
+        <staff>1</staff>
+        </note>
+      <note>
+        <rest/>
+        <duration>2</duration>
+        <voice>1</voice>
+        <type>quarter</type>
+        <staff>1</staff>
+        </note>
+      <backup>
+        <duration>8</duration>
+        </backup>
+      <note default-x="10.00" default-y="-130.00">
+        <pitch>
+          <step>C</step>
+          <octave>3</octave>
+          </pitch>
+        <duration>2</duration>
+        <voice>5</voice>
+        <type>quarter</type>
+        <stem>up</stem>
+        <staff>2</staff>
+        </note>
+      <note>
+        <rest/>
+        <duration>2</duration>
+        <voice>5</voice>
+        <type>quarter</type>
+        <staff>2</staff>
+        </note>
+      <note>
+        <rest/>
+        <duration>4</duration>
+        <voice>5</voice>
+        <type>half</type>
+        <staff>2</staff>
+        </note>
+      </measure>
+    <measure number="3" width="254.19">
+      <note>
+        <rest/>
+        <duration>8</duration>
+        <voice>1</voice>
+        <staff>1</staff>
+        </note>
+      <backup>
+        <duration>8</duration>
+        </backup>
+      <note default-x="21.87" default-y="-130.00">
+        <pitch>
+          <step>C</step>
+          <octave>3</octave>
+          </pitch>
+        <duration>2</duration>
+        <voice>5</voice>
+        <type>quarter</type>
+        <stem>up</stem>
+        <staff>2</staff>
+        </note>
+      <note>
+        <rest/>
+        <duration>2</duration>
+        <voice>5</voice>
+        <type>quarter</type>
+        <staff>2</staff>
+        </note>
+      <note>
+        <rest/>
+        <duration>4</duration>
+        <voice>5</voice>
+        <type>half</type>
+        <staff>2</staff>
+        </note>
+      <barline location="right">
+        <bar-style>light-heavy</bar-style>
+        </barline>
+      </measure>
+    </part>
+  <part id="P2">
+    <measure number="1" width="344.86">
+      <print>
+        <staff-layout number="1">
+          <staff-distance>65.00</staff-distance>
+          </staff-layout>
+        </print>
+      <attributes>
+        <divisions>2</divisions>
+        <key>
+          <fifths>0</fifths>
+          </key>
+        <time symbol="common">
+          <beats>4</beats>
+          <beat-type>4</beat-type>
+          </time>
+        <clef>
+          <sign>percussion</sign>
+          <line>2</line>
+          </clef>
+        <staff-details>
+          <staff-lines>1</staff-lines>
+          </staff-details>
+        </attributes>
+      <note default-x="85.50" default-y="-210.00">
+        <unpitched>
+          <display-step>E</display-step>
+          <display-octave>4</display-octave>
+          </unpitched>
+        <duration>2</duration>
+        <instrument id="P2-I38"/>
+        <voice>1</voice>
+        <type>quarter</type>
+        <stem>down</stem>
+        <notehead>x</notehead>
+        </note>
+      <note default-x="139.76" default-y="-210.00">
+        <unpitched>
+          <display-step>E</display-step>
+          <display-octave>4</display-octave>
+          </unpitched>
+        <duration>1</duration>
+        <instrument id="P2-I38"/>
+        <voice>1</voice>
+        <type>eighth</type>
+        <stem>down</stem>
+        <notehead>x</notehead>
+        <beam number="1">begin</beam>
+        </note>
+      <note default-x="173.68" default-y="-210.00">
+        <unpitched>
+          <display-step>E</display-step>
+          <display-octave>4</display-octave>
+          </unpitched>
+        <duration>1</duration>
+        <instrument id="P2-I38"/>
+        <voice>1</voice>
+        <type>eighth</type>
+        <stem>down</stem>
+        <notehead>x</notehead>
+        <beam number="1">end</beam>
+        </note>
+      <note default-x="207.60" default-y="-210.00">
+        <unpitched>
+          <display-step>E</display-step>
+          <display-octave>4</display-octave>
+          </unpitched>
+        <duration>1</duration>
+        <instrument id="P2-I39"/>
+        <voice>1</voice>
+        <type>eighth</type>
+        <stem>down</stem>
+        <beam number="1">begin</beam>
+        </note>
+      <note default-x="241.51" default-y="-210.00">
+        <unpitched>
+          <display-step>E</display-step>
+          <display-octave>4</display-octave>
+          </unpitched>
+        <duration>1</duration>
+        <instrument id="P2-I39"/>
+        <voice>1</voice>
+        <type>eighth</type>
+        <stem>down</stem>
+        <beam number="1">continue</beam>
+        </note>
+      <note default-x="275.43" default-y="-210.00">
+        <unpitched>
+          <display-step>E</display-step>
+          <display-octave>4</display-octave>
+          </unpitched>
+        <duration>1</duration>
+        <instrument id="P2-I38"/>
+        <voice>1</voice>
+        <type>eighth</type>
+        <stem>down</stem>
+        <notehead>x</notehead>
+        <beam number="1">continue</beam>
+        </note>
+      <note default-x="309.34" default-y="-210.00">
+        <unpitched>
+          <display-step>E</display-step>
+          <display-octave>4</display-octave>
+          </unpitched>
+        <duration>1</duration>
+        <instrument id="P2-I38"/>
+        <voice>1</voice>
+        <type>eighth</type>
+        <stem>down</stem>
+        <notehead>x</notehead>
+        <beam number="1">end</beam>
+        </note>
+      </measure>
+    <measure number="2" width="249.92">
+      <note default-x="10.00" default-y="-210.00">
+        <unpitched>
+          <display-step>E</display-step>
+          <display-octave>4</display-octave>
+          </unpitched>
+        <duration>1</duration>
+        <instrument id="P2-I38"/>
+        <voice>1</voice>
+        <type>eighth</type>
+        <stem>down</stem>
+        <notehead>x</notehead>
+        </note>
+      <note>
+        <rest/>
+        <duration>1</duration>
+        <voice>1</voice>
+        <type>eighth</type>
+        </note>
+      <note>
+        <rest/>
+        <duration>2</duration>
+        <voice>1</voice>
+        <type>quarter</type>
+        </note>
+      <note default-x="129.16" default-y="-210.00">
+        <unpitched>
+          <display-step>E</display-step>
+          <display-octave>4</display-octave>
+          </unpitched>
+        <duration>1</duration>
+        <instrument id="P2-I38"/>
+        <voice>1</voice>
+        <type>eighth</type>
+        <stem>down</stem>
+        <notehead>x</notehead>
+        </note>
+      <note>
+        <rest/>
+        <duration>1</duration>
+        <voice>1</voice>
+        <type>eighth</type>
+        </note>
+      <note>
+        <rest/>
+        <duration>2</duration>
+        <voice>1</voice>
+        <type>quarter</type>
+        </note>
+      </measure>
+    <measure number="3" width="254.19">
+      <note default-x="10.00" default-y="-210.00">
+        <unpitched>
+          <display-step>E</display-step>
+          <display-octave>4</display-octave>
+          </unpitched>
+        <duration>1</duration>
+        <instrument id="P2-I38"/>
+        <voice>1</voice>
+        <type>eighth</type>
+        <stem>down</stem>
+        <notehead>x</notehead>
+        </note>
+      <note default-x="21.87" default-y="-210.00">
+        <chord/>
+        <unpitched>
+          <display-step>E</display-step>
+          <display-octave>4</display-octave>
+          </unpitched>
+        <duration>1</duration>
+        <instrument id="P2-I39"/>
+        <voice>1</voice>
+        <type>eighth</type>
+        <stem>down</stem>
+        </note>
+      <note>
+        <rest/>
+        <duration>1</duration>
+        <voice>1</voice>
+        <type>eighth</type>
+        </note>
+      <note default-x="93.39" default-y="-210.00">
+        <unpitched>
+          <display-step>E</display-step>
+          <display-octave>4</display-octave>
+          </unpitched>
+        <duration>1</duration>
+        <instrument id="P2-I39"/>
+        <voice>1</voice>
+        <type>eighth</type>
+        <stem>down</stem>
+        </note>
+      <note>
+        <rest/>
+        <duration>1</duration>
+        <voice>1</voice>
+        <type>eighth</type>
+        </note>
+      <note>
+        <rest/>
+        <duration>4</duration>
+        <voice>1</voice>
+        <type>half</type>
+        </note>
+      <barline location="right">
+        <bar-style>light-heavy</bar-style>
+        </barline>
+      </measure>
+    </part>
+  </score-partwise>