Skip to content

Commit

Permalink
feat(Tabs): Add Slides, Bends, Hammer-on and Pull-offs (PR #839)
Browse files Browse the repository at this point in the history
code and PR by momolarson

needs a few refactors and polishes to merge to develop.

squashed commits:

* trying to add bends

* fixed lint errors

* made bends array, check for release

* added proper phrase text

* package.json changes

* fixed bends and added vibrato

* changes for adding a tie

* hammerons, pull offs and slides

* missing file

* calculate slide up and down

* fixed number
  • Loading branch information
momolarson authored Jul 28, 2020
1 parent 2bc5139 commit 8de1400
Show file tree
Hide file tree
Showing 10 changed files with 133 additions and 40 deletions.
10 changes: 10 additions & 0 deletions src/Common/Enums/TieTypes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/**
* The types of ties available
*/
export enum TieTypes {
"SIMPLE" = "",
"HAMMERON" = "H",
"PULLOFF" = "P",
"SLIDE" = "S",
"TAPPING" = "T"
}
1 change: 1 addition & 0 deletions src/Common/Enums/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@
export * from "./FontStyles";
export * from "./Fonts";
export * from "./TextAlignment";
export * from "./TieTypes";
3 changes: 3 additions & 0 deletions src/MusicalScore/Graphical/GraphicalTie.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ export class GraphicalTie {
public get StartNote(): GraphicalNote {
return this.startNote;
}
public get Tie(): Tie {
return this.tie;
}
public set StartNote(value: GraphicalNote) {
this.startNote = value;
}
Expand Down
4 changes: 2 additions & 2 deletions src/MusicalScore/Graphical/MusicSheetCalculator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -438,7 +438,7 @@ export abstract class MusicSheetCalculator {
* @param tie
* @param tieIsAtSystemBreak
*/
protected layoutGraphicalTie(tie: GraphicalTie, tieIsAtSystemBreak: boolean): void {
protected layoutGraphicalTie(tie: GraphicalTie, tieIsAtSystemBreak: boolean, isTab: boolean): void {
throw new Error("abstract, not implemented");
}

Expand Down Expand Up @@ -2436,7 +2436,7 @@ export abstract class MusicSheetCalculator {
graphicalTie.StartNote.parentVoiceEntry.parentStaffEntry.parentMeasure.ParentStaffLine !==
graphicalTie.EndNote.parentVoiceEntry.parentStaffEntry.parentMeasure.ParentStaffLine
);
this.layoutGraphicalTie(graphicalTie, tieIsAtSystemBreak);
this.layoutGraphicalTie(graphicalTie, tieIsAtSystemBreak, measure.ParentStaff.isTab);
}
}
}
Expand Down
51 changes: 30 additions & 21 deletions src/MusicalScore/Graphical/VexFlow/VexFlowConverter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -544,6 +544,7 @@ export class VexFlowConverter {
*/
public static CreateTabNote(gve: GraphicalVoiceEntry): Vex.Flow.TabNote {
const tabPositions: {str: number, fret: number}[] = [];
const notes: GraphicalNote[] = gve.notes.reverse();
const tabPhrases: { type: number, text: string, width: number }[] = [];
const frac: Fraction = gve.notes[0].graphicalNoteLength;
const isTuplet: boolean = gve.notes[0].sourceNote.NoteTuplet !== undefined;
Expand All @@ -554,37 +555,50 @@ export class VexFlowConverter {
const tabNote: TabNote = note.sourceNote as TabNote;
const tabPosition: {str: number, fret: number} = {str: tabNote.StringNumber, fret: tabNote.FretNumber};
tabPositions.push(tabPosition);
tabNote.BendArray.forEach( function( bend: {bendalter: number, direction: string} ): void {
let phraseText: string;
const phraseStep: number = bend.bendalter - tabPosition.fret;
if (phraseStep > 1) {
phraseText = "Full";
} else if (phraseStep === 1) {
phraseText = "1/2";
} else {
phraseText = "1/4";
}
if (bend.direction === "up") {
tabPhrases.push({type: Vex.Flow.Bend.UP, text: phraseText, width: 10});
} else {
tabPhrases.push({type: Vex.Flow.Bend.DOWN, text: phraseText, width: 10});
}
});
if (tabNote.BendArray) {
tabNote.BendArray.forEach( function( bend: {bendalter: number, direction: string} ): void {
let phraseText: string;
const phraseStep: number = bend.bendalter - tabPosition.fret;
if (phraseStep > 1) {
phraseText = "Full";
} else if (phraseStep === 1) {
phraseText = "1/2";
} else {
phraseText = "1/4";
}
if (bend.direction === "up") {
tabPhrases.push({type: Vex.Flow.Bend.UP, text: phraseText, width: 10});
} else {
tabPhrases.push({type: Vex.Flow.Bend.DOWN, text: phraseText, width: 10});
}
});
}

/* if (tabNote.NoteTie) {
tabTies = tabNote.NoteTie;
} */

if (tabNote.VibratoStroke) {
tabVibrato = true;
}

if (numDots < note.numberOfDots) {
numDots = note.numberOfDots;
}
}
for (let i: number = 0, len: number = numDots; i < len; ++i) {
duration += "d";
}

const vfnote: Vex.Flow.TabNote = new Vex.Flow.TabNote({
duration: duration,
positions: tabPositions,
});

for (let i: number = 0, len: number = notes.length; i < len; i += 1) {
(notes[i] as VexFlowGraphicalNote).setIndex(vfnote, i);
}

tabPhrases.forEach(function(phrase: { type: number, text: string, width: number }): void {
if (phrase.type === Vex.Flow.Bend.UP) {
vfnote.addModifier (new Vex.Flow.Bend(phrase.text, false));
Expand All @@ -593,11 +607,6 @@ export class VexFlowConverter {
}
});
// does not work well to add phrases as array
/*
if (tabPhrases.length > 0) {
vfnote.addModifier (new Vex.Flow.Bend(undefined, undefined, tabPhrases), 1);
}
*/
if (tabVibrato) {
vfnote.addModifier(new Vex.Flow.Vibrato());
}
Expand Down
4 changes: 2 additions & 2 deletions src/MusicalScore/Graphical/VexFlow/VexFlowGraphicalNote.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export class VexFlowGraphicalNote extends GraphicalNote {
// The pitch of this note as given by VexFlowConverter.pitch
public vfpitch: [string, string, ClefInstruction];
// The corresponding VexFlow StaveNote (plus its index in the chord)
public vfnote: [Vex.Flow.StaveNote, number];
public vfnote: [Vex.Flow.StemmableNote, number];
// The current clef
private clef: ClefInstruction;

Expand Down Expand Up @@ -67,7 +67,7 @@ export class VexFlowGraphicalNote extends GraphicalNote {
* @param note
* @param index
*/
public setIndex(note: Vex.Flow.StaveNote, index: number): void {
public setIndex(note: Vex.Flow.StemmableNote, index: number): void {
this.vfnote = [note, index];
}

Expand Down
2 changes: 1 addition & 1 deletion src/MusicalScore/Graphical/VexFlow/VexFlowMeasure.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1161,7 +1161,7 @@ export class VexFlowMeasure extends GraphicalMeasure {
for ( const vfStaffEntry of this.staffEntries ) {
for ( const gVoiceEntry of vfStaffEntry.graphicalVoiceEntries) {
for ( const gnote of gVoiceEntry.notes) {
const vfnote: [StaveNote, number] = (gnote as VexFlowGraphicalNote).vfnote;
const vfnote: [StemmableNote , number] = (gnote as VexFlowGraphicalNote).vfnote;
if (!vfnote || !vfnote[0]) {
continue;
}
Expand Down
60 changes: 51 additions & 9 deletions src/MusicalScore/Graphical/VexFlow/VexFlowMusicSheetCalculator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ import { EngravingRules } from "../EngravingRules";
import { VexflowStafflineNoteCalculator } from "./VexflowStafflineNoteCalculator";
import { NoteTypeHandler } from "../../VoiceData/NoteType";
import { VexFlowConverter } from "./VexFlowConverter";
import { TabNote } from "../../VoiceData/TabNote";

export class VexFlowMusicSheetCalculator extends MusicSheetCalculator {
/** space needed for a dash for lyrics spacing, calculated once */
Expand Down Expand Up @@ -136,6 +137,13 @@ export class VexFlowMusicSheetCalculator extends MusicSheetCalculator {
softmaxFactor: this.rules.SoftmaxFactorVexFlow // this setting is only applied in Vexflow 3.x. also this needs @types/vexflow ^3.0.0
});

/*
{
// maxIterations: 2,
softmaxFactor: this.rules.SoftmaxFactorVexFlow // this setting is only applied in Vexflow 3.x. also this needs @types/vexflow ^3.0.0
}
*/

for (const measure of measures) {
if (!measure) {
continue;
Expand Down Expand Up @@ -467,18 +475,18 @@ export class VexFlowMusicSheetCalculator extends MusicSheetCalculator {
* @param tie
* @param tieIsAtSystemBreak
*/
protected layoutGraphicalTie(tie: GraphicalTie, tieIsAtSystemBreak: boolean): void {
protected layoutGraphicalTie(tie: GraphicalTie, tieIsAtSystemBreak: boolean, isTab: boolean): void {
const startNote: VexFlowGraphicalNote = (tie.StartNote as VexFlowGraphicalNote);
const endNote: VexFlowGraphicalNote = (tie.EndNote as VexFlowGraphicalNote);

let vfStartNote: Vex.Flow.StaveNote = undefined;
let vfStartNote: Vex.Flow.StemmableNote = undefined;
let startNoteIndexInTie: number = 0;
if (startNote && startNote.vfnote && startNote.vfnote.length >= 2) {
vfStartNote = startNote.vfnote[0];
startNoteIndexInTie = startNote.vfnote[1];
}

let vfEndNote: Vex.Flow.StaveNote = undefined;
let vfEndNote: Vex.Flow.StemmableNote = undefined;
let endNoteIndexInTie: number = 0;
if (endNote && endNote.vfnote && endNote.vfnote.length >= 2) {
vfEndNote = endNote.vfnote[0];
Expand Down Expand Up @@ -507,12 +515,46 @@ export class VexFlowMusicSheetCalculator extends MusicSheetCalculator {
} else {
// normal case
if (vfStartNote || vfEndNote) { // one of these must be not null in Vexflow
const vfTie: Vex.Flow.StaveTie = new Vex.Flow.StaveTie({
first_indices: [startNoteIndexInTie],
first_note: vfStartNote,
last_indices: [endNoteIndexInTie],
last_note: vfEndNote
});
let vfTie: any;
if (isTab) {
if (tie.Tie.Type === "S") {
//calculate direction
const startTieNote: TabNote = <TabNote> tie.StartNote.sourceNote;
const endTieNote: TabNote = <TabNote> tie.EndNote.sourceNote;
let slideDirection: number = 1;
if (startTieNote.FretNumber > endTieNote.FretNumber) {
slideDirection = -1;
}
vfTie = new Vex.Flow.TabSlide(
{
first_indices: [startNoteIndexInTie],
first_note: vfStartNote,
last_indices: [endNoteIndexInTie],
last_note: vfEndNote,
},
slideDirection
);
} else {
vfTie = new Vex.Flow.TabTie(
{
first_indices: [startNoteIndexInTie],
first_note: vfStartNote,
last_indices: [endNoteIndexInTie],
last_note: vfEndNote,
},
tie.Tie.Type
);
}

} else {
vfTie = new Vex.Flow.StaveTie({
first_indices: [startNoteIndexInTie],
first_note: vfStartNote,
last_indices: [endNoteIndexInTie],
last_note: vfEndNote
});
}

const measure: VexFlowMeasure = (endNote.parentVoiceEntry.parentStaffEntry.parentMeasure as VexFlowMeasure);
measure.vfTies.push(vfTie);
}
Expand Down
29 changes: 25 additions & 4 deletions src/MusicalScore/ScoreIO/VoiceGenerator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { SourceMeasure } from "../VoiceData/SourceMeasure";
import { SourceStaffEntry } from "../VoiceData/SourceStaffEntry";
import { Beam } from "../VoiceData/Beam";
import { Tie } from "../VoiceData/Tie";
import { TieTypes } from "../../Common/Enums/";
import { Tuplet } from "../VoiceData/Tuplet";
import { Fraction } from "../../Common/DataObjects/Fraction";
import { IXmlElement } from "../../Common/FileIO/Xml";
Expand Down Expand Up @@ -190,9 +191,23 @@ export class VoiceGenerator {
// check for Ties - must be the last check
const tiedNodeList: IXmlElement[] = notationNode.elements("tied");
if (tiedNodeList.length > 0) {
this.addTie(tiedNodeList, measureStartAbsoluteTimestamp, maxTieNoteFraction);
this.addTie(tiedNodeList, measureStartAbsoluteTimestamp, maxTieNoteFraction, TieTypes.SIMPLE);
}
//check for slides, they are the same as Ties but with a different connection
const slideNodeList: IXmlElement[] = notationNode.elements("slide");
if (slideNodeList.length > 0) {
this.addTie(slideNodeList, measureStartAbsoluteTimestamp, maxTieNoteFraction, TieTypes.SLIDE);
}
//check for slides, they are the same as Ties but with a different connection
const technicalNode: IXmlElement = notationNode.element("technical");
const hammerNodeList: IXmlElement[] = technicalNode.elements("hammer-on");
if (hammerNodeList.length > 0) {
this.addTie(hammerNodeList, measureStartAbsoluteTimestamp, maxTieNoteFraction, TieTypes.HAMMERON);
}
const pulloffNodeList: IXmlElement[] = technicalNode.elements("pull-off");
if (pulloffNodeList.length > 0) {
this.addTie(pulloffNodeList, measureStartAbsoluteTimestamp, maxTieNoteFraction, TieTypes.PULLOFF);
}

// remove open ties, if there is already a gap between the last tie note and now.
const openTieDict: { [_: number]: Tie; } = this.openTieDict;
for (const key in openTieDict) {
Expand Down Expand Up @@ -757,7 +772,7 @@ export class VoiceGenerator {
}
}

private addTie(tieNodeList: IXmlElement[], measureStartAbsoluteTimestamp: Fraction, maxTieNoteFraction: Fraction): void {
private addTie(tieNodeList: IXmlElement[], measureStartAbsoluteTimestamp: Fraction, maxTieNoteFraction: Fraction, tieType: TieTypes): void {
if (tieNodeList) {
if (tieNodeList.length === 1) {
const tieNode: IXmlElement = tieNodeList[0];
Expand All @@ -770,7 +785,7 @@ export class VoiceGenerator {
delete this.openTieDict[num];
}
const newTieNumber: number = this.getNextAvailableNumberForTie();
const tie: Tie = new Tie(this.currentNote);
const tie: Tie = new Tie(this.currentNote, tieType);
this.openTieDict[newTieNumber] = tie;
} else if (type === "stop") {
const tieNumber: number = this.findCurrentNoteInTieDict(this.currentNote);
Expand Down Expand Up @@ -830,8 +845,14 @@ export class VoiceGenerator {
for (const key in openTieDict) {
if (openTieDict.hasOwnProperty(key)) {
const tie: Tie = openTieDict[key];
const tieTabNote: TabNote = tie.Notes[0] as TabNote;
const tieCandidateNote: TabNote = candidateNote as TabNote;
if (tie.Pitch.FundamentalNote === candidateNote.Pitch.FundamentalNote && tie.Pitch.Octave === candidateNote.Pitch.Octave) {
return +key;
} else {
if (tieTabNote.StringNumber === tieCandidateNote.StringNumber) {
return +key;
}
}
}
}
Expand Down
9 changes: 8 additions & 1 deletion src/MusicalScore/VoiceData/Tie.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,29 @@
import {Note} from "./Note";
import { Fraction } from "../../Common/DataObjects/Fraction";
import { Pitch } from "../../Common/DataObjects/Pitch";
import { TieTypes } from "../../Common/Enums/";

/**
* A [[Tie]] connects two notes of the same pitch and name, indicating that they have to be played as a single note.
*/
export class Tie {

constructor(note: Note) {
constructor(note: Note, type: TieTypes) {
this.AddNote(note);
this.type = type;
}

private notes: Note[] = [];
private type: TieTypes;

public get Notes(): Note[] {
return this.notes;
}

public get Type(): TieTypes {
return this.type;
}

public get StartNote(): Note {
return this.notes[0];
}
Expand Down

0 comments on commit 8de1400

Please sign in to comment.