VexFlow - Copyright (c) Mohit Muthanna 2010.

This class implements a musical system, which is a collection of staves, each which can have one or more voices. All voices across all staves in the system are formatted together.

import { Element } from './element';
import { Factory } from './factory';
import { Formatter } from './formatter';
import { Note } from './note';

function setDefaults(params, defaults) {
  const default_options = defaults.options;
  params = Object.assign(defaults, params);
  params.options = Object.assign(default_options, params.options);
  return params;
}

export class System extends Element {
  constructor(params = {}) {
    super();
    this.setAttribute('type', 'System');
    this.setOptions(params);
    this.parts = [];
  }

  setOptions(options = {}) {
    this.options = setDefaults(options, {
      x: 10,
      y: 10,
      width: 500,
      connector: null,
      spaceBetweenStaves: 12, // stave spaces
      factory: null,
      debugFormatter: false,
      formatIterations: 0,   // number of formatter tuning steps
      options: {},
    });

    this.factory = this.options.factory || new Factory({ renderer: { el: null } });
  }

  setContext(context) {
    super.setContext(context);
    this.factory.setContext(context);
    return this;
  }

  addConnector() {
    this.connector = this.factory.StaveConnector({
      top_stave: this.parts[0].stave,
      bottom_stave: this.parts[this.parts.length - 1].stave,
    });
    return this.connector;
  }

  addStave(params) {
    params = setDefaults(params, {
      stave: null,
      voices: [],
      spaceAbove: 0, // stave spaces
      spaceBelow: 0, // stave spaces
      debugNoteMetrics: false,
      options: {},
    });

    if (!params.stave) {
      const options = { left_bar: false };
      params.stave = this.factory.Stave(
        { x: this.options.x, y: this.options.y, width: this.options.width, options });
    }

    params.voices.forEach(voice => voice.setContext(this.context).setStave(params.stave));
    this.parts.push(params);
    return params.stave;
  }

  draw() {
    const ctx = this.checkContext();
    const formatter = new Formatter();

    let y = this.options.y;
    let startX = 0;
    let allVoices = [];
    const debugNoteMetricsYs = [];

Join the voices for each stave.

    this.parts.forEach(part => {
      y = y + part.stave.space(part.spaceAbove);
      part.stave.setY(y);
      formatter.joinVoices(part.voices);
      y = y + part.stave.space(part.spaceBelow);
      y = y + part.stave.space(this.options.spaceBetweenStaves);
      if (part.debugNoteMetrics) {
        debugNoteMetricsYs.push({ y, voice: part.voices[0] });
        y += 15;
      }
      allVoices = allVoices.concat(part.voices);

      startX = Math.max(startX, part.stave.getNoteStartX());
    });

Update the start position of all staves.

    this.parts.forEach(part => part.stave.setNoteStartX(startX));
    const justifyWidth = this.options.width - (startX - this.options.x) - Note.STAVEPADDING;
    formatter.format(allVoices, justifyWidth);

    for (let i = 0; i < this.options.formatIterations; i++) {
      formatter.tune();
    }

Render.

    this.parts.forEach(part => {
      part.voices.forEach(voice => voice.draw());
    });

Render debug info.

    if (this.options.debugFormatter) {
      Formatter.plotDebugging(ctx, formatter, startX, this.options.y, y);
    }

    debugNoteMetricsYs.forEach(d => {
      d.voice.getTickables().forEach(note => Note.plotMetrics(ctx, note, d.y));
    });
  }
}
h