UNPKG

satie

Version:

A sheet music renderer for the web

320 lines (266 loc) 11.2 kB
/** * @source: https://github.com/jnetterf/satie/ * * @license * (C) Josh Netterfield <joshua@nettek.ca> 2015. * Part of the Satie music engraver <https://github.com/jnetterf/satie>. * * This program 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. * * This program 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 this program. If not, see <http://www.gnu.org/licenses/>. */ import {Barline, Segno, Coda, BarlineLocation, WavyLine, Fermata, BarStyle, Ending, Repeat, Footnote, Level, BarStyleType, PartGroup, PartSymbol, serializeBarline, Attributes} from "musicxml-interfaces"; import {buildBarStyle} from "musicxml-interfaces/builders"; import {some, forEach} from "lodash"; import * as invariant from "invariant"; import {IModel, ILayout, Type} from "./document"; import {IReadOnlyValidationCursor, LayoutCursor} from "./private_cursor"; import {IBoundingRect} from "./private_boundingRect"; import {groupsForPart} from "./private_part"; import {bravura} from "./private_smufl"; import AttributesExports from "./implAttributes_attributesModel"; import {needsWarning, clefsEqual, CLEF_INDENTATION} from "./implAttributes_attributesData"; class BarlineModel implements Export.IBarlineModel { _class = "Barline"; /*---- I.1 IModel ---------------------------------------------------------------------------*/ /** @prototype only */ divCount: number; /** defined externally */ staffIdx: number; /*---- I.2 Barline --------------------------------------------------------------------------*/ segno: Segno; coda: Coda; location: BarlineLocation; codaAttrib: string; wavyLine: WavyLine; fermatas: Fermata[]; segnoAttrib: string; divisions: number; barStyle: BarStyle; ending: Ending; repeat: Repeat; /*---- I.3 Editorial ------------------------------------------------------------------------*/ footnote: Footnote; level: Level; /*---- II. BarlineModel (extension) ---------------------------------------------------------*/ defaultX: number; defaultY: number; satieAttributes: AttributesExports.IAttributesLayout; satieAttribsOffset: number; /*---- Implementation -----------------------------------------------------------------------*/ constructor(spec: Barline) { forEach(spec, (value, key) => { (this as any)[key] = value; }); } toJSON() { let {_class, segno, coda, location, codaAttrib, wavyLine, fermatas, segnoAttrib, divisions, barStyle, ending, repeat, footnote} = this; return {_class, segno, coda, location, codaAttrib, wavyLine, fermatas, segnoAttrib, divisions, barStyle, ending, repeat, footnote}; } refresh(cursor: IReadOnlyValidationCursor): void { if (!this.barStyle) { cursor.patch(staff => staff .barline(barline => barline .barStyle(buildBarStyle(barStyle => barStyle .data(BarStyleType.Regular) .color("black") )) ) ); } if (!isFinite(this.barStyle.data) || this.barStyle.data === null) { let lastBarlineInSegment = !some(cursor.segmentInstance.slice(cursor.segmentPosition + 1), model => cursor.factory.modelHasType(model, Type.Barline)); cursor.patch(staff => staff .barline(barline => barline .barStyle({ data: lastBarlineInSegment && cursor.measureIsLast ? BarStyleType.LightHeavy : BarStyleType.Regular, }) ) ); } if (!this.barStyle.color) { cursor.patch(staff => staff .barline(barline => barline .barStyle(barStyle => barStyle .color("black") ) ) ); } } getLayout(cursor: LayoutCursor): Export.IBarlineLayout { // mutates cursor as required. return new BarlineModel.Layout(this, cursor); } toXML(): string { return `${serializeBarline(this)}\n<forward><duration>${this.divCount}</duration></forward>\n`; } inspect() { return this.toXML(); } calcWidth(shortest: number) { return 8; // TODO } } BarlineModel.prototype.divCount = 0; module BarlineModel { export class Layout implements Export.IBarlineLayout { constructor(origModel: BarlineModel, cursor: LayoutCursor) { this.division = cursor.segmentDivision; this.x = cursor.segmentX; let attributes = cursor.staffAttributes; let {measureStyle, partSymbol} = attributes; if (measureStyle.multipleRest && measureStyle.multipleRest.count > 1) { // TODO: removing this shows that measures are slightly misplaced return; } this.partGroups = groupsForPart(cursor.header.partList, cursor.segmentInstance.part); this.partSymbol = partSymbol; this.model = Object.create(origModel, { defaultX: { get: () => this.overrideX } }); let clefOffset = 0; if (cursor.lineTotalBarsOnLine === cursor.lineBarOnLine + 1) { // TODO: Figure out a way to get this to work when the attributes on the next // line change let nextMeasure = cursor.document.measures[cursor.measureInstance.idx + 1]; let part = nextMeasure && nextMeasure.parts[cursor.segmentInstance.part]; let segment = part && part.staves[cursor.staffIdx]; let nextAttributes: Attributes; if (segment) { let n = cursor.factory.search(segment, 0, Type.Attributes)[0]; if (n) { nextAttributes = n._snapshot; } } let addWarning = nextAttributes && needsWarning(attributes, nextAttributes, cursor.staffIdx); if (addWarning) { const clefsAreEqual = clefsEqual(attributes, nextAttributes, cursor.staffIdx); clefOffset = clefsAreEqual ? 0 : CLEF_INDENTATION; this.model.satieAttributes = AttributesExports.createWarningLayout(cursor, attributes, nextAttributes); } } this.model.defaultY = 0; this.yOffset = 0; // TODO this.height = 20; // TODO /*---- Geometry ---------------------------------------*/ const lineWidths = cursor.header.defaults.appearance.lineWidths; const barlineSep = bravura.engravingDefaults.barlineSeparation; let setLines = (lines: string[]) => { let x = 0; this.lineStarts = []; this.lineWidths = []; forEach(lines, (line, idx) => { if (idx > 0) { x += barlineSep * 10; } this.lineStarts.push(x); const width = lineWidths[line].tenths; this.lineWidths.push(width); x += width; }); this.model.satieAttribsOffset = x + 8 + clefOffset; cursor.segmentX += x; }; switch (this.model.barStyle.data) { case BarStyleType.LightHeavy: setLines(["light barline", "heavy barline"]); break; case BarStyleType.LightLight: setLines(["light barline", "light barline"]); break; case BarStyleType.HeavyHeavy: setLines(["heavy barline", "heavy barline"]); break; case BarStyleType.HeavyLight: setLines(["heavy barline", "light barline"]); break; case BarStyleType.Regular: case BarStyleType.Dashed: case BarStyleType.Dotted: case BarStyleType.Short: case BarStyleType.Tick: setLines(["light barline"]); break; case BarStyleType.Heavy: setLines(["heavy barline"]); break; case BarStyleType.None: setLines([]); break; default: invariant(false, "Not implemented"); } this.renderedWidth = cursor.segmentX - this.x + 8; } /*---- ILayout ------------------------------------------------------*/ // Constructed: model: BarlineModel; x: number; division: number; height: number; yOffset: number; renderedWidth: number; /** * Set by layout engine. */ overrideX: number; // Prototype: boundingBoxes: IBoundingRect[]; renderClass: Type; expandPolicy: "none"; /*---- Extensions ---------------------------------------------------*/ lineStarts: number[]; lineWidths: number[]; partGroups: PartGroup[]; partSymbol: PartSymbol; } Layout.prototype.expandPolicy = "none"; Layout.prototype.renderClass = Type.Barline; Layout.prototype.boundingBoxes = []; Object.freeze(Layout.prototype.boundingBoxes); }; /** * Registers Barline in the factory structure passed in. */ function Export(constructors: { [key: number]: any }) { constructors[Type.Barline] = BarlineModel; } module Export { export interface IBarlineModel extends IModel, Barline { divisions: number; defaultX: number; defaultY: number; satieAttributes: AttributesExports.IAttributesLayout; satieAttribsOffset: number; } export interface IBarlineLayout extends ILayout { model: IBarlineModel; height: number; yOffset: number; lineStarts: number[]; lineWidths: number[]; partSymbol: PartSymbol; partGroups: PartGroup[]; } } export default Export;