vexflow
Version:
A JavaScript library for rendering music notation and guitar tablature.
433 lines (432 loc) • 24.6 kB
JavaScript
import { VexFlow } from '../src/vexflow.js';
import { VexFlowTests } from './vexflow_test_helpers.js';
import { Articulation } from '../src/articulation.js';
import { Beam } from '../src/beam.js';
import { Formatter } from '../src/formatter.js';
import { Glyphs } from '../src/glyphs.js';
import { Metrics } from '../src/metrics.js';
import { ModifierPosition } from '../src/modifier.js';
import { Stave } from '../src/stave.js';
import { Barline } from '../src/stavebarline.js';
import { StaveNote } from '../src/stavenote.js';
import { Stem } from '../src/stem.js';
import { TabNote } from '../src/tabnote.js';
import { TabStave } from '../src/tabstave.js';
import { Voice } from '../src/voice.js';
const ArticulationTests = {
Start() {
QUnit.module('Articulation');
const run = VexFlowTests.runTests;
run('Bounding Box', verticalPlacement, { drawBoundingBox: true });
run('Vertical Placement', verticalPlacement, { drawBoundingBox: false });
run('Vertical Placement (Glyph codes)', verticalPlacement2);
run('Staccato/Staccatissimo', drawArticulations, { sym1: 'a.', sym2: 'av' });
run('Accent/Tenuto', drawArticulations, { sym1: 'a>', sym2: 'a-' });
run('Marcato/L.H. Pizzicato', drawArticulations, { sym1: 'a^', sym2: 'a+' });
run('Snap Pizzicato/Fermata', drawArticulations, { sym1: 'ao', sym2: 'ao' });
run('Up-stroke/Down-Stroke', drawArticulations, { sym1: 'a|', sym2: 'am' });
run('Fermata Above/Below', drawFermata, { sym1: 'a@a', sym2: 'a@u' });
run('Fermata Short Above/Below', drawFermata, { sym1: 'a@as', sym2: 'a@us' });
run('Fermata Long Above/Below', drawFermata, { sym1: 'a@al', sym2: 'a@ul' });
run('Fermata Very Long Above/Below', drawFermata, { sym1: 'a@avl', sym2: 'a@uvl' });
run('Inline/Multiple', drawArticulations2, { sym1: 'a.', sym2: 'a.' });
run('TabNote Articulation', tabNotes, { sym1: 'a.', sym2: 'a.' });
},
};
function drawArticulations(options) {
const sym1 = options.params.sym1;
const sym2 = options.params.sym2;
const width = 125 - Stave.defaultPadding;
const f = VexFlowTests.makeFactory(options, 675, 195);
const ctx = f.getContext();
options.assert.expect(0);
let x = 10;
const y = 30;
const score = f.EasyScore();
const formatAndDrawToWidth = (x, y, width, notes, barline) => {
const voices = [score.voice(notes, { time: '4/4' })];
const formatter = f.Formatter();
voices.forEach((v) => formatter.joinVoices([v]));
const nwidth = Math.max(formatter.preCalculateMinTotalWidth(voices), width);
formatter.format(voices, nwidth);
const stave = f
.Stave({ x, y, width: nwidth + Stave.defaultPadding })
.setEndBarType(barline)
.setContext(ctx)
.drawWithStyle();
voices.forEach((voice) => voice.draw(ctx, stave));
return stave.getWidth();
};
const notesBar1 = [
f.StaveNote({ keys: ['a/3'], duration: 'q', stemDirection: 1 }),
f.StaveNote({ keys: ['a/4'], duration: 'q', stemDirection: 1 }),
f.StaveNote({ keys: ['c/4'], duration: 'q', stemDirection: 1 }),
f.StaveNote({ keys: ['a/4'], duration: 'q', stemDirection: 1 }),
];
notesBar1[0].addModifier(new Articulation(sym1).setPosition(4), 0);
notesBar1[1].addModifier(new Articulation(sym1).setPosition(4), 0);
notesBar1[2].addModifier(new Articulation(sym1).setPosition(3), 0);
notesBar1[3].addModifier(new Articulation(sym1).setPosition(3), 0);
x += formatAndDrawToWidth(x, y, width, notesBar1, Barline.type.NONE);
const notesBar2 = [
f.StaveNote({ keys: ['c/5'], duration: 'q', stemDirection: -1 }),
f.StaveNote({ keys: ['a/5'], duration: 'q', stemDirection: -1 }),
f.StaveNote({ keys: ['c/5'], duration: 'q', stemDirection: -1 }),
f.StaveNote({ keys: ['a/5'], duration: 'q', stemDirection: -1 }),
];
notesBar2[0].addModifier(new Articulation(sym1).setPosition(3), 0);
notesBar2[1].addModifier(new Articulation(sym1).setPosition(3), 0);
notesBar2[2].addModifier(new Articulation(sym1).setPosition(4), 0);
notesBar2[3].addModifier(new Articulation(sym1).setPosition(4), 0);
x += formatAndDrawToWidth(x, y, width, notesBar2, Barline.type.DOUBLE);
const notesBar3 = [
f.StaveNote({ keys: ['c/4'], duration: 'q', stemDirection: 1 }),
f.StaveNote({ keys: ['c/5'], duration: 'q', stemDirection: 1 }),
f.StaveNote({ keys: ['c/4'], duration: 'q', stemDirection: 1 }),
f.StaveNote({ keys: ['a/4'], duration: 'q', stemDirection: 1 }),
];
notesBar3[0].addModifier(new Articulation(sym2).setPosition(4), 0);
notesBar3[1].addModifier(new Articulation(sym2).setPosition(4), 0);
notesBar3[2].addModifier(new Articulation(sym2).setPosition(3), 0);
notesBar3[3].addModifier(new Articulation(sym2).setPosition(3), 0);
x += formatAndDrawToWidth(x, y, width, notesBar3, Barline.type.NONE);
const notesBar4 = [
f.StaveNote({ keys: ['a/4'], duration: 'q', stemDirection: -1 }),
f.StaveNote({ keys: ['a/5'], duration: 'q', stemDirection: -1 }),
f.StaveNote({ keys: ['c/5'], duration: 'q', stemDirection: -1 }),
f.StaveNote({ keys: ['a/5'], duration: 'q', stemDirection: -1 }),
];
notesBar4[0].addModifier(new Articulation(sym2).setPosition(3), 0);
notesBar4[1].addModifier(new Articulation(sym2).setPosition(3), 0);
notesBar4[2].addModifier(new Articulation(sym2).setPosition(4), 0);
notesBar4[3].addModifier(new Articulation(sym2).setPosition(4), 0);
formatAndDrawToWidth(x, y, width, notesBar4, Barline.type.END);
}
function drawFermata(options) {
const sym1 = options.params.sym1;
const sym2 = options.params.sym2;
const f = VexFlowTests.makeFactory(options, 400, 195);
const ctx = f.getContext();
const score = f.EasyScore();
const width = 150 - Stave.defaultPadding;
let x = 50;
const y = 30;
const formatAndDrawToWidth = (x, y, width, notes, barline) => {
const voices = [score.voice(notes, { time: '4/4' })];
const formatter = f.Formatter();
voices.forEach((v) => formatter.joinVoices([v]));
const nwidth = Math.max(formatter.preCalculateMinTotalWidth(voices), width);
formatter.format(voices, nwidth);
const stave = f
.Stave({ x, y, width: nwidth + Stave.defaultPadding })
.setEndBarType(barline)
.setContext(ctx)
.drawWithStyle();
voices.forEach((voice) => voice.draw(ctx, stave));
return stave.getWidth();
};
options.assert.expect(0);
const notesBar1 = [
f.StaveNote({ keys: ['c/4'], duration: 'q', stemDirection: 1 }),
f.StaveNote({ keys: ['a/4'], duration: 'q', stemDirection: 1 }),
f.StaveNote({ keys: ['c/4'], duration: 'q', stemDirection: -1 }),
f.StaveNote({ keys: ['a/4'], duration: 'q', stemDirection: -1 }),
];
notesBar1[0].addModifier(new Articulation(sym1).setPosition(3), 0);
notesBar1[1].addModifier(new Articulation(sym1).setPosition(3), 0);
notesBar1[2].addModifier(new Articulation(sym2).setPosition(4), 0);
notesBar1[3].addModifier(new Articulation(sym2).setPosition(4), 0);
x += formatAndDrawToWidth(x, y, width, notesBar1, Barline.type.NONE);
const notesBar2 = [
f.StaveNote({ keys: ['c/5'], duration: 'q', stemDirection: 1 }),
f.StaveNote({ keys: ['a/5'], duration: 'q', stemDirection: 1 }),
f.StaveNote({ keys: ['c/5'], duration: 'q', stemDirection: -1 }),
f.StaveNote({ keys: ['a/5'], duration: 'q', stemDirection: -1 }),
];
notesBar2[0].addModifier(new Articulation(sym1).setPosition(3), 0);
notesBar2[1].addModifier(new Articulation(sym1).setPosition(3), 0);
notesBar2[2].addModifier(new Articulation(sym2).setPosition(4), 0);
notesBar2[3].addModifier(new Articulation(sym2).setPosition(4), 0);
formatAndDrawToWidth(x, y, width, notesBar2, Barline.type.DOUBLE);
}
function verticalPlacement(options, contextBuilder) {
const ctx = contextBuilder(options.elementId, 750, 300);
const staveNote = (noteStruct) => new StaveNote(noteStruct);
const stave = new Stave(10, 50, 750).addClef('treble').setContext(ctx).drawWithStyle();
const notes = [
staveNote({ keys: ['f/4'], duration: 'q' })
.addModifier(new Articulation('a@u').setPosition(ModifierPosition.BELOW), 0)
.addModifier(new Articulation('a.').setPosition(ModifierPosition.BELOW), 0)
.addModifier(new Articulation('a-').setPosition(ModifierPosition.BELOW), 0),
staveNote({ keys: ['g/4'], duration: 'q', stemDirection: Stem.DOWN })
.addModifier(new Articulation('a@u').setPosition(ModifierPosition.BELOW), 0)
.addModifier(new Articulation('a.').setPosition(ModifierPosition.BELOW), 0)
.addModifier(new Articulation('a-').setPosition(ModifierPosition.BELOW), 0),
staveNote({ keys: ['c/5'], duration: 'q' })
.addModifier(new Articulation('a@u').setPosition(ModifierPosition.BELOW), 0)
.addModifier(new Articulation('a.').setPosition(ModifierPosition.BELOW), 0)
.addModifier(new Articulation('a-').setPosition(ModifierPosition.BELOW), 0),
staveNote({ keys: ['f/4'], duration: 'q' })
.addModifier(new Articulation('a.').setPosition(ModifierPosition.BELOW), 0)
.addModifier(new Articulation('a-').setPosition(ModifierPosition.BELOW), 0)
.addModifier(new Articulation('a@u').setPosition(ModifierPosition.BELOW), 0),
staveNote({ keys: ['g/4'], duration: 'q', stemDirection: Stem.DOWN })
.addModifier(new Articulation('a.').setPosition(ModifierPosition.BELOW), 0)
.addModifier(new Articulation('a-').setPosition(ModifierPosition.BELOW), 0)
.addModifier(new Articulation('a@u').setPosition(ModifierPosition.BELOW), 0),
staveNote({ keys: ['c/5'], duration: 'q' })
.addModifier(new Articulation('a.').setPosition(ModifierPosition.BELOW), 0)
.addModifier(new Articulation('a-').setPosition(ModifierPosition.BELOW), 0)
.addModifier(new Articulation('a@u').setPosition(ModifierPosition.BELOW), 0),
staveNote({ keys: ['a/5'], duration: 'q', stemDirection: Stem.DOWN })
.addModifier(new Articulation('a@a').setPosition(ModifierPosition.ABOVE), 0)
.addModifier(new Articulation('a.').setPosition(ModifierPosition.ABOVE), 0)
.addModifier(new Articulation('a-').setPosition(ModifierPosition.ABOVE), 0),
staveNote({ keys: ['f/5'], duration: 'q' })
.addModifier(new Articulation('a@a').setPosition(ModifierPosition.ABOVE), 0)
.addModifier(new Articulation('a.').setPosition(ModifierPosition.ABOVE), 0)
.addModifier(new Articulation('a-').setPosition(ModifierPosition.ABOVE), 0),
staveNote({ keys: ['b/4'], duration: 'q', stemDirection: Stem.DOWN })
.addModifier(new Articulation('a@a').setPosition(ModifierPosition.ABOVE), 0)
.addModifier(new Articulation('a.').setPosition(ModifierPosition.ABOVE), 0)
.addModifier(new Articulation('a-').setPosition(ModifierPosition.ABOVE), 0),
staveNote({ keys: ['a/5'], duration: 'q', stemDirection: Stem.DOWN })
.addModifier(new Articulation('a.').setPosition(ModifierPosition.ABOVE), 0)
.addModifier(new Articulation('a-').setPosition(ModifierPosition.ABOVE), 0)
.addModifier(new Articulation('a@a').setPosition(ModifierPosition.ABOVE), 0),
staveNote({ keys: ['f/5'], duration: 'q' })
.addModifier(new Articulation('a.').setPosition(ModifierPosition.ABOVE), 0)
.addModifier(new Articulation('a-').setPosition(ModifierPosition.ABOVE), 0)
.addModifier(new Articulation('a@a').setPosition(ModifierPosition.ABOVE), 0),
staveNote({ keys: ['b/4'], duration: 'q', stemDirection: Stem.DOWN })
.addModifier(new Articulation('a.').setPosition(ModifierPosition.ABOVE), 0)
.addModifier(new Articulation('a-').setPosition(ModifierPosition.ABOVE), 0)
.addModifier(new Articulation('a@a').setPosition(ModifierPosition.ABOVE), 0),
];
Formatter.FormatAndDraw(ctx, stave, notes);
if (options.params.drawBoundingBox === true) {
notes.forEach((note) => {
const elements = note.getModifiersByType('Articulation');
elements.forEach((element) => VexFlowTests.drawBoundingBox(ctx, element));
});
}
options.assert.ok(true, ' Annotation Placement');
}
function verticalPlacement2(options, contextBuilder) {
const ctx = contextBuilder(options.elementId, 750, 300);
const staveNote = (noteStruct) => new StaveNote(noteStruct);
const stave = new Stave(10, 50, 750).addClef('treble').setContext(ctx).drawWithStyle();
const notes = [
staveNote({ keys: ['f/4'], duration: 'q' })
.addModifier(new Articulation(Glyphs.fermataBelow), 0)
.addModifier(new Articulation(Glyphs.augmentationDot).setPosition(ModifierPosition.BELOW), 0)
.addModifier(new Articulation(Glyphs.articTenutoBelow), 0),
staveNote({ keys: ['g/4'], duration: 'q', stemDirection: Stem.DOWN })
.addModifier(new Articulation(Glyphs.fermataShortBelow), 0)
.addModifier(new Articulation(Glyphs.augmentationDot).setPosition(ModifierPosition.BELOW), 0)
.addModifier(new Articulation(Glyphs.articTenutoBelow), 0),
staveNote({ keys: ['c/5'], duration: 'q' })
.addModifier(new Articulation(Glyphs.fermataLongBelow), 0)
.addModifier(new Articulation(Glyphs.augmentationDot).setPosition(ModifierPosition.BELOW), 0)
.addModifier(new Articulation(Glyphs.articTenutoBelow), 0),
staveNote({ keys: ['f/4'], duration: 'q' })
.addModifier(new Articulation(Glyphs.augmentationDot).setPosition(ModifierPosition.BELOW), 0)
.addModifier(new Articulation(Glyphs.articTenutoBelow), 0)
.addModifier(new Articulation(Glyphs.fermataVeryShortBelow), 0),
staveNote({ keys: ['g/4'], duration: 'q', stemDirection: Stem.DOWN })
.addModifier(new Articulation(Glyphs.augmentationDot).setPosition(ModifierPosition.BELOW), 0)
.addModifier(new Articulation(Glyphs.articTenutoBelow), 0)
.addModifier(new Articulation(Glyphs.fermataVeryLongBelow), 0),
staveNote({ keys: ['c/5'], duration: 'q' })
.addModifier(new Articulation(Glyphs.augmentationDot).setPosition(ModifierPosition.BELOW).setBetweenLines(), 0)
.addModifier(new Articulation(Glyphs.articTenutoBelow).setBetweenLines(), 0)
.addModifier(new Articulation(Glyphs.fermataBelow), 0),
staveNote({ keys: ['a/5'], duration: 'q', stemDirection: Stem.DOWN })
.addModifier(new Articulation(Glyphs.fermataAbove), 0)
.addModifier(new Articulation(Glyphs.augmentationDot).setPosition(ModifierPosition.ABOVE), 0)
.addModifier(new Articulation(Glyphs.articTenutoAbove), 0),
staveNote({ keys: ['f/5'], duration: 'q' })
.addModifier(new Articulation(Glyphs.fermataShortAbove), 0)
.addModifier(new Articulation(Glyphs.augmentationDot).setPosition(ModifierPosition.ABOVE), 0)
.addModifier(new Articulation(Glyphs.articTenutoAbove), 0),
staveNote({ keys: ['b/4'], duration: 'q', stemDirection: Stem.DOWN })
.addModifier(new Articulation(Glyphs.fermataLongAbove), 0)
.addModifier(new Articulation(Glyphs.augmentationDot).setPosition(ModifierPosition.ABOVE), 0)
.addModifier(new Articulation(Glyphs.articTenutoAbove), 0),
staveNote({ keys: ['a/5'], duration: 'q', stemDirection: Stem.DOWN })
.addModifier(new Articulation(Glyphs.augmentationDot).setPosition(ModifierPosition.ABOVE), 0)
.addModifier(new Articulation(Glyphs.articTenutoAbove), 0)
.addModifier(new Articulation(Glyphs.fermataVeryShortAbove), 0),
staveNote({ keys: ['f/5'], duration: 'q' })
.addModifier(new Articulation(Glyphs.augmentationDot).setPosition(ModifierPosition.ABOVE), 0)
.addModifier(new Articulation(Glyphs.articTenutoAbove), 0)
.addModifier(new Articulation(Glyphs.fermataVeryLongAbove), 0),
staveNote({ keys: ['b/4'], duration: 'q', stemDirection: Stem.DOWN })
.addModifier(new Articulation(Glyphs.augmentationDot).setPosition(ModifierPosition.ABOVE).setBetweenLines(), 0)
.addModifier(new Articulation(Glyphs.articTenutoAbove).setBetweenLines(), 0)
.addModifier(new Articulation(Glyphs.fermataAbove), 0),
];
Formatter.FormatAndDraw(ctx, stave, notes);
options.assert.ok(true, ' Annotation Placement (Glyph codes)');
}
function drawArticulations2(options) {
options.assert.expect(0);
const scale = 0.8;
const f = VexFlowTests.makeFactory(options, 1500, 195);
const ctx = f.getContext();
ctx.scale(scale, scale);
const stave1 = new Stave(10, 50, 500).setContext(ctx).drawWithStyle();
const notesBar1 = [
f.StaveNote({ keys: ['c/4'], duration: '16', stemDirection: 1 }),
f.StaveNote({ keys: ['d/4'], duration: '16', stemDirection: 1 }),
f.StaveNote({ keys: ['e/4'], duration: '16', stemDirection: 1 }),
f.StaveNote({ keys: ['f/4'], duration: '16', stemDirection: 1 }),
f.StaveNote({ keys: ['g/4'], duration: '16', stemDirection: 1 }),
f.StaveNote({ keys: ['a/4'], duration: '16', stemDirection: 1 }),
f.StaveNote({ keys: ['b/4'], duration: '16', stemDirection: 1 }),
f.StaveNote({ keys: ['c/5'], duration: '16', stemDirection: 1 }),
f.StaveNote({ keys: ['d/5'], duration: '16', stemDirection: -1 }),
f.StaveNote({ keys: ['e/5'], duration: '16', stemDirection: -1 }),
f.StaveNote({ keys: ['f/5'], duration: '16', stemDirection: -1 }),
f.StaveNote({ keys: ['g/5'], duration: '16', stemDirection: -1 }),
f.StaveNote({ keys: ['a/5'], duration: '16', stemDirection: -1 }),
f.StaveNote({ keys: ['b/5'], duration: '16', stemDirection: -1 }),
f.StaveNote({ keys: ['c/6'], duration: '16', stemDirection: -1 }),
f.StaveNote({ keys: ['d/6'], duration: '16', stemDirection: -1 }),
];
let i;
for (i = 0; i < 16; i++) {
notesBar1[i].addModifier(new Articulation('a.').setPosition(4), 0);
notesBar1[i].addModifier(new Articulation('a>').setPosition(4), 0);
if (i === 15) {
notesBar1[i].addModifier(new Articulation('a@u').setPosition(4), 0);
}
}
const beam1 = new Beam(notesBar1.slice(0, 8));
const beam2 = new Beam(notesBar1.slice(8, 16));
Formatter.FormatAndDraw(ctx, stave1, notesBar1);
beam1.setContext(ctx).drawWithStyle();
beam2.setContext(ctx).drawWithStyle();
const stave2 = new Stave(510, 50, 500).setContext(ctx).drawWithStyle();
const notesBar2 = [
f.StaveNote({ keys: ['f/3'], duration: '16', stemDirection: 1 }),
f.StaveNote({ keys: ['g/3'], duration: '16', stemDirection: 1 }),
f.StaveNote({ keys: ['a/3'], duration: '16', stemDirection: 1 }),
f.StaveNote({ keys: ['b/3'], duration: '16', stemDirection: 1 }),
f.StaveNote({ keys: ['c/4'], duration: '16', stemDirection: 1 }),
f.StaveNote({ keys: ['d/4'], duration: '16', stemDirection: 1 }),
f.StaveNote({ keys: ['e/4'], duration: '16', stemDirection: 1 }),
f.StaveNote({ keys: ['f/4'], duration: '16', stemDirection: 1 }),
f.StaveNote({ keys: ['g/4'], duration: '16', stemDirection: -1 }),
f.StaveNote({ keys: ['a/4'], duration: '16', stemDirection: -1 }),
f.StaveNote({ keys: ['b/4'], duration: '16', stemDirection: -1 }),
f.StaveNote({ keys: ['c/5'], duration: '16', stemDirection: -1 }),
f.StaveNote({ keys: ['d/5'], duration: '16', stemDirection: -1 }),
f.StaveNote({ keys: ['e/5'], duration: '16', stemDirection: -1 }),
f.StaveNote({ keys: ['f/5'], duration: '16', stemDirection: -1 }),
f.StaveNote({ keys: ['g/5'], duration: '16', stemDirection: -1 }),
];
for (i = 0; i < 16; i++) {
notesBar2[i].addModifier(new Articulation('a-').setPosition(3), 0);
notesBar2[i].addModifier(new Articulation('a^').setPosition(3), 0);
if (i === 15) {
notesBar2[i].addModifier(new Articulation('a@u').setPosition(4), 0);
}
}
const beam3 = new Beam(notesBar2.slice(0, 8));
const beam4 = new Beam(notesBar2.slice(8, 16));
Formatter.FormatAndDraw(ctx, stave2, notesBar2);
beam3.setContext(ctx).drawWithStyle();
beam4.setContext(ctx).drawWithStyle();
const stave3 = new Stave(1010, 50, 100).setContext(ctx).drawWithStyle();
const notesBar3 = [f.StaveNote({ keys: ['c/4'], duration: 'w', stemDirection: 1 })];
notesBar3[0].addModifier(new Articulation('a-').setPosition(3), 0);
notesBar3[0].addModifier(new Articulation('a>').setPosition(3), 0);
notesBar3[0].addModifier(new Articulation('a@a').setPosition(3), 0);
Formatter.FormatAndDraw(ctx, stave3, notesBar3);
const stave4 = new Stave(1110, 50, 250).setContext(ctx).drawWithStyle();
const notesBar4 = [
f.StaveNote({ keys: ['c/5'], duration: 'q', stemDirection: -1 }),
f.StaveNote({ keys: ['a/5'], duration: 'q', stemDirection: -1 }),
f.StaveNote({ keys: ['c/5'], duration: 'q', stemDirection: -1 }),
f.StaveNote({ keys: ['a/5'], duration: 'q', stemDirection: -1 }),
];
for (i = 0; i < 4; i++) {
let position1 = 3;
if (i > 1) {
position1 = 4;
}
notesBar4[i].addModifier(new Articulation('a-').setPosition(position1), 0);
}
Formatter.FormatAndDraw(ctx, stave4, notesBar4);
}
function tabNotes(options, contextBuilder) {
const ctx = contextBuilder(options.elementId, 600, 200);
ctx.font = '10pt ' + Metrics.get('fontFamily');
const stave = new TabStave(10, 10, 550);
stave.setContext(ctx);
stave.drawWithStyle();
const specs = [
{
positions: [
{ str: 3, fret: 6 },
{ str: 4, fret: 25 },
],
duration: '8',
},
{
positions: [
{ str: 2, fret: 10 },
{ str: 5, fret: 12 },
],
duration: '8',
},
{
positions: [
{ str: 1, fret: 6 },
{ str: 3, fret: 5 },
],
duration: '8',
},
{
positions: [
{ str: 1, fret: 6 },
{ str: 3, fret: 5 },
],
duration: '8',
},
];
const notes1 = specs.map((noteSpec) => {
const tabNote = new TabNote(noteSpec);
tabNote.renderOptions.drawStem = true;
return tabNote;
});
const notes2 = specs.map((noteSpec) => {
const tabNote = new TabNote(noteSpec);
tabNote.renderOptions.drawStem = true;
tabNote.setStemDirection(-1);
return tabNote;
});
const notes3 = specs.map((noteSpec) => new TabNote(noteSpec));
notes1[0].addModifier(new Articulation('a>').setPosition(3), 0);
notes1[1].addModifier(new Articulation('a>').setPosition(4), 0);
notes1[2].addModifier(new Articulation('a.').setPosition(3), 0);
notes1[3].addModifier(new Articulation('a.').setPosition(4), 0);
notes2[0].addModifier(new Articulation('a>').setPosition(3), 0);
notes2[1].addModifier(new Articulation('a>').setPosition(4), 0);
notes2[2].addModifier(new Articulation('a.').setPosition(3), 0);
notes2[3].addModifier(new Articulation('a.').setPosition(4), 0);
notes3[0].addModifier(new Articulation('a>').setPosition(3), 0);
notes3[1].addModifier(new Articulation('a>').setPosition(4), 0);
notes3[2].addModifier(new Articulation('a.').setPosition(3), 0);
notes3[3].addModifier(new Articulation('a.').setPosition(4), 0);
const voice = new Voice(VexFlow.TIME4_4).setMode(Voice.Mode.SOFT);
voice.addTickables(notes1);
voice.addTickables(notes2);
voice.addTickables(notes3);
new Formatter().joinVoices([voice]).formatToStave([voice], stave);
voice.draw(ctx, stave);
options.assert.ok(true, 'TabNotes successfully drawn');
}
VexFlowTests.register(ArticulationTests);
export { ArticulationTests };