UNPKG

satie

Version:

A sheet music renderer for the web

321 lines (290 loc) 11.2 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 {UprightInverted, Notations, NormalAngledSquare, AboveBelow} from "musicxml-interfaces"; import {createFactory, Component, DOM, ReactElement, PropTypes} from "react"; import {forEach} from "lodash"; import * as invariant from "invariant"; import Bezier from "./private_views_bezier"; import Glyph from "./private_views_glyph"; import {bboxes} from "./private_smufl"; import Articulation from "./implChord_articulationView"; import Chord from "./implChord_chordModel"; const $Bezier = createFactory(Bezier); const $Glyph = createFactory(Glyph); const $Articulation = createFactory(Articulation); export interface IProps { spec: Notations; layout?: Chord.IChordLayout; defaultY?: number; } /** * Notations are things that are attached to notes. */ export default class NotationView extends Component<IProps, void> { static contextTypes = { originY: PropTypes.number, } as any; context: { originY: number; }; render() { const model = this.props.spec; const nlayout = this.props.layout; const notehead = nlayout ? nlayout.model.noteheadGlyph[0] : "noteheadBlack"; const bbox = bboxes[notehead]; const noteheadCenter = 10 * (bbox[0] - bbox[2]) / 2; const originX = nlayout ? nlayout.model[0].defaultX + noteheadCenter : 0; let children: ReactElement<any>[] = []; forEach(model.accidentalMarks, accidentalMark => { // TODO }); forEach(model.arpeggiates, arpeggiate => { // TODO }); forEach(model.articulations, (articulation, idx) => { children.push($Articulation({ articulation: articulation, key: `art${idx}`, defaultX: nlayout ? nlayout.model[0].defaultX : 0, })); }); forEach(model.dynamics, dynamic => { // TODO }); forEach(model.fermatas, (fermata, idx) => { let direction = (fermata.type === UprightInverted.Inverted) ? "Below" : "Above"; let shape; switch (fermata.shape) { case NormalAngledSquare.Angled: shape = "fermataShort"; break; case NormalAngledSquare.Square: shape = "fermataLong"; break; case NormalAngledSquare.Normal: default: shape = "fermata"; break; } children.push($Glyph({ fill: "black", glyphName: `${shape}${direction}`, key: `fer${idx}`, x: originX + fermata.defaultX + (fermata.relativeX || 0), y: (this.context.originY || 0) - fermata.defaultY - (fermata.relativeY || 0) })); }); forEach(model.glissandos, glissando => { // TODO }); forEach(model.nonArpeggiates, nonArpeggiate => { // TODO }); forEach(model.ornaments, ornament => { // TODO }); forEach(model.slides, slide => { // TODO }); forEach(model.slurs, slur => { // TODO }); forEach(model.technicals, technical => { if (technical.tripleTongue) { // TODO } if (technical.toe) { // TODO } if (technical.hole) { // TODO } if (technical.hammerOn) { // TODO } if (technical.upBow) { let t = technical.upBow; children.push($Glyph({ fill: t.color || "black", glyphName: `stringsUpBow${t.placement === AboveBelow.Below ? "Reversed" : ""}`, key: "techUpBow", x: originX + t.defaultX + (t.relativeX || 0), y: (this.context.originY || 0) - t.defaultY - (t.relativeY || 0), })); } if (technical.downBow) { let t = technical.downBow; children.push($Glyph({ fill: t.color || "black", glyphName: `stringsDownBow${t.placement === AboveBelow.Below ? "Reversed" : ""}`, key: "techDownBow", x: originX + t.defaultX + (t.relativeX || 0), y: (this.context.originY || 0) - t.defaultY - (t.relativeY || 0), })); } if (technical.fret) { // TODO } if (technical.tap) { // TODO } if (technical.pullOff) { // TODO } if (technical.handbell) { // TODO } if (technical.bend) { // TODO } if (technical.thumbPosition) { // TODO } if (technical.stopped) { let t = technical.stopped; children.push($Glyph({ fill: t.color || "black", glyphName: "pluckedLeftHandPizzicato", key: "techStopped", x: originX + t.defaultX + (t.relativeX || 0), y: (this.context.originY || 0) - t.defaultY - (t.relativeY || 0), })); } if (technical.pluck) { // TODO } if (technical.doubleTongue) { // TODO } if (technical.string) { // TODO } if (technical.openString) { let t = technical.openString; children.push($Glyph({ fill: t.color || "black", glyphName: "stringsHarmonic", key: "techOpenString", x: originX + t.defaultX + (t.relativeX || 0), y: (this.context.originY || 0) - t.defaultY - (t.relativeY || 0), })); } if (technical.fingernails) { // TODO } if (technical.arrow) { // TODO } if (technical.harmonic) { // TODO } if (technical.heel) { // TODO } if (technical.otherTechnical) { // TODO } if (technical.snapPizzicato) { let t = technical.snapPizzicato; children.push($Glyph({ fill: t.color || "black", glyphName: `pluckedSnapPizzicato${t.placement === AboveBelow.Below ? "Below" : "Above"}`, key: "techSnapPizzicato", x: originX + t.defaultX + (t.relativeX || 0), y: (this.context.originY || 0) - t.defaultY - (t.relativeY || 0), })); } if (technical.fingering) { // TODO } }); forEach(model.tieds, tied => { let tieTo: Chord.IChordLayout = (<any>tied).satieTieTo; if (!tieTo) { return; } let bbox2 = bboxes[notehead]; let noteheadCenter2 = 10 * (bbox2[0] - bbox2[2]) / 2; let offset2 = noteheadCenter2 - noteheadCenter - 4; let defaultY = (this.context.originY || 0) - (this.props.defaultY || 0); let stem1 = this.props.layout.satieStem; let stem2 = tieTo.satieStem; let dir = -1; if (stem1 && stem2 && stem1.direction === stem2.direction) { dir = -stem1.direction; } else if (stem1) { dir = -stem1.direction; } else if (stem2) { dir = -stem2.direction; } // This is the correct style only if space permits. See B.B. page 62. let x2: number = originX - this.props.layout.overrideX + tieTo.x + offset2; let x1: number = originX; let y2: number = defaultY - (dir === -1 ? -10 : 10); let y1: number = defaultY - (dir === -1 ? -10 : 10); let x2mx1: number = x2 - x1; let x1mx2: number = -x2mx1; let relw: number = 3.2; // How "curved" it is let y1my2: number = y1 - y2; let absw: number = -dir * 8.321228 / Math.max(1, (Math.abs(y1my2))); if ((y1my2 > 0 ? -1 : 1) * dir === 1) { absw = absw * 2; } invariant(!isNaN(x2), "Invalid x2 %s", x2); invariant(!isNaN(x1), "Invalid x1 %s", x1); invariant(!isNaN(y2), "Invalid y2 %s", y2); invariant(!isNaN(y1), "Invalid y1 %s", y1); invariant(!isNaN(dir), "Invalid dir %s", dir); invariant(!isNaN(x2mx1), "Invalid x2mx1 %s", x2mx1); invariant(!isNaN(x1mx2), "Invalid x1mx2 %s", x1mx2); invariant(!isNaN(relw), "Invalid relw %s", relw); invariant(!isNaN(y1my2), "Invalid y1my2 %s", y1my2); invariant(!isNaN(absw), "Invalid absw %s", absw); children.push($Bezier({ fill: "#000000", stroke: "#000000", strokeWidth: 1.2, x1: x2, x2: 0.28278198 / 1.23897534 * x1mx2 + x2, x3: 0.9561935 / 1.23897534 * x1mx2 + x2, x4: x1, x5: 0.28278198 / 1.23897534 * x2mx1 + x1, x6: 0.95619358 / 1.23897534 * x2mx1 + x1, y1: y2, y2: ((dir === -1 ? y1my2 : 0) + absw) + y2, y3: ((dir === -1 ? y1my2 : 0) + absw) + y2, y4: y1, y5: ((dir === -1 ? 0 : -y1my2) + absw + relw) + y1, y6: ((dir === -1 ? 0 : -y1my2) + absw + relw) + y1 })); }); forEach(model.tuplets, tuplet => { // TODO }); switch (children.length) { case 0: return null; case 1: return children[0]; default: return DOM.g(null, children ); } } };