Pārlūkot izejas kodu

feat(percussion_stafflines) Fixes from matt-uib, Initial note position impl.
Rendering + code corrections from matt-uib (fixes skyline/bottomline and spacing issue)
Initial Implementation of StafflineNoteCalculator to adjust note locations depending on staffline count, stem direction, voice number, etc.

Justin Litten 5 gadi atpakaļ
vecāks
revīzija
3530ee773e

+ 1 - 1
demo/index.js

@@ -436,7 +436,7 @@ import { OpenSheetMusicDisplay } from '../src/OpenSheetMusicDisplay/OpenSheetMus
                 //groups: [[3,4], [1,1]],
                 maintain_stem_directions: false
             },
-
+            renderPercussionOneLine: true,
             pageFormat: pageFormat,
             pageBackgroundColor: pageBackgroundColor,
             renderSingleHorizontalStaffline: singleHorizontalStaffline

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

@@ -41,7 +41,7 @@ export class EngravingRules {
     private staffDistance: number;
     private betweenStaffDistance: number;
     private staffHeight: number;
-    private tabStaffHeight: number;
+    private tabStaffInterlineHeight: number;
     private betweenStaffLinesDistance: number;
     /** Whether to automatically beam notes that don't already have beams in XML. */
     private autoBeamNotes: boolean;
@@ -52,6 +52,8 @@ export class EngravingRules {
     private beamForwardLength: number;
     private clefLeftMargin: number;
     private clefRightMargin: number;
+    /** Whether to automatically convert any lines with a percussion clef to a single staff line. */
+    private renderPercussionOneLine: boolean;
     private betweenKeySymbolsDistance: number;
     private keyRightMargin: number;
     private rhythmRightMargin: number;
@@ -252,7 +254,7 @@ export class EngravingRules {
 
         // System Sizing and Label Variables
         this.staffHeight = 4.0;
-        this.tabStaffHeight = 6.67;
+        this.tabStaffInterlineHeight = 1.1111;
         this.betweenStaffLinesDistance = EngravingRules.unit;
         this.systemLeftMargin = 0.0;
         this.systemRightMargin = 0.0;
@@ -279,6 +281,7 @@ export class EngravingRules {
         // Beam Sizing Variables
         this.clefLeftMargin = 0.5;
         this.clefRightMargin = 0.75;
+        this.renderPercussionOneLine = false;
         this.betweenKeySymbolsDistance = 0.2;
         this.keyRightMargin = 0.75;
         this.rhythmRightMargin = 1.25;
@@ -638,11 +641,11 @@ export class EngravingRules {
     public set StaffHeight(value: number) {
         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 {
         return this.betweenStaffLinesDistance;
@@ -698,6 +701,12 @@ export class EngravingRules {
     public set ClefRightMargin(value: number) {
         this.clefRightMargin = value;
     }
+    public get RenderPercussionOneLine(): boolean {
+        return this.renderPercussionOneLine;
+    }
+    public set RenderPercussionOneLine(value: boolean) {
+        this.renderPercussionOneLine = value;
+    }
     public get KeyRightMargin(): number {
         return this.keyRightMargin;
     }

+ 0 - 8
src/MusicalScore/Graphical/GraphicalMeasure.ts

@@ -134,14 +134,6 @@ export abstract class GraphicalMeasure extends GraphicalObject {
     }
 
     /**
-     * Sets the number of stafflines that are rendered, so that they are centered properly
-     * @param lineNumber
-     */
-    public setLineNumber(lineNumber: number): void {
-        throw new Error("not implemented");
-    }
-
-    /**
      * Add the given key to the begin of the measure.
      * This has to update/increase BeginInstructionsWidth.
      * @param currentKey - The new valid key.

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

@@ -67,6 +67,7 @@ import { GraphicalInstantaneousDynamicExpression } from "./GraphicalInstantaneou
 import { ContDynamicEnum } from "../VoiceData/Expressions/ContinuousExpressions/ContinuousDynamicExpression";
 import { GraphicalContinuousDynamicExpression } from "./GraphicalContinuousDynamicExpression";
 import { FillEmptyMeasuresWithWholeRests } from "../../OpenSheetMusicDisplay/OSMDOptions";
+import { IStafflineNoteCalculator } from "../Interfaces/IStafflineNoteCalculator";
 
 /**
  * Class used to do all the calculations in a MusicSheet, which in the end populates a GraphicalMusicSheet.
@@ -74,6 +75,7 @@ import { FillEmptyMeasuresWithWholeRests } from "../../OpenSheetMusicDisplay/OSM
 export abstract class MusicSheetCalculator {
     public static symbolFactory: IGraphicalSymbolFactory;
     public static transposeCalculator: ITransposeCalculator;
+    public static stafflineNoteCalculator: IStafflineNoteCalculator;
     protected static textMeasurer: ITextMeasurer;
 
     protected staffEntriesWithGraphicalTies: GraphicalStaffEntry[] = [];
@@ -1483,6 +1485,8 @@ export abstract class MusicSheetCalculator {
                 graphicalNote = MusicSheetCalculator.symbolFactory.createGraceNote(note, gve, activeClef, octaveShiftValue);
             } else {
                 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) {
                 this.checkNoteForAccidental(graphicalNote, accidentalCalculator, activeClef, octaveShiftValue);
@@ -1926,6 +1930,10 @@ export abstract class MusicSheetCalculator {
                                    staffEntryLinks: StaffEntryLink[]): GraphicalMeasure {
         const staff: Staff = this.graphicalMusicSheet.ParentMusicSheet.getStaffFromIndex(staffIndex);
         let measure: GraphicalMeasure = undefined;
+        //If the rule is set to render only one line for percussion clefs, and we have percussion clef, do so
+        if (this.rules.RenderPercussionOneLine && activeClefs[staffIndex].ClefType === ClefEnum.percussion) {
+            staff.StafflineCount = 1;
+          }
         if (activeClefs[staffIndex].ClefType === ClefEnum.TAB) {
             staff.isTab = true;
             measure = MusicSheetCalculator.symbolFactory.createTabStaffMeasure(sourceMeasure, staff);
@@ -2068,10 +2076,14 @@ export abstract class MusicSheetCalculator {
                 graphicalStaffEntry.relInMeasureTimestamp = voiceEntry.Timestamp;
                 const gve: GraphicalVoiceEntry = MusicSheetCalculator.symbolFactory.createVoiceEntry(voiceEntry, graphicalStaffEntry);
                 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);
             }
         }

+ 0 - 17
src/MusicalScore/Graphical/MusicSystemBuilder.ts

@@ -20,7 +20,6 @@ import {MusicSheetCalculator} from "./MusicSheetCalculator";
 import {MidiInstrument} from "../VoiceData/Instructions/ClefInstruction";
 import {CollectionUtil} from "../../Util/CollectionUtil";
 import {SystemLinePosition} from "./SystemLinePosition";
-import { StaffLinesInstruction } from "../VoiceData/Instructions/StaffLinesInstruction";
 
 export class MusicSystemBuilder {
     protected measureList: GraphicalMeasure[][];
@@ -38,7 +37,6 @@ export class MusicSystemBuilder {
     protected activeRhythm: RhythmInstruction[];
     protected activeKeys: KeyInstruction[];
     protected activeClefs: ClefInstruction[];
-    protected activeStafflines: StaffLinesInstruction[];
     protected globalSystemIndex: number = 0;
     protected leadSheet: boolean = false;
 
@@ -52,7 +50,6 @@ export class MusicSystemBuilder {
         this.activeRhythm = new Array(this.numberOfVisibleStaffLines);
         this.activeKeys = new Array(this.numberOfVisibleStaffLines);
         this.activeClefs = new Array(this.numberOfVisibleStaffLines);
-        this.activeStafflines = new Array(this.numberOfVisibleStaffLines);
         this.initializeActiveInstructions(this.measureList[0]);
     }
 
@@ -366,7 +363,6 @@ export class MusicSystemBuilder {
                 keyInstruction = this.transposeKeyInstruction(keyInstruction, graphicalMeasure);
                 this.activeKeys[i] = keyInstruction;
                 this.activeRhythm[i] = <RhythmInstruction>firstSourceMeasure.FirstInstructionsStaffEntries[staffIndex].Instructions[2];
-                this.activeStafflines[i] = <StaffLinesInstruction>firstSourceMeasure.FirstInstructionsStaffEntries[staffIndex].Instructions[3];
             }
         }
     }
@@ -440,7 +436,6 @@ export class MusicSystemBuilder {
         let currentClef: ClefInstruction = undefined;
         let currentKey: KeyInstruction = undefined;
         let currentRhythm: RhythmInstruction = undefined;
-        let currentStafflines: StaffLinesInstruction = undefined;
         if (firstEntry !== undefined) {
             for (let idx: number = 0, len: number = firstEntry.Instructions.length; idx < len; ++idx) {
                 const abstractNotationInstruction: AbstractNotationInstruction = firstEntry.Instructions[idx];
@@ -450,8 +445,6 @@ export class MusicSystemBuilder {
                     currentKey = <KeyInstruction>abstractNotationInstruction;
                 } else if (abstractNotationInstruction instanceof RhythmInstruction) {
                     currentRhythm = <RhythmInstruction>abstractNotationInstruction;
-                } else if (abstractNotationInstruction instanceof StaffLinesInstruction) {
-                    currentStafflines = <StaffLinesInstruction>abstractNotationInstruction;
                 }
             }
         }
@@ -466,17 +459,9 @@ export class MusicSystemBuilder {
                 currentRhythm = this.activeRhythm[visibleStaffIdx];
             }
         }
-        if (currentStafflines === undefined) {
-            currentStafflines = this.activeStafflines[visibleStaffIdx];
-        }
         let clefAdded: boolean = false;
         let keyAdded: boolean = false;
         let rhythmAdded: boolean = false;
-        //Instruction applies to entire staff, don't need to check for start
-        if (currentStafflines !== undefined) {
-            measure.setLineNumber(currentStafflines.NumberOfLines);
-        }
-
         if (currentClef !== undefined) {
             measure.addClefAtBegin(currentClef);
             clefAdded = true;
@@ -536,8 +521,6 @@ export class MusicSystemBuilder {
                         this.activeKeys[visStaffIdx] = <KeyInstruction>abstractNotationInstruction;
                     } else if (abstractNotationInstruction instanceof RhythmInstruction) {
                         this.activeRhythm[visStaffIdx] = <RhythmInstruction>abstractNotationInstruction;
-                    } else if (abstractNotationInstruction instanceof StaffLinesInstruction) {
-                        this.activeStafflines[visStaffIdx] = <StaffLinesInstruction>abstractNotationInstruction;
                     }
                 }
             }

+ 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})`);
         }
         // 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[] = [];
     /** The staff height in units */
     private staffHeight: number;
+    private topLineOffset: number;
+    private bottomLineOffset: number;
 
     // For displaying Slurs
     protected graphicalSlurs: GraphicalSlur[] = [];
@@ -41,8 +43,48 @@ export abstract class StaffLine extends GraphicalObject {
         this.boundingBox = new BoundingBox(this, parentSystem.PositionAndShape);
         this.skyBottomLine = new SkyBottomLineCalculator(this);
         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;
     }
 
+    public get TopLineOffset(): number {
+        return this.topLineOffset;
+    }
+    public get BottomLineOffset(): number {
+        return this.bottomLineOffset;
+    }
+
     // get all Graphical Slurs of a staffline
     public get GraphicalSlurs(): GraphicalSlur[] {
         return this.graphicalSlurs;

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

@@ -107,6 +107,7 @@ export class VexFlowMeasure extends GraphicalMeasure {
             space_above_staff_ln: 0,
             space_below_staff_ln: 0,
         });
+        this.setLineNumber(this.ParentStaff.StafflineCount);
         // 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
@@ -175,6 +176,9 @@ export class VexFlowMeasure extends GraphicalMeasure {
         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
@@ -185,6 +189,12 @@ export class VexFlowMeasure extends GraphicalMeasure {
                     { 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 },
@@ -193,6 +203,9 @@ export class VexFlowMeasure extends GraphicalMeasure {
                     { 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 },
@@ -201,8 +214,14 @@ export class VexFlowMeasure extends GraphicalMeasure {
                     { 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);
+                };
             }
         }
     }

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

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

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

@@ -0,0 +1,66 @@
+import { IStafflineNoteCalculator } from "../../Interfaces/IStafflineNoteCalculator";
+import { GraphicalNote } from "../GraphicalNote";
+import { ClefInstruction, ClefEnum, StemDirectionType } from "../../VoiceData";
+import { Pitch, NoteEnum, AccidentalEnum } from "../../../Common";
+import { VexFlowGraphicalNote } from "./VexFlowGraphicalNote";
+
+export class VexflowStafflineNoteCalculator implements IStafflineNoteCalculator {
+    public positionNote(graphicalNote: GraphicalNote, currentClef: ClefInstruction, stafflineCount: number): GraphicalNote {
+        if (!(graphicalNote instanceof VexFlowGraphicalNote) || currentClef.ClefType !== ClefEnum.percussion) {
+            return graphicalNote;
+        }
+
+        const vfGraphicalNote: VexFlowGraphicalNote = graphicalNote as VexFlowGraphicalNote;
+        const voiceCount: number = graphicalNote.parentVoiceEntry.parentStaffEntry.sourceStaffEntry.ParentStaff.Voices.length;
+        const voiceNumber: number = graphicalNote.parentVoiceEntry.parentVoiceEntry.ParentVoice.VoiceId;
+        let fundamental: NoteEnum = NoteEnum.B;
+        let octave: number = 1;
+        let renderPitch: Pitch = undefined;
+        let i: number = Pitch.pitchEnumValues.indexOf(fundamental);
+        let direction: boolean = true;
+
+        if (voiceCount > 1) {
+            if (voiceNumber > 1) {
+                if (vfGraphicalNote.sourceNote.StemDirectionXml === StemDirectionType.Up) {
+                    i += voiceNumber;
+                    direction = true;
+                } else {
+                    i -= voiceNumber;
+                    direction = false;
+                }
+            }
+        }
+        const wrapAround: {value: number, overflow: number} = this.progressPitch(fundamental, i, direction);
+        fundamental = wrapAround.value;
+        octave += wrapAround.overflow;
+
+        renderPitch = new Pitch(fundamental, octave, AccidentalEnum.NONE);
+        if (renderPitch !== undefined) {
+            vfGraphicalNote.setAccidental(renderPitch);
+        }
+        return graphicalNote;
+    }
+
+    private progressPitch(fundamental: NoteEnum, idx: number, direction: boolean): { value: number; overflow: number; } {
+        //Get the number of fundamentals up that the voice will render
+        idx = idx % Pitch.pitchEnumValues.length;
+        const newFundamental: NoteEnum = Pitch.pitchEnumValues[idx];
+        let halfToneDiff: number = 0;
+        //need the correct direction
+        if (direction) {
+            if (newFundamental < fundamental) {
+                halfToneDiff = (12 - fundamental) + newFundamental;
+            } else {
+                halfToneDiff = fundamental - newFundamental;
+            }
+        } else {
+            if (newFundamental > fundamental) {
+                halfToneDiff = (12 - newFundamental) + fundamental;
+            } else {
+                halfToneDiff = fundamental - newFundamental;
+            }
+        }
+        //check for a wrap around
+        return Pitch.WrapAroundCheck((fundamental - halfToneDiff), 12);
+    }
+}

+ 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 - 39
src/MusicalScore/ScoreIO/InstrumentReader.ts

@@ -26,7 +26,6 @@ import {SlurReader} from "./MusicSymbolModules/SlurReader";
 import {StemDirectionType} from "../VoiceData/VoiceEntry";
 import {NoteType, NoteTypeHandler} from "../VoiceData";
 import {SystemLinesEnumHelper} from "../Graphical";
-import { StaffLinesInstruction } from "../VoiceData/Instructions/StaffLinesInstruction";
 //import Dictionary from "typescript-collections/dist/lib/Dictionary";
 
 // FIXME: The following classes are missing
@@ -87,7 +86,6 @@ export class InstrumentReader {
   private activeClefs: ClefInstruction[];
   private activeKey: KeyInstruction;
   private activeRhythm: RhythmInstruction;
-  private activeStafflines: StaffLinesInstruction;
   private activeClefsHaveBeenInitialized: boolean[];
   private activeKeyHasBeenInitialized: boolean = false;
   private abstractInstructions: [number, AbstractNotationInstruction][] = [];
@@ -437,6 +435,18 @@ export class InstrumentReader {
           if (this.isAttributesNodeAtEndOfMeasure(this.xmlMeasureList[this.currentXmlMeasureIndex], xmlNode)) {
             this.saveClefInstructionAtEndOfMeasure();
           }
+          const staffDetailsNode: IXmlElement = xmlNode.element("staff-details");
+          if (staffDetailsNode !== undefined) {
+            const staffLinesNode: IXmlElement = staffDetailsNode.element("staff-lines");
+            if (staffLinesNode !== undefined) {
+              let staffNumber: number = 1;
+              const staffNumberAttr: Attr = staffDetailsNode.attribute("number");
+              if (staffNumberAttr !== undefined) {
+                staffNumber = parseInt(staffNumberAttr.value, 10);
+              }
+              this.instrument.Staves[staffNumber - 1].StafflineCount = parseInt(staffLinesNode.value, 10);
+            }
+          }
         } else if (xmlNode.name === "forward") {
           const forFraction: number = parseInt(xmlNode.element("duration").value, 10);
           currentFraction.Add(new Fraction(forFraction, 4 * this.divisions));
@@ -834,20 +844,6 @@ export class InstrumentReader {
         const clefInstruction: ClefInstruction = new ClefInstruction(clefEnum, clefOctaveOffset, line);
         this.abstractInstructions.push([staffNumber, clefInstruction]);
       }
-
-      //default to 5 stafflines
-      const staffLinesInstruction: StaffLinesInstruction = new StaffLinesInstruction();
-      //check for different numbers of lines on the staff
-      const staffDetailsNode: IXmlElement = node.element("staff-details");
-      if (staffDetailsNode !== undefined) {
-        const staffLinesNode: IXmlElement = staffDetailsNode.element("staff-lines");
-        if (staffLinesNode !== undefined) {
-          //detected different number of stafflines
-          staffLinesInstruction.NumberOfLines = parseInt(staffLinesNode.value, 10);
-        }
-      }
-
-      this.abstractInstructions.push([1, staffLinesInstruction]);
     }
     if (node.element("key") !== undefined && this.instrument.MidiInstrumentId !== MidiInstrument.Percussion) {
       let key: number = 0;
@@ -1114,29 +1110,6 @@ export class InstrumentReader {
           this.abstractInstructions.splice(i, 1);
         }
       }
-      if (value instanceof StaffLinesInstruction) {
-        const stafflinesInstruction: StaffLinesInstruction = <StaffLinesInstruction>value;
-        if (this.activeStafflines === undefined || this.activeStafflines !== stafflinesInstruction) {
-          this.activeStafflines = stafflinesInstruction;
-          this.abstractInstructions.splice(i, 1);
-          if (this.currentMeasure !== undefined) {
-            for (let j: number = this.inSourceMeasureInstrumentIndex; j < this.inSourceMeasureInstrumentIndex + numberOfStaves; j++) {
-              const newStafflinesInstruction: StaffLinesInstruction = stafflinesInstruction;
-              let firstStaffEntry: SourceStaffEntry;
-              if (this.currentMeasure.FirstInstructionsStaffEntries[j] === undefined) {
-                firstStaffEntry = new SourceStaffEntry(undefined, undefined);
-                this.currentMeasure.FirstInstructionsStaffEntries[j] = firstStaffEntry;
-              } else {
-                firstStaffEntry = this.currentMeasure.FirstInstructionsStaffEntries[j];
-              }
-              newStafflinesInstruction.Parent = firstStaffEntry;
-              firstStaffEntry.Instructions.push(newStafflinesInstruction);
-            }
-          }
-        } else {
-          this.abstractInstructions.splice(i, 1);
-        }
-      }
     }
   }
 

+ 0 - 45
src/MusicalScore/VoiceData/Instructions/StaffLinesInstruction.ts

@@ -1,45 +0,0 @@
-import {AbstractNotationInstruction} from "./AbstractNotationInstruction";
-
-/**
- * A [[StaffLinesInstruction]] is the number of note lines that are rendered for a staff.
- */
-export class StaffLinesInstruction extends AbstractNotationInstruction {
-    constructor(numberOfLines: number = 5) {
-        super(undefined); // FIXME no parent SourceStaffEntry
-        this.numberOfLines = numberOfLines;
-    }
-
-    private numberOfLines: number = 5;
-
-    public get NumberOfLines(): number {
-        return this.numberOfLines;
-    }
-
-    public set NumberOfLines(value: number) {
-        this.numberOfLines = value;
-    }
-
-    public clone(): StaffLinesInstruction {
-        return new StaffLinesInstruction(this.numberOfLines);
-    }
-
-    public OperatorEquals(staffline2: StaffLinesInstruction): boolean {
-        const staffline1: StaffLinesInstruction = this;
-        if (staffline1 === staffline2) {
-            return true;
-        }
-        if ((<Object>staffline1 === undefined) || (<Object>staffline2 === undefined)) {
-            return false;
-        }
-        return (staffline1.NumberOfLines === staffline2.NumberOfLines);
-    }
-
-    public OperatorNotEqual(staffline2: StaffLinesInstruction): boolean {
-        const staffline1: StaffLinesInstruction = this;
-        return !(staffline1 === staffline2);
-    }
-
-    public ToString(): string {
-        return "Number of Stafflines: " + this.numberOfLines;
-    }
-}

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

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

+ 4 - 0
src/OpenSheetMusicDisplay/OSMDOptions.ts

@@ -138,6 +138,10 @@ export interface IOSMDOptions {
      *  at different measures. So this option may result in a page break after a single measure on a page.
      */
     newPageFromXML?: boolean;
+    /** Whether to render percussion lines as single lines or not (ignores staff-lines xml element)
+     * Default false, use staff-lines element or if not present default to 5 lines
+     */
+    renderPercussionOneLine?: boolean;
 }
 
 export enum AlignRestOption {

+ 3 - 1
src/OpenSheetMusicDisplay/OpenSheetMusicDisplay.ts

@@ -376,7 +376,9 @@ export class OpenSheetMusicDisplay {
                 }
             }
         }
-
+        if (options.renderPercussionOneLine !== undefined) {
+            this.rules.RenderPercussionOneLine = options.renderPercussionOneLine;
+        }
         if (options.alignRests !== undefined) {
             this.rules.AlignRests = options.alignRests;
         }