瀏覽代碼

feat(PercussionOneLine): add EngravingRules.PercussionOneLineUseXMLDisplayStep (#945)

this enables placing the notes in the position (on the staffline) as given in the XML.
sschmid 4 年之前
父節點
當前提交
33a7184b52

+ 13 - 0
src/Common/DataObjects/Pitch.ts

@@ -66,6 +66,19 @@ export class Pitch {
         }
     }
 
+    /** This method goes x steps from a NoteEnum on a keyboard.
+     * E.g. Two steps to the left (-2) from a D is a B.
+     * Two steps to the right from an A is a C. */
+    public static stepFromNoteEnum(noteEnum: NoteEnum, step: number): NoteEnum {
+        const enums: NoteEnum[] = Pitch.pitchEnumValues;
+        const originalIndex: number = enums.indexOf(noteEnum);
+        let newIndex: number = originalIndex + step % enums.length; // modulo only handles positive overflow
+        if (newIndex < 0) {
+            newIndex = enums.length + newIndex; // handle underflow, e.g. - 1: enums.length + (-1) = last element
+        }
+        return enums[newIndex];
+    }
+
     /**
      * @param the input pitch
      * @param the number of halftones to transpose with

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

@@ -61,6 +61,7 @@ export class EngravingRules {
     /** How many unique note positions a percussion score needs to have to not be rendered on one line. */
     public PercussionOneLineCutoff: number;
     public PercussionForceVoicesOneLineCutoff: number;
+    public PercussionOneLineUseXMLDisplayStep: boolean;
     public BetweenKeySymbolsDistance: number;
     public KeyRightMargin: number;
     public RhythmRightMargin: number;
