UNPKG

vexflow

Version:

A JavaScript library for rendering music notation and guitar tablature.

382 lines (381 loc) 19.6 kB
import { VexFlow } from '../src/vexflow.js'; import { VexFlowTests } from './vexflow_test_helpers.js'; import { Annotation, AnnotationHorizontalJustify, AnnotationVerticalJustify } from '../src/annotation.js'; import { Articulation } from '../src/articulation.js'; import { Beam } from '../src/beam.js'; import { Bend } from '../src/bend.js'; import { Formatter } from '../src/formatter.js'; import { Metrics } from '../src/metrics.js'; import { ModifierPosition } from '../src/modifier.js'; import { Registry } from '../src/registry.js'; import { Stave } from '../src/stave.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 { Vibrato } from '../src/vibrato.js'; import { Voice } from '../src/voice.js'; const AnnotationTests = { Start() { QUnit.module('Annotation'); const run = VexFlowTests.runTests; run('Bounding Box', placement, { drawBoundingBox: true }); run('Placement', placement, { drawBoundingBox: false }); run('Lyrics', lyrics); run('Simple Annotation', simple); run('Styled Annotation', styling); run('Standard Notation Annotation', standard); run('Harmonics', harmonic); run('Fingerpicking', picking); run('Bottom Annotation', bottom); run('Bottom Annotations with Beams', bottomWithBeam); run('Test Justification Annotation Stem Up', justificationStemUp); run('Test Justification Annotation Stem Down', justificationStemDown); run('TabNote Annotations', tabNotes); }, }; const FONT_SIZE = VexFlowTests.Font.size; const tabNote = (noteStruct) => new TabNote(noteStruct); const staveNote = (noteStruct) => new StaveNote(noteStruct); function lyrics(options) { let fontSize = FONT_SIZE; let x = 10; let width = 170; let ratio = 1; const registry = new Registry(); Registry.enableDefaultRegistry(registry); const f = VexFlowTests.makeFactory(options, 750, 260); for (let i = 0; i < 3; ++i) { const score = f.EasyScore(); score.set({ time: '3/4' }); const system = f.System({ width, x }); system.addStave({ voices: [ score.voice(score.notes('(C4 F4)/2[id="n0"]').concat(score.beam(score.notes('(C4 A4)/8[id="n1"], (C4 A4)/8[id="n2"]')))), ], }); ['Handily', 'and', 'me', 'Pears', 'lead', 'the'].forEach((text, ix) => { const verse = Math.floor(ix / 3); const noteGroupID = 'n' + (ix % 3); const noteGroup = registry.getElementById(noteGroupID); const lyricsAnnotation = f.Annotation({ text }).setFontSize(fontSize); lyricsAnnotation.setPosition(ModifierPosition.BELOW); if (ix % 3 === 0) lyricsAnnotation.setJustification(AnnotationHorizontalJustify.LEFT); noteGroup.addModifier(lyricsAnnotation, verse); }); system.addStave({ voices: [score.voice(score.notes('(F4 D5)/2').concat(score.beam(score.notes('(F4 F5)/8, (F4 F5)/8'))))], }); f.draw(); ratio = (fontSize + 2) / fontSize; width = width * ratio; x = x + width; fontSize = fontSize + 2; } options.assert.ok(true); } function simple(options, contextBuilder) { const ctx = contextBuilder(options.elementId, 500, 240); ctx.scale(1.5, 1.5); ctx.font = '10pt Arial, sans-serif'; const stave = new TabStave(10, 10, 450).addTabGlyph().setContext(ctx).drawWithStyle(); const notes = [ tabNote({ positions: [ { str: 2, fret: 10 }, { str: 4, fret: 9 }, ], duration: 'h', }).addModifier(new Annotation('T'), 0), tabNote({ positions: [{ str: 2, fret: 10 }], duration: 'h', }).addModifier(new Bend([{ type: Bend.UP, text: 'Full' }]).setTap('T'), 0), ]; Formatter.FormatAndDraw(ctx, stave, notes); options.assert.ok(true, 'Simple Annotation'); } function standard(options, contextBuilder) { const ctx = contextBuilder(options.elementId, 500, 240); ctx.scale(1.5, 1.5); const stave = new Stave(10, 10, 450).addClef('treble').setContext(ctx).drawWithStyle(); const annotation = (text) => new Annotation(text).setFont(Metrics.get('Annotation.fontFamily'), FONT_SIZE, 'normal', 'italic'); const notes = [ staveNote({ keys: ['c/4', 'e/4'], duration: 'h' }).addModifier(annotation('quiet'), 0), staveNote({ keys: ['c/4', 'e/4', 'c/5'], duration: 'h' }).addModifier(annotation('Allegro'), 2), ]; Formatter.FormatAndDraw(ctx, stave, notes); options.assert.ok(true, 'Standard Notation Annotation'); } function styling(options, contextBuilder) { const ctx = contextBuilder(options.elementId, 500, 240); ctx.scale(1.5, 1.5); const stave = new Stave(10, 10, 450).addClef('treble').setContext(ctx).drawWithStyle(); const annotation = (text, style) => new Annotation(text).setFont(Metrics.get('Annotation.fontFamily'), FONT_SIZE, 'normal', 'italic').setStyle(style); const notes = [ staveNote({ keys: ['c/4', 'e/4'], duration: 'h' }).addModifier(annotation('quiet', { fillStyle: '#0F0' }), 0), staveNote({ keys: ['c/4', 'e/4', 'c/5'], duration: 'h' }).addModifier(annotation('Allegro', { fillStyle: '#00F' }), 2), ]; Formatter.FormatAndDraw(ctx, stave, notes); options.assert.ok(true, 'Standard Notation Annotation'); } function harmonic(options, contextBuilder) { const ctx = contextBuilder(options.elementId, 500, 240); ctx.scale(1.5, 1.5); ctx.font = '10pt Arial'; const stave = new TabStave(10, 10, 450).addClef('tab').setContext(ctx).drawWithStyle(); const notes = [ tabNote({ positions: [ { str: 2, fret: 12 }, { str: 3, fret: 12 }, ], duration: 'h', }).addModifier(new Annotation('Harm.'), 0), tabNote({ positions: [{ str: 2, fret: 9 }], duration: 'h', }) .addModifier(new Annotation('(8va)').setFont(Metrics.get('Annotation.fontFamily'), FONT_SIZE, 'normal', 'italic'), 0) .addModifier(new Annotation('A.H.'), 0), ]; Formatter.FormatAndDraw(ctx, stave, notes); options.assert.ok(true, 'Simple Annotation'); } function picking(options, contextBuilder) { const ctx = contextBuilder(options.elementId, 500, 240); ctx.setFont(Metrics.get('fontFamily'), FONT_SIZE); const stave = new TabStave(10, 10, 450).addClef('tab').setContext(ctx).drawWithStyle(); const annotation = (text) => new Annotation(text).setFont(Metrics.get('Annotation.fontFamily'), FONT_SIZE, 'normal', 'italic'); const notes = [ tabNote({ positions: [ { str: 1, fret: 0 }, { str: 2, fret: 1 }, { str: 3, fret: 2 }, { str: 4, fret: 2 }, { str: 5, fret: 0 }, ], duration: 'h', }).addModifier(new Vibrato().setVibratoWidth(40), 0), tabNote({ positions: [{ str: 6, fret: 9 }], duration: '8', }).addModifier(annotation('p').setVerticalJustification(AnnotationVerticalJustify.TOP), 0), tabNote({ positions: [{ str: 3, fret: 9 }], duration: '8', }).addModifier(annotation('i').setVerticalJustification(AnnotationVerticalJustify.TOP), 0), tabNote({ positions: [{ str: 2, fret: 9 }], duration: '8', }).addModifier(annotation('m').setVerticalJustification(AnnotationVerticalJustify.TOP), 0), tabNote({ positions: [{ str: 1, fret: 9 }], duration: '8', }).addModifier(annotation('a').setVerticalJustification(AnnotationVerticalJustify.TOP), 0), ]; Formatter.FormatAndDraw(ctx, stave, notes); options.assert.ok(true, 'Fingerpicking'); } function placement(options, contextBuilder) { const ctx = contextBuilder(options.elementId, 750, 300); const stave = new Stave(10, 50, 750).addClef('treble').setContext(ctx).drawWithStyle(); const annotation = (text, fontSize, vj) => new Annotation(text).setFontSize(fontSize).setVerticalJustification(vj); const notes = [ staveNote({ keys: ['e/4'], duration: 'q', stemDirection: Stem.DOWN }) .addModifier(new Articulation('a.').setPosition(ModifierPosition.ABOVE), 0) .addModifier(new Articulation('a-').setPosition(ModifierPosition.ABOVE), 0) .addModifier(annotation('v1', 10, AnnotationVerticalJustify.TOP), 0) .addModifier(annotation('v2', 10, AnnotationVerticalJustify.TOP), 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(annotation('v1', 10, AnnotationVerticalJustify.TOP), 0) .addModifier(annotation('v2', 10, AnnotationVerticalJustify.TOP), 0), staveNote({ keys: ['c/5'], duration: 'q', stemDirection: Stem.DOWN }) .addModifier(new Articulation('a.').setPosition(ModifierPosition.ABOVE), 0) .addModifier(new Articulation('a-').setPosition(ModifierPosition.ABOVE), 0) .addModifier(annotation('v1', 10, AnnotationVerticalJustify.TOP), 0) .addModifier(annotation('v2', 10, AnnotationVerticalJustify.TOP), 0), staveNote({ keys: ['f/4'], duration: 'q' }) .addModifier(annotation('v1', 14, AnnotationVerticalJustify.TOP), 0) .addModifier(annotation('v2', 14, AnnotationVerticalJustify.TOP), 0), staveNote({ keys: ['f/4'], duration: 'q', stemDirection: Stem.DOWN }) .addModifier(new Articulation('am').setPosition(ModifierPosition.ABOVE), 0) .addModifier(new Articulation('a.').setPosition(ModifierPosition.ABOVE), 0) .addModifier(new Articulation('a-').setPosition(ModifierPosition.ABOVE), 0) .addModifier(annotation('v1', 10, AnnotationVerticalJustify.TOP), 0) .addModifier(annotation('v2', 20, AnnotationVerticalJustify.TOP), 0), staveNote({ keys: ['f/5'], duration: 'q' }) .addModifier(annotation('v1', 11, AnnotationVerticalJustify.TOP), 0) .addModifier(annotation('v2', 11, AnnotationVerticalJustify.TOP), 0), staveNote({ keys: ['f/5'], duration: 'q' }) .addModifier(annotation('v1', 11, AnnotationVerticalJustify.TOP), 0) .addModifier(annotation('v2', 20, AnnotationVerticalJustify.TOP), 0), staveNote({ keys: ['f/4'], duration: 'q' }) .addModifier(annotation('v1', 12, AnnotationVerticalJustify.BOTTOM), 0) .addModifier(annotation('v2', 12, AnnotationVerticalJustify.BOTTOM), 0), staveNote({ keys: ['f/5'], duration: 'q' }) .addModifier(new Articulation('a.').setPosition(ModifierPosition.BELOW), 0) .addModifier(annotation('v1', 11, AnnotationVerticalJustify.BOTTOM), 0) .addModifier(annotation('v2', 20, AnnotationVerticalJustify.BOTTOM), 0), staveNote({ keys: ['f/5'], duration: 'q', stemDirection: Stem.DOWN }) .addModifier(new Articulation('am').setPosition(ModifierPosition.BELOW), 0) .addModifier(annotation('v1', 10, AnnotationVerticalJustify.BOTTOM), 0) .addModifier(annotation('v2', 20, AnnotationVerticalJustify.BOTTOM), 0), staveNote({ keys: ['f/4'], duration: 'q', stemDirection: Stem.DOWN }) .addModifier(annotation('v1', 10, AnnotationVerticalJustify.BOTTOM), 0) .addModifier(annotation('v2', 20, AnnotationVerticalJustify.BOTTOM), 0), staveNote({ keys: ['f/5'], duration: 'w' }) .addModifier(new Articulation('a@u').setPosition(ModifierPosition.BELOW), 0) .addModifier(annotation('v1', 11, AnnotationVerticalJustify.BOTTOM), 0) .addModifier(annotation('v2', 16, AnnotationVerticalJustify.BOTTOM), 0), ]; Formatter.FormatAndDraw(ctx, stave, notes); if (options.params.drawBoundingBox === true) { notes.forEach((note) => { const elements = note.getModifiersByType('Annotation'); elements.forEach((element) => VexFlowTests.drawBoundingBox(ctx, element)); }); } options.assert.ok(true, ' Annotation Placement'); } function bottom(options, contextBuilder) { const ctx = contextBuilder(options.elementId, 500, 240); ctx.scale(1.5, 1.5); const stave = new Stave(10, 10, 300).addClef('treble').setContext(ctx).drawWithStyle(); const annotation = (text) => new Annotation(text).setFontSize(FONT_SIZE).setVerticalJustification(Annotation.VerticalJustify.BOTTOM); const notes = [ staveNote({ keys: ['f/4'], duration: 'w' }).addModifier(annotation('F'), 0), staveNote({ keys: ['a/4'], duration: 'w' }).addModifier(annotation('A'), 0), staveNote({ keys: ['c/5'], duration: 'w' }).addModifier(annotation('C'), 0), staveNote({ keys: ['e/5'], duration: 'w' }).addModifier(annotation('E'), 0), ]; Formatter.FormatAndDraw(ctx, stave, notes); options.assert.ok(true, 'Bottom Annotation'); } function bottomWithBeam(options, contextBuilder) { const ctx = contextBuilder(options.elementId, 500, 240); ctx.scale(1.5, 1.5); const stave = new Stave(10, 10, 300).addClef('treble').setContext(ctx).drawWithStyle(); const notes = [ new StaveNote({ keys: ['a/3'], duration: '8' }).addModifier(new Annotation('good').setVerticalJustification(Annotation.VerticalJustify.BOTTOM)), new StaveNote({ keys: ['g/3'], duration: '8' }).addModifier(new Annotation('even').setVerticalJustification(Annotation.VerticalJustify.BOTTOM)), new StaveNote({ keys: ['c/4'], duration: '8' }).addModifier(new Annotation('under').setVerticalJustification(Annotation.VerticalJustify.BOTTOM)), new StaveNote({ keys: ['d/4'], duration: '8' }).addModifier(new Annotation('beam').setVerticalJustification(Annotation.VerticalJustify.BOTTOM)), ]; const beam = new Beam(notes.slice(1)); Formatter.FormatAndDraw(ctx, stave, notes); beam.setContext(ctx).drawWithStyle(); options.assert.ok(true, 'Bottom Annotation with Beams'); } function justificationStemUp(options, contextBuilder) { const ctx = contextBuilder(options.elementId, 650, 950); ctx.scale(1.5, 1.5); const annotation = (text, hJustification, vJustification) => new Annotation(text) .setFontSize(FONT_SIZE) .setJustification(hJustification) .setVerticalJustification(vJustification); for (let v = 1; v <= 4; ++v) { const stave = new Stave(10, (v - 1) * 150 + 40, 400).addClef('treble').setContext(ctx).drawWithStyle(); const notes = [ staveNote({ keys: ['c/3'], duration: 'q' }).addModifier(annotation('Text', 1, v), 0), staveNote({ keys: ['c/4'], duration: 'q' }).addModifier(annotation('Text', 2, v), 0), staveNote({ keys: ['c/4', 'e/4', 'c/5'], duration: 'q' }).addModifier(annotation('Text', 3, v), 0), staveNote({ keys: ['c/6'], duration: 'q' }).addModifier(annotation('Text', 4, v), 0), ]; Formatter.FormatAndDraw(ctx, stave, notes); } options.assert.ok(true, 'Test Justification Annotation'); } function justificationStemDown(options, contextBuilder) { const ctx = contextBuilder(options.elementId, 650, 1000); ctx.scale(1.5, 1.5); const annotation = (text, hJustification, vJustification) => new Annotation(text) .setFontSize(FONT_SIZE) .setJustification(hJustification) .setVerticalJustification(vJustification); for (let v = 1; v <= 4; ++v) { const stave = new Stave(10, (v - 1) * 150 + 40, 400).addClef('treble').setContext(ctx).drawWithStyle(); const notes = [ staveNote({ keys: ['c/3'], duration: 'q', stemDirection: -1 }).addModifier(annotation('Text', 1, v), 0), staveNote({ keys: ['c/4', 'e/4', 'c/5'], duration: 'q', stemDirection: -1 }).addModifier(annotation('Text', 2, v), 0), staveNote({ keys: ['c/5'], duration: 'q', stemDirection: -1 }).addModifier(annotation('Text', 3, v), 0), staveNote({ keys: ['c/6'], duration: 'q', stemDirection: -1 }).addModifier(annotation('Text', 4, v), 0), ]; Formatter.FormatAndDraw(ctx, stave, notes); } options.assert.ok(true, 'Test Justification Annotation'); } function tabNotes(options, contextBuilder) { const ctx = contextBuilder(options.elementId, 600, 200); ctx.font = '10pt Arial, sans-serif'; 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 note = new TabNote(noteSpec); note.renderOptions.drawStem = true; return note; }); const notes2 = specs.map((noteSpec) => { const note = new TabNote(noteSpec); note.renderOptions.drawStem = true; note.setStemDirection(-1); return note; }); const notes3 = specs.map((noteSpec) => new TabNote(noteSpec)); notes1[0].addModifier(new Annotation('Text').setJustification(1).setVerticalJustification(1)); notes1[1].addModifier(new Annotation('Text').setJustification(2).setVerticalJustification(2)); notes1[2].addModifier(new Annotation('Text').setJustification(3).setVerticalJustification(3)); notes1[3].addModifier(new Annotation('Text').setJustification(4).setVerticalJustification(4)); notes2[0].addModifier(new Annotation('Text').setJustification(3).setVerticalJustification(1)); notes2[1].addModifier(new Annotation('Text').setJustification(3).setVerticalJustification(2)); notes2[2].addModifier(new Annotation('Text').setJustification(3).setVerticalJustification(3)); notes2[3].addModifier(new Annotation('Text').setJustification(3).setVerticalJustification(4)); notes3[0].addModifier(new Annotation('Text').setVerticalJustification(1)); notes3[1].addModifier(new Annotation('Text').setVerticalJustification(2)); notes3[2].addModifier(new Annotation('Text').setVerticalJustification(3)); notes3[3].addModifier(new Annotation('Text').setVerticalJustification(4)); 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, { stave }); voice.draw(ctx, stave); options.assert.ok(true, 'TabNotes successfully drawn'); } VexFlowTests.register(AnnotationTests); export { AnnotationTests };