UNPKG

vexflow

Version:

A JavaScript library for rendering music notation and guitar tablature

1,408 lines (1,160 loc) 530 kB
/**! * VexFlow 3.0.9 built on 2020-04-21. * Copyright (c) 2010 Mohit Muthanna Cheppudira <mohit@muthanna.com> * * http://www.vexflow.com http://github.com/0xfe/vexflow *//** * VexFlow Test Support Library * Copyright Mohit Muthanna 2010 <mohit@muthanna.com> */ /* eslint-disable global-require, import/no-unresolved, import/no-extraneous-dependencies */ /* eslint max-classes-per-file: "off" */ // Mock out the QUnit stuff for generating svg images, // since we don't really care about the assertions. if (!window.QUnit) { window.QUnit = {}; QUnit = window.QUnit; QUnit.assertions = { ok: () => true, equal: () => true, deepEqual: () => true, expect: () => true, throws: () => true, notOk: () => true, notEqual: () => true, notDeepEqual: () => true, strictEqual: () => true, notStrictEqual: () => true, }; QUnit.module = (name) => { QUnit.current_module = name; }; /* eslint-disable */ QUnit.test = (name, func) => { QUnit.current_test = name; process.stdout.write(" \u001B[0G" + QUnit.current_module + " :: " + name + "\u001B[0K"); func(QUnit.assertions); }; test = QUnit.test; ok = QUnit.assertions.ok; equal = QUnit.assertions.equal; deepEqual = QUnit.assertions.deepEqual; expect = QUnit.assertions.expect; throws = QUnit.assertions.throws; notOk = QUnit.assertions.notOk; notEqual = QUnit.assertions.notEqual; notDeepEqual = QUnit.assertions.notDeepEqual; strictEqual = QUnit.assertions.strictEqual; notStrictEqual = QUnit.assertions.notStrictEqual; } if (typeof require === 'function') { Vex = require('./vexflow-debug.js'); } var VF = Vex.Flow; VF.Test = (function () { var Test = { // Test Options. RUN_CANVAS_TESTS: true, RUN_SVG_TESTS: true, RUN_RAPHAEL_TESTS: false, RUN_NODE_TESTS: false, // Where images are stored for NodeJS tests. NODE_IMAGEDIR: 'images', // Default font properties for tests. Font: { size: 10 }, // Returns a unique ID for a test. genID: function (prefix) { return prefix + VF.Test.genID.ID++; }, genTitle: function (type, assert, name) { return assert.test.module.name + ' (' + type + '): ' + name; }, // Run `func` inside a QUnit test for each of the enabled // rendering backends. runTests: function (name, func, params) { if (VF.Test.RUN_CANVAS_TESTS) { VF.Test.runCanvasTest(name, func, params); } if (VF.Test.RUN_SVG_TESTS) { VF.Test.runSVGTest(name, func, params); } if (VF.Test.RUN_RAPHAEL_TESTS) { VF.Test.runRaphaelTest(name, func, params); } if (VF.Test.RUN_NODE_TESTS) { VF.Test.runNodeTest(name, func, params); } }, // Run `func` inside a QUnit test for each of the enabled // rendering backends. These are for interactivity tests, and // currently only work with the SVG backend. runUITests: function (name, func, params) { if (VF.Test.RUN_SVG_TESTS) { VF.Test.runSVGTest(name, func, params); } }, createTestCanvas: function (testId, testName) { var testContainer = $('<div></div>').addClass('testcanvas'); testContainer.append( $('<div></div>') .addClass('name') .text(testName) ); testContainer.append( $('<canvas></canvas>') .addClass('vex-tabdiv') .attr('id', testId) .addClass('name') .text(name) ); $(VF.Test.testRootSelector).append(testContainer); }, createTestSVG: function (testId, testName) { var testContainer = $('<div></div>').addClass('testcanvas'); testContainer.append( $('<div></div>') .addClass('name') .text(testName) ); testContainer.append( $('<div></div>') .addClass('vex-tabdiv') .attr('id', testId) ); $(VF.Test.testRootSelector).append(testContainer); }, resizeCanvas: function (elementId, width, height) { $('#' + elementId).width(width); $('#' + elementId).attr('width', width); $('#' + elementId).attr('height', height); }, makeFactory: function (options, width, height) { return new VF.Factory({ renderer: { elementId: options.elementId, backend: options.backend, width: width || 450, height: height || 140, }, }); }, runCanvasTest: function (name, func, params) { QUnit.test(name, function (assert) { var elementId = VF.Test.genID('canvas_'); var title = VF.Test.genTitle('Canvas', assert, name); VF.Test.createTestCanvas(elementId, title); var testOptions = { backend: VF.Renderer.Backends.CANVAS, elementId: elementId, params: params, assert: assert, }; func(testOptions, VF.Renderer.getCanvasContext); }); }, runRaphaelTest: function (name, func, params) { QUnit.test(name, function (assert) { var elementId = VF.Test.genID('raphael_'); var title = VF.Test.genTitle('Raphael', assert, name); VF.Test.createTestSVG(elementId, title); var testOptions = { elementId: elementId, backend: VF.Renderer.Backends.RAPHAEL, params: params, assert: assert, }; func(testOptions, VF.Renderer.getRaphaelContext); }); }, runSVGTest: function (name, func, params) { if (!VF.Test.RUN_SVG_TESTS) return; const fontStacks = { Bravura: [VF.Fonts.Bravura, VF.Fonts.Gonville, VF.Fonts.Custom], Gonville: [VF.Fonts.Gonville, VF.Fonts.Bravura, VF.Fonts.Custom], Petaluma: [VF.Fonts.Petaluma, VF.Fonts.Gonville, VF.Fonts.Custom], } const testFunc = (fontName) => (assert) => { const defaultFontStack = VF.DEFAULT_FONT_STACK; VF.DEFAULT_FONT_STACK = fontStacks[fontName]; var elementId = VF.Test.genID('svg_'+fontName); var title = VF.Test.genTitle('SVG '+fontName, assert, name); VF.Test.createTestSVG(elementId, title); var testOptions = { elementId: elementId, backend: VF.Renderer.Backends.SVG, params: params, assert: assert, }; func(testOptions, VF.Renderer.getSVGContext); VF.DEFAULT_FONT_STACK = defaultFontStack; } QUnit.test(name, testFunc('Bravura')); QUnit.test(name, testFunc('Gonville')); QUnit.test(name, testFunc('Petaluma')); }, runNodeTest: function (name, func, params) { var fs = require('fs'); // Allows `name` to be used inside file names. function sanitizeName(name) { return name.replace(/[^a-zA-Z0-9]/g, '_'); } QUnit.test(name, function (assert) { var elementId = VF.Test.genID('nodecanvas_'); var canvas = document.createElement('canvas'); canvas.setAttribute('id', elementId); document.body.appendChild(canvas); var testOptions = { elementId: elementId, backend: VF.Renderer.Backends.CANVAS, params: params, assert: assert, }; func(testOptions, VF.Renderer.getCanvasContext); if (VF.Renderer.lastContext !== null) { var moduleName = sanitizeName(QUnit.current_module); var testName = sanitizeName(QUnit.current_test); var fileName = `${VF.Test.NODE_IMAGEDIR}/${moduleName}.${testName}.png`; var imageData = canvas.toDataURL().split(';base64,').pop(); var image = Buffer.from(imageData, 'base64'); fs.writeFileSync(fileName, image, { encoding: 'base64' }); } }); }, plotNoteWidth: VF.Note.plotMetrics, plotLegendForNoteWidth: function (ctx, x, y) { ctx.save(); ctx.setFont('Arial', 8, ''); var spacing = 12; var lastY = y; function legend(color, text) { ctx.beginPath(); ctx.setStrokeStyle(color); ctx.setFillStyle(color); ctx.setLineWidth(10); ctx.moveTo(x, lastY - 4); ctx.lineTo(x + 10, lastY - 4); ctx.stroke(); ctx.setFillStyle('black'); ctx.fillText(text, x + 15, lastY); lastY += spacing; } legend('green', 'Note + Flag'); legend('red', 'Modifiers'); legend('#999', 'Displaced Head'); legend('#DDD', 'Formatter Shift'); ctx.restore(); }, almostEqual: function (value, expectedValue, errorMargin) { return equal(Math.abs(value - expectedValue) < errorMargin, true); }, }; Test.genID.ID = 0; Test.testRootSelector = '#vexflow_testoutput'; return Test; }()); /** * VexFlow - TickContext Mocks * Copyright Mohit Muthanna 2010 <mohit@muthanna.com> */ /* Mock Tickable */ VF.Test.MockTickable = (function() { function MockTickable() { this.ignore_ticks = false; } MockTickable.prototype = { init: function() {}, getX: function() { return this.tickContext.getX(); }, getIntrinsicTicks: function() { return this.ticks; }, getTicks: function() { return this.ticks; }, setTicks: function(t) { this.ticks = new VF.Fraction(t, 1); return this; }, getMetrics: function() { return { width: 0, glyphWidth: 0, notePx: this.width, left_shift: 0, modLeftPx: 0, modRightPx: 0, leftDisplacedHeadPx: 0, rightDisplacedHeadPx: 0, }; }, getWidth: function() { return this.width; }, setWidth: function(w) { this.width = w; return this; }, setVoice: function(v) { this.voice = v; return this; }, setStave: function(stave) { this.stave = stave; return this; }, setTickContext: function(tc) { this.tickContext = tc; return this; }, setIgnoreTicks: function(ignore_ticks) { this.ignore_ticks = ignore_ticks; return this; }, shouldIgnoreTicks: function() { return this.ignore_ticks; }, preFormat: function() {}, }; return MockTickable; })(); /** * VexFlow - Accidental Tests * Copyright Mohit Muthanna 2010 <mohit@muthanna.com> */ Vex.Flow.Test.Accidental = (function() { function hasAccidental(note) { return note.modifiers.reduce(function(hasAcc, modifier) { return hasAcc || modifier.getCategory() === 'accidentals'; }, false); } // newAccid factory function makeNewAccid(factory) { return function(accidType) { return factory.Accidental({ type: accidType }); }; } var Accidental = { Start: function() { QUnit.module('Accidental'); Vex.Flow.Test.runTests('Basic', Vex.Flow.Test.Accidental.basic); Vex.Flow.Test.runTests('Stem Down', Vex.Flow.Test.Accidental.basicStemDown); Vex.Flow.Test.runTests('Cautionary Accidental', Vex.Flow.Test.Accidental.cautionary); Vex.Flow.Test.runTests('Accidental Arrangement Special Cases', Vex.Flow.Test.Accidental.specialCases); Vex.Flow.Test.runTests('Multi Voice', Vex.Flow.Test.Accidental.multiVoice); Vex.Flow.Test.runTests('Microtonal', Vex.Flow.Test.Accidental.microtonal); Vex.Flow.Test.runTests('Microtonal (Iranian)', Vex.Flow.Test.Accidental.microtonal_iranian); test('Automatic Accidentals - Simple Tests', Vex.Flow.Test.Accidental.autoAccidentalWorking); Vex.Flow.Test.runTests('Automatic Accidentals', Vex.Flow.Test.Accidental.automaticAccidentals0); Vex.Flow.Test.runTests('Automatic Accidentals - C major scale in Ab', Vex.Flow.Test.Accidental.automaticAccidentals1); Vex.Flow.Test.runTests('Automatic Accidentals - No Accidentals Necsesary', Vex.Flow.Test.Accidental.automaticAccidentals2); Vex.Flow.Test.runTests('Automatic Accidentals - Multi Voice Inline', Vex.Flow.Test.Accidental.automaticAccidentalsMultiVoiceInline); Vex.Flow.Test.runTests('Automatic Accidentals - Multi Voice Offset', Vex.Flow.Test.Accidental.automaticAccidentalsMultiVoiceOffset); Vex.Flow.Test.runTests('Factory API', Vex.Flow.Test.Accidental.factoryAPI); }, basic: function(options) { var vf = VF.Test.makeFactory(options, 700, 240); var newAccid = makeNewAccid(vf); vf.Stave({ x: 10, y: 10, width: 550 }); var notes = [ vf.StaveNote({ keys: ['c/4', 'e/4', 'a/4'], duration: '1' }) .addAccidental(0, newAccid('b')) .addAccidental(1, newAccid('#')), vf.StaveNote({ keys: ['d/4', 'e/4', 'f/4', 'a/4', 'c/5', 'e/5', 'g/5'], duration: '2' }) .addAccidental(0, newAccid('##')) .addAccidental(1, newAccid('n')) .addAccidental(2, newAccid('bb')) .addAccidental(3, newAccid('b')) .addAccidental(4, newAccid('#')) .addAccidental(5, newAccid('n')) .addAccidental(6, newAccid('bb')), vf.StaveNote({ keys: ['f/4', 'g/4', 'a/4', 'b/4', 'c/5', 'e/5', 'g/5'], duration: '16' }) .addAccidental(0, newAccid('n')) .addAccidental(1, newAccid('#')) .addAccidental(2, newAccid('#')) .addAccidental(3, newAccid('b')) .addAccidental(4, newAccid('bb')) .addAccidental(5, newAccid('##')) .addAccidental(6, newAccid('#')), vf.StaveNote({ keys: ['a/3', 'c/4', 'e/4', 'b/4', 'd/5', 'g/5'], duration: '1' }) .addAccidental(0, newAccid('#')) .addAccidental(1, newAccid('##').setAsCautionary()) .addAccidental(2, newAccid('#').setAsCautionary()) .addAccidental(3, newAccid('b')) .addAccidental(4, newAccid('bb').setAsCautionary()) .addAccidental(5, newAccid('b').setAsCautionary()), ]; VF.Formatter.SimpleFormat(notes, 10, { paddingBetween: 45 }); notes.forEach(function(note, index) { Vex.Flow.Test.plotNoteWidth(vf.getContext(), note, 140); ok(note.getAccidentals().length > 0, 'Note ' + index + ' has accidentals'); note.getAccidentals().forEach(function(accid, index) { ok(accid.getWidth() > 0, 'Accidental ' + index + ' has set width'); }); }); vf.draw(); Vex.Flow.Test.plotLegendForNoteWidth(vf.getContext(), 480, 140); ok(true, 'Full Accidental'); }, cautionary: function(options) { var vf = VF.Test.makeFactory(options, 800, 240); var stave = vf.Stave({ x: 0, y: 10, width: 780 }); var score = vf.EasyScore(); var accids = Object .keys(VF.accidentalCodes.accidentals) .filter(function(accid) { return accid !== '{' && accid !== '}'; }); var notes = accids .map(function(accid) { return vf .StaveNote({ keys: ['a/4'], duration: '4', stem_direction: VF.Stem.UP }) .addAccidental(0, vf.Accidental({ type: accid })); }); var voice = score.voice(notes, { time: accids.length + '/4' }); voice .getTickables() .forEach(function(tickable) { tickable.modifiers .filter(function(modifier) { return modifier.getAttribute('type') === 'Accidental'; }) .forEach(function(accid) { accid.setAsCautionary(); }); }); vf.Formatter() .joinVoices([voice]) .formatToStave([voice], stave); vf.draw(); ok(true, 'Must successfully render cautionary accidentals'); }, specialCases: function(options) { var vf = VF.Test.makeFactory(options, 700, 240); var newAccid = makeNewAccid(vf); vf.Stave({ x: 10, y: 10, width: 550 }); var notes = [ vf.StaveNote({ keys: ['f/4', 'd/5'], duration: '1' }) .addAccidental(0, newAccid('#')) .addAccidental(1, newAccid('b')), vf.StaveNote({ keys: ['c/4', 'g/4'], duration: '2' }) .addAccidental(0, newAccid('##')) .addAccidental(1, newAccid('##')), vf.StaveNote({ keys: ['b/3', 'd/4', 'f/4'], duration: '16' }) .addAccidental(0, newAccid('#')) .addAccidental(1, newAccid('#')) .addAccidental(2, newAccid('##')), vf.StaveNote({ keys: ['g/4', 'a/4', 'c/5', 'e/5'], duration: '16' }) .addAccidental(0, newAccid('b')) .addAccidental(1, newAccid('b')) .addAccidental(3, newAccid('n')), vf.StaveNote({ keys: ['e/4', 'g/4', 'b/4', 'c/5'], duration: '4' }) .addAccidental(0, newAccid('b').setAsCautionary()) .addAccidental(1, newAccid('b').setAsCautionary()) .addAccidental(2, newAccid('bb')) .addAccidental(3, newAccid('b')), vf.StaveNote({ keys: ['b/3', 'e/4', 'a/4', 'd/5', 'g/5'], duration: '8' }) .addAccidental(0, newAccid('bb')) .addAccidental(1, newAccid('b').setAsCautionary()) .addAccidental(2, newAccid('n').setAsCautionary()) .addAccidental(3, newAccid('#')) .addAccidental(4, newAccid('n').setAsCautionary()), ]; VF.Formatter.SimpleFormat(notes, 0, { paddingBetween: 20 }); notes.forEach(function(note, index) { Vex.Flow.Test.plotNoteWidth(vf.getContext(), note, 140); ok(note.getAccidentals().length > 0, 'Note ' + index + ' has accidentals'); note.getAccidentals().forEach(function(accid, index) { ok(accid.getWidth() > 0, 'Accidental ' + index + ' has set width'); }); }); vf.draw(); Vex.Flow.Test.plotLegendForNoteWidth(vf.getContext(), 480, 140); ok(true, 'Full Accidental'); }, basicStemDown: function(options) { var vf = VF.Test.makeFactory(options, 700, 240); var newAccid = makeNewAccid(vf); vf.Stave({ x: 10, y: 10, width: 550 }); var notes = [ vf.StaveNote({ keys: ['c/4', 'e/4', 'a/4'], duration: 'w', stem_direction: -1 }) .addAccidental(0, newAccid('b')) .addAccidental(1, newAccid('#')), vf.StaveNote({ keys: ['d/4', 'e/4', 'f/4', 'a/4', 'c/5', 'e/5', 'g/5'], duration: '2', stem_direction: -1 }) .addAccidental(0, newAccid('##')) .addAccidental(1, newAccid('n')) .addAccidental(2, newAccid('bb')) .addAccidental(3, newAccid('b')) .addAccidental(4, newAccid('#')) .addAccidental(5, newAccid('n')) .addAccidental(6, newAccid('bb')), vf.StaveNote({ keys: ['f/4', 'g/4', 'a/4', 'b/4', 'c/5', 'e/5', 'g/5'], duration: '16', stem_direction: -1 }) .addAccidental(0, newAccid('n')) .addAccidental(1, newAccid('#')) .addAccidental(2, newAccid('#')) .addAccidental(3, newAccid('b')) .addAccidental(4, newAccid('bb')) .addAccidental(5, newAccid('##')) .addAccidental(6, newAccid('#')), ]; VF.Formatter.SimpleFormat(notes, 0, { paddingBetween: 30 }); notes.forEach(function(note, index) { Vex.Flow.Test.plotNoteWidth(vf.getContext(), note, 140); ok(note.getAccidentals().length > 0, 'Note ' + index + ' has accidentals'); note.getAccidentals().forEach(function(accid, index) { ok(accid.getWidth() > 0, 'Accidental ' + index + ' has set width'); }); }); vf.draw(); Vex.Flow.Test.plotLegendForNoteWidth(vf.getContext(), 480, 140); ok(true, 'Full Accidental'); }, showNotes: function(note1, note2, stave, ctx, x) { var modifierContext = new Vex.Flow.ModifierContext(); note1.addToModifierContext(modifierContext); note2.addToModifierContext(modifierContext); new VF.TickContext() .addTickable(note1) .addTickable(note2) .preFormat() .setX(x); note1.setContext(ctx).draw(); note2.setContext(ctx).draw(); Vex.Flow.Test.plotNoteWidth(ctx, note1, 180); Vex.Flow.Test.plotNoteWidth(ctx, note2, 15); }, multiVoice: function(options) { var vf = VF.Test.makeFactory(options, 460, 250); var newAccid = makeNewAccid(vf); var stave = vf.Stave({ x: 10, y: 45, width: 420 }); var ctx = vf.getContext(); stave.draw(); var note1 = vf.StaveNote({ keys: ['c/4', 'e/4', 'a/4'], duration: '2', stem_direction: -1 }) .addAccidental(0, newAccid('b')) .addAccidental(1, newAccid('n')) .addAccidental(2, newAccid('#')) .setStave(stave); var note2 = vf.StaveNote({ keys: ['d/5', 'a/5', 'b/5'], duration: '2', stem_direction: 1 }) .addAccidental(0, newAccid('b')) .addAccidental(1, newAccid('bb')) .addAccidental(2, newAccid('##')) .setStave(stave); Vex.Flow.Test.Accidental.showNotes(note1, note2, stave, ctx, 60); note1 = vf.StaveNote({ keys: ['c/4', 'e/4', 'c/5'], duration: '2', stem_direction: -1 }) .addAccidental(0, newAccid('b')) .addAccidental(1, newAccid('n')) .addAccidental(2, newAccid('#')) .setStave(stave); note2 = vf.StaveNote({ keys: ['d/5', 'a/5', 'b/5'], duration: '4', stem_direction: 1 }) .addAccidental(0, newAccid('b')) .setStave(stave); Vex.Flow.Test.Accidental.showNotes(note1, note2, stave, ctx, 150); note1 = vf.StaveNote({ keys: ['d/4', 'c/5', 'd/5'], duration: '2', stem_direction: -1 }) .addAccidental(0, newAccid('b')) .addAccidental(1, newAccid('n')) .addAccidental(2, newAccid('#')) .setStave(stave); note2 = vf.StaveNote({ keys: ['d/5', 'a/5', 'b/5'], duration: '4', stem_direction: 1 }) .addAccidental(0, newAccid('b')) .setStave(stave); Vex.Flow.Test.Accidental.showNotes(note1, note2, stave, ctx, 250); Vex.Flow.Test.plotLegendForNoteWidth(ctx, 350, 150); ok(true, 'Full Accidental'); }, microtonal: function(options) { var assert = options.assert; var vf = VF.Test.makeFactory(options, 700, 240); var newAccid = makeNewAccid(vf); var ctx = vf.getContext(); vf.Stave({ x: 10, y: 10, width: 650 }); var notes = [ vf.StaveNote({ keys: ['c/4', 'e/4', 'a/4'], duration: '1' }) .addAccidental(0, newAccid('db')) .addAccidental(1, newAccid('d')), vf.StaveNote({ keys: ['d/4', 'e/4', 'f/4', 'a/4', 'c/5', 'e/5', 'g/5'], duration: '2' }) .addAccidental(0, newAccid('bbs')) .addAccidental(1, newAccid('++')) .addAccidental(2, newAccid('+')) .addAccidental(3, newAccid('d')) .addAccidental(4, newAccid('db')) .addAccidental(5, newAccid('+')) .addAccidental(6, newAccid('##')), vf.StaveNote({ keys: ['f/4', 'g/4', 'a/4', 'b/4', 'c/5', 'e/5', 'g/5'], duration: '16' }) .addAccidental(0, newAccid('++')) .addAccidental(1, newAccid('bbs')) .addAccidental(2, newAccid('+')) .addAccidental(3, newAccid('b')) .addAccidental(4, newAccid('db')) .addAccidental(5, newAccid('##')) .addAccidental(6, newAccid('#')), vf.StaveNote({ keys: ['a/3', 'c/4', 'e/4', 'b/4', 'd/5', 'g/5'], duration: '1' }) .addAccidental(0, newAccid('#')) .addAccidental(1, newAccid('db').setAsCautionary()) .addAccidental(2, newAccid('bbs').setAsCautionary()) .addAccidental(3, newAccid('b')) .addAccidental(4, newAccid('++').setAsCautionary()) .addAccidental(5, newAccid('d').setAsCautionary()), vf.StaveNote({ keys: ['f/4', 'g/4', 'a/4', 'b/4', 'd/5', 'g/5'], duration: '16' }) .addAccidental(0, newAccid('++-')) .addAccidental(1, newAccid('+-')) .addAccidental(2, newAccid('bs')) .addAccidental(3, newAccid('bss')) .addAccidental(4, newAccid('afhf')) .addAccidental(5, newAccid('ashs')), ]; VF.Formatter.SimpleFormat(notes, 0, { paddingBetween: 35 }); notes.forEach(function(note, index) { Vex.Flow.Test.plotNoteWidth(vf.getContext(), note, 140); assert.ok(note.getAccidentals().length > 0, 'Note ' + index + ' has accidentals'); note.getAccidentals().forEach(function(accid, index) { assert.ok(accid.getWidth() > 0, 'Accidental ' + index + ' has set width'); }); }); vf.draw(); Vex.Flow.Test.plotLegendForNoteWidth(ctx, 580, 140); ok(true, 'Microtonal Accidental'); }, microtonal_iranian: function(options) { var assert = options.assert; var vf = VF.Test.makeFactory(options, 700, 240); var newAccid = makeNewAccid(vf); var ctx = vf.getContext(); vf.Stave({ x: 10, y: 10, width: 650 }); var notes = [ vf.StaveNote({ keys: ['c/4', 'e/4', 'a/4'], duration: '1' }) .addAccidental(0, newAccid('k')) .addAccidental(1, newAccid('o')), vf.StaveNote({ keys: ['d/4', 'e/4', 'f/4', 'a/4', 'c/5', 'e/5', 'g/5'], duration: '2' }) .addAccidental(0, newAccid('b')) .addAccidental(1, newAccid('k')) .addAccidental(2, newAccid('n')) .addAccidental(3, newAccid('o')) .addAccidental(4, newAccid('#')) .addAccidental(5, newAccid('bb')) .addAccidental(6, newAccid('##')), vf.StaveNote({ keys: ['f/4', 'g/4', 'a/4', 'b/4', 'c/5', 'e/5', 'g/5'], duration: '16' }) .addAccidental(0, newAccid('o')) .addAccidental(1, newAccid('k')) .addAccidental(2, newAccid('n')) .addAccidental(3, newAccid('b')) .addAccidental(4, newAccid('bb')) .addAccidental(5, newAccid('##')) .addAccidental(6, newAccid('#')), vf.StaveNote({ keys: ['a/3', 'c/4', 'e/4', 'b/4', 'd/5', 'g/5'], duration: '1' }) .addAccidental(0, newAccid('#')) .addAccidental(1, newAccid('o').setAsCautionary()) .addAccidental(2, newAccid('n').setAsCautionary()) .addAccidental(3, newAccid('b')) .addAccidental(4, newAccid('k').setAsCautionary()), vf.StaveNote({ keys: ['f/4', 'g/4', 'a/4', 'b/4'], duration: '16' }) .addAccidental(0, newAccid('k')) .addAccidental(1, newAccid('k')) .addAccidental(2, newAccid('k')) .addAccidental(3, newAccid('k')), ]; VF.Formatter.SimpleFormat(notes, 0, { paddingBetween: 35 }); notes.forEach(function(note, index) { Vex.Flow.Test.plotNoteWidth(vf.getContext(), note, 140); assert.ok(note.getAccidentals().length > 0, 'Note ' + index + ' has accidentals'); note.getAccidentals().forEach(function(accid, index) { assert.ok(accid.getWidth() > 0, 'Accidental ' + index + ' has set width'); }); }); vf.draw(); Vex.Flow.Test.plotLegendForNoteWidth(ctx, 580, 140); ok(true, 'Microtonal Accidental (Iranian)'); }, automaticAccidentals0: function(options) { var vf = VF.Test.makeFactory(options, 700, 200); var stave = vf.Stave(); const notes = [ { keys: ['c/4', 'c/5'], duration: '4' }, { keys: ['c#/4', 'c#/5'], duration: '4' }, { keys: ['c#/4', 'c#/5'], duration: '4' }, { keys: ['c##/4', 'c##/5'], duration: '4' }, { keys: ['c##/4', 'c##/5'], duration: '4' }, { keys: ['c/4', 'c/5'], duration: '4' }, { keys: ['cn/4', 'cn/5'], duration: '4' }, { keys: ['cbb/4', 'cbb/5'], duration: '4' }, { keys: ['cbb/4', 'cbb/5'], duration: '4' }, { keys: ['cb/4', 'cb/5'], duration: '4' }, { keys: ['cb/4', 'cb/5'], duration: '4' }, { keys: ['c/4', 'c/5'], duration: '4' }, ].map(vf.StaveNote.bind(vf)); const gracenotes = [ { keys: ['d#/4'], duration: '16', slash: true }, ].map(vf.GraceNote.bind(vf)); notes[0].addModifier(0, vf.GraceNoteGroup({ notes: gracenotes }).beamNotes()); const voice = vf.Voice() .setMode(Vex.Flow.Voice.Mode.SOFT) .addTickable(new Vex.Flow.TimeSigNote('12/4').setStave(stave)) .addTickables(notes); Vex.Flow.Accidental.applyAccidentals([voice], 'C'); new Vex.Flow.Formatter() .joinVoices([voice]) .formatToStave([voice], stave); vf.draw(); ok(true); }, automaticAccidentals1: function(options) { var vf = VF.Test.makeFactory(options, 700, 150); var stave = vf.Stave().addKeySignature('Ab'); var notes = [ { keys: ['c/4'], duration: '4' }, { keys: ['d/4'], duration: '4' }, { keys: ['e/4'], duration: '4' }, { keys: ['f/4'], duration: '4' }, { keys: ['g/4'], duration: '4' }, { keys: ['a/4'], duration: '4' }, { keys: ['b/4'], duration: '4' }, { keys: ['c/5'], duration: '4' }, ].map(vf.StaveNote.bind(vf)); var voice = vf.Voice() .setMode(Vex.Flow.Voice.Mode.SOFT) .addTickables(notes); Vex.Flow.Accidental.applyAccidentals([voice], 'Ab'); new Vex.Flow.Formatter() .joinVoices([voice]) .formatToStave([voice], stave); vf.draw(); ok(true); }, automaticAccidentals2: function(options) { var vf = VF.Test.makeFactory(options, 700, 150); var stave = vf.Stave().addKeySignature('A'); var notes = [ { keys: ['a/4'], duration: '4' }, { keys: ['b/4'], duration: '4' }, { keys: ['c#/5'], duration: '4' }, { keys: ['d/5'], duration: '4' }, { keys: ['e/5'], duration: '4' }, { keys: ['f#/5'], duration: '4' }, { keys: ['g#/5'], duration: '4' }, { keys: ['a/5'], duration: '4' }, ].map(vf.StaveNote.bind(vf)); var voice = vf.Voice() .setMode(Vex.Flow.Voice.Mode.SOFT) .addTickables(notes); Vex.Flow.Accidental.applyAccidentals([voice], 'A'); new Vex.Flow.Formatter() .joinVoices([voice]) .formatToStave([voice], stave); vf.draw(); ok(true); }, automaticAccidentalsMultiVoiceInline: function(options) { var vf = VF.Test.makeFactory(options, 700, 150); var stave = vf.Stave().addKeySignature('Ab'); var notes0 = [ { keys: ['c/4'], duration: '4', stem_direction: -1 }, { keys: ['d/4'], duration: '4', stem_direction: -1 }, { keys: ['e/4'], duration: '4', stem_direction: -1 }, { keys: ['f/4'], duration: '4', stem_direction: -1 }, { keys: ['g/4'], duration: '4', stem_direction: -1 }, { keys: ['a/4'], duration: '4', stem_direction: -1 }, { keys: ['b/4'], duration: '4', stem_direction: -1 }, { keys: ['c/5'], duration: '4', stem_direction: -1 }, ].map(vf.StaveNote.bind(vf)); var notes1 = [ { keys: ['c/5'], duration: '4' }, { keys: ['d/5'], duration: '4' }, { keys: ['e/5'], duration: '4' }, { keys: ['f/5'], duration: '4' }, { keys: ['g/5'], duration: '4' }, { keys: ['a/5'], duration: '4' }, { keys: ['b/5'], duration: '4' }, { keys: ['c/6'], duration: '4' }, ].map(vf.StaveNote.bind(vf)); var voice0 = vf.Voice() .setMode(Vex.Flow.Voice.Mode.SOFT) .addTickables(notes0); var voice1 = vf.Voice() .setMode(Vex.Flow.Voice.Mode.SOFT) .addTickables(notes1); // Ab Major Vex.Flow.Accidental.applyAccidentals([voice0, voice1], 'Ab'); equal(hasAccidental(notes0[0]), false); equal(hasAccidental(notes0[1]), true); equal(hasAccidental(notes0[2]), true); equal(hasAccidental(notes0[3]), false); equal(hasAccidental(notes0[4]), false); equal(hasAccidental(notes0[5]), true); equal(hasAccidental(notes0[6]), true); equal(hasAccidental(notes0[7]), false); equal(hasAccidental(notes1[0]), false); equal(hasAccidental(notes1[1]), true); equal(hasAccidental(notes1[2]), true); equal(hasAccidental(notes1[3]), false); equal(hasAccidental(notes1[4]), false); equal(hasAccidental(notes1[5]), true); equal(hasAccidental(notes1[6]), true); equal(hasAccidental(notes1[7]), false); new Vex.Flow.Formatter() .joinVoices([voice0, voice1]) .formatToStave([voice0, voice1], stave); vf.draw(); ok(true); }, automaticAccidentalsMultiVoiceOffset: function(options) { var vf = VF.Test.makeFactory(options, 700, 150); var stave = vf.Stave().addKeySignature('Cb'); var notes0 = [ { keys: ['c/4'], duration: '4', stem_direction: -1 }, { keys: ['d/4'], duration: '4', stem_direction: -1 }, { keys: ['e/4'], duration: '4', stem_direction: -1 }, { keys: ['f/4'], duration: '4', stem_direction: -1 }, { keys: ['g/4'], duration: '4', stem_direction: -1 }, { keys: ['a/4'], duration: '4', stem_direction: -1 }, { keys: ['b/4'], duration: '4', stem_direction: -1 }, { keys: ['c/5'], duration: '4', stem_direction: -1 }, ].map(vf.StaveNote.bind(vf)); var notes1 = [ { keys: ['c/5'], duration: '8' }, { keys: ['c/5'], duration: '4' }, { keys: ['d/5'], duration: '4' }, { keys: ['e/5'], duration: '4' }, { keys: ['f/5'], duration: '4' }, { keys: ['g/5'], duration: '4' }, { keys: ['a/5'], duration: '4' }, { keys: ['b/5'], duration: '4' }, { keys: ['c/6'], duration: '4' }, ].map(vf.StaveNote.bind(vf)); var voice0 = vf.Voice() .setMode(Vex.Flow.Voice.Mode.SOFT) .addTickables(notes0); var voice1 = vf.Voice() .setMode(Vex.Flow.Voice.Mode.SOFT) .addTickables(notes1); // Cb Major (All flats) Vex.Flow.Accidental.applyAccidentals([voice0, voice1], 'Cb'); equal(hasAccidental(notes0[0]), true); equal(hasAccidental(notes0[1]), true); equal(hasAccidental(notes0[2]), true); equal(hasAccidental(notes0[3]), true); equal(hasAccidental(notes0[4]), true); equal(hasAccidental(notes0[5]), true); equal(hasAccidental(notes0[6]), true); equal(hasAccidental(notes0[7]), false, 'Natural Remembered'); equal(hasAccidental(notes1[0]), true); equal(hasAccidental(notes1[1]), false); equal(hasAccidental(notes1[2]), false); equal(hasAccidental(notes1[3]), false); equal(hasAccidental(notes1[4]), false); equal(hasAccidental(notes1[5]), false); equal(hasAccidental(notes1[6]), false); equal(hasAccidental(notes1[7]), false); new Vex.Flow.Formatter() .joinVoices([voice0, voice1]) .formatToStave([voice0, voice1], stave); vf.draw(); ok(true); }, autoAccidentalWorking: function() { function makeNote(noteStruct) { return new VF.StaveNote(noteStruct); } var notes = [ { keys: ['bb/4'], duration: '4' }, { keys: ['bb/4'], duration: '4' }, { keys: ['g#/4'], duration: '4' }, { keys: ['g/4'], duration: '4' }, { keys: ['b/4'], duration: '4' }, { keys: ['b/4'], duration: '4' }, { keys: ['a#/4'], duration: '4' }, { keys: ['g#/4'], duration: '4' }, ].map(makeNote); var voice = new VF.Voice() .setMode(Vex.Flow.Voice.Mode.SOFT) .addTickables(notes); // F Major (Bb) Vex.Flow.Accidental.applyAccidentals([voice], 'F'); equal(hasAccidental(notes[0]), false, 'No flat because of key signature'); equal(hasAccidental(notes[1]), false, 'No flat because of key signature'); equal(hasAccidental(notes[2]), true, 'Added a sharp'); equal(hasAccidental(notes[3]), true, 'Back to natural'); equal(hasAccidental(notes[4]), true, 'Back to natural'); equal(hasAccidental(notes[5]), false, 'Natural remembered'); equal(hasAccidental(notes[6]), true, 'Added sharp'); equal(hasAccidental(notes[7]), true, 'Added sharp'); notes = [ { keys: ['e#/4'], duration: '4' }, { keys: ['cb/4'], duration: '4' }, { keys: ['fb/4'], duration: '4' }, { keys: ['b#/4'], duration: '4' }, { keys: ['b#/4'], duration: '4' }, { keys: ['cb/5'], duration: '4' }, { keys: ['fb/5'], duration: '4' }, { keys: ['e#/4'], duration: '4' }, ].map(makeNote); voice = new VF.Voice() .setMode(Vex.Flow.Voice.Mode.SOFT) .addTickables(notes); // A Major (F#,G#,C#) Vex.Flow.Accidental.applyAccidentals([voice], 'A'); equal(hasAccidental(notes[0]), true, 'Added sharp'); equal(hasAccidental(notes[1]), true, 'Added flat'); equal(hasAccidental(notes[2]), true, 'Added flat'); equal(hasAccidental(notes[3]), true, 'Added sharp'); equal(hasAccidental(notes[4]), false, 'Sharp remembered'); equal(hasAccidental(notes[5]), false, 'Flat remembered'); equal(hasAccidental(notes[6]), false, 'Flat remembered'); equal(hasAccidental(notes[7]), false, 'sharp remembered'); notes = [ { keys: ['c/4'], duration: '4' }, { keys: ['cb/4'], duration: '4' }, { keys: ['cb/4'], duration: '4' }, { keys: ['c#/4'], duration: '4' }, { keys: ['c#/4'], duration: '4' }, { keys: ['cbb/4'], duration: '4' }, { keys: ['cbb/4'], duration: '4' }, { keys: ['c##/4'], duration: '4' }, { keys: ['c##/4'], duration: '4' }, { keys: ['c/4'], duration: '4' }, { keys: ['c/4'], duration: '4' }, ].map(makeNote); voice = new VF.Voice() .setMode(Vex.Flow.Voice.Mode.SOFT) .addTickables(notes); // C Major (no sharps/flats) Vex.Flow.Accidental.applyAccidentals([voice], 'C'); equal(hasAccidental(notes[0]), false, 'No accidental'); equal(hasAccidental(notes[1]), true, 'Added flat'); equal(hasAccidental(notes[2]), false, 'Flat remembered'); equal(hasAccidental(notes[3]), true, 'Sharp added'); equal(hasAccidental(notes[4]), false, 'Sharp remembered'); equal(hasAccidental(notes[5]), true, 'Added doubled flat'); equal(hasAccidental(notes[6]), false, 'Double flat remembered'); equal(hasAccidental(notes[7]), true, 'Added double sharp'); equal(hasAccidental(notes[8]), false, 'Double sharp rememberd'); equal(hasAccidental(notes[9]), true, 'Added natural'); equal(hasAccidental(notes[10]), false, 'Natural remembered'); }, factoryAPI: function(options) { var assert = options.assert; var vf = VF.Test.makeFactory(options, 700, 240); vf.Stave({ x: 10, y: 10, width: 550 }); function newAcc(type) { return vf.Accidental({ type: type }); } var notes = [ vf.StaveNote({ keys: ['c/4', 'e/4', 'a/4'], duration: 'w' }) .addAccidental(0, newAcc('b')) .addAccidental(1, newAcc('#')), vf.StaveNote({ keys: ['d/4', 'e/4', 'f/4', 'a/4', 'c/5', 'e/5', 'g/5'], duration: 'h' }) .addAccidental(0, newAcc('##')) .addAccidental(1, newAcc('n')) .addAccidental(2, newAcc('bb')) .addAccidental(3, newAcc('b')) .addAccidental(4, newAcc('#')) .addAccidental(5, newAcc('n')) .addAccidental(6, newAcc('bb')), vf.StaveNote({ keys: ['f/4', 'g/4', 'a/4', 'b/4', 'c/5', 'e/5', 'g/5'], duration: '16' }) .addAccidental(0, newAcc('n')) .addAccidental(1, newAcc('#')) .addAccidental(2, newAcc('#')) .addAccidental(3, newAcc('b')) .addAccidental(4, newAcc('bb')) .addAccidental(5, newAcc('##')) .addAccidental(6, newAcc('#')), vf.StaveNote({ keys: ['a/3', 'c/4', 'e/4', 'b/4', 'd/5', 'g/5'], duration: 'w' }) .addAccidental(0, newAcc('#')) .addAccidental(1, newAcc('##').setAsCautionary()) .addAccidental(2, newAcc('#').setAsCautionary()) .addAccidental(3, newAcc('b')) .addAccidental(4, newAcc('bb').setAsCautionary()) .addAccidental(5, newAcc('b').setAsCautionary()), ]; VF.Formatter.SimpleFormat(notes); notes.forEach(function(n, i) { assert.ok(n.getAccidentals().length > 0, 'Note ' + i + ' has accidentals'); n.getAccidentals().forEach(function(accid, i) { assert.ok(accid.getWidth() > 0, 'Accidental ' + i + ' has set width'); }); }); vf.draw(); assert.ok(true, 'Factory API'); }, }; return Accidental; })(); /** * VexFlow - Annotation Tests * Copyright Mohit Muthanna 2010 <mohit@muthanna.com> */ VF.Test.Annotation = (function() { var runTests = VF.Test.runTests; var Annotation = { Start: function() { QUnit.module('Annotation'); runTests('Simple Annotation', Annotation.simple); runTests('Standard Notation Annotation', Annotation.standard); runTests('Harmonics', Annotation.harmonic); runTests('Fingerpicking', Annotation.picking); runTests('Bottom Annotation', Annotation.bottom); runTests('Bottom Annotations with Beams', Annotation.bottomWithBeam); runTests('Test Justification Annotation Stem Up', Annotation.justificationStemUp); runTests('Test Justification Annotation Stem Down', Annotation.justificationStemDown); runTests('TabNote Annotations', Annotation.tabNotes); }, simple: function(options, contextBuilder) { var ctx = contextBuilder(options.elementId, 500, 240); ctx.scale(1.5, 1.5); ctx.fillStyle = '#221'; ctx.strokeStyle = '#221'; ctx.font = ' 10pt Arial'; var stave = new VF.TabStave(10, 10, 450) .addTabGlyph().setContext(ctx).draw(); function newNote(tab_struct) { return new VF.TabNote(tab_struct); } function newBend(text) { return new VF.Bend(text); } function newAnnotation(text) { return new VF.Annotation(text); } var notes = [ newNote({ positions: [{ str: 2, fret: 10 }, { str: 4, fret: 9 }], duration: 'h', }) .addModifier(newAnnotation('T'), 0), newNote({ positions: [{ str: 2, fret: 10 }], duration: 'h', }) .addModifier(newAnnotation('T'), 0) .addModifier(newBend('Full'), 0), ]; VF.Formatter.FormatAndDraw(ctx, stave, notes, 200); ok(true, 'Simple Annotation'); }, standard: function(options, contextBuilder) { var ctx = contextBuilder(options.elementId, 500, 240); ctx.scale(1.5, 1.5); ctx.fillStyle = '#221'; ctx.strokeStyle = '#221'; var stave = new VF.Stave(10, 10, 450) .addClef('treble').setContext(ctx).draw(); function newNote(note_struct) { return new VF.StaveNote(note_struct); } function newAnnotation(text) { return (new VF.Annotation(text)).setFont('Times', VF.Test.Font.size, 'italic'); } var notes = [ newNote({ keys: ['c/4', 'e/4'], duration: 'h' }) .addAnnotation(0, newAnnotation('quiet')), newNote({ keys: ['c/4', 'e/4', 'c/5'], duration: 'h' }) .addAnnotation(2, newAnnotation('Allegro')), ]; VF.Formatter.FormatAndDraw(ctx, stave, notes, 200); ok(true, 'Standard Notation Annotation'); }, harmonic: function(options, contextBuilder) { var ctx = contextBuilder(options.elementId, 500, 240); ctx.scale(1.5, 1.5); ctx.fillStyle = '#221'; ctx.strokeStyle = '#221'; ctx.font = ' 10pt Arial'; var stave = new VF.TabStave(10, 10, 450) .addTabGlyph().setContext(ctx).draw(); function newNote(tab_struct) { return new VF.TabNote(tab_struct); } function newAnnotation(text) { return new VF.Annotation(text); } var notes = [ newNote({ positions: [{ str: 2, fret: 12 }, { str: 3, fret: 12 }], duration: 'h', }) .addModifier(newAnnotation('Harm.'), 0), newNote({ positions: [{ str: 2, fret: 9 }], duration: 'h', }) .addModifier(newAnnotation('(8va)').setFont('Times', VF.Test.Font.size, 'italic'), 0) .addModifier(newAnnotation('A.H.'), 0), ]; VF.Formatter.FormatAndDraw(ctx, stave, notes, 200); ok(true, 'Simple Annotation'); }, picking: function(options, contextBuilder) { var ctx = contextBuilder(options.elementId, 500, 240); ctx.scale(1.5, 1.5); ctx.setFillStyle('#221'); ctx.setStrokeStyle('#221'); ctx.setFont('Arial', VF.Test.Font.size, ''); var stave = new VF.TabStave(10, 10, 450) .addTabGlyph().setContext(ctx).draw(); function newNote(tab_struct) { return new VF.TabNote(tab_struct); } function newAnnotation(text) { return new VF.Annotation(text) .setFont('Times', VF.Test.Font.size, 'italic'); } var notes = [ newNote({ 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 VF.Vibrato().setVibratoWidth(40)), newNote({ positions: [{ str: 6, fret: 9 }], duration: '8', }) .addModifier(newAnnotation('p'), 0), newNote({ positions: [{ str: 3, fret: 9 }], duration: '8', }) .addModifier(newAnnotation('i'), 0), newNote({ positions: [{ str: 2, fret: 9 }], duration: '8', }) .addModifier(newAnnotation('m'), 0), newNote({ positions: [{ str: 1, fret: 9 }], duration: '8', }) .addModifier(newAnnotation('a'), 0), ]; VF.Formatter.FormatAndDraw(ctx, stave, notes, 200); ok(true, 'Fingerpicking'); }, bottom: function(options, contextBuilder) { var ctx = contextBuilder(options.elementId, 500, 240); ctx.scale(1.5, 1.5); ctx.fillStyle = '#221'; ctx.strokeStyle = '#221'; var stave = new VF.Stave(10, 10, 300) .addClef('treble').setContext(ctx).draw(); function newNote(note_struct) { return new VF.StaveNote(note_struct); } function newAnnotation(text) { return ( new VF.Annotation(text)) .setFont('Times', VF.Test.Font.size) .setVerticalJustification(VF.Annotation.VerticalJustify.BOTTOM); } var notes = [ newNote({ keys: ['f/4'], duration: 'w' }) .addAnnotation(0, newAnnotation('F')), newNote({ keys: ['a/4'], duration: 'w' }) .addAnnotation(0, newAnnotation('A')), newNote({ keys: ['c/5'], duration: 'w' }) .addAnnotation(0, newAnnotation('C')), newNote({ keys: ['e/5'], duration: 'w' }) .addAnnotation(0, newAnnotation('E')), ]; VF.Formatter.FormatAndDraw(ctx, stave, notes, 100); ok(true, 'Bottom Annotation'); }, bottomWithBeam: function(options, contextBuilder) { var ctx = contextBuilder(options.elementId, 500, 240); ctx.scale(1.5, 1.5); ctx.fillStyle = '#221'; ctx.strokeStyle = '#221'; var stave = new VF.Stave(10, 10, 300) .addClef('treble').setContext(ctx).draw(); // Create some notes var notes = [ new VF.StaveNote({ keys: ['a/3'], duration: '8' }) .addModifier(0, new VF.Annotation('good') .setVerticalJustification(VF.Annotation.VerticalJustify.BOTTOM)), new VF.StaveNote({ keys: ['g/3'], duration: '8' }) .addModifier(0, new VF.Annotation('even') .setVerticalJustification(VF.Annotation.VerticalJustify.BOTTOM)), new VF.StaveNote({ keys: ['c/4'], duration: '8' }) .addModifier(0, new VF.Annotation('under') .setVerticalJustification(VF.Annotation.VerticalJustify.BOTTOM)), new VF.StaveNote({ keys: ['d/4'], duration: '8' }) .addModifier(0, new VF.Annotation('beam') .setVerticalJustification(VF.Annotation.VerticalJustify.BOTTOM)), ]; var beam = new VF.Beam(notes.slice(1)); VF.Formatter.FormatAndDraw(ctx, stave, notes); beam.setContext(ctx).draw(); ok(true, 'Bottom Annotation with Beams'); }, justificationStemUp: function(options, contextBuilder) { var ctx = contextBuilder(options.elementId, 650, 950); ctx.scale(1.5, 1.5); ctx.fillStyle = '#221'; ctx.strokeStyle = '#221'; function newNote(note_struct) { return new VF.StaveNote(note_struct); } function newAnnotation(text, hJustifcation, vJustifcation) { return ( new VF.Annotation(text)) .setFont('Arial', VF.Test.Font.size) .setJustification(hJustifcation) .setVerticalJustification(vJustifcation); } for (var v = 1; v <= 4; ++v) { var stave = new VF.Stave(10, (v - 1) * 150 + 40, 400) .addClef('treble').setContext(ctx).draw(); var notes = []; notes.push(newNote({ keys: ['c/3'], duration: 'q' }).addAnnotation(0, newAnnotation('Text', 1, v))); notes.push(newNote({ keys: ['c/4'], duration: 'q' }).addAnnotation(0, newAnnotation('Text', 2, v))); notes.push(newNote({ keys: ['c/5'], duration: 'q' }).addAnnotation(0, newAnnotation('Text', 3, v))); notes.push(newNote({ keys: ['c/6'], duration: 'q' }).addAnnotation(0, newAnnotation('Text', 4, v))); VF.Formatter.FormatAndDraw(ctx, stave, notes, 100); } ok(true, 'Test Justification Annotation'); }, justificationStemDown: function(options, contextBuilder) { var ctx = contextBuilder(options.elementId, 650, 1000); ctx.scale(1.5, 1.5); ctx.fillStyle = '#221'; ctx.strokeStyle = '#221'; function newNote(note_struct) { return new VF.StaveNote(note_struct); } function newAnnotation(text, hJustifcation, vJustifcation) { return ( new VF.Annotation(text)) .setFont('Arial', VF.Test.Font.size) .setJustification(hJustifcation) .setVerticalJustification(vJustifcation); } for (var v = 1; v <= 4; ++v) { var stave = new VF.Stave(10, (v - 1) * 150 + 40, 400) .addClef('treble').setContext(ctx).draw(); var notes = []; notes.push(newNote({ keys: ['c/3'], duration: 'q', stem_direction: -1