UNPKG

vexflow

Version:

A JavaScript library for rendering music notation and guitar tablature.

725 lines (724 loc) 31.8 kB
import { VexFlow } from '../src/vexflow.js'; import { VexFlowTests } from './vexflow_test_helpers.js'; import { Accidental } from '../src/accidental.js'; import { Annotation, AnnotationVerticalJustify } from '../src/annotation.js'; import { Articulation } from '../src/articulation.js'; import { Beam } from '../src/beam.js'; import { Bend } from '../src/bend.js'; import { Dot } from '../src/dot.js'; import { Element } from '../src/element.js'; import { FontWeight } from '../src/font.js'; import { Formatter } from '../src/formatter.js'; import { FretHandFinger } from '../src/frethandfinger.js'; import { Glyphs } from '../src/glyphs.js'; import { Metrics } from '../src/metrics.js'; import { ModifierPosition } from '../src/modifier.js'; import { Note } from '../src/note.js'; import { Registry } from '../src/registry.js'; import { Stave } from '../src/stave.js'; import { StaveConnector } from '../src/staveconnector.js'; import { StaveNote } from '../src/stavenote.js'; import { Stem } from '../src/stem.js'; import { StringNumber } from '../src/stringnumber.js'; import { Tuplet } from '../src/tuplet.js'; import { Voice } from '../src/voice.js'; import { MockTickable } from './mocks.js'; const FormatterTests = { Start() { QUnit.module('Formatter'); QUnit.test('TickContext Building', buildTickContexts); const run = VexFlowTests.runTests; run('Penultimate Note Padding', penultimateNote); run('Whitespace and justify', rightJustify); run('Notehead padding', noteHeadPadding); run('Justification and alignment with accidentals', accidentalJustification); run('Long measure taking full space', longMeasureProblems); run('Vertical alignment - few unaligned beats', unalignedNoteDurations1); run('Vertical alignment - many unaligned beats', unalignedNoteDurations2, { globalSoftmax: false }); run('Vertical alignment - many unaligned beats (global softmax)', unalignedNoteDurations2, { globalSoftmax: true }); run('Vertical alignment - many mixed elements', alignedMixedElements, { globalSoftmax: true }); run('StaveNote - Justification', justifyStaveNotes); run('Notes with Tab', notesWithTab); run('Multiple Staves - Justified', multiStaves, { debug: true }); run('Softmax', softMax); run('Mixtime', mixTime); run('Tight', tightNotes1); run('Tight 2', tightNotes2); run('Annotations', annotations); run('Proportional Formatting - No Tuning', proportional, { debug: true, iterations: 0 }); run('Proportional Formatting - No Justification', proportional, { justify: false, debug: true, iterations: 0 }); run('Proportional Formatting (20 iterations)', proportional, { debug: true, iterations: 20, alpha: 0.5 }); }, }; function getGlyphWidth(glyph) { const el = new Element(); el.setText(glyph); return el.getWidth(); } function buildTickContexts(assert) { function createTickable(beat) { return new MockTickable().setTicks(beat); } const BEAT = (1 * VexFlow.RESOLUTION) / 4; const tickables1 = [ createTickable(BEAT).setWidth(10), createTickable(BEAT * 2).setWidth(20), createTickable(BEAT).setWidth(30), ]; const tickables2 = [ createTickable(BEAT * 2).setWidth(10), createTickable(BEAT).setWidth(20), createTickable(BEAT).setWidth(30), ]; const voice1 = new Voice(VexFlow.TIME4_4); const voice2 = new Voice(VexFlow.TIME4_4); voice1.addTickables(tickables1); voice2.addTickables(tickables2); const formatter = new Formatter(); const tContexts = formatter.createTickContexts([voice1, voice2]); assert.equal(tContexts.list.length, 4, 'Voices should have four tick contexts'); assert.throws(() => formatter.getMinTotalWidth(), /NoMinTotalWidth/, 'Expected to throw exception'); assert.ok(formatter.preCalculateMinTotalWidth([voice1, voice2]), 'Successfully runs preCalculateMinTotalWidth'); assert.equal(formatter.getMinTotalWidth(), 88, 'Get minimum total width without passing voices'); formatter.preFormat(); assert.equal(formatter.getMinTotalWidth(), 88, 'Minimum total width'); assert.equal(tickables1[0].getX(), tickables2[0].getX(), 'First notes of both voices have the same X'); assert.equal(tickables1[2].getX(), tickables2[2].getX(), 'Last notes of both voices have the same X'); assert.ok(tickables1[1].getX() < tickables2[1].getX(), 'Second note of voice 2 is to the right of the second note of voice 1'); } function rightJustify(options) { const f = VexFlowTests.makeFactory(options, 1200, 150); const getTickables = (time, n, duration, duration2) => { const tickar = []; let i = 0; for (i = 0; i < n; ++i) { const dd = i === n - 1 ? duration2 : duration; tickar.push(new StaveNote({ keys: ['f/4'], duration: dd })); } return new Voice(time).addTickables(tickar); }; const renderTest = (time, n, duration, duration2, x, width) => { const formatter = f.Formatter(); const stave = f.Stave({ x, y: 20, width }); const voice = getTickables(time, n, duration, duration2); formatter.joinVoices([voice]).formatToStave([voice], stave); stave.drawWithStyle(); voice.draw(f.getContext(), stave); }; renderTest({ numBeats: 4, beatValue: 4, resolution: 4 * 4096 }, 3, '4', '2', 10, 300); renderTest({ numBeats: 4, beatValue: 4, resolution: 4 * 4096 }, 1, 'w', 'w', 310, 300); renderTest({ numBeats: 3, beatValue: 4, resolution: 4 * 4096 }, 3, '4', '4', 610, 300); renderTest({ numBeats: 3, beatValue: 4, resolution: 4 * 4096 }, 6, '8', '8', 910, 300); options.assert.ok(true); } function penultimateNote(options) { const f = VexFlowTests.makeFactory(options, 500, 550); const score = f.EasyScore(); const staffWidth = 310; let system = undefined; let voices = []; let notes = []; let note = undefined; let stave = undefined; let y = 10; const draw = (softmax) => { system = f.System({ width: staffWidth, y, formatOptions: { alignRests: true }, details: { softmaxFactor: softmax }, }); notes = []; voices = []; note = score.notes('C4/8/r', { clef: 'bass' })[0]; notes.push(note); note = score.notes('A3/8', { stem: 'up', clef: 'bass' })[0]; notes.push(note); note = score.notes('C4/4', { stem: 'up', clef: 'bass' })[0]; notes.push(note); voices.push(score.voice(notes).setMode(2)); notes = []; note = score.notes('( F3 A3 )/4', { stem: 'down', clef: 'bass' })[0]; notes.push(note); note = score.notes('B4/4/r', {})[0]; notes.push(note); voices.push(score.voice(notes).setMode(2)); notes = []; stave = system.addStave({ voices: voices }); stave.addClef('bass'); stave.addTimeSignature('2/4'); voices = []; f.draw(); f.getContext().fillText(`softmax: ${softmax.toString()}`, staffWidth + 20, y + 50); y += 100; }; draw(15); draw(10); draw(5); draw(2); draw(1); options.assert.ok(true); } function noteHeadPadding(options) { const registry = new Registry(); Registry.enableDefaultRegistry(registry); const f = VexFlowTests.makeFactory(options, 600, 300); const score = f.EasyScore(); score.set({ time: '9/8' }); const notes1 = score.notes('(d5 f5)/8,(c5 e5)/8,(d5 f5)/8,(c5 e5)/2.'); const beams = [new Beam(notes1.slice(0, 3), true)]; const voice1 = new Voice().setMode(Voice.Mode.SOFT); const notes2 = score.notes('(g4 an4)/2.,(g4 a4)/4.', { clef: 'treble' }); const voice2 = new Voice().setMode(Voice.Mode.SOFT); voice2.addTickables(notes2); voice1.addTickables(notes1); const formatter = f.Formatter().joinVoices([voice1]).joinVoices([voice2]); const width = formatter.preCalculateMinTotalWidth([voice1, voice2]); formatter.format([voice1, voice2], width); const staveWidth = width + Stave.defaultPadding; const stave1 = f.Stave({ y: 50, width: staveWidth }); const stave2 = f.Stave({ y: 150, width: staveWidth }); stave1.drawWithStyle(); stave2.drawWithStyle(); voice1.draw(f.getContext(), stave1); voice2.draw(f.getContext(), stave2); beams.forEach((b) => b.setContext(f.getContext()).drawWithStyle()); options.assert.ok(true); } function longMeasureProblems(options) { const registry = new Registry(); Registry.enableDefaultRegistry(registry); const f = VexFlowTests.makeFactory(options, 1500, 300); const score = f.EasyScore(); score.set({ time: '4/4' }); const notes1 = score.notes('b4/4,b4/8,b4/8,b4/4,b4/4,b4/2,b4/2,b4/4,b4/8,b4/8,b4/4,b4/4,b4/2,b4/2,b4/4,b4/8,b4/8,b4/4,b4/4,b4/2,b4/2,b4/4,b4/2,b4/8,b4/8'); const voice1 = new Voice().setMode(Voice.Mode.SOFT); const notes2 = score.notes('d3/4,(ab3 f4)/2,d3/4,ab3/4,d3/2,ab3/4,d3/4,ab3/2,d3/4,ab3/4,d3/2,ab3/4,d3/4,ab3/2,d3/4,ab3/4,d3/2,ab3/4,d4/4,d4/2,d4/4', { clef: 'bass' }); const voice2 = new Voice().setMode(Voice.Mode.SOFT); voice2.addTickables(notes2); voice1.addTickables(notes1); const formatter = f.Formatter().joinVoices([voice1]).joinVoices([voice2]); const width = formatter.preCalculateMinTotalWidth([voice1, voice2]); formatter.format([voice1, voice2], width); const stave1 = f.Stave({ y: 50, width: width + Stave.defaultPadding }); const stave2 = f.Stave({ y: 200, width: width + Stave.defaultPadding }); stave1.drawWithStyle(); stave2.drawWithStyle(); voice1.draw(f.getContext(), stave1); voice2.draw(f.getContext(), stave2); options.assert.ok(true); } function accidentalJustification(options) { const f = VexFlowTests.makeFactory(options, 600, 300); const score = f.EasyScore(); const notes11 = score.notes('a4/2, a4/4, a4/8, ab4/16, an4/16'); const voice11 = score.voice(notes11, { time: '4/4' }); const notes21 = score.notes('c4/2, d4/8, d4/8, e4/8, e4/8'); const voice21 = score.voice(notes21, { time: '4/4' }); let beams = Beam.generateBeams(notes11.slice(2)); beams = beams.concat(beams, Beam.generateBeams(notes21.slice(1, 3))); beams = beams.concat(Beam.generateBeams(notes21.slice(3))); const formatter = f.Formatter({}).joinVoices([voice11]).joinVoices([voice21]); const width = formatter.preCalculateMinTotalWidth([voice11, voice21]); const stave11 = f.Stave({ y: 20, width: width + Stave.defaultPadding }); const stave21 = f.Stave({ y: 130, width: width + Stave.defaultPadding }); formatter.format([voice11, voice21], width); const ctx = f.getContext(); stave11.setContext(ctx).drawWithStyle(); stave21.setContext(ctx).drawWithStyle(); voice11.draw(ctx, stave11); voice21.draw(ctx, stave21); beams.forEach((b) => b.setContext(ctx).drawWithStyle()); options.assert.ok(true); } function unalignedNoteDurations1(options) { const f = VexFlowTests.makeFactory(options, 600, 250); const score = f.EasyScore(); const notes11 = [ new StaveNote({ keys: ['a/4'], duration: '8' }), new StaveNote({ keys: ['b/4'], duration: '4' }), new StaveNote({ keys: ['b/4'], duration: '8' }), ]; const notes21 = [ new StaveNote({ keys: ['a/4'], duration: '16' }), new StaveNote({ keys: ['b/4.'], duration: '4' }), new StaveNote({ keys: ['a/4'], duration: '8d' }), ]; Dot.buildAndAttach([notes21[2]], { all: true }); const ctx = f.getContext(); const voice11 = score.voice(notes11, { time: '2/4' }).setMode(Voice.Mode.SOFT); const voice21 = score.voice(notes21, { time: '2/4' }).setMode(Voice.Mode.SOFT); const beams21 = Beam.generateBeams(notes21); const beams11 = Beam.generateBeams(notes11); const formatter = new Formatter(); formatter.joinVoices([voice11]); formatter.joinVoices([voice21]); const width = formatter.preCalculateMinTotalWidth([voice11, voice21]); const stave11 = f.Stave({ y: 20, width: width + Stave.defaultPadding }); const stave21 = f.Stave({ y: 130, width: width + Stave.defaultPadding }); formatter.format([voice11, voice21], width); stave11.setContext(ctx).drawWithStyle(); stave21.setContext(ctx).drawWithStyle(); voice11.draw(ctx, stave11); voice21.draw(ctx, stave21); beams21.forEach((b) => b.setContext(ctx).drawWithStyle()); beams11.forEach((b) => b.setContext(ctx).drawWithStyle()); options.assert.ok(voice11.getTickables()[1].getX() > voice21.getTickables()[1].getX()); } function unalignedNoteDurations2(options) { const notes1 = [ new StaveNote({ keys: ['b/4'], duration: '8r' }), new StaveNote({ keys: ['g/4'], duration: '16' }), new StaveNote({ keys: ['c/5'], duration: '16' }), new StaveNote({ keys: ['e/5'], duration: '16' }), new StaveNote({ keys: ['g/4'], duration: '16' }), new StaveNote({ keys: ['c/5'], duration: '16' }), new StaveNote({ keys: ['e/5'], duration: '16' }), new StaveNote({ keys: ['b/4'], duration: '8r' }), new StaveNote({ keys: ['g/4'], duration: '16' }), new StaveNote({ keys: ['c/5'], duration: '16' }), new StaveNote({ keys: ['e/5'], duration: '16' }), new StaveNote({ keys: ['g/4'], duration: '16' }), new StaveNote({ keys: ['c/5'], duration: '16' }), new StaveNote({ keys: ['e/5'], duration: '16' }), ]; const notes2 = [ new StaveNote({ keys: ['a/4'], duration: '16r' }), new StaveNote({ keys: ['e/4.'], duration: '8d' }), new StaveNote({ keys: ['e/4'], duration: '4' }), new StaveNote({ keys: ['a/4'], duration: '16r' }), new StaveNote({ keys: ['e/4.'], duration: '8d' }), new StaveNote({ keys: ['e/4'], duration: '4' }), ]; const f = VexFlowTests.makeFactory(options, 750, 280); const context = f.getContext(); const voice1 = new Voice({ numBeats: 4, beatValue: 4 }); voice1.addTickables(notes1); const voice2 = new Voice({ numBeats: 4, beatValue: 4 }); voice2.addTickables(notes2); const formatter = new Formatter({ globalSoftmax: options.params.globalSoftmax }); formatter.joinVoices([voice1]); formatter.joinVoices([voice2]); const width = formatter.preCalculateMinTotalWidth([voice1, voice2]); formatter.format([voice1, voice2], width); const stave1 = new Stave(10, 40, width + Stave.defaultPadding); const stave2 = new Stave(10, 100, width + Stave.defaultPadding); stave1.setContext(context).drawWithStyle(); stave2.setContext(context).drawWithStyle(); voice1.draw(context, stave1); voice2.draw(context, stave2); options.assert.ok(voice1.getTickables()[1].getX() > voice2.getTickables()[1].getX()); } function alignedMixedElements(options) { const f = VexFlowTests.makeFactory(options, 800, 500); const context = f.getContext(); const stave = new Stave(10, 200, 400); const stave2 = new Stave(410, 200, 400); const notes = [ new StaveNote({ keys: ['c/5'], duration: '8' }) .addModifier(new Accidental('##'), 0) .addModifier(new FretHandFinger('4').setPosition(ModifierPosition.BELOW), 0) .addModifier(new StringNumber('3').setPosition(ModifierPosition.BELOW), 0) .addModifier(new Articulation('a.').setPosition(ModifierPosition.BELOW), 0) .addModifier(new Articulation('a>').setPosition(ModifierPosition.BELOW), 0) .addModifier(new Articulation('a^').setPosition(ModifierPosition.BELOW), 0) .addModifier(new Articulation('am').setPosition(ModifierPosition.BELOW), 0) .addModifier(new Articulation('a@u').setPosition(ModifierPosition.BELOW), 0) .addModifier(new Annotation('yyyy').setVerticalJustification(AnnotationVerticalJustify.BOTTOM), 0) .addModifier(new Annotation('xxxx').setVerticalJustification(AnnotationVerticalJustify.BOTTOM).setFont('sans-serif', 20), 0) .addModifier(new Annotation('ttt').setVerticalJustification(AnnotationVerticalJustify.BOTTOM).setFont('sans-serif', 20), 0), new StaveNote({ keys: ['c/5'], duration: '8', stemDirection: Stem.DOWN }) .addModifier(new StringNumber('3').setPosition(ModifierPosition.BELOW), 0) .addModifier(new Articulation('a.').setPosition(ModifierPosition.BELOW), 0) .addModifier(new Articulation('a>').setPosition(ModifierPosition.BELOW), 0), new StaveNote({ keys: ['c/5'], duration: '8' }), ]; const notes2 = [ new StaveNote({ keys: ['c/5'], duration: '8' }) .addModifier(new StringNumber('3').setPosition(ModifierPosition.ABOVE), 0) .addModifier(new Articulation('a.').setPosition(ModifierPosition.ABOVE), 0) .addModifier(new Annotation('yyyy').setVerticalJustification(AnnotationVerticalJustify.TOP), 0), new StaveNote({ keys: ['c/5'], duration: '8', stemDirection: Stem.DOWN }) .addModifier(new FretHandFinger('4').setPosition(ModifierPosition.ABOVE), 0) .addModifier(new StringNumber('3').setPosition(ModifierPosition.ABOVE), 0) .addModifier(new Articulation('a.').setPosition(ModifierPosition.ABOVE), 0) .addModifier(new Articulation('a>').setPosition(ModifierPosition.ABOVE), 0) .addModifier(new Articulation('a^').setPosition(ModifierPosition.ABOVE), 0) .addModifier(new Articulation('am').setPosition(ModifierPosition.ABOVE), 0) .addModifier(new Articulation('a@u').setPosition(ModifierPosition.ABOVE), 0) .addModifier(new Annotation('yyyy').setVerticalJustification(AnnotationVerticalJustify.TOP), 0) .addModifier(new Annotation('xxxx').setVerticalJustification(AnnotationVerticalJustify.TOP).setFont('sans-serif', 20), 0) .addModifier(new Annotation('ttt').setVerticalJustification(AnnotationVerticalJustify.TOP).setFont('sans-serif', 20), 0), new StaveNote({ keys: ['c/5'], duration: '8' }), ]; const tuplet = new Tuplet(notes).setTupletLocation(-1); const tuplet2 = new Tuplet(notes2).setTupletLocation(1); Formatter.FormatAndDraw(context, stave, notes); Formatter.FormatAndDraw(context, stave2, notes2); stave.setContext(context).drawWithStyle(); stave2.setContext(context).drawWithStyle(); tuplet.setContext(context).drawWithStyle(); tuplet2.setContext(context).drawWithStyle(); options.assert.ok(true); } function justifyStaveNotes(options) { const f = VexFlowTests.makeFactory(options, 520, 280); const ctx = f.getContext(); const score = f.EasyScore(); let y = 30; function justifyToWidth(width) { f.Stave({ y: y }).addClef('treble'); const voices = [ score.voice(score.notes('(cbb4 en4 a4)/2, (d4 e4 f4)/8, (d4 f4 a4)/8, (cn4 f#4 a4)/4', { stem: 'down' })), score.voice(score.notes('(bb4 e#5 a5)/4, (d5 e5 f5)/2, (c##5 fb5 a5)/4', { stem: 'up' })), ]; f.Formatter() .joinVoices(voices) .format(voices, width - (Stave.defaultPadding + getGlyphWidth(Glyphs.gClef))); voices[0].getTickables().forEach((note) => Note.plotMetrics(ctx, note, y + 140)); voices[1].getTickables().forEach((note) => Note.plotMetrics(ctx, note, y - 20)); y += 210; } justifyToWidth(520); f.draw(); options.assert.ok(true); } function notesWithTab(options) { const f = VexFlowTests.makeFactory(options, 420, 580); const score = f.EasyScore(); let y = 10; function justifyToWidth(width) { const stave = f.Stave({ y: y }).addClef('treble'); const voice = score.voice(score.notes('d#4/2, (c4 d4)/8, d4/8, (c#4 e4 a4)/4', { stem: 'up' })); y += 100; f.TabStave({ y: y }).addTabGlyph().setNoteStartX(stave.getNoteStartX()); const tabVoice = score.voice([ f .TabNote({ positions: [{ str: 3, fret: 6 }], duration: '2' }) .addModifier(new Bend([{ type: Bend.UP, text: 'Full' }]), 0), f .TabNote({ positions: [ { str: 2, fret: 3 }, { str: 3, fret: 5 }, ], duration: '8', }) .addModifier(new Bend([{ type: Bend.UP, text: 'Unison' }]), 1), f.TabNote({ positions: [{ str: 3, fret: 7 }], duration: '8' }), f.TabNote({ positions: [ { str: 3, fret: 6 }, { str: 4, fret: 7 }, { str: 2, fret: 5 }, ], duration: '4', }), ]); f.Formatter().joinVoices([voice]).joinVoices([tabVoice]).format([voice, tabVoice], width); y += 150; } justifyToWidth(0); justifyToWidth(300); f.draw(); options.assert.ok(true); } function multiStaves(options) { const f = VexFlowTests.makeFactory(options, 600, 400); const ctx = f.getContext(); const score = f.EasyScore(); const notes11 = score.notes('f4/4, d4/8, g4/4, eb4/8'); const notes21 = score.notes('d4/8, d4, d4, d4, e4, eb4'); const notes31 = score.notes('a5/8, a5, a5, a5, a5, a5', { stem: 'down' }); let voices = [ score.voice(notes11, { time: '6/8' }), score.voice(notes21, { time: '6/8' }), score.voice(notes31, { time: '6/8' }), ]; let formatter = f.Formatter(); voices.forEach((v) => formatter.joinVoices([v])); let width = formatter.preCalculateMinTotalWidth(voices); formatter.format(voices, width); let beams = [ new Beam(notes21.slice(0, 3), true), new Beam(notes21.slice(3, 6), true), new Beam(notes31.slice(0, 3), true), new Beam(notes31.slice(3, 6), true), ]; const staveYs = [20, 130, 250]; let staveWidth = width + getGlyphWidth(Glyphs.gClef) + getGlyphWidth(Glyphs.timeSig8) + Stave.defaultPadding; let staves = [ f.Stave({ y: staveYs[0], width: staveWidth }).addClef('treble').addTimeSignature('6/8'), f.Stave({ y: staveYs[1], width: staveWidth }).addClef('treble').addTimeSignature('6/8'), f.Stave({ y: staveYs[2], width: staveWidth }).addClef('bass').addTimeSignature('6/8'), ]; f.StaveConnector({ topStave: staves[1], bottomStave: staves[2], type: 'brace', }); for (let i = 0; i < staves.length; ++i) { staves[i].setContext(ctx).drawWithStyle(); voices[i].draw(ctx, staves[i]); } beams.forEach((beam) => beam.setContext(ctx).drawWithStyle()); const notes12 = score.notes('ab4/4, bb4/8, (cb5 eb5)/4[stem="down"], d5/8[stem="down"]'); const notes22 = score.notes('(eb4 ab4)/4., (c4 eb4 ab4)/4, db5/8', { stem: 'up' }); const notes32 = score.notes('a5/8, a5, a5, a5, a5, a5', { stem: 'down' }); voices = [ score.voice(notes12, { time: '6/8' }), score.voice(notes22, { time: '6/8' }), score.voice(notes32, { time: '6/8' }), ]; formatter = f.Formatter(); voices.forEach((v) => formatter.joinVoices([v])); width = formatter.preCalculateMinTotalWidth(voices); const staveX = staves[0].getX() + staves[0].getWidth(); staveWidth = width + Stave.defaultPadding; staves = [ f.Stave({ x: staveX, y: staveYs[0], width: staveWidth }), f.Stave({ x: staveX, y: staveYs[1], width: staveWidth }), f.Stave({ x: staveX, y: staveYs[2], width: staveWidth }), ]; formatter.format(voices, width); beams = [ new Beam(notes32.slice(0, 3), true), new Beam(notes32.slice(3, 6), true), ]; for (let i = 0; i < staves.length; ++i) { staves[i].setContext(ctx).drawWithStyle(); voices[i].draw(ctx, staves[i]); voices[i].getTickables().forEach((note) => Note.plotMetrics(ctx, note, staveYs[i] - 20)); } beams.forEach((beam) => beam.setContext(ctx).drawWithStyle()); options.assert.ok(true); } function proportional(options) { const debug = options.params.debug; Registry.enableDefaultRegistry(new Registry()); const f = VexFlowTests.makeFactory(options, 775, 750); const system = f.System({ x: 50, autoWidth: true, debugFormatter: debug, noJustification: !(options.params.justify === undefined && true), formatIterations: options.params.iterations, details: { alpha: options.params.alpha }, }); const score = f.EasyScore(); const voices = [ score.notes('c5/8, c5'), score.tuplet(score.notes('a4/8, a4, a4'), { notesOccupied: 2 }), score.notes('c5/16, c5, c5, c5'), score.tuplet(score.notes('a4/16, a4, a4, a4, a4'), { notesOccupied: 4 }), score.tuplet(score.notes('a4/32, a4, a4, a4, a4, a4, a4'), { notesOccupied: 8 }), ]; const createVoice = (notes) => score.voice(notes, { time: '1/4' }); const createStave = (voice) => system .addStave({ voices: [voice], debugNoteMetrics: debug }) .addClef('treble') .addTimeSignature('1/4'); voices.map(createVoice).forEach(createStave); system.addConnector().setType(StaveConnector.type.BRACKET); f.draw(); Registry.disableDefaultRegistry(); options.assert.ok(true); } function softMax(options) { const f = VexFlowTests.makeFactory(options, 550, 500); const textX = 450 / 0.8; f.getContext().scale(0.8, 0.8); function draw(y, factor) { const score = f.EasyScore(); const system = f.System({ x: 100, y, details: { softmaxFactor: factor }, autoWidth: true, }); system .addStave({ voices: [ score.voice(score .notes('C#5/h, a4/q') .concat(score.beam(score.notes('Abb4/8, A4/8'))) .concat(score.beam(score.notes('A4/16, A#4, A4, Ab4/32, A4'))), { time: '5/4' }), ], }) .addClef('treble') .addTimeSignature('5/4'); f.draw(); f.getContext().fillText(`softmax: ${factor.toString()}`, textX, y + 50); options.assert.ok(true); } draw(50, 1); draw(150, 2); draw(250, 5); draw(350, 10); draw(450, 15); } function mixTime(options) { const f = VexFlowTests.makeFactory(options, 400 + Stave.defaultPadding, 250); f.getContext().scale(0.8, 0.8); const score = f.EasyScore(); const system = f.System({ details: {}, autoWidth: true, debugFormatter: true, }); system .addStave({ voices: [score.voice(score.notes('C#5/q, B4').concat(score.beam(score.notes('A4/8, E4, C4, D4'))))], }) .addClef('treble') .addTimeSignature('4/4'); system .addStave({ voices: [score.voice(score.notes('C#5/q, B4, B4').concat(score.tuplet(score.beam(score.notes('A4/8, E4, C4')))))], }) .addClef('treble') .addTimeSignature('4/4'); f.draw(); options.assert.ok(true); } function tightNotes1(options) { const f = VexFlowTests.makeFactory(options, 440, 250); f.getContext().scale(0.8, 0.8); const score = f.EasyScore(); const system = f.System({ autoWidth: true, debugFormatter: true, details: { maxIterations: 10 }, }); system .addStave({ voices: [ score.voice(score.beam(score.notes('B4/16, B4, B4, B4, B4, B4, B4, B4')).concat(score.notes('B4/q, B4'))), ], }) .addClef('treble') .addTimeSignature('4/4'); system .addStave({ voices: [ score.voice(score.notes('B4/q, B4').concat(score.beam(score.notes('B4/16, B4, B4, B4, B4, B4, B4, B4')))), ], }) .addClef('treble') .addTimeSignature('4/4'); f.draw(); options.assert.ok(true); } function tightNotes2(options) { const f = VexFlowTests.makeFactory(options, 440, 250); f.getContext().scale(0.8, 0.8); const score = f.EasyScore(); const system = f.System({ autoWidth: true, debugFormatter: true, }); system .addStave({ voices: [ score.voice(score.beam(score.notes('B4/16, B4, B4, B4, B4, B4, B4, B4')).concat(score.notes('B4/q, B4'))), ], }) .addClef('treble') .addTimeSignature('4/4'); system .addStave({ voices: [score.voice(score.notes('B4/w'))], }) .addClef('treble') .addTimeSignature('4/4'); f.draw(); options.assert.ok(true); } function annotations(options) { const pageWidth = 916; const pageHeight = 600; const f = VexFlowTests.makeFactory(options, pageWidth, pageHeight); const context = f.getContext(); const lyrics1 = ['ipso', 'ipso-', 'ipso', 'ipso', 'ipsoz', 'ipso-', 'ipso', 'ipso', 'ipso', 'ip', 'ipso']; const lyrics2 = ['ipso', 'ipso-', 'ipsoz', 'ipso', 'ipso', 'ipso-', 'ipso', 'ipso', 'ipso', 'ip', 'ipso']; const smar = [ { sm: 5, width: 550, lyrics: lyrics1, title: '550px,softMax:5', }, { sm: 5, width: 550, lyrics: lyrics2, title: '550px,softmax:5,different word order', }, { sm: 10, width: 550, lyrics: lyrics2, title: '550px,softmax:10', }, { sm: 15, width: 550, lyrics: lyrics2, title: '550px,softmax:15', }, ]; const rowSize = 140; const beats = 12; const beatsPer = 8; const beamGroup = 3; const durations = ['8d', '16', '8', '8d', '16', '8', '8d', '16', '8', '4', '8']; const beams = []; let y = 40; smar.forEach((sm) => { const stave = new Stave(10, y, sm.width); const notes = []; let iii = 0; context.fillText(sm.title, 100, y); y += rowSize; durations.forEach((dd) => { const note = new StaveNote({ keys: ['b/4'], duration: dd }); if (dd.indexOf('d') >= 0) { Dot.buildAndAttach([note], { all: true }); } if (sm.lyrics.length > iii) { note.addModifier(new Annotation(sm.lyrics[iii]) .setVerticalJustification(Annotation.VerticalJustify.BOTTOM) .setFont(Metrics.get('fontFamily'), 12, FontWeight.NORMAL)); } notes.push(note); iii += 1; }); notes.forEach((note) => { if (note.getDuration().indexOf('d') >= 0) { Dot.buildAndAttach([note], { all: true }); } }); let notesToBeam = []; notes.forEach((note) => { if (note.getIntrinsicTicks() < 4096) { notesToBeam.push(note); if (notesToBeam.length >= beamGroup) { beams.push(new Beam(notesToBeam)); notesToBeam = []; } } else { notesToBeam = []; } }); const voice1 = new Voice({ numBeats: beats, beatValue: beatsPer }).setMode(Voice.Mode.SOFT).addTickables(notes); const fmt = new Formatter({ softmaxFactor: sm.sm, maxIterations: 2 }).joinVoices([voice1]); fmt.format([voice1], sm.width - 11); stave.setContext(context).drawWithStyle(); voice1.draw(context, stave); beams.forEach((b) => b.setContext(context).drawWithStyle()); }); options.assert.ok(true); } VexFlowTests.register(FormatterTests); export { FormatterTests };