@@ -344,6 +345,7 @@ export class EngravingRules {
         this.ClefRightMargin = 0.75;
         this.PercussionOneLineCutoff = 3; // percussion parts with <3 unique note positions rendered on one line
         this.PercussionForceVoicesOneLineCutoff = 1;
+        this.PercussionOneLineUseXMLDisplayStep = true;
         this.BetweenKeySymbolsDistance = 0.2;
         this.KeyRightMargin = 0.75;
         this.RhythmRightMargin = 1.25;

+ 8 - 2
src/MusicalScore/Graphical/VexFlow/VexflowStafflineNoteCalculator.ts

@@ -20,7 +20,7 @@ export class VexflowStafflineNoteCalculator implements IStafflineNoteCalculator
     /**
      * This method is called for each note during the calc phase. We want to track all possible positions to make decisions
      * during layout about where notes should be positioned.
-     * This directly notes that share a line to the same position, regardless of voice
+     * This directly puts notes that share a line to the same position, regardless of voice
      * @param graphicalNote The note to be checked/positioned
      * @param staffIndex The staffline the note is on
      */
@@ -98,7 +98,13 @@ export class VexflowStafflineNoteCalculator implements IStafflineNoteCalculator
 
         //If we only need to render on one line
         if (currentPitchList.length <= this.rules.PercussionForceVoicesOneLineCutoff) {
-            vfGraphicalNote.setAccidental(new Pitch(this.baseLineNote, this.baseLineOctave, notePitch.Accidental));
+            let displayNote: NoteEnum = this.baseLineNote;
+            let displayOctave: number = this.baseLineOctave;
+            if (this.rules.PercussionOneLineUseXMLDisplayStep && graphicalNote.sourceNote.displayStepUnpitched !== undefined) {
+                displayNote = graphicalNote.sourceNote.displayStepUnpitched;
+                displayOctave = graphicalNote.sourceNote.displayOctaveUnpitched;
+            }
+            vfGraphicalNote.setAccidental(new Pitch(displayNote, displayOctave, notePitch.Accidental));
         } else {
             const pitchIndex: number = VexflowStafflineNoteCalculator.PitchIndexOf(currentPitchList, notePitch);
             if (pitchIndex > -1) {

+ 25 - 13
src/MusicalScore/ScoreIO/VoiceGenerator.ts

@@ -324,7 +324,9 @@ export class VoiceGenerator {
     let noteAlter: number = 0;
     let noteAccidental: AccidentalEnum = AccidentalEnum.NONE;
     let noteStep: NoteEnum = NoteEnum.C;
+    let displayStepUnpitched: NoteEnum = NoteEnum.C;
     let noteOctave: number = 0;
+    let displayOctaveUnpitched: number = 0;
     let playbackInstrumentId: string = undefined;
     let noteheadShapeXml: string = undefined;
     let noteheadFilledXml: boolean = undefined; // if undefined, the final filled parameter will be calculated from duration
@@ -381,13 +383,15 @@ export class VoiceGenerator {
             noteAccidental = AccidentalEnum.NATURAL;
           }
         } else if (noteElement.name === "unpitched") {
-          const displayStep: IXmlElement = noteElement.element("display-step");
-          if (displayStep) {
-            noteStep = NoteEnum[displayStep.value.toUpperCase()];
+          const displayStepElement: IXmlElement = noteElement.element("display-step");
+          if (displayStepElement) {
+            noteStep = NoteEnum[displayStepElement.value.toUpperCase()];
+            displayStepUnpitched = Pitch.stepFromNoteEnum(noteStep, -3);
           }
           const octave: IXmlElement = noteElement.element("display-octave");
           if (octave) {
             noteOctave = parseInt(octave.value, 10);
+            displayOctaveUnpitched = noteOctave - 3;
             if (guitarPro) {
               noteOctave += 1;
             }
@@ -449,7 +453,9 @@ export class VoiceGenerator {
                          stringNumber, fretNumber, bends, vibratoStrokes);
     }
 
-    this.addNoteInfo(note, noteTypeXml, printObject, isCueNote, normalNotes, noteheadColorXml, noteheadColorXml);
+    this.addNoteInfo(note, noteTypeXml, printObject, isCueNote, normalNotes,
+                     displayStepUnpitched, displayOctaveUnpitched,
+                     noteheadColorXml, noteheadColorXml);
     note.TypeLength = typeDuration;
     note.IsGraceNote = isGraceNote;
     note.StemDirectionXml = stemDirectionXml; // maybe unnecessary, also in VoiceEntry
@@ -480,15 +486,18 @@ export class VoiceGenerator {
   private addRestNote(node: IXmlElement, noteDuration: Fraction, noteTypeXml: NoteType,
                       normalNotes: number, printObject: boolean, isCueNote: boolean, noteheadColorXml: string): Note {
     const restFraction: Fraction = Fraction.createFromFraction(noteDuration);
-    const displayStep: IXmlElement = node.element("display-step");
-    const octave: IXmlElement = node.element("display-octave");
+    const displayStepElement: IXmlElement = node.element("display-step");
+    const octaveElement: IXmlElement = node.element("display-octave");
+    let displayStep: NoteEnum;
+    let displayOctave: number;
     let pitch: Pitch = undefined;
-    if (displayStep && octave) {
-        const noteStep: NoteEnum = NoteEnum[displayStep.value.toUpperCase()];
-        pitch = new Pitch(noteStep, parseInt(octave.value, 10), AccidentalEnum.NONE);
+    if (displayStepElement && octaveElement) {
+        displayStep = NoteEnum[displayStepElement.value.toUpperCase()];
+        displayOctave = parseInt(octaveElement.value, 10);
+        pitch = new Pitch(displayStep, displayOctave, AccidentalEnum.NONE);
     }
     const restNote: Note = new Note(this.currentVoiceEntry, this.currentStaffEntry, restFraction, pitch, this.currentMeasure, true);
-    this.addNoteInfo(restNote, noteTypeXml, printObject, isCueNote, normalNotes, noteheadColorXml, noteheadColorXml);
+    this.addNoteInfo(restNote, noteTypeXml, printObject, isCueNote, normalNotes, displayStep, displayOctave, noteheadColorXml, noteheadColorXml);
     this.currentVoiceEntry.Notes.push(restNote);
     if (this.openBeams.length > 0) {
       this.openBeams.last().ExtendedNoteList.push(restNote);
@@ -497,13 +506,16 @@ export class VoiceGenerator {
   }
 
   // common for "normal" notes and rest notes
-  private addNoteInfo(note: Note, noteTypeXml: NoteType, printObject: boolean, isCueNote: boolean,
-                      normalNotes: number, noteheadColorXml: string, noteheadColor: string): void {
+  private addNoteInfo(note: Note, noteTypeXml: NoteType, printObject: boolean, isCueNote: boolean, normalNotes: number,
+                      displayStep: NoteEnum, displayOctave: number,
+                      noteheadColorXml: string, noteheadColor: string): void {
       // common for normal notes and rest note
       note.NoteTypeXml = noteTypeXml;
       note.PrintObject = printObject;
-      note.NormalNotes = normalNotes; // how many rhythmical notes the notes replace (e.g. for tuplets), see xml "actual-notes" and "normal-notes"
       note.IsCueNote = isCueNote;
+      note.NormalNotes = normalNotes; // how many rhythmical notes the notes replace (e.g. for tuplets), see xml "actual-notes" and "normal-notes"
+      note.displayStepUnpitched = displayStep;
+      note.displayOctaveUnpitched = displayOctave;
       note.NoteheadColorXml = noteheadColorXml; // color set in Xml, shouldn't be changed.
       note.NoteheadColor = noteheadColorXml; // color currently used
       // add TypeLength for rest notes like with Note?

+ 4 - 2
src/MusicalScore/VoiceData/Note.ts

@@ -1,7 +1,7 @@
 import {VoiceEntry, StemDirectionType} from "./VoiceEntry";
 import {SourceStaffEntry} from "./SourceStaffEntry";
 import {Fraction} from "../../Common/DataObjects/Fraction";
-import {Pitch} from "../../Common/DataObjects/Pitch";
+import {NoteEnum, Pitch} from "../../Common/DataObjects/Pitch";
 import {Beam} from "./Beam";
 import {Tuplet} from "./Tuplet";
 import {Tie} from "./Tie";
@@ -53,6 +53,8 @@ export class Note {
      * The untransposed (!!!) source data.
      */
     private pitch: Pitch;
+    public displayStepUnpitched: NoteEnum;
+    public displayOctaveUnpitched: number;
     public get NoteAsString(): string {
         return this.pitch.toString();
     }
@@ -94,7 +96,7 @@ export class Note {
     private noteheadColor: string;
     private noteheadColorCurrentlyRendered: string;
     public Fingering: TechnicalInstruction; // this is also stored in VoiceEntry.TechnicalInstructions
-    /** Used by GraphicalNote.FromNote() to get a GraphicalNote from a Note. */
+    /** Used by GraphicalNote.FromNote(note) and osmd.rules.GNote(note) to get a GraphicalNote from a Note. */
     public NoteToGraphicalNoteObjectId: number; // used with EngravingRules.NoteToGraphicalNoteMap
 
     public get ParentVoiceEntry(): VoiceEntry {