UNPKG

satie

Version:

A sheet music renderer for the web

188 lines (167 loc) 6.4 kB
/** * This file is part of Satie music engraver <https://github.com/jnetterf/satie>. * Copyright (C) Joshua Netterfield <joshua.ca> 2015 - present. * * Satie is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * Satie is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with Satie. If not, see <http://www.gnu.org/licenses/>. */ import {Attributes, Key} from "musicxml-interfaces"; import {times, forEach, map, reduce} from "lodash"; import * as invariant from "invariant"; import {IAttributesSnapshot} from "./private_attributesSnapshot"; export const NUMBER_SPACING = 28; export const PLUS_SPACING = 12; // Gould(6): "A clef is indented into the stave by one stave-space or a little less" export const CLEF_INDENTATION = 7; export const FLAT_WIDTH = 10; export const DOUBLE_FLAT_WIDTH = 19; export const DOUBLE_SHARP_WIDTH = 13; export const SHARP_WIDTH = 11; export const NATURAL_WIDTH = 11; /** * Returns true if warning Attributes are required at the end of a line, and false otherwise. */ export function needsWarning(end: Attributes, start: Attributes, staff: number) { invariant(!!end && !!start, "A null end or start was passed to needsWarning. Check your types!!"); invariant(!("P1" in end || "P1" in start), "An object with 'P1' was passed to needsWarning. Check your types!!"); return !clefsEqual(end, start, staff) || !timesEqual(end, start) || !keysEqual(end, start); } export function clefWidth(attributes: Attributes) { return 24; } export function timeWidth(attributes: Attributes) { if (!attributes.times[0] || !attributes.times[0].beatTypes) { return 0; } let beats = attributes.times[0].beats; let numeratorSegments = reduce(beats, (memo, beats) => memo + beats.split("+").length, 0); return NUMBER_SPACING * numeratorSegments + (attributes.times[0].beatTypes.length - 1) * PLUS_SPACING; } export function keyWidth(attributes: Attributes) { if (!attributes.keySignatures[0]) { return 0; } const keySignature = attributes.keySignatures[0]; if (keySignature.fifths || keySignature.keyAlters) { return 2 + reduce(keyWidths(keySignature), (memo, width) => memo + width, 0); } else { return -5; } } export function clefsEqual(from: Attributes, to: Attributes, staff: number) { let cA = from && from.clefs[staff]; let cB = to && to.clefs[staff]; if (!cA || !cB) { return false; } return cA.sign === cB.sign && cA.line === cB.line && cA.clefOctaveChange === cB.clefOctaveChange; } export function timesEqual(from: Attributes, to: Attributes) { let tA = from && from.times[0]; let tB = to && to.times[0]; if (!tA || !tB) { return false; } return JSON.stringify(tA.beats) === JSON.stringify(tB.beats) && JSON.stringify(tA.beatTypes) === JSON.stringify(tB.beatTypes) && !!tA.senzaMisura === !!tB.senzaMisura && tA.symbol === tB.symbol; } export function keysEqual(from: Attributes, to: Attributes) { let keyA = from && from.keySignatures[0]; let keyB = to && to.keySignatures[0]; if (!keyA || !keyB) { return false; } return keyA.fifths === keyB.fifths && JSON.stringify(keyA.keySteps) === JSON.stringify(keyB.keySteps) && JSON.stringify(keyA.keyAccidentals) === JSON.stringify(keyB.keyAccidentals) && JSON.stringify(keyA.keyAlters) === JSON.stringify(keyB.keyAlters) && keyA.mode === keyB.mode; } export function approximateWidth(attributes: IAttributesSnapshot, atEnd = AtEnd.No) { if (atEnd) { return 80; } return 150; } export const enum AtEnd { No = 0, Yes = 1 } export function keyWidths(spec: Key) { let widths: number[] = []; if (spec.keyAlters) { return map(spec.keyAlters, alter => { switch (alter) { case "-2": case "-1.5": return DOUBLE_FLAT_WIDTH; case "-1": case "-0.5": return FLAT_WIDTH; case "0": return NATURAL_WIDTH; case "0.5": case "1": return SHARP_WIDTH; case "1.5": case "2": return DOUBLE_SHARP_WIDTH; default: console.warn("Unknown accidental ", alter); return SHARP_WIDTH; } }); } let accidentalCount = Math.min(7, Math.abs(spec.fifths)); let idxes = times(accidentalCount, i => (i + Math.max(0, Math.abs(spec.fifths) - 7)) % 7); forEach(idxes, i => widths[i] = getWidth(i, spec.fifths >= 0)); return widths; function getWidth(i: number, sharp: boolean): number { switch (true) { case (sharp && 7 + i < spec.fifths): return DOUBLE_SHARP_WIDTH; case (sharp && 7 + i >= spec.fifths): return SHARP_WIDTH; case (!sharp && (7 + i < -spec.fifths)): return DOUBLE_FLAT_WIDTH; case (!sharp && (7 + i >= -spec.fifths)): return FLAT_WIDTH; default: throw new Error("Impossible."); } } } export function getNativeKeyAccidentals(spec: Key): {[note: string]: number} { let accidentals: {[note: string]: number} = {}; const sharps = "FCGDAEB"; const flats = "BEADGCF"; if (spec.fifths) { let accCount = Math.min(7, Math.abs(spec.fifths)); let sharp = spec.fifths >= 0; (sharp ? sharps : flats).slice(0, accCount).split("").forEach(note => { accidentals[note] = sharp ? 1 : -1; }); } else if (spec.keySteps) { for (let i = 0; i < spec.keySteps.length; ++i) { accidentals[spec.keySteps[i]] = parseInt(spec.keyAlters[i], 10); } } return accidentals; }