vexflow
Version:
A JavaScript library for rendering music notation and guitar tablature.
133 lines (111 loc) • 3.64 kB
text/typescript
// [VexFlow](https://vexflow.com) - Copyright (c) Mohit Muthanna 2010.
//
// ## Description
// Implements time signatures glyphs for staffs
// See tables.js for the internal time signatures
// representation
import { Glyph } from './glyph';
import { StaveModifier, StaveModifierPosition } from './stavemodifier';
import { Tables } from './tables';
import { TimeSignatureGlyph } from './timesigglyph';
import { Category } from './typeguard';
import { defined, RuntimeError } from './util';
export interface TimeSignatureInfo {
glyph: Glyph;
line?: number;
num: boolean;
}
const assertIsValidTimeSig = (timeSpec: string) => {
const numbers = timeSpec.split('/');
if (numbers.length !== 2 && numbers[0] !== '+' && numbers[0] !== '-') {
throw new RuntimeError(
'BadTimeSignature',
`Invalid time spec: ${timeSpec}. Must be in the form "<numerator>/<denominator>"`
);
}
numbers.forEach((number) => {
// Characters consisting in number 0..9, '+', '-', '(' or ')'
if (/^[0-9+\-()]+$/.test(number) == false) {
throw new RuntimeError('BadTimeSignature', `Invalid time spec: ${timeSpec}. Must contain valid signatures.`);
}
});
};
export class TimeSignature extends StaveModifier {
static get CATEGORY(): string {
return Category.TimeSignature;
}
static get glyphs(): Record<string, { code: string; point: number; line: number }> {
return {
C: {
code: 'timeSigCommon',
point: 40,
line: 2,
},
'C|': {
code: 'timeSigCutCommon',
point: 40,
line: 2,
},
};
}
point: number;
bottomLine: number;
topLine: number;
protected info: TimeSignatureInfo;
protected validate_args: boolean;
constructor(timeSpec: string = '4/4', customPadding = 15, validate_args = true) {
super();
this.validate_args = validate_args;
const padding = customPadding;
const musicFont = Tables.currentMusicFont();
this.point = musicFont.lookupMetric('digits.point');
const fontLineShift = musicFont.lookupMetric('digits.shiftLine', 0);
this.topLine = 2 + fontLineShift;
this.bottomLine = 4 + fontLineShift;
this.setPosition(StaveModifierPosition.BEGIN);
this.info = this.parseTimeSpec(timeSpec);
this.setWidth(defined(this.info.glyph.getMetrics().width));
this.setPadding(padding);
}
parseTimeSpec(timeSpec: string): TimeSignatureInfo {
if (timeSpec === 'C' || timeSpec === 'C|') {
const { line, code, point } = TimeSignature.glyphs[timeSpec];
return {
line,
num: false,
glyph: new Glyph(code, point),
};
}
if (this.validate_args) {
assertIsValidTimeSig(timeSpec);
}
const parts = timeSpec.split('/');
return {
num: true,
glyph: this.makeTimeSignatureGlyph(parts[0] ?? '', parts[1] ?? ''),
};
}
makeTimeSignatureGlyph(topDigits: string, botDigits: string): Glyph {
return new TimeSignatureGlyph(this, topDigits, botDigits, 'timeSig0', this.point);
}
getInfo(): TimeSignatureInfo {
return this.info;
}
setTimeSig(timeSpec: string): this {
this.info = this.parseTimeSpec(timeSpec);
return this;
}
draw(): void {
const stave = this.checkStave();
const ctx = stave.checkContext();
this.setRendered();
this.applyStyle(ctx);
ctx.openGroup('timesignature', this.getAttribute('id'));
this.info.glyph.setStave(stave);
this.info.glyph.setContext(ctx);
this.placeGlyphOnLine(this.info.glyph, stave, this.info.line);
this.info.glyph.renderToStave(this.x);
ctx.closeGroup();
this.restoreStyle(ctx);
}
}