vexflow
Version:
A JavaScript library for rendering music notation and guitar tablature.
463 lines (462 loc) • 13.8 kB
JavaScript
import { VexFlow } from '../src/vexflow.js';
import { VexFlowTests } from './vexflow_test_helpers.js';
import { Dot } from '../src/dot.js';
import { Formatter } from '../src/formatter.js';
import { Metrics } from '../src/metrics.js';
import { Stave } from '../src/stave.js';
import { TabNote } from '../src/tabnote.js';
import { TabStave } from '../src/tabstave.js';
import { TickContext } from '../src/tickcontext.js';
import { Voice, VoiceMode } from '../src/voice.js';
const TabNoteTests = {
Start() {
QUnit.module('TabNote');
QUnit.test('Tick', ticks);
QUnit.test('TabStave Line', tabStaveLine);
QUnit.test('Width', width);
const run = VexFlowTests.runTests;
run('TabNote Draw', draw);
run('TabNote Stems Up', drawStemsUp);
run('TabNote Stems Down', drawStemsDown);
run('TabNote Stems Up Through Stave', drawStemsUpThrough);
run('TabNote Stems Down Through Stave', drawStemsDownThrough);
run('TabNote Stems with Dots', drawStemsDotted);
},
};
function ticks(assert) {
const BEAT = (1 * VexFlow.RESOLUTION) / 4;
let note = new TabNote({ positions: [{ str: 6, fret: 6 }], duration: '1' });
assert.equal(note.getTicks().value(), BEAT * 4, 'Whole note has 4 beats');
note = new TabNote({ positions: [{ str: 3, fret: 4 }], duration: '4' });
assert.equal(note.getTicks().value(), BEAT, 'Quarter note has 1 beat');
}
function tabStaveLine(assert) {
const note = new TabNote({
positions: [
{ str: 6, fret: 6 },
{ str: 4, fret: 5 },
],
duration: '1',
});
const positions = note.getPositions();
assert.equal(positions[0].str, 6, 'String 6, Fret 6');
assert.equal(positions[0].fret, 6, 'String 6, Fret 6');
assert.equal(positions[1].str, 4, 'String 4, Fret 5');
assert.equal(positions[1].fret, 5, 'String 4, Fret 5');
const stave = new Stave(10, 10, 300);
note.setStave(stave);
const ys = note.getYs();
assert.equal(ys.length, 2, 'Chord should be rendered on two lines');
assert.equal(ys[0], 100, 'Line for String 6, Fret 6');
assert.equal(ys[1], 80, 'Line for String 4, Fret 5');
}
function width(assert) {
assert.expect(1);
const note = new TabNote({
positions: [
{ str: 6, fret: 6 },
{ str: 4, fret: 5 },
],
duration: '1',
});
assert.throws(() => note.getWidth(), /UnformattedNote/, 'Unformatted note should have no width');
}
function draw(options, contextBuilder) {
const ctx = contextBuilder(options.elementId, 600, 140);
ctx.font = '10pt Arial';
const stave = new TabStave(10, 10, 550);
stave.setContext(ctx);
stave.drawWithStyle();
const notes = [
{ positions: [{ str: 6, fret: 6 }], duration: '4' },
{
positions: [
{ str: 3, fret: 6 },
{ str: 4, fret: 25 },
],
duration: '4',
},
{
positions: [
{ str: 2, fret: 'x' },
{ str: 5, fret: 15 },
],
duration: '4',
},
{
positions: [
{ str: 2, fret: 'x' },
{ str: 5, fret: 5 },
],
duration: '4',
},
{
positions: [
{ str: 2, fret: 10 },
{ str: 5, fret: 12 },
],
duration: '4',
},
{
positions: [
{ str: 6, fret: 0 },
{ str: 5, fret: 5 },
{ str: 4, fret: 5 },
{ str: 3, fret: 4 },
{ str: 2, fret: 3 },
{ str: 1, fret: 0 },
],
duration: '4',
},
{
positions: [
{ str: 1, fret: 6 },
{ str: 4, fret: 5 },
],
duration: '4',
},
];
function showNote(noteStruct, stave, ctx, x) {
const tabNote = new TabNote(noteStruct);
const tickContext = new TickContext();
tickContext.addTickable(tabNote).preFormat().setX(x);
tabNote.setContext(ctx).setStave(stave);
tabNote.drawWithStyle();
return tabNote;
}
for (let i = 0; i < notes.length; ++i) {
const note = notes[i];
const tabNote = showNote(note, stave, ctx, (i + 1) * 25);
options.assert.ok(tabNote.getX() > 0, 'Note ' + i + ' has X value');
options.assert.ok(tabNote.getYs().length > 0, 'Note ' + i + ' has Y values');
}
}
function drawStemsUp(options, contextBuilder) {
const ctx = contextBuilder(options.elementId, 600, 200);
ctx.font = '10pt Arial';
const stave = new TabStave(10, 30, 550);
stave.setContext(ctx);
stave.drawWithStyle();
const specs = [
{
positions: [
{ str: 3, fret: 6 },
{ str: 4, fret: 25 },
],
duration: '4',
},
{
positions: [
{ str: 2, fret: 10 },
{ str: 5, fret: 12 },
],
duration: '8',
},
{
positions: [
{ str: 1, fret: 6 },
{ str: 4, fret: 5 },
],
duration: '8',
},
{
positions: [
{ str: 1, fret: 6 },
{ str: 4, fret: 5 },
],
duration: '16',
},
{
positions: [
{ str: 1, fret: 6 },
{ str: 4, fret: 5 },
],
duration: '32',
},
{
positions: [
{ str: 1, fret: 6 },
{ str: 4, fret: 5 },
],
duration: '64',
},
{
positions: [
{ str: 1, fret: 6 },
{ str: 4, fret: 5 },
],
duration: '128',
},
];
const notes = specs.map((struct) => {
const tabNote = new TabNote(struct);
tabNote.renderOptions.drawStem = true;
return tabNote;
});
const voice = new Voice(VexFlow.TIME4_4).setMode(VoiceMode.SOFT);
voice.addTickables(notes);
new Formatter().joinVoices([voice]).formatToStave([voice], stave);
voice.draw(ctx, stave);
options.assert.ok(true, 'TabNotes successfully drawn');
}
function drawStemsDown(options, contextBuilder) {
const ctx = contextBuilder(options.elementId, 600, 200);
ctx.font = '10pt Arial';
const stave = new TabStave(10, 10, 550);
stave.setContext(ctx);
stave.drawWithStyle();
const specs = [
{
positions: [
{ str: 3, fret: 6 },
{ str: 4, fret: 25 },
],
duration: '4',
},
{
positions: [
{ str: 2, fret: 10 },
{ str: 5, fret: 12 },
],
duration: '8',
},
{
positions: [
{ str: 1, fret: 6 },
{ str: 4, fret: 5 },
],
duration: '8',
},
{
positions: [
{ str: 1, fret: 6 },
{ str: 4, fret: 5 },
],
duration: '16',
},
{
positions: [
{ str: 1, fret: 6 },
{ str: 4, fret: 5 },
],
duration: '32',
},
{
positions: [
{ str: 1, fret: 6 },
{ str: 4, fret: 5 },
],
duration: '64',
},
{
positions: [
{ str: 1, fret: 6 },
{ str: 4, fret: 5 },
],
duration: '128',
},
];
const notes = specs.map((struct) => {
const tabNote = new TabNote(struct);
tabNote.renderOptions.drawStem = true;
tabNote.setStemDirection(-1);
return tabNote;
});
const voice = new Voice(VexFlow.TIME4_4).setMode(VoiceMode.SOFT);
voice.addTickables(notes);
new Formatter().joinVoices([voice]).formatToStave([voice], stave);
voice.draw(ctx, stave);
options.assert.ok(true, 'All objects have been drawn');
}
function drawStemsUpThrough(options, contextBuilder) {
const ctx = contextBuilder(options.elementId, 600, 200);
ctx.font = '10pt Arial';
const stave = new TabStave(10, 30, 550);
stave.setContext(ctx);
stave.drawWithStyle();
const specs = [
{
positions: [
{ str: 3, fret: 6 },
{ str: 4, fret: 25 },
],
duration: '4',
},
{
positions: [
{ str: 2, fret: 10 },
{ str: 5, fret: 12 },
],
duration: '8',
},
{
positions: [
{ str: 1, fret: 6 },
{ str: 4, fret: 5 },
],
duration: '8',
},
{
positions: [
{ str: 1, fret: 6 },
{ str: 4, fret: 5 },
],
duration: '16',
},
{
positions: [
{ str: 1, fret: 6 },
{ str: 4, fret: 5 },
],
duration: '32',
},
{
positions: [
{ str: 1, fret: 6 },
{ str: 4, fret: 5 },
],
duration: '64',
},
{
positions: [
{ str: 1, fret: 6 },
{ str: 4, fret: 5 },
],
duration: '128',
},
];
const notes = specs.map((struct) => {
const tabNote = new TabNote(struct);
tabNote.renderOptions.drawStem = true;
tabNote.renderOptions.drawStemThroughStave = true;
return tabNote;
});
ctx.setFont(Metrics.get('fontFamily'), 10, 'bold');
const voice = new Voice(VexFlow.TIME4_4).setMode(VoiceMode.SOFT);
voice.addTickables(notes);
new Formatter().joinVoices([voice]).formatToStave([voice], stave);
voice.draw(ctx, stave);
options.assert.ok(true, 'TabNotes successfully drawn');
}
function drawStemsDownThrough(options, contextBuilder) {
const ctx = contextBuilder(options.elementId, 600, 250);
ctx.font = '10pt Arial';
const stave = new TabStave(10, 10, 550, { numLines: 8 });
stave.setContext(ctx);
stave.drawWithStyle();
const specs = [
{
positions: [
{ str: 3, fret: 6 },
{ str: 4, fret: 25 },
],
duration: '4',
},
{
positions: [
{ str: 2, fret: 10 },
{ str: 5, fret: 12 },
],
duration: '8',
},
{
positions: [
{ str: 1, fret: 6 },
{ str: 4, fret: 5 },
],
duration: '8',
},
{
positions: [
{ str: 1, fret: 6 },
{ str: 4, fret: 5 },
],
duration: '16',
},
{
positions: [
{ str: 1, fret: 6 },
{ str: 4, fret: 5 },
{ str: 6, fret: 10 },
],
duration: '32',
},
{
positions: [
{ str: 1, fret: 6 },
{ str: 4, fret: 5 },
],
duration: '64',
},
{
positions: [
{ str: 1, fret: 6 },
{ str: 3, fret: 5 },
{ str: 5, fret: 5 },
{ str: 7, fret: 5 },
],
duration: '128',
},
];
const notes = specs.map((struct) => {
const tabNote = new TabNote(struct);
tabNote.renderOptions.drawStem = true;
tabNote.renderOptions.drawStemThroughStave = true;
tabNote.setStemDirection(-1);
return tabNote;
});
ctx.setFont('Arial', 10, 'bold');
const voice = new Voice(VexFlow.TIME4_4).setMode(VoiceMode.SOFT);
voice.addTickables(notes);
new Formatter().joinVoices([voice]).formatToStave([voice], stave);
voice.draw(ctx, stave);
options.assert.ok(true, 'All objects have been drawn');
}
function drawStemsDotted(options, contextBuilder) {
const ctx = contextBuilder(options.elementId, 600, 200);
ctx.font = '10pt Arial';
const stave = new TabStave(10, 10, 550);
stave.setContext(ctx);
stave.drawWithStyle();
const specs = [
{
positions: [
{ str: 3, fret: 6 },
{ str: 4, fret: 25 },
],
duration: '4d',
},
{
positions: [
{ str: 2, fret: 10 },
{ str: 5, fret: 12 },
],
duration: '8',
},
{
positions: [
{ str: 1, fret: 6 },
{ str: 4, fret: 5 },
],
duration: '4dd',
stemDirection: -1,
},
{
positions: [
{ str: 1, fret: 6 },
{ str: 4, fret: 5 },
],
duration: '16',
stemDirection: -1,
},
];
const notes = specs.map((struct) => new TabNote(struct, true));
Dot.buildAndAttach([notes[0], notes[2], notes[2]]);
const voice = new Voice(VexFlow.TIME4_4).setMode(VoiceMode.SOFT);
voice.addTickables(notes);
new Formatter().joinVoices([voice]).formatToStave([voice], stave);
voice.draw(ctx, stave);
options.assert.ok(true, 'TabNotes successfully drawn');
}
VexFlowTests.register(TabNoteTests);
export { TabNoteTests };