satie
Version:
A sheet music renderer for the web
163 lines (142 loc) • 5.91 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 {createFactory, Component, DOM, PropTypes, ReactElement} from "react";
import {times, map} from "lodash";
import {bravura, getFontOffset} from "./private_smufl";
import {IBeamLayout} from "./implChord_beamLayout";
import TupletNumber from "./implChord_tupletNumberView";
const $TupletNumber = createFactory(TupletNumber);
export interface IProps {
key?: string | number;
layout: IBeamLayout;
stemWidth: number;
stroke: string;
}
/**
* Renders a beam based on a computed layout.
*/
export default class Beam extends Component<IProps, {}> {
static contextTypes = {
originY: PropTypes.number.isRequired
} as any;
context: {
originY: number;
};
render(): ReactElement<any> {
let xLow = this._getX1();
let xHigh = this._getX2();
let {layout} = this.props;
let {tuplet, beamCount, x, direction} = layout;
return DOM.g(null,
map(beamCount, (beams: number, idx: number): any => {
return times(beams, beam => {
let x1: number;
let x2: number = this._withXOffset(x[idx]);
if (beamCount[idx - 1] <= beam) {
if (x[idx + 1] && beamCount[idx + 1] === beams) {
return null;
}
x1 = this._withXOffset((x[idx - 1] + x[idx] * 3) / 4);
if (idx === 0) {
return null;
}
} else if (beamCount[idx + 1] <= beam && (!x[idx + 1] || beamCount[idx - 1] !== beams)) {
x1 = this._withXOffset(x[idx]);
x2 = this._withXOffset((x[idx + 1] + x[idx] * 3) / 4);
} else {
x1 = this._withXOffset(x[idx - 1]);
if (idx === 0) {
return null;
}
}
return DOM.polygon({
fill: this.props.stroke,
key: idx + "_" + beam,
points: x1 + "," +
this._getYVar(0, beam, (x1 - xLow) / (xHigh - xLow)) + " " +
x2 + "," +
this._getYVar(0, beam, (x2 - xLow) / (xHigh - xLow)) + " " +
x2 + "," +
this._getYVar(1, beam, (x2 - xLow) / (xHigh - xLow)) + " " +
x1 + "," +
this._getYVar(1, beam, (x1 - xLow) / (xHigh - xLow)),
stroke: this.props.stroke,
strokeWidth: 0
});
});
}),
tuplet && $TupletNumber({
tuplet,
x1: xLow,
x2: xHigh,
y1: this._getYVar(0, -1, 0) - (direction >= 1 ? 8.5 : -1.8),
y2: this._getYVar(0, -1, 1) - (direction >= 1 ? 8.5 : -1.8)
})
/* DOM.g */);
}
/**
* Offset because the note-head has a non-zero width.
*/
getLineXOffset() {
return this.props.layout.direction * -this.props.stemWidth / 2;
}
private _withXOffset(x: number) {
// Note that we use notehadBlack regardless of the notehead.
// This keeps spacing consistent, even in beam groups with rests.
return x +
getFontOffset("noteheadBlack", this.props.layout.direction)[0] * 10 +
this.getLineXOffset();
}
private _getX1() {
return this._withXOffset(this.props.layout.x[0]);
}
private _getX2() {
return this._withXOffset(this.props.layout.x[this.props.layout.x.length - 1]);
}
private _getY1(incl: number, idx: number) {
// Note that we use notehadBlack regardless of the notehead.
// This keeps spacing consistent, even in beam groups with rests.
return this.context.originY -
this.props.layout.y1 -
this._getYOffset() +
this.props.layout.direction * idx * 8.8 -
// TODO: use print defaults
(incl || 0) * (bravura.engravingDefaults.beamThickness * 10);
}
private _getY2(incl: number, idx: number) {
// Note that we use notehadBlack regardless of the notehead.
// This keeps spacing consistent, even in beam groups with rests.
return this.context.originY -
this.props.layout.y2 -
this._getYOffset() +
this.props.layout.direction * idx * 8.8 -
(incl || 0) * (bravura.engravingDefaults.beamThickness * 10);
}
private _getYVar(incl: number, idx: number, percent: number) {
let y1 = this._getY1(incl, idx);
let y2 = this._getY2(incl, idx);
return (1 - percent) * y1 + percent * y2;
}
/**
* Offset because the note-head has a non-zero height.
* The note-head is NOT CENTERED at its local origin.
*/
private _getYOffset() {
return -3;
}
};