UNPKG

vexflow

Version:

A JavaScript library for rendering music notation and guitar tablature.

192 lines (191 loc) 6.33 kB
import { Element } from './element.js'; import { Fraction } from './fraction.js'; import { Tables } from './tables.js'; import { defined, RuntimeError, sumArray } from './util.js'; export var VoiceMode; (function (VoiceMode) { VoiceMode[VoiceMode["STRICT"] = 1] = "STRICT"; VoiceMode[VoiceMode["SOFT"] = 2] = "SOFT"; VoiceMode[VoiceMode["FULL"] = 3] = "FULL"; })(VoiceMode || (VoiceMode = {})); export class Voice extends Element { static get CATEGORY() { return "Voice"; } static get Mode() { return VoiceMode; } constructor(time) { super(); this.resolutionMultiplier = 1; this.mode = VoiceMode.STRICT; this.preFormatted = false; this.ticksUsed = new Fraction(0, 1); this.largestTickWidth = 0; this.tickables = []; this.options = { softmaxFactor: Tables.SOFTMAX_FACTOR, }; let voiceTime; if (typeof time === 'string') { const match = time.match(/(\d+)\/(\d+)/); if (match) { voiceTime = { num_beats: parseInt(match[1]), beat_value: parseInt(match[2]), }; } } else { voiceTime = time; } this.time = Object.assign({ num_beats: 4, beat_value: 4, resolution: Tables.RESOLUTION }, voiceTime); this.totalTicks = new Fraction(this.time.num_beats * (this.time.resolution / this.time.beat_value), 1); this.smallestTickCount = this.totalTicks.clone(); } getTotalTicks() { return this.totalTicks; } getTicksUsed() { return this.ticksUsed; } getLargestTickWidth() { return this.largestTickWidth; } getSmallestTickCount() { return this.smallestTickCount; } getTickables() { return this.tickables; } getMode() { return this.mode; } setMode(mode) { this.mode = mode; return this; } getResolutionMultiplier() { return this.resolutionMultiplier; } getActualResolution() { return this.resolutionMultiplier * this.time.resolution; } setStave(stave) { this.stave = stave; this.boundingBox = undefined; return this; } getStave() { return this.stave; } getBoundingBox() { if (!this.boundingBox) { const stave = this.checkStave(); let boundingBox = undefined; for (let i = 0; i < this.tickables.length; ++i) { const tickable = this.tickables[i]; if (!tickable.getStave()) tickable.setStave(stave); const bb = tickable.getBoundingBox(); if (bb) { boundingBox = boundingBox ? boundingBox.mergeWith(bb) : bb; } } this.boundingBox = boundingBox; } return this.boundingBox; } setStrict(strict) { this.mode = strict ? VoiceMode.STRICT : VoiceMode.SOFT; return this; } isComplete() { if (this.mode === VoiceMode.STRICT || this.mode === VoiceMode.FULL) { return this.ticksUsed.equals(this.totalTicks); } else { return true; } } setSoftmaxFactor(factor) { this.options.softmaxFactor = factor; this.expTicksUsed = 0; return this; } reCalculateExpTicksUsed() { const totalTicks = this.ticksUsed.value(); const exp = (tickable) => Math.pow(this.options.softmaxFactor, tickable.getTicks().value() / totalTicks); this.expTicksUsed = sumArray(this.tickables.map(exp)); return this.expTicksUsed; } softmax(tickValue) { if (!this.expTicksUsed) { this.expTicksUsed = this.reCalculateExpTicksUsed(); } const totalTicks = this.ticksUsed.value(); const exp = (v) => Math.pow(this.options.softmaxFactor, v / totalTicks); const sm = exp(tickValue) / this.expTicksUsed; return sm; } addTickable(tickable) { if (!tickable.shouldIgnoreTicks()) { const ticks = tickable.getTicks(); this.ticksUsed.add(ticks); this.expTicksUsed = 0; if ((this.mode === VoiceMode.STRICT || this.mode === VoiceMode.FULL) && this.ticksUsed.greaterThan(this.totalTicks)) { this.ticksUsed.subtract(ticks); throw new RuntimeError('BadArgument', 'Too many ticks.'); } if (ticks.lessThan(this.smallestTickCount)) { this.smallestTickCount = ticks.clone(); } this.resolutionMultiplier = this.ticksUsed.denominator; this.totalTicks.add(0, this.ticksUsed.denominator); } this.tickables.push(tickable); tickable.setVoice(this); return this; } addTickables(tickables) { for (let i = 0; i < tickables.length; ++i) { this.addTickable(tickables[i]); } return this; } preFormat() { if (this.preFormatted) return this; const stave = this.checkStave(); this.tickables.forEach((tickable) => { if (!tickable.getStave()) { tickable.setStave(stave); } }); this.preFormatted = true; return this; } checkStave() { return defined(this.stave, 'NoStave', 'No stave attached to instance.'); } draw(context = this.checkContext(), stave) { stave = stave !== null && stave !== void 0 ? stave : this.stave; this.setRendered(); let boundingBox = undefined; for (let i = 0; i < this.tickables.length; ++i) { const tickable = this.tickables[i]; if (stave) { tickable.setStave(stave); } defined(tickable.getStave(), 'MissingStave', 'The voice cannot draw tickables without staves.'); const bb = tickable.getBoundingBox(); if (bb) { boundingBox = boundingBox ? boundingBox.mergeWith(bb) : bb; } tickable.setContext(context); tickable.drawWithStyle(); } this.boundingBox = boundingBox; } }