@stringsync/vexml
Version:
MusicXML to Vexflow
97 lines (96 loc) • 3.69 kB
JavaScript
import * as util from '../util';
import { Fraction } from '../util';
import { Fragment } from './fragment';
export class Measure {
config;
log;
document;
measureRender;
fragments;
constructor(config, log, document, measureRender, fragments) {
this.config = config;
this.log = log;
this.document = document;
this.measureRender = measureRender;
this.fragments = fragments;
}
static create(config, log, document, measureRender) {
const fragments = measureRender.fragmentRenders.map((fragmentRender) => Fragment.create(config, log, document, fragmentRender));
return new Measure(config, log, document, measureRender, fragments);
}
/** The name of the element, which can be used as a type discriminant. */
name = 'measure';
/** Returns the bounding box of the element. */
rect() {
return this.measureRender.rect;
}
/** Returns whether this measure is the last in the system. */
isLastMeasureInSystem() {
return this.document.isLastMeasure(this.measureRender.key);
}
/** Returns the system index of the measure. */
getSystemIndex() {
return this.measureRender.key.systemIndex;
}
/** Returns whether the measure is a multimeasure. */
isMultiMeasure() {
return this.measureRender.multiRestCount > 1;
}
/** Returns the fragments of the measure. */
getFragments() {
return this.fragments;
}
/** Returns the max number of parts in this score. */
getPartCount() {
return Math.max(0, ...this.fragments.map((fragment) => fragment.getPartCount()));
}
/** Returns the jumps that occur in this measure. */
getJumps() {
return this.measureRender.jumps;
}
/** Returns the absolute measure index. */
getAbsoluteMeasureIndex() {
return this.measureRender.absoluteIndex;
}
/**
* Sometimes document measures are folded into one (e.g. multimeasure rest). This method returns the [start, end]
* _absolute_ index range that the measure covers.
*/
includesAbsoluteMeasureIndex(absoluteMeasureIndex) {
const start = this.measureRender.absoluteIndex;
const multiMeasureCount = this.measureRender.multiRestCount;
if (multiMeasureCount > 1) {
return new util.NumberRange(start, start + multiMeasureCount - 1).includes(absoluteMeasureIndex);
}
else {
return new util.NumberRange(start, start).includes(absoluteMeasureIndex);
}
}
/** Always returns zero. This is used for sequencing with other playback elements. */
getStartMeasureBeat() {
return Fraction.zero();
}
/** Returns how many beats are in this measure. */
getBeatCount() {
const time = this.document
.getMeasure(this.measureRender.key)
.fragments.flatMap((fragment) => fragment.parts)
.flatMap((part) => part.staves)
.map((stave) => stave.signature.time)
.at(0);
if (!time) {
this.log.warn('could not find time signature for measure', this.measureRender.key);
return Fraction.zero();
}
const components = time.components.map(Fraction.fromFractionLike);
const beatsPerMeasure = Fraction.sum(...components).multiply(new Fraction(4));
const measureCount = Math.max(1, this.measureRender.multiRestCount);
return beatsPerMeasure.multiply(new Fraction(measureCount));
}
/** Returns the BPM of the measure. */
getBpm() {
const bpm = this.fragments.at(0)?.getBpm();
util.assertDefined(bpm);
return bpm;
}
}