Browse Source

feat(Arpeggios): Display arpeggios (#411)

add class Arpeggio, saved in VoiceEntry and Note
add enum Arpeggio.ArpeggioType

enable upwards, downwards, directionless arpeggio

test: add Arpeggios to OSMD Function Test - all
Simon 6 years ago
parent
commit
90ae82f531

+ 6 - 0
external/vexflow/vexflow.d.ts

@@ -68,6 +68,7 @@ declare namespace Vex {
         }
 
         export class Note extends Tickable {
+            public addStroke(index: number, stroke: Stroke): void;
         }
 
         export class TextBracket {
@@ -230,6 +231,11 @@ declare namespace Vex {
             constructor(finger: string);
         }
 
+        export class Stroke extends Modifier {
+            constructor(type: number);
+            public static Type: any; // unreliable values, use Arpeggio.ArpeggioType instead
+        }
+
         export class NoteSubGroup extends Modifier {
             constructor(notes: Object);
         }

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

@@ -31,7 +31,8 @@ import {LinkedVoice} from "../../VoiceData/LinkedVoice";
 import {EngravingRules} from "../EngravingRules";
 import {OrnamentContainer} from "../../VoiceData/OrnamentContainer";
 import {TechnicalInstruction} from "../../VoiceData/Instructions/TechnicalInstruction";
-import { VexFlowGraphicalNote } from "./VexFlowGraphicalNote";
+import {ArpeggioType} from "../../VoiceData/Arpeggio";
+import {VexFlowGraphicalNote} from "./VexFlowGraphicalNote";
 
 export class VexFlowMeasure extends GraphicalMeasure {
     constructor(staff: Staff, staffLine: StaffLine = undefined, sourceMeasure: SourceMeasure = undefined) {
@@ -723,11 +724,17 @@ export class VexFlowMeasure extends GraphicalMeasure {
                     for (let i: number = 0; i < technicalInstructions.length; i++) {
                         const technicalInstruction: TechnicalInstruction = technicalInstructions[i];
                         const fretFinger: Vex.Flow.FretHandFinger = new Vex.Flow.FretHandFinger(technicalInstruction.value);
-                        fretFinger.setPosition(Vex.Flow.Modifier.Position.LEFT); // could be EngravingRule, though ABOVE doesn't work for chords
+                        fretFinger.setPosition(Vex.Flow.Modifier.Position.LEFT); // could be EngravingRule, see branch feature/fingeringsAboveEtc
                         vexFlowVoiceEntry.vfStaveNote.addModifier(i, fretFinger);
                     }
                 }
 
+                // add Arpeggio
+                if (voiceEntry.parentVoiceEntry && voiceEntry.parentVoiceEntry.Arpeggio !== undefined) {
+                    const type: ArpeggioType = voiceEntry.parentVoiceEntry.Arpeggio.type;
+                    vexFlowVoiceEntry.vfStaveNote.addStroke(0, new Vex.Flow.Stroke(type));
+                }
+
                 this.vfVoices[voice.VoiceId].addTickable(vexFlowVoiceEntry.vfStaveNote);
             }
         }

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

@@ -26,6 +26,7 @@ import { CollectionUtil } from "../../Util/CollectionUtil";
 import { ArticulationReader } from "./MusicSymbolModules/ArticulationReader";
 import { SlurReader } from "./MusicSymbolModules/SlurReader";
 import { NoteHead } from "../VoiceData/NoteHead";
+import { Arpeggio, ArpeggioType } from "../VoiceData/Arpeggio";
 
 export class VoiceGenerator {
   constructor(instrument: Instrument, voiceId: number, slurReader: SlurReader, mainVoice: Voice = undefined) {
@@ -140,8 +141,31 @@ export class VoiceGenerator {
           hasTupletCommand = true;
         }
         // check for Arpeggios
-        if (notationNode.element("arpeggiate") !== undefined && !this.currentVoiceEntry.IsGrace) {
-          this.currentVoiceEntry.ArpeggiosNotesIndices.push(this.currentVoiceEntry.Notes.indexOf(this.currentNote));
+        const arpeggioNode: IXmlElement = notationNode.element("arpeggiate");
+        if (arpeggioNode !== undefined && !this.currentVoiceEntry.IsGrace) {
+          let currentArpeggio: Arpeggio;
+          if (this.currentVoiceEntry.Arpeggio !== undefined) { // add note to existing Arpeggio
+            currentArpeggio = this.currentVoiceEntry.Arpeggio;
+          } else { // create new Arpeggio
+            let arpeggioType: ArpeggioType;
+            const directionAttr: Attr = arpeggioNode.attribute("direction");
+            if (directionAttr !== null) {
+              switch (directionAttr.value) {
+                case "up":
+                  arpeggioType = ArpeggioType.ROLL_UP;
+                  break;
+                case "down":
+                  arpeggioType = ArpeggioType.ROLL_DOWN;
+                  break;
+                default:
+                  arpeggioType = ArpeggioType.ARPEGGIO_DIRECTIONLESS;
+              }
+            }
+
+            currentArpeggio = new Arpeggio(this.currentVoiceEntry, arpeggioType);
+            this.currentVoiceEntry.Arpeggio = currentArpeggio;
+          }
+          currentArpeggio.addNote(this.currentNote);
         }
         // check for Ties - must be the last check
         const tiedNodeList: IXmlElement[] = notationNode.elements("tied");

+ 34 - 0
src/MusicalScore/VoiceData/Arpeggio.ts

@@ -0,0 +1,34 @@
+import { VoiceEntry } from "./VoiceEntry";
+import { Note } from "./Note";
+
+/** Type and direction of an Arpeggio.
+ * The values should correspond to Vex.Flow.Strokes.Type values.
+ * Somehow they don't correspond to Vexflow code, but they were confirmed to work, for whatever reason.
+ * For now, we only support one Arpeggio per VoiceEntry.
+ */
+export enum ArpeggioType {
+    BRUSH_DOWN = 2,
+    BRUSH_UP = 1,
+    ROLL_DOWN = 4, // Arpeggio with downwards arrow
+    ROLL_UP = 3, // Arpeggio with upwards arrow
+    RASQUEDO_DOWN = 5, // this is UP, can't find a value for DOWN that works in Vexflow right now
+    RASQUEDO_UP = 5,
+    ARPEGGIO_DIRECTIONLESS = 7 // currently not supported in Vexflow
+}
+
+export class Arpeggio {
+    constructor(parentVoiceEntry: VoiceEntry, type: ArpeggioType = ArpeggioType.ARPEGGIO_DIRECTIONLESS) {
+        this.parentVoiceEntry = parentVoiceEntry;
+        this.type = type;
+        this.notes = [];
+    }
+
+    public parentVoiceEntry: VoiceEntry;
+    public notes: Note[];
+    public type: ArpeggioType;
+
+    public addNote(note: Note): void {
+        this.notes.push(note);
+        note.Arpeggio = this;
+    }
+}

+ 9 - 1
src/MusicalScore/VoiceData/Note.ts

@@ -9,6 +9,7 @@ import {Staff} from "./Staff";
 import {Slur} from "./Expressions/ContinuousExpressions/Slur";
 import {NoteState} from "../Graphical/DrawingEnums";
 import {NoteHead} from "./NoteHead";
+import {Arpeggio} from "./Arpeggio";
 
 /**
  * Represents a single pitch with a duration (length)
@@ -47,10 +48,11 @@ export class Note {
     private noteHead: NoteHead = undefined;
     /** States whether the note should be displayed. False if xmlNode.attribute("print-object").value = "no". */
     private printObject: boolean = true;
+    /** The Arpeggio this note is part of. */
+    private arpeggio: Arpeggio;
     /** States whether this is a cue note (Stichnote) (smaller size). */
     private isCueNote: boolean;
 
-
     public get ParentVoiceEntry(): VoiceEntry {
         return this.voiceEntry;
     }
@@ -114,6 +116,12 @@ export class Note {
     public set PrintObject(value: boolean) {
         this.printObject = value;
     }
+    public get Arpeggio(): Arpeggio {
+        return this.arpeggio;
+    }
+    public set Arpeggio(value: Arpeggio) {
+        this.arpeggio = value;
+    }
     public get IsCueNote(): boolean {
         return this.isCueNote;
     }

+ 7 - 5
src/MusicalScore/VoiceData/VoiceEntry.ts

@@ -10,6 +10,7 @@ import {KeyInstruction} from "./Instructions/KeyInstruction";
 import {OrnamentEnum} from "./OrnamentContainer";
 import {AccidentalEnum} from "../../Common/DataObjects/Pitch";
 import Dictionary from "typescript-collections/dist/lib/Dictionary";
+import {Arpeggio} from "./Arpeggio";
 
 /**
  * A [[VoiceEntry]] contains the notes in a voice at a timestamp.
@@ -46,7 +47,8 @@ export class VoiceEntry {
     private articulations: ArticulationEnum[] = [];
     private technicalInstructions: TechnicalInstruction[] = [];
     private lyricsEntries: Dictionary<number, LyricsEntry> = new Dictionary<number, LyricsEntry>();
-    private arpeggiosNotesIndices: number[] = [];
+    /** The Arpeggio consisting of this VoiceEntry's notes. Undefined if no arpeggio exists. */
+    private arpeggio: Arpeggio;
     private ornamentContainer: OrnamentContainer;
     private wantedStemDirection: StemDirectionType = StemDirectionType.Undefined;
     private stemDirection: StemDirectionType = StemDirectionType.Undefined;
@@ -99,11 +101,11 @@ export class VoiceEntry {
     public get LyricsEntries(): Dictionary<number, LyricsEntry> {
         return this.lyricsEntries;
     }
-    public get ArpeggiosNotesIndices(): number[] {
-        return this.arpeggiosNotesIndices;
+    public get Arpeggio(): Arpeggio {
+        return this.arpeggio;
     }
-    public set ArpeggiosNotesIndices(value: number[]) {
-        this.arpeggiosNotesIndices = value;
+    public set Arpeggio(value: Arpeggio) {
+        this.arpeggio = value;
     }
     public get OrnamentContainer(): OrnamentContainer {
         return this.ornamentContainer;

+ 322 - 0
test/data/OSMD_function_test_all.xml

@@ -3095,5 +3095,327 @@
         <bar-style>light-heavy</bar-style>
         </barline>
       </measure>
+      <measure number="39" width="310.04">
+      <attributes>
+        <divisions>1</divisions>
+	</attributes>
+      <note default-x="110.18" default-y="-15.00">
+        <pitch>
+          <step>C</step>
+          <octave>5</octave>
+          </pitch>
+        <duration>4</duration>
+        <voice>1</voice>
+        <type>whole</type>
+        <lyric number="1" default-x="9.90" default-y="-80.00">
+          <syllabic>single</syllabic>
+          <text>Arpeggios</text>
+          </lyric>
+        </note>
+      </measure>
+    <measure number="40" width="263.63">
+      <note default-x="37.83" default-y="-40.00">
+        <pitch>
+          <step>E</step>
+          <octave>4</octave>
+          </pitch>
+        <duration>2</duration>
+        <voice>1</voice>
+        <type>half</type>
+        <stem>up</stem>
+        <notations>
+          <arpeggiate default-x="-13.81" default-y="-14.55"/>
+          </notations>
+        <lyric number="1" default-x="6.22" default-y="-80.00">
+          <syllabic>single</syllabic>
+          <text>Neutral</text>
+          </lyric>
+        </note>
+      <note default-x="37.83" default-y="-30.00">
+        <chord/>
+        <pitch>
+          <step>G</step>
+          <octave>4</octave>
+          </pitch>
+        <duration>2</duration>
+        <voice>1</voice>
+        <type>half</type>
+        <stem>up</stem>
+        <notations>
+          <arpeggiate default-x="-13.81" default-y="-14.55"/>
+          </notations>
+        </note>
+      <note default-x="37.83" default-y="-20.00">
+        <chord/>
+        <pitch>
+          <step>B</step>
+          <octave>4</octave>
+          </pitch>
+        <duration>2</duration>
+        <voice>1</voice>
+        <type>half</type>
+        <stem>up</stem>
+        <notations>
+          <arpeggiate default-x="-13.81" default-y="-14.55"/>
+          </notations>
+        </note>
+      <note default-x="149.75" default-y="-40.00">
+        <pitch>
+          <step>E</step>
+          <octave>4</octave>
+          </pitch>
+        <duration>2</duration>
+        <voice>1</voice>
+        <type>half</type>
+        <stem>up</stem>
+        <notations>
+          <arpeggiate direction="up" default-x="-18.53" default-y="-14.55"/>
+          </notations>
+        <lyric number="1" default-x="6.22" default-y="-80.00">
+          <syllabic>single</syllabic>
+          <text>Up</text>
+          </lyric>
+        </note>
+      <note default-x="149.75" default-y="-30.00">
+        <chord/>
+        <pitch>
+          <step>G</step>
+          <octave>4</octave>
+          </pitch>
+        <duration>2</duration>
+        <voice>1</voice>
+        <type>half</type>
+        <stem>up</stem>
+        <notations>
+          <arpeggiate direction="up" default-x="-18.53" default-y="-14.55"/>
+          </notations>
+        </note>
+      <note default-x="149.75" default-y="-20.00">
+        <chord/>
+        <pitch>
+          <step>B</step>
+          <octave>4</octave>
+          </pitch>
+        <duration>2</duration>
+        <voice>1</voice>
+        <type>half</type>
+        <stem>up</stem>
+        <notations>
+          <arpeggiate direction="up" default-x="-18.53" default-y="-14.55"/>
+          </notations>
+        </note>
+      </measure>
+    <measure number="41" width="226.96">
+      <note default-x="31.27" default-y="-40.00">
+        <pitch>
+          <step>E</step>
+          <octave>4</octave>
+          </pitch>
+        <duration>2</duration>
+        <voice>1</voice>
+        <type>half</type>
+        <stem>up</stem>
+        <notations>
+          <arpeggiate direction="down" default-x="-18.53" default-y="-14.55"/>
+          </notations>
+        <lyric number="1" default-x="6.22" default-y="-80.00">
+          <syllabic>single</syllabic>
+          <text>Down</text>
+          </lyric>
+        </note>
+      <note default-x="31.27" default-y="-30.00">
+        <chord/>
+        <pitch>
+          <step>G</step>
+          <octave>4</octave>
+          </pitch>
+        <duration>2</duration>
+        <voice>1</voice>
+        <type>half</type>
+        <stem>up</stem>
+        <notations>
+          <arpeggiate direction="down" default-x="-18.53" default-y="-14.55"/>
+          </notations>
+        </note>
+      <note default-x="31.27" default-y="-20.00">
+        <chord/>
+        <pitch>
+          <step>B</step>
+          <octave>4</octave>
+          </pitch>
+        <duration>2</duration>
+        <voice>1</voice>
+        <type>half</type>
+        <stem>up</stem>
+        <notations>
+          <arpeggiate direction="down" default-x="-18.53" default-y="-14.55"/>
+          </notations>
+        </note>
+      <note>
+        <rest/>
+        <duration>2</duration>
+        <voice>1</voice>
+        <type>half</type>
+        </note>
+      </measure>
+    <measure number="42" width="309.98">
+      <note default-x="8.11" default-y="-5.00">
+        <grace/>
+        <pitch>
+          <step>E</step>
+          <octave>5</octave>
+          </pitch>
+        <voice>1</voice>
+        <type>eighth</type>
+        <stem>up</stem>
+        <beam number="1">begin</beam>
+        </note>
+      <note default-x="30.53" default-y="0.00">
+        <grace/>
+        <pitch>
+          <step>F</step>
+          <alter>1</alter>
+          <octave>5</octave>
+          </pitch>
+        <voice>1</voice>
+        <type>eighth</type>
+        <accidental>sharp</accidental>
+        <stem>up</stem>
+        <beam number="1">continue</beam>
+        </note>
+      <note default-x="41.49" default-y="5.00">
+        <grace/>
+        <pitch>
+          <step>G</step>
+          <octave>5</octave>
+          </pitch>
+        <voice>1</voice>
+        <type>eighth</type>
+        <stem>up</stem>
+        <beam number="1">end</beam>
+        </note>
+      <note default-x="66.66" default-y="-40.00">
+        <pitch>
+          <step>E</step>
+          <octave>4</octave>
+          </pitch>
+        <duration>2</duration>
+        <voice>1</voice>
+        <type>half</type>
+        <stem>up</stem>
+        <notations>
+          <arpeggiate default-x="-13.81" default-y="0.45"/>
+          </notations>
+        </note>
+      <note default-x="66.66" default-y="-30.00">
+        <chord/>
+        <pitch>
+          <step>G</step>
+          <octave>4</octave>
+          </pitch>
+        <duration>2</duration>
+        <voice>1</voice>
+        <type>half</type>
+        <stem>up</stem>
+        <notations>
+          <arpeggiate default-x="-13.81" default-y="0.45"/>
+          </notations>
+        </note>
+      <note default-x="66.66" default-y="-20.00">
+        <chord/>
+        <pitch>
+          <step>B</step>
+          <octave>4</octave>
+          </pitch>
+        <duration>2</duration>
+        <voice>1</voice>
+        <type>half</type>
+        <stem>up</stem>
+        <notations>
+          <arpeggiate default-x="-13.81" default-y="0.45"/>
+          </notations>
+        </note>
+      <note default-x="66.66" default-y="-5.00">
+        <chord/>
+        <pitch>
+          <step>E</step>
+          <octave>5</octave>
+          </pitch>
+        <duration>2</duration>
+        <voice>1</voice>
+        <type>half</type>
+        <stem>up</stem>
+        <notations>
+          <arpeggiate default-x="-13.81" default-y="0.45"/>
+          </notations>
+        </note>
+      <attributes>
+        <clef>
+          <sign>G</sign>
+          <line>2</line>
+          <clef-octave-change>1</clef-octave-change>
+          </clef>
+        </attributes>
+      <note default-x="182.84" default-y="-40.00">
+        <pitch>
+          <step>E</step>
+          <octave>5</octave>
+          </pitch>
+        <duration>2</duration>
+        <voice>1</voice>
+        <type>half</type>
+        <stem>up</stem>
+        <notations>
+          <arpeggiate default-x="-29.22" default-y="0.45"/>
+          </notations>
+        </note>
+      <note default-x="182.84" default-y="-30.00">
+        <chord/>
+        <pitch>
+          <step>G</step>
+          <alter>1</alter>
+          <octave>5</octave>
+          </pitch>
+        <duration>2</duration>
+        <voice>1</voice>
+        <type>half</type>
+        <accidental>sharp</accidental>
+        <stem>up</stem>
+        <notations>
+          <arpeggiate default-x="-29.22" default-y="0.45"/>
+          </notations>
+        </note>
+      <note default-x="182.84" default-y="-20.00">
+        <chord/>
+        <pitch>
+          <step>B</step>
+          <octave>5</octave>
+          </pitch>
+        <duration>2</duration>
+        <voice>1</voice>
+        <type>half</type>
+        <stem>up</stem>
+        <notations>
+          <arpeggiate default-x="-29.22" default-y="0.45"/>
+          </notations>
+        </note>
+      <note default-x="182.84" default-y="-5.00">
+        <chord/>
+        <pitch>
+          <step>E</step>
+          <octave>6</octave>
+          </pitch>
+        <duration>2</duration>
+        <voice>1</voice>
+        <type>half</type>
+        <stem>up</stem>
+        <notations>
+          <arpeggiate default-x="-29.22" default-y="0.45"/>
+          </notations>
+        </note>
+      <barline location="right">
+        <bar-style>light-heavy</bar-style>
+        </barline>
+      </measure>
     </part>
   </score-partwise>