Source: src/vex/Measure.js

import Vex from 'vexflow';
import { Voice } from './Voice.js';
import { MeasureVisitor } from '../visitors/index';

const { Flow } = Vex;

export class Measure {
  constructor(xmlMeasure, format, ctx) {
    this.staveList = [];
    this.voiceList = [];
    this.connectors = [];
    this.xmlMeasure = xmlMeasure;
    this.context = ctx;
    this.format = format;
    // FIXME: The formatter should be available in all vex components
    this.formatter = new Flow.Formatter();
    // console.log(xmlMeasure.Attributes, xmlMeasure.Part);

    // TODO: Figure out a way to use the given width with a dynamic layout
    const width = format.staveWidth; // this.xmlMeasure.Width;
    this.width = document.body.clientWidth < width ? document.body.clientWidth : width;
    // Get the part we are in
    const part = this.xmlMeasure.Part - 1;
    // Get the absolute measure number
    const number = this.xmlMeasure.Number;
    // Calculate the line where this measure is on the page
    const lineOnPage = Math.ceil(number / format.measuresPerStave) - 1;

    this.firstInLine = (number - 1) % format.measuresPerStave === 0;
    const lastMeasure = number === format.totalMeasures;

    this.x = format.staveXOffset + (number - 1) % format.measuresPerStave * format.staveWidth;
    this.y = lineOnPage * format.systemSpace;

    const staves = xmlMeasure.accept(MeasureVisitor);

    for (let s = 0; s < staves.length; s++) {
      // TODO: Separate formatter and converter into MeasureContainer and MeasureVisitor
      const flowStave = staves[s].staff;
      flowStave.setContext(ctx)
        .setX(this.x)
        .setY(this.y + s * 100 + part * 100)
        .setWidth(this.width);

      if (this.firstInLine) {
        flowStave.addKeySignature(staves[s].key);
      }
      if (JSON.stringify(this.xmlMeasure.StartClefs) !== JSON.stringify(this.xmlMeasure.lastMeasure.StartClefs)) {
        flowStave.addClef(staves[s].clef);
      }
      const options = {
        ctx,
        formatter: this.formatter,
        stave: s + 1,
        staveClef: staves[s].clef,
        flowStave,
        time: xmlMeasure.Attributes.Time,
      };
      const v = new Voice(xmlMeasure, options);
      this.voiceList.push(v);
    } // Staves
    this.staveList = staves.map(s => s.staff);
    this.addConnectors(this.firstInLine, lastMeasure);
  } // Constructor

  draw() {
    this.staveList.forEach(s => s.draw());
    this.voiceList.forEach(n => n.draw());
    this.connectors.forEach(c => c.draw());
  }

  addConnectors(firstInLine, lastMeasure) {
    if (this.staveList.length === 1 && lastMeasure) {
      this.staveList[0].setEndBarType(Flow.Barline.type.END);
    }
    for (let s = 0; s < this.staveList.length - 1; s++) {
      const firstStave = this.staveList[s];
      const secondStave = this.staveList[s + 1];
      // Beginning of system line
      if (firstInLine) {
        this.addConnector(firstStave, secondStave, Flow.StaveConnector.type.SINGLE_LEFT);
        this.addConnector(firstStave, secondStave, Flow.StaveConnector.type.BRACE);
      }
      // Every measure
      this.addConnector(firstStave, secondStave, Flow.StaveConnector.type.SINGLE_RIGHT);
      // End of score
      if (lastMeasure) {
        this.addConnector(firstStave, secondStave, Flow.StaveConnector.type.BOLD_DOUBLE_RIGHT);
      }
    }
  }

  /**
   * Adds a connector between two staves
   *
   * @param {Stave} stave1: First stave
   * @param {Stave} stave2: Second stave
   * @param {Flow.StaveConnector.type} type: Type of connector
   */
  addConnector(stave1, stave2, type) {
    this.connectors.push(new Flow.StaveConnector(stave1, stave2)
      .setType(type)
      .setContext(this.context));
  }
}