UNPKG

music21j-port

Version:

A toolkit for computer-aided musicology, Javascript version

1,545 lines (1,380 loc) 57.9 kB
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>Music21j Source: music21/vfShow.js</title> <!--[if lt IE 9]> <script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script> <![endif]--> <link type="text/css" rel="stylesheet" href="styles/sunlight.default.css"> <link type="text/css" rel="stylesheet" href="styles/site.spruce.css"> </head> <body> <div class="container-fluid"> <div class="navbar navbar-fixed-top navbar-inverse"> <div class="navbar-inner"> <a class="brand" href="index.html">Music21j</a> <ul class="nav"> <li class="dropdown"> <a href="namespaces.list.html" class="dropdown-toggle" data-toggle="dropdown">Namespaces<b class="caret"></b></a> <ul class="dropdown-menu "> <li> <a href="music21.html">music21</a> </li> <li> <a href="music21.articulations.html">music21.articulations</a> </li> <li> <a href="music21.audioSearch.html">music21.audioSearch</a> </li> <li> <a href="music21.base.html">music21.base</a> </li> <li> <a href="music21.beam.html">music21.beam</a> </li> <li> <a href="music21.chord.html">music21.chord</a> </li> <li> <a href="music21.clef.html">music21.clef</a> </li> <li> <a href="music21.common.html">music21.common</a> </li> <li> <a href="music21.duration.html">music21.duration</a> </li> <li> <a href="music21.dynamics.html">music21.dynamics</a> </li> <li> <a href="music21.expressions.html">music21.expressions</a> </li> <li> <a href="music21.fromPython.html">music21.fromPython</a> </li> <li> <a href="music21.instrument.html">music21.instrument</a> </li> <li> <a href="music21.interval.html">music21.interval</a> </li> <li> <a href="music21.key.html">music21.key</a> </li> <li> <a href="music21.keyboard.html">music21.keyboard</a> </li> <li> <a href="music21.meter.html">music21.meter</a> </li> <li> <a href="music21.miditools.html">music21.miditools</a> </li> <li> <a href="music21.note.html">music21.note</a> </li> <li> <a href="music21.pitch.html">music21.pitch</a> </li> <li> <a href="music21.prebase.html">music21.prebase</a> </li> <li> <a href="music21.renderOptions.html">music21.renderOptions</a> </li> <li> <a href="music21.roman.html">music21.roman</a> </li> <li> <a href="music21.scale.html">music21.scale</a> </li> <li> <a href="music21.sites.html">music21.sites</a> </li> <li> <a href="music21.stream.html">music21.stream</a> </li> <li> <a href="music21.tempo.html">music21.tempo</a> </li> <li> <a href="music21.tie.html">music21.tie</a> </li> <li> <a href="music21.tinyNotation.html">music21.tinyNotation</a> </li> <li> <a href="music21.vfShow.html">music21.vfShow</a> </li> <li> <a href="music21.webmidi.html">music21.webmidi</a> </li> </ul> </li> <li class="dropdown"> <a href="classes.list.html" class="dropdown-toggle" data-toggle="dropdown">Classes<b class="caret"></b></a> <ul class="dropdown-menu "> <li> <a href="music21.articulations.Accent.html">music21.articulations.Accent</a> </li> <li> <a href="music21.articulations.Articulation.html">music21.articulations.Articulation</a> </li> <li> <a href="music21.articulations.DynamicArticulation.html">music21.articulations.DynamicArticulation</a> </li> <li> <a href="music21.articulations.LengthArticulation.html">music21.articulations.LengthArticulation</a> </li> <li> <a href="music21.articulations.Marcato.html">music21.articulations.Marcato</a> </li> <li> <a href="music21.articulations.PitchArticulation.html">music21.articulations.PitchArticulation</a> </li> <li> <a href="music21.articulations.Spiccato.html">music21.articulations.Spiccato</a> </li> <li> <a href="music21.articulations.Staccatissimo.html">music21.articulations.Staccatissimo</a> </li> <li> <a href="music21.articulations.Staccato.html">music21.articulations.Staccato</a> </li> <li> <a href="music21.articulations.StrongAccent.html">music21.articulations.StrongAccent</a> </li> <li> <a href="music21.articulations.Tenuto.html">music21.articulations.Tenuto</a> </li> <li> <a href="music21.articulations.TimbreArticulation.html">music21.articulations.TimbreArticulation</a> </li> <li> <a href="music21.base.Music21Object.html">music21.base.Music21Object</a> </li> <li> <a href="music21.beam.Beam.html">music21.beam.Beam</a> </li> <li> <a href="music21.beam.Beams.html">music21.beam.Beams</a> </li> <li> <a href="music21.chord.Chord.html">music21.chord.Chord</a> </li> <li> <a href="music21.clef.AltoClef.html">music21.clef.AltoClef</a> </li> <li> <a href="music21.clef.BassClef.html">music21.clef.BassClef</a> </li> <li> <a href="music21.clef.Clef.html">music21.clef.Clef</a> </li> <li> <a href="music21.clef.MezzoSopranoClef.html">music21.clef.MezzoSopranoClef</a> </li> <li> <a href="music21.clef.PercussionClef.html">music21.clef.PercussionClef</a> </li> <li> <a href="music21.clef.SopranoClef.html">music21.clef.SopranoClef</a> </li> <li> <a href="music21.clef.TenorClef.html">music21.clef.TenorClef</a> </li> <li> <a href="music21.clef.Treble8vaClef.html">music21.clef.Treble8vaClef</a> </li> <li> <a href="music21.clef.Treble8vbClef.html">music21.clef.Treble8vbClef</a> </li> <li> <a href="music21.clef.TrebleClef.html">music21.clef.TrebleClef</a> </li> <li> <a href="music21.duration.Duration.html">music21.duration.Duration</a> </li> <li> <a href="music21.duration.Tuplet.html">music21.duration.Tuplet</a> </li> <li> <a href="music21.dynamics.Dynamic.html">music21.dynamics.Dynamic</a> </li> <li> <a href="music21.expressions.Expression.html">music21.expressions.Expression</a> </li> <li> <a href="music21.expressions.Fermata.html">music21.expressions.Fermata</a> </li> <li> <a href="music21.fromPython.Converter.html">music21.fromPython.Converter</a> </li> <li> <a href="music21.instrument.Instrument.html">music21.instrument.Instrument</a> </li> <li> <a href="music21.interval.ChromaticInterval.html">music21.interval.ChromaticInterval</a> </li> <li> <a href="music21.interval.DiatonicInterval.html">music21.interval.DiatonicInterval</a> </li> <li> <a href="music21.interval.GenericInterval.html">music21.interval.GenericInterval</a> </li> <li> <a href="music21.interval.Interval.html">music21.interval.Interval</a> </li> <li> <a href="music21.key.Key.html">music21.key.Key</a> </li> <li> <a href="music21.key.KeySignature.html">music21.key.KeySignature</a> </li> <li> <a href="music21.keyboard.BlackKey.html">music21.keyboard.BlackKey</a> </li> <li> <a href="music21.keyboard.Key.html">music21.keyboard.Key</a> </li> <li> <a href="music21.keyboard.Keyboard.html">music21.keyboard.Keyboard</a> </li> <li> <a href="music21.keyboard.WhiteKey.html">music21.keyboard.WhiteKey</a> </li> <li> <a href="music21.meter.TimeSignature.html">music21.meter.TimeSignature</a> </li> <li> <a href="music21.miditools.Event.html">music21.miditools.Event</a> </li> <li> <a href="music21.miditools.MidiPlayer.html">music21.miditools.MidiPlayer</a> </li> <li> <a href="music21.note.GeneralNote.html">music21.note.GeneralNote</a> </li> <li> <a href="music21.note.Lyric.html">music21.note.Lyric</a> </li> <li> <a href="music21.note.Note.html">music21.note.Note</a> </li> <li> <a href="music21.note.NotRest.html">music21.note.NotRest</a> </li> <li> <a href="music21.note.Rest.html">music21.note.Rest</a> </li> <li> <a href="music21.pitch.Accidental.html">music21.pitch.Accidental</a> </li> <li> <a href="music21.pitch.Pitch.html">music21.pitch.Pitch</a> </li> <li> <a href="music21.prebase.ProtoM21Object.html">music21.prebase.ProtoM21Object</a> </li> <li> <a href="music21.renderOptions.RenderOptions.html">music21.renderOptions.RenderOptions</a> </li> <li> <a href="music21.roman.RomanNumeral.html">music21.roman.RomanNumeral</a> </li> <li> <a href="music21.stream.Measure.html">music21.stream.Measure</a> </li> <li> <a href="music21.stream.Part.html">music21.stream.Part</a> </li> <li> <a href="music21.stream.Score.html">music21.stream.Score</a> </li> <li> <a href="music21.stream.Stream.html">music21.stream.Stream</a> </li> <li> <a href="music21.stream.Voice.html">music21.stream.Voice</a> </li> <li> <a href="music21.tempo.Metronome.html">music21.tempo.Metronome</a> </li> <li> <a href="music21.tie.Tie.html">music21.tie.Tie</a> </li> <li> <a href="music21.vfShow.Renderer.html">music21.vfShow.Renderer</a> </li> <li> <a href="music21.vfShow.RenderStack.html">music21.vfShow.RenderStack</a> </li> <li> <a href="Notation.html">Notation</a> </li> <li> <a href="Page.html">Page</a> </li> <li> <a href="Recorder.html">Recorder</a> </li> </ul> </li> <li class="dropdown"> <a href="global.html" class="dropdown-toggle" data-toggle="dropdown">Global<b class="caret"></b></a> <ul class="dropdown-menu "> <li> <a href="global.html#getM21attribute">getM21attribute</a> </li> <li> <a href="global.html#layout">layout</a> </li> <li> <a href="global.html#midiNumDiffFromFrequency">midiNumDiffFromFrequency</a> </li> <li> <a href="global.html#recorderWorkerJs">recorderWorkerJs</a> </li> <li> <a href="global.html#unpickler">unpickler</a> </li> <li> <a href="global.html#warnBanner">warnBanner</a> </li> </ul> </li> </ul> </div> </div> <div class="row-fluid"> <div class="span12"> <div id="main"> <h1 class="page-title">Source: music21/vfShow.js</h1> <section> <article> <pre class="sunlight-highlight-javascript linenums">/** * music21j -- Javascript reimplementation of Core music21p features. * music21/vfShow -- Vexflow integration * * Copyright (c) 2013-17, Michael Scott Cuthbert and cuthbertLab * Based on music21 (=music21p), Copyright (c) 2006–17, Michael Scott Cuthbert and cuthbertLab * */ import * as $ from 'jquery'; import * as Vex from 'vexflow'; import { debug } from './debug.js'; import { duration } from './duration.js'; /** * for rendering vexflow. Will eventually go to music21/converter/vexflow * * See {@link music21.vfShow} namespace for details * * @exports music21/vfShow */ /** * Vexflow display related objects and methods. * * @namespace music21.vfShow * @memberof music21 * @requires music21/common * @requires vexflow */ export const vfShow = {}; /** * Represents a stack of objects that need to be rendered together. * * An intermediary state for showing created by {@link music21.vfShow.Renderer}. * * @class RenderStack * @memberof music21.vfShow * @property {Array&lt;Vex.Flow.Voice>} voices - Vex.Flow.Voice objects * @property {Array&lt;music21.stream.Stream>} streams - {@link music21.stream.Stream} objects * associated with the voices * @property {Array} textVoices - Vex.Flow.Voice objects for the text. */ export class RenderStack { constructor() { this.voices = []; this.streams = []; this.textVoices = []; this.voiceToStreamMapping = new Map(); } /** * @memberof music21.vfShow.RenderStack * @returns {Array} this.voices and this.textVoices as one array */ allTickables() { const t = []; t.push(...this.voices); t.push(...this.textVoices); return t; } /** * @memberof music21.vfShow.RenderStack * @returns {Array&lt;Array>} each array represents one staff.... * where this.voices and this.textVoices are all in that staff... */ tickablesByStave() { const tickablesByStave = []; // a list of lists of tickables being placed on the same Stave. const knownStaves = []; // a list of Vex.Flow.Stave objects... for (const t of this.allTickables()) { const thisStaveIndex = knownStaves.indexOf(t.stave); let currentStaveHolder; if (thisStaveIndex === -1) { knownStaves.push(t.stave); currentStaveHolder = []; tickablesByStave.push(currentStaveHolder); } else { currentStaveHolder = tickablesByStave[thisStaveIndex]; } currentStaveHolder.push(t); } return tickablesByStave; } } vfShow.RenderStack = RenderStack; /** * Renderer is a function that takes a stream, an * optional existing canvas or SVG element and a DOM * element where the canvas or SVG element should be placed * and renders the stream as Vexflow on the * canvas or svg element, placing it then in the where * DOM. * * "s" can be any type of Stream. * * "div" and "where" can be either a DOM * element or a jQuery object. * * @class Renderer * @memberof music21.vfShow * @param {music21.stream.Stream} s - main stream to render * @param {div} [div] - existing canvas or div-surroundingSVG element * @param {DOMObject|jQueryDOMObject} [where=document.body] - where to render the stream * @property {Vex.Flow.Renderer} vfRenderer - a Vex.Flow.Renderer to use * (will create if not existing) * @property {string} rendererType - canvas or svg * @property {Vex.Flow.Context} ctx - a Vex.Flow.Context (Canvas or SVG) to use. * @property {div} div - div-with-svg-or-canvas element * @property {jQueryDOMObject} $div - jQuery div or canvas element * @property {jQueryDOMObject} $where - jQuery element to render onto * @property {Vex.Flow.Formatter} activeFormatter - formatter * @property {Array&lt;Vex.Flow.Beam>} beamGroups - beamGroups * @property {Array&lt;Vex.Flow.StaveTie>} vfTies - ties as instances of Vex.Flow.StaveTie * @property {Array&lt;number>} systemBreakOffsets - where to break the systems * @property {Array&lt;Vex.Flow.Tuplet>} vfTuplets - tuplets represented in Vexflow * @property {Array&lt;music21.vfShow.RenderStack>} stacks - array of RenderStack objects */ export class Renderer { constructor(s, div, where) { this.stream = s; // this.streamType = s.classes[-1]; this.rendererType = 'svg'; this.div = undefined; this.$div = undefined; this.$where = undefined; this.activeFormatter = undefined; this._vfRenderer = undefined; this._ctx = undefined; this.beamGroups = []; this.stacks = []; // an Array of RenderStacks: {voices: [Array of Vex.Flow.Voice objects], // streams: [Array of Streams, usually Measures]} this.vfTies = []; this.systemBreakOffsets = []; this.vfTuplets = []; // this.measureFormatters = []; if (where !== undefined) { if (where.jquery !== undefined) { this.$where = where; } else { this.$where = $(where); } } if (div !== undefined) { if (div.jquery !== undefined) { this.$div = div; this.div = div[0]; } else { this.div = div; this.$div = $(div); } } } get vfRenderer() { let backend; if (this.rendererType === 'canvas') { backend = Vex.Flow.Renderer.Backends.CANVAS; } else { backend = Vex.Flow.Renderer.Backends.SVG; } if (this._vfRenderer !== undefined) { return this._vfRenderer; } else { this._vfRenderer = new Vex.Flow.Renderer(this.div, backend); if (this.rendererType === 'svg') { this._vfRenderer.resize( this.$div.attr('width'), this.$div.attr('height') ); } return this._vfRenderer; } } set vfRenderer(vfr) { this._vfRenderer = vfr; } get ctx() { if (this._ctx !== undefined) { return this._ctx; } else { this._ctx = this.vfRenderer.getContext(); if ( this.stream &amp;&amp; this.stream.renderOptions &amp;&amp; this.stream.renderOptions.scaleFactor.x &amp;&amp; this.stream.renderOptions.scaleFactor.y ) { this._ctx.scale( this.stream.renderOptions.scaleFactor.x, this.stream.renderOptions.scaleFactor.y ); } return this._ctx; } } set ctx(ctx) { this._ctx = ctx; } /** * * main function to render a Stream. * * if s is undefined, uses the stored Stream from * the constructor object. * * @memberof music21.vfShow.Renderer * @param {music21.stream.Stream} [s=this.stream] */ render(s) { if (s === undefined) { s = this.stream; } let isScorelike = false; let isPartlike = false; const hasSubStreams = s.hasSubStreams(); if (s.isClassOrSubclass('Score')) { isScorelike = true; } else if (hasSubStreams &amp;&amp; s.get(0).hasSubStreams()) { isScorelike = true; } else if (hasSubStreams) { isPartlike = true; } // requires organization Score -> Part -> Measure -> elements... if (isScorelike) { this.prepareScorelike(s); } else if (isPartlike) { this.preparePartlike(s); } else { this.prepareArrivedFlat(s); } this.formatMeasureStacks(); this.drawTies(); this.drawMeasureStacks(); this.drawBeamGroups(); this.drawTuplets(); } /** * Prepares a scorelike stream (i.e., one with parts or * Streams that should be rendered vertically like parts) * for rendering and adds Staff Connectors * * @memberof music21.vfShow.Renderer * @param {music21.stream.Score} s - prepare a stream of parts (i.e., Score) */ prepareScorelike(s) { // console.log('prepareScorelike called'); // const parts = s.parts; for (const subStream of parts) { this.preparePartlike(subStream); } this.addStaffConnectors(s); } /** * * Prepares a Partlike stream (that is one with Measures * or substreams that should be considered like Measures) * for rendering. * * @memberof music21.vfShow.Renderer * @param {music21.stream.Part} p */ preparePartlike(p) { // console.log('preparePartlike called'); this.systemBreakOffsets = []; const measureList = p.measures; for (let i = 0; i &lt; measureList.length; i++) { const subStream = measureList.get(i); if (subStream.renderOptions.startNewSystem) { this.systemBreakOffsets.push(subStream.offset); } if (i === p.length - 1) { subStream.renderOptions.rightBarline = 'end'; } if (this.stacks[i] === undefined) { this.stacks[i] = new vfShow.RenderStack(); } this.prepareMeasure(subStream, this.stacks[i]); } this.prepareTies(p); } /** * * Prepares a score that arrived flat... sets up * stacks and vfTies after calling prepareFlat * * @memberof music21.vfShow.Renderer * @param {music21.stream.Stream} m - a flat stream (maybe a measure or voice) */ prepareArrivedFlat(m) { const stack = new vfShow.RenderStack(); this.prepareMeasure(m, stack); this.stacks[0] = stack; this.prepareTies(m); } /** * * Prepares a measure (w/ or w/o voices) or generic Stream -- makes accidentals, * associates a Vex.Flow.Stave with the stream and * returns a vexflow Voice object * * @memberof music21.vfShow.Renderer * @param {music21.stream.Measure} m - a measure object (w or w/o voices) * @param {music21.vfShow.RenderStack} stack - a RenderStack object to prepare into. */ prepareMeasure(m, stack) { if (m.hasVoices === undefined || m.hasVoices() === false) { this.prepareFlat(m, stack); } else { // TODO: don't assume that all elements are Voices; let stave; const rendOp = m.renderOptions; // get render options from Measure; for (let voiceIndex = 0; voiceIndex &lt; m.length; voiceIndex++) { const voiceStream = m.get(voiceIndex); stave = this.prepareFlat(voiceStream, stack, stave, rendOp); } } return stack; } /** * Main internal routine to prepare a flat stream * * @memberof music21.vfShow.Renderer * @param {music21.stream.Stream} s - a flat stream object * @param {music21.vfShow.RenderStack} stack - a RenderStack object to prepare into. * @param {Vex.Flow.Stave} [optionalStave] - an optional existing stave. * @param {Object} [optional_renderOp] - render options. * Passed to {@link music21.vfShow.Renderer#renderStave} * @returns {Vex.Flow.Stave} staff to return too * (also changes the `stack` parameter and runs `makeAccidentals` on s) */ prepareFlat(s, stack, optionalStave, optional_renderOp) { s.makeAccidentals(); let stave; if (optionalStave !== undefined) { stave = optionalStave; } else { stave = this.renderStave(s, optional_renderOp); } s.activeVFStave = stave; const voice = this.getVoice(s, stave); stack.voices.push(voice); stack.streams.push(s); stack.voiceToStreamMapping.set(voice, s); if (s.hasLyrics()) { stack.textVoices.push(this.getLyricVoice(s, stave)); } return stave; } /** * Render the Vex.Flow.Stave from a flat stream and draws it. * * Just draws the stave, not the notes, etc. * * @memberof music21.vfShow.Renderer * @param {music21.stream.Stream} [m=this.stream] - a flat stream * @param {Object} [optional_rendOp] - render options, passed * to {@link music21.vfShow.Renderer#newStave} and {@link music21.vfShow.Renderer#setClefEtc} * @returns {Vex.Flow.Stave} stave */ renderStave(m, optional_rendOp) { if (m === undefined) { m = this.stream; } const ctx = this.ctx; // stave will be passed in from Measure when we have Voices const stave = this.newStave(m, optional_rendOp); this.setClefEtc(m, stave, optional_rendOp); stave.setContext(ctx); stave.draw(); return stave; } /** * Draws the Voices (music and text) from `this.stacks` * * @memberof music21.vfShow.Renderer */ drawMeasureStacks() { const ctx = this.ctx; for (let i = 0; i &lt; this.stacks.length; i++) { const voices = this.stacks[i].allTickables(); for (let j = 0; j &lt; voices.length; j++) { const v = voices[j]; v.draw(ctx); } } } /** * draws the tuplets. * * @memberof music21.vfShow.Renderer */ drawTuplets() { const ctx = this.ctx; this.vfTuplets.forEach(vft => { vft.setContext(ctx).draw(); }); } /** * draws the vfTies * * @memberof music21.vfShow.Renderer */ drawTies() { const ctx = this.ctx; for (let i = 0; i &lt; this.vfTies.length; i++) { this.vfTies[i].setContext(ctx).draw(); } } /** * Finds all tied notes and creates the proper Vex.Flow.StaveTie objects in * `this.vfTies`. * * @memberof music21.vfShow.Renderer * @param {music21.stream.Part} p - a Part or similar object */ prepareTies(p) { const pf = p.flat.notesAndRests; // console.log('newSystemsAt', this.systemBreakOffsets); for (let i = 0; i &lt; pf.length - 1; i++) { const thisNote = pf.get(i); if (thisNote.tie === undefined || thisNote.tie.type === 'stop') { continue; } const nextNote = pf.get(i + 1); let onSameSystem = true; // this.systemBreakOffsets.length will be 0 for a flat score for (let sbI = 0; sbI &lt; this.systemBreakOffsets.length; sbI++) { const thisSystemBreak = this.systemBreakOffsets[sbI]; if ( thisNote.offset &lt; thisSystemBreak &amp;&amp; nextNote.offset >= thisSystemBreak ) { onSameSystem = false; break; } } if (onSameSystem) { const vfTie = new Vex.Flow.StaveTie({ first_note: thisNote.activeVexflowNote, last_note: nextNote.activeVexflowNote, first_indices: [0], last_indices: [0], }); this.vfTies.push(vfTie); } else { // console.log('got me a tie across systemBreaks!'); const vfTie1 = new Vex.Flow.StaveTie({ first_note: thisNote.activeVexflowNote, first_indices: [0], }); this.vfTies.push(vfTie1); const vfTie2 = new Vex.Flow.StaveTie({ last_note: nextNote.activeVexflowNote, first_indices: [0], }); this.vfTies.push(vfTie2); } } } /** * Returns a Vex.Flow.Voice object with all the tickables (i.e., Notes, Voices, etc.) * * Does not draw it... * * @memberof music21.vfShow.Renderer * @param {music21.stream.Stream} [s=this.stream] -- usually a Measure or Voice * @param {Vex.Flow.Stave} stave - not optional (would never fly in Python...) * @returns {Vex.Flow.Voice} */ getVoice(s, stave) { if (s === undefined) { s = this.stream; } // gets a group of notes as a voice, but completely unformatted and not drawn. const notes = this.vexflowNotes(s, stave); const voice = this.vexflowVoice(s); voice.setStave(stave); voice.addTickables(notes); return voice; } /** * Returns a Vex.Flow.Voice with the lyrics set to render in the proper place. * * @memberof music21.vfShow.Renderer * @param {music21.stream.Stream} s -- usually a Measure or Voice * @param {Vex.Flow.Stave} stave * @returns {Vex.Flow.Voice} */ getLyricVoice(s, stave) { const textVoice = this.vexflowVoice(s); const lyrics = this.vexflowLyrics(s, stave); textVoice.setStave(stave); textVoice.addTickables(lyrics); return textVoice; } /** * Aligns all of `this.stacks` (after they've been prepared) so they align properly. * * @memberof music21.vfShow.Renderer */ formatMeasureStacks() { // adds formats the voices, then adds the formatter information to every note in a voice... for (let i = 0; i &lt; this.stacks.length; i++) { const stack = this.stacks[i]; const voices = stack.voices; const measures = stack.streams; const formatter = this.formatVoiceGroup(stack); for (let j = 0; j &lt; measures.length; j++) { const m = measures[j]; const v = voices[j]; this.applyFormatterInformationToNotes(v.stave, m, formatter); } } } /** * Formats a single voice group from a stack. * * @memberof music21.vfShow.Renderer * @param {music21.vfShow.RenderStack} stack * @param {Boolean} [autoBeam=measures[0].autoBeam] * @returns {Vex.Flow.Formatter} */ formatVoiceGroup(stack, autoBeam) { // formats a group of voices to use the same formatter; returns the formatter // if autoBeam is true then it will apply beams for each voice and put them in // this.beamGroups; const allTickables = stack.allTickables(); const voices = stack.voices; const measures = stack.streams; if (autoBeam === undefined) { autoBeam = measures[0].autoBeam; } const formatter = new Vex.Flow.Formatter(); // var minLength = formatter.preCalculateMinTotalWidth([voices]); // console.log(minLength); if (voices.length === 0) { return formatter; } let maxGlyphStart = 0; // find the stave with the farthest start point -- diff key sig, etc. for (let i = 0; i &lt; allTickables.length; i++) { // console.log(voices[i], voices[i].stave, i); if (allTickables[i].stave.getNoteStartX() > maxGlyphStart) { maxGlyphStart = allTickables[i].stave.getNoteStartX(); } } for (let i = 0; i &lt; allTickables.length; i++) { allTickables[i].stave.setNoteStartX(maxGlyphStart); // corrected! } // TODO: should do the same for end_x -- for key sig changes, etc... const stave = voices[0].stave; // all staves should be same length, so does not matter; const tickablesByStave = stack.tickablesByStave(); for (const staveTickables of tickablesByStave) { formatter.joinVoices(staveTickables); } formatter.formatToStave(allTickables, stave); // const vf_auto_stem = false; // for (const voice of voices) { // let activeBeamGroupNotes = []; // for (let j = 0; j &lt; voice.notes.length; j++) { // const n = voice.notes[j]; // if (n.beams === undefined || !n.beams.getNumbers().includes(1)) { // continue; // } // const eighthNoteBeam = n.beams.getByNumber(1); // if (eighthNoteBeam.type === 'start') { // activeBeamGroupNotes = [n]; // } else { // activeBeamGroupNotes.push(n); // } // if (eighthNoteBeam.type === 'stop') { // const vfBeam = new Vex.Flow.Beam(activeBeamGroupNotes, vf_auto_stem); // this.beamGroups.push(vfBeam); // activeBeamGroupNotes = []; // housekeeping, not really necessary... // } // } // } if (autoBeam) { for (let i = 0; i &lt; voices.length; i++) { // find beam groups -- n.b. this wipes out stemDirection. worth it usually... const voice = voices[i]; const associatedStream = stack.voiceToStreamMapping.get(voice); let beatGroups; if ( associatedStream !== undefined &amp;&amp; associatedStream.timeSignature !== undefined ) { beatGroups = associatedStream.timeSignature.vexflowBeatGroups( Vex ); // TODO: getContextByClass... // console.log(beatGroups); } else { beatGroups = [new Vex.Flow.Fraction(2, 8)]; // default beam groups } const beamGroups = Vex.Flow.Beam.applyAndGetBeams( voice, undefined, beatGroups ); this.beamGroups.push(...beamGroups); } } return formatter; } /** * Draws the beam groups. * * @memberof music21.vfShow.Renderer */ drawBeamGroups() { const ctx = this.ctx; for (let i = 0; i &lt; this.beamGroups.length; i++) { this.beamGroups[i].setContext(ctx).draw(); } } /** * Return a new Vex.Flow.Stave object, which represents * a single MEASURE of notation in m21j * * @memberof music21.vfShow.Renderer * @param {music21.stream.Stream} s * @returns {Vex.Flow.Stave} */ newStave(s, rendOp) { if (s === undefined) { s = this.stream; } if (rendOp === undefined) { rendOp = s.renderOptions; } // measure level... let width = rendOp.width; if (width === undefined) { width = s.estimateStaffLength() + rendOp.staffPadding; } let top = rendOp.top; // * rendOp.scaleFactor.y; if (top === undefined) { top = 0; } let left = rendOp.left; if (left === undefined) { left = 10; } // console.log('streamLength: ' + streamLength); if (debug) { console.log( 'creating new stave: left:' + left + ' top: ' + top + ' width: ' + width ); } const stave = new Vex.Flow.Stave(left, top, width); return stave; } /** * Sets the number of stafflines, puts the clef on the Stave, * adds keySignature, timeSignature, and rightBarline * * @memberof music21.vfShow.Renderer * @param {music21.stream.Stream} s * @param {Vex.Flow.Stave} stave * @param {Object} [rendOp=s.renderOptions] - a {@link music21.renderOptions.RenderOptions} * object that might have * `{showMeasureNumber: boolean, rightBarLine: string&lt;{'single', 'double', 'end'}>}` */ setClefEtc(s, stave, rendOp) { if (rendOp === undefined) { rendOp = s.renderOptions; } this.setStafflines(s, stave); if (rendOp.showMeasureNumber) { stave.setMeasure(rendOp.measureIndex + 1); } if (rendOp.displayClef) { let ottava; const size = 'default'; if (s.clef.octaveChange === 1) { ottava = '8va'; } else if (s.clef.octaveChange === -1) { ottava = '8vb'; } stave.addClef(s.clef.name, size, ottava); } if (s.keySignature !== undefined &amp;&amp; rendOp.displayKeySignature) { const ksVFName = s.keySignature.majorName().replace(/-/g, 'b'); stave.addKeySignature(ksVFName); } if (s.timeSignature !== undefined &amp;&amp; rendOp.displayTimeSignature) { stave.addTimeSignature( s.timeSignature.numerator.toString() + '/' + s.timeSignature.denominator.toString() ); } if (rendOp.rightBarline !== undefined) { const bl = rendOp.rightBarline; const barlineMap = { single: 'SINGLE', double: 'DOUBLE', end: 'END', }; const vxBL = barlineMap[bl]; if (vxBL !== undefined) { stave.setEndBarType(Vex.Flow.Barline.type[vxBL]); } } } /** * Sets the number of stafflines properly for the Stave object. * * This method does not just set Vex.Flow.Stave#setNumLines() except * if the number of lines is 0 or >=4, because the default in VexFlow is * to show the bottom(top?), not middle, lines and that looks bad. * * @memberof music21.vfShow.Renderer * @param {music21.stream.Stream} s - stream to get the `.staffLines` * from `s.renderOptions` from -- should allow for overriding. * @param {Vex.Flow.Stave} vexflowStave - stave to set the staff lines for. */ setStafflines(s, vexflowStave) { const rendOp = s.renderOptions; if (rendOp.staffLines !== 5) { if (rendOp.staffLines === 0) { vexflowStave.setNumLines(0); } else if (rendOp.staffLines === 1) { // Vex.Flow.Stave.setNumLines hides all but the top line. // this is better vexflowStave.options.line_config = [ { visible: false }, { visible: false }, { visible: true }, // show middle { visible: false }, { visible: false }, ]; } else if (rendOp.staffLines === 2) { vexflowStave.options.line_config = [ { visible: false }, { visible: false }, { visible: true }, // show middle { visible: true }, { visible: false }, ]; } else if (rendOp.staffLines === 3) { vexflowStave.options.line_config = [ { visible: false }, { visible: true }, { visible: true }, // show middle { visible: true }, { visible: false }, ]; } else { vexflowStave.setNumLines(rendOp.staffLines); } } } /** * Gets the Vex.Flow.StaveNote objects from a Stream. * * Also changes `this.vfTuplets`. * * @memberof music21.vfShow.Renderer * @param {music21.stream.Stream} [s=this.stream] - flat stream to find notes in * @param {Vex.Flow.Stave} stave - Vex.Flow.Stave to render notes on to. * @returns {Array&lt;Vex.Flow.StaveNote>} notes to return */ vexflowNotes(s, stave) { if (s === undefined) { s = this.stream; } // runs on a flat stream, returns a list of voices... const notes = []; const vfTuplets = []; let activeTuplet; let activeTupletLength = 0.0; let activeTupletVexflowNotes = []; const options = { clef: s.clef, stave }; for (const thisEl of s) { if ( thisEl.isClassOrSubclass('GeneralNote') &amp;&amp; thisEl.duration !== undefined ) { // sets thisEl.activeVexflowNote -- may be overwritten but not so fast... const vfn = thisEl.vexflowNote(options); if (vfn === undefined) { console.error('Cannot create a vexflowNote from: ', thisEl); } if (stave !== undefined) { vfn.setStave(stave); } notes.push(vfn); // account for tuplets... if (thisEl.duration.tuplets.length > 0) { // only support one tuplet per note -- like vexflow if (activeTuplet === undefined) { activeTuplet = thisEl.duration.tuplets[0]; } activeTupletVexflowNotes.push(vfn); activeTupletLength += thisEl.duration.quarterLength; // console.log(activeTupletLength, activeTuplet.totalTupletLength()); // // Add tuplet when complete. if ( activeTupletLength >= activeTuplet.totalTupletLength() || Math.abs( activeTupletLength - activeTuplet.totalTupletLength() ) &lt; 0.001 ) { // console.log(activeTupletVexflowNotes); const tupletOptions = { num_notes: activeTuplet.numberNotesActual, notes_occupied: activeTuplet.numberNotesNormal, }; // console.log('tupletOptions', tupletOptions); const vfTuplet = new Vex.Flow.Tuplet( activeTupletVexflowNotes, tupletOptions ); if (activeTuplet.tupletNormalShow === 'ratio') { vfTuplet.setRatioed(true); } vfTuplets.push(vfTuplet); activeTupletLength = 0.0; activeTuplet = undefined; activeTupletVexflowNotes = []; } } } } if (activeTuplet !== undefined) { console.warn('incomplete tuplet found in stream: ', s); } if (vfTuplets.length > 0) { this.vfTuplets.push(...vfTuplets); } return notes; } /** * Gets an Array of `Vex.Flow.TextNote` objects from any lyrics found in s * * @memberof music21.vfShow.Renderer * @param {music21.stream.Stream} s - flat stream to search. * @param {Vex.Flow.Stave} stave * @returns {Array&lt;Vex.Flow.TextNote>} */ vexflowLyrics(s, stave) { const getTextNote = (text, font, d, lyricObj) => { // console.log(text, font, d); const t1 = new Vex.Flow.TextNote({ text, font, duration: d.vexflowDuration, }) .setLine(11) .setStave(stave) .setJustification(Vex.Flow.TextNote.Justification.LEFT); if (lyricObj) { t1.setStyle(lyricObj.style); } if (d.tuplets.length > 0) { t1.applyTickMultiplier(d.tuplets[0].numberNotesNormal, d.tuplets[0].numberNotesActual); } return t1; }; if (s === undefined) { s = this.stream; } // runs on a flat, gapless, no-overlap stream, returns a list of TextNote objects... const lyricsObjects = []; for (const el of s) { const lyricsArray = el.lyrics; let text; let d = el.duration; let addConnector = false; let firstLyric; const font = { family: 'Serif', size: 12, weight: '', }; if (lyricsArray.length === 0) { text = ''; } else { firstLyric = lyricsArray[0]; text = firstLyric.text; if (text === undefined) { text = ''; } if ( firstLyric.syllabic === 'middle' || firstLyric.syllabic === 'begin' ) { addConnector = ' ' + firstLyric.lyricConnector; const tempQl = el.duration.quarterLength / 2.0; d = new duration.Duration(tempQl); } if (firstLyric.style.fontFamily) { font.family = firstLyric.style.fontFamily; } if (firstLyric.style.fontSize) { font.size = firstLyric.style.fontSize; } if (firstLyric.style.fontWeight) { font.weight = firstLyric.style.fontWeight; } } const t1 = getTextNote(text, font, d, firstLyric); lyricsObjects.push(t1); if (addConnector !== false) { const connector = getTextNote(addConnector, font, d); lyricsObjects.push(connector); } } return lyricsObjects; } /** * Creates a Vex.Flow.Voice of the appropriate length given a Stream. * * @memberof music21.vfShow.Renderer * @param {music21.stream.Stream} s * @returns {Vex.Flow.Voice} */ vexflowVoice(s) { const totalLength = s.duration.quarterLength; let num1024 = Math.round(totalLength / (1 / 256)); let beatValue = 1024; if (num1024 % 512 === 0) { beatValue = 2; num1024 /= 512; } else if (num1024 % 256 === 0) { beatValue = 4; num1024 /= 256; } else if (num1024 % 128 === 0) { beatValue = 8; num1024 /= 128; } else if (num1024 % 64 === 0) { beatValue = 16; num1024 /= 64; } else if (num1024 % 32 === 0) { beatValue = 32; num1024 /= 32; } else if (num1024 % 16 === 0) { beatValue = 64; num1024 /= 16; } else if (num1024 % 8 === 0) { beatValue = 128; num1024 /= 8; } else if (num1024 % 4 === 0) { beatValue = 256; num1024 /= 4; } else if (num1024 % 2 === 0) { beatValue = 512; num1024 /= 2; } // console.log('creating voice'); if (debug) { console.log( 'New voice, num_beats: ' + num1024.toString() + ' beat_value: ' + beatValue.toString() ); } const vfv = new Vex.Flow.Voice({ num_beats: num1024, beat_value: beatValue, resolution: Vex.Flow.RESOLUTION, }); // from vexflow/src/voice.js // // Modes allow the addition of ticks in three different ways: // // STRICT: This is the default. Ticks must fill the voice. // SOFT: Ticks can be added without restrictions. // FULL: Ticks do not need to fill the voice, but can't exceed the maximum // tick length. vfv.setMode(Vex.Flow.Voice.Mode.SOFT); return vfv; } staffConnectorsMap(connectorType) { const connectorMap = { brace: Vex.Flow.StaveConnector.type.BRACE, single: Vex.Flow.StaveConnector.type.SINGLE, double: Vex.Flow.StaveConnector.type.DOUBLE, bracket: Vex.Flow.StaveConnector.type.BRACKET, }; return connectorMap[connectorType]; } /** * If a stream has parts (NOT CHECKED HERE) create and * draw an appropriate Vex.Flow.StaveConnector * * @memberof music21.vfShow.Renderer * @param {music21.stream.Score} s */ addStaffConnectors(s) { if (s === undefined) { s = this.stream; } const parts = s.parts; const numParts = parts.length; if (numParts &lt; 2) { return; } const firstPart = parts.get(0); const lastPart = parts.get(-1); const firstPartMeasures = firstPart.measures; const lastPartMeasures = lastPart.measures; const numMeasures = firstPartMeasures.length; for (let mIndex = 0; mIndex &lt; numMeasures; mIndex++) { const thisPartMeasure = firstPartMeasures.get(mIndex); const lastPartMeasure = lastPartMeasures.get(mIndex); // only needed once per system but // good for symmetry. if (thisPartMeasure.renderOptions.startNewSystem) { let topVFStaff = thisPartMeasure.activeVFStave; let bottomVFStaff = lastPartMeasure.activeVFStave; if (topVFStaff === undefined) { if (thisPartMeasure.hasSubStreams()) { const thisPartVoice = thisPartMeasure .getElementsByClass('Stream')