satie
Version:
A sheet music renderer for the web
321 lines (290 loc) • 11.2 kB
text/typescript
/**
* 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
);
}
}
};