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';
export class System extends Element {
constructor(params = {}) {
super();
this.setAttribute('type', 'System');
this.setOptions(params);
this.parts = [];
}
setOptions(options = {}) {
this.options = {
x: 10,
y: 10,
width: 500,
connector: null,
spaceBetweenStaves: 12, // stave spaces
factory: null,
noJustification: false,
debugFormatter: false,
formatIterations: 0, // number of formatter tuning steps
noPadding: false,
...options,
details: {
alpha: 0.5, // formatter tuner learning/shifting rate
...options.details,
},
};
this.factory = this.options.factory || new Factory({ renderer: { el: null } });
}
setContext(context) {
super.setContext(context);
this.factory.setContext(context);
return this;
}
addConnector(type = 'double') {
this.connector = this.factory.StaveConnector({
top_stave: this.parts[0].stave,
bottom_stave: this.parts[this.parts.length - 1].stave,
type,
});
return this.connector;
}
addStave(params) {
params = {
stave: null,
voices: [],
spaceAbove: 0, // stave spaces
spaceBelow: 0, // stave spaces
debugNoteMetrics: false,
...params,
options: {
left_bar: false,
...params.options,
},
};
if (!params.stave) {
params.stave = this.factory.Stave({
x: this.options.x,
y: this.options.y,
width: this.options.width,
options: params.options,
});
}
params.voices.forEach(voice =>
voice
.setContext(this.context)
.setStave(params.stave)
.getTickables()
.forEach(tickable => tickable.setStave(params.stave))
);
this.parts.push(params);
return params.stave;
}
format() {
const formatter = new Formatter({ ...this.options.details });
this.formatter = 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.noPadding ?
this.options.width - this.options.x :
this.options.width - (startX - this.options.x) - this.musicFont.lookupMetric('stave.padding');
formatter.format(allVoices, this.options.noJustification ? 0 : justifyWidth);
for (let i = 0; i < this.options.formatIterations; i++) {
formatter.tune({ alpha: this.options.details.alpha });
}
this.startX = startX;
this.debugNoteMetricsYs = debugNoteMetricsYs;
this.lastY = y;
}
draw() {
Render debugging information, if requested.
const ctx = this.checkContext();
this.setRendered();
if (this.options.debugFormatter) {
Formatter.plotDebugging(ctx, this.formatter, this.startX, this.options.y, this.lastY);
}
this.debugNoteMetricsYs.forEach(d => {
d.voice.getTickables().forEach(note => Note.plotMetrics(ctx, note, d.y));
});
}
}