UNPKG

vexflow

Version:

A JavaScript library for rendering music notation and guitar tablature.

259 lines (258 loc) 7.79 kB
import { Glyph } from './glyph.js'; import { StaveModifier, StaveModifierPosition } from './stavemodifier.js'; import { Tables } from './tables.js'; import { defined } from './util.js'; class KeySignature extends StaveModifier { static get CATEGORY() { return "KeySignature"; } constructor(keySpec, cancelKeySpec, alterKeySpec) { super(); this.accList = []; this.setKeySig(keySpec, cancelKeySpec, alterKeySpec); this.setPosition(StaveModifierPosition.BEGIN); this.glyphFontScale = Tables.NOTATION_FONT_SCALE; this.glyphs = []; this.xPositions = []; this.paddingForced = false; } convertToGlyph(acc, nextAcc) { const accGlyphData = Tables.accidentalCodes(acc.type); const glyph = new Glyph(accGlyphData.code, this.glyphFontScale); let extraWidth = 1; if (acc.type === 'n' && nextAcc) { const spacing = KeySignature.accidentalSpacing[nextAcc.type]; if (spacing) { const isAbove = nextAcc.line >= acc.line; extraWidth = isAbove ? spacing.above : spacing.below; } } this.placeGlyphOnLine(glyph, this.checkStave(), acc.line); this.glyphs.push(glyph); const xPosition = this.xPositions[this.xPositions.length - 1]; const glyphWidth = glyph.getMetrics().width + extraWidth; this.xPositions.push(xPosition + glyphWidth); this.width += glyphWidth; } cancelKey(spec) { this.formatted = false; this.cancelKeySpec = spec; return this; } convertToCancelAccList(spec) { const cancel_accList = Tables.keySignature(spec); const different_types = this.accList.length > 0 && cancel_accList.length > 0 && cancel_accList[0].type !== this.accList[0].type; const naturals = different_types ? cancel_accList.length : cancel_accList.length - this.accList.length; if (naturals < 1) return undefined; const cancelled = []; for (let i = 0; i < naturals; i++) { let index = i; if (!different_types) { index = cancel_accList.length - naturals + i; } const acc = cancel_accList[index]; cancelled.push({ type: 'n', line: acc.line }); } this.accList = cancelled.concat(this.accList); return { accList: cancelled, type: cancel_accList[0].type, }; } addToStave(stave) { this.paddingForced = true; stave.addModifier(this); return this; } convertAccLines(clef, type, accList = this.accList) { let offset = 0.0; let customLines; switch (clef) { case 'soprano': if (type === '#') customLines = [2.5, 0.5, 2, 0, 1.5, -0.5, 1]; else offset = -1; break; case 'mezzo-soprano': if (type === 'b') customLines = [0, 2, 0.5, 2.5, 1, 3, 1.5]; else offset = 1.5; break; case 'alto': offset = 0.5; break; case 'tenor': if (type === '#') customLines = [3, 1, 2.5, 0.5, 2, 0, 1.5]; else offset = -0.5; break; case 'baritone-f': case 'baritone-c': if (type === 'b') customLines = [0.5, 2.5, 1, 3, 1.5, 3.5, 2]; else offset = 2; break; case 'bass': case 'french': offset = 1; break; default: break; } let i; if (typeof customLines !== 'undefined') { for (i = 0; i < accList.length; ++i) { accList[i].line = customLines[i]; } } else if (offset !== 0) { for (i = 0; i < accList.length; ++i) { accList[i].line += offset; } } } getPadding(index) { if (!this.formatted) this.format(); return this.glyphs.length === 0 || (!this.paddingForced && index < 2) ? 0 : this.padding; } getWidth() { if (!this.formatted) this.format(); return this.width; } setKeySig(keySpec, cancelKeySpec, alterKeySpec) { this.formatted = false; this.keySpec = keySpec; this.cancelKeySpec = cancelKeySpec; this.alterKeySpec = alterKeySpec; return this; } alterKey(alterKeySpec) { this.formatted = false; this.alterKeySpec = alterKeySpec; return this; } convertToAlterAccList(alterKeySpec) { const max = Math.min(alterKeySpec.length, this.accList.length); for (let i = 0; i < max; ++i) { if (alterKeySpec[i]) { this.accList[i].type = alterKeySpec[i]; } } } format() { const stave = this.checkStave(); this.width = 0; this.glyphs = []; this.xPositions = [0]; this.accList = Tables.keySignature(defined(this.keySpec)); const accList = this.accList; const firstAccidentalType = accList.length > 0 ? accList[0].type : undefined; let cancelAccList; if (this.cancelKeySpec) { cancelAccList = this.convertToCancelAccList(this.cancelKeySpec); } if (this.alterKeySpec) { this.convertToAlterAccList(this.alterKeySpec); } if (this.accList.length > 0) { const clef = (this.position === StaveModifierPosition.END ? stave.getEndClef() : stave.getClef()) || stave.getClef(); if (cancelAccList) { this.convertAccLines(clef, cancelAccList.type, cancelAccList.accList); } this.convertAccLines(clef, firstAccidentalType, accList); for (let i = 0; i < this.accList.length; ++i) { this.convertToGlyph(this.accList[i], this.accList[i + 1]); } } this.formatted = true; } getGlyphs() { if (!this.formatted) this.format(); return this.glyphs; } draw() { const stave = this.checkStave(); const ctx = stave.checkContext(); if (!this.formatted) this.format(); this.setRendered(); this.applyStyle(ctx); ctx.openGroup('keysignature', this.getAttribute('id')); for (let i = 0; i < this.glyphs.length; i++) { const glyph = this.glyphs[i]; const x = this.x + this.xPositions[i]; glyph.setStave(stave); glyph.setContext(ctx); glyph.renderToStave(x); } ctx.closeGroup(); this.restoreStyle(ctx); } } KeySignature.accidentalSpacing = { '#': { above: 6, below: 4, }, b: { above: 4, below: 7, }, n: { above: 4, below: 1, }, '##': { above: 6, below: 4, }, bb: { above: 4, below: 7, }, db: { above: 4, below: 7, }, d: { above: 4, below: 7, }, bbs: { above: 4, below: 7, }, '++': { above: 6, below: 4, }, '+': { above: 6, below: 4, }, '+-': { above: 6, below: 4, }, '++-': { above: 6, below: 4, }, bs: { above: 4, below: 10, }, bss: { above: 4, below: 10, }, }; export { KeySignature };