satie
Version:
A sheet music renderer for the web
369 lines (334 loc) • 12.3 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/>.
*/
/**
* @file engine/scoreHeader.ts holds default header information as well
* as convienience utilites for score headers.
*/
import {ScoreHeader, Credit, Identification, Defaults, NormalItalic, NormalBold,
OddEvenBoth, Work, PartList, LeftCenterRight, serializeScoreHeader} from "musicxml-interfaces";
import {forEach, some, defaultsDeep} from "lodash";
import {getPageMargins} from "./private_print";
import {distances, bravura} from "./private_smufl";
import {mmToTenths, defaultStaveHeight, defaultPageSize, defaultMargins} from "./private_renderUtil";
/**
* A header is a child of parts, and includes the title and other basic
* information.
*/
class ScoreHeaderModel implements ScoreHeader {
/*---- ScoreHeader --------------------------------------------------------------------------*/
credits: Credit[] = [];
identification: Identification = {
creators: [],
encoding: {
encodingDescriptions: [],
encodingDate: null,
supports: {},
encoders: [],
softwares: []
},
miscellaneous: null,
relations: [],
rights: [],
source: null
};
defaults: Defaults = {
appearance: {
distances: {
hyphen: {
tenths: 10 * distances.hyphen,
type: "hyphen"
},
beam: {
tenths: 10 * distances.beam,
type: "beam"
}
},
lineWidths: {
staff: {
"tenths": 10 * bravura.engravingDefaults.staffLineThickness,
"type": "staff"
},
wedge: {
"tenths": 10 * bravura.engravingDefaults.hairpinThickness,
"type": "wedge"
},
ending: {
"tenths": 10 * bravura.engravingDefaults.repeatEndingLineThickness,
"type": "ending"
},
"heavy barline": {
"tenths": 10 * bravura.engravingDefaults.thickBarlineThickness,
"type": "heavy barline"
},
leger: {
"tenths": 10 * bravura.engravingDefaults.legerLineThickness,
"type": "leger"
},
stem: {
"tenths": 10 * bravura.engravingDefaults.stemThickness,
"type": "stem"
},
"tuplet bracket": {
"tenths": 10 * bravura.engravingDefaults.tupletBracketThickness,
"type": "tuplet bracket"
},
beam: {
"tenths": 10 * bravura.engravingDefaults.beamThickness,
"type": "beam"
},
"light barline": {
"tenths": 10 * bravura.engravingDefaults.thinBarlineThickness,
"type": "light barline"
},
enclosure: {
"tenths": 10 * bravura.engravingDefaults.textEnclosureThickness,
"type": "enclosure"
}
},
noteSizes: {
1: { // Grace
"type": 1,
"size": 60 // Not sure what 60 refers to. Our grace notes are 1.9 spaces
},
0: { // Cue
"type": 0,
"size": 60 // Not sure what 60 refers to. Our cue notes are 1.9 spaces.
}
},
otherAppearances: []
},
lyricFonts: [],
lyricLanguages: [],
musicFont: {
fontSize: "20.5", // This value is completely ignored. See "scaling"
fontFamily: "Bravura, Maestro, engraved",
fontStyle: NormalItalic.Normal,
fontWeight: NormalBold.Normal
},
pageLayout: {
pageHeight: mmToTenths(
defaultStaveHeight, defaultPageSize().height),
pageWidth: mmToTenths(
defaultStaveHeight, defaultPageSize().width),
pageMargins: [
{
bottomMargin: mmToTenths(
defaultStaveHeight, defaultMargins.bottom),
leftMargin: mmToTenths(
defaultStaveHeight, defaultMargins.left),
rightMargin: mmToTenths(
defaultStaveHeight, defaultMargins.right),
topMargin: mmToTenths(
defaultStaveHeight, defaultMargins.top),
type: OddEvenBoth.Both
}
]
},
scaling: {
millimeters: defaultStaveHeight,
tenths: 40
},
staffLayouts: [],
systemLayout: {
systemDistance: 131,
systemDividers: null,
systemMargins: {
leftMargin: 0,
rightMargin: 0
},
topSystemDistance: 70
},
wordFont: {
fontSize: "12",
fontFamily: "Alegreya, Times New Roman, serif",
fontStyle: NormalItalic.Normal,
fontWeight: NormalBold.Normal
}
};
work: Work = {
opus: null,
workNumber: "",
workTitle: ""
};
movementTitle: string = "";
movementNumber: string = "";
partList: PartList = [];
get composer() {
return this._getIdentificationOrCredit("composer");
}
set composer(composer: string) {
this._setIdentification("composer", composer);
this._setCredits("composer", composer, LeftCenterRight.Right, "12px", 20);
}
get arranger() {
return this._getIdentificationOrCredit("arranger");
}
set arranger(arranger: string) {
this._setIdentification("arranger", arranger);
this._setCredits("arranger", arranger, LeftCenterRight.Right, "12px", 35);
}
get lyricist() {
return this._getIdentificationOrCredit("lyricist");
}
set lyricist(lyricist: string) {
this._setIdentification("lyricist", lyricist);
this._setCredits("lyricist", lyricist, LeftCenterRight.Right, "12px", 50);
}
get title() {
return this.movementTitle;
}
set title(title: string) {
// Set meta-data
this.movementTitle = title;
this._setCredits("title", title, LeftCenterRight.Center, "18px", 10);
}
/*---- Extensions ---------------------------------------------------------------------------*/
constructor(spec: ScoreHeader) {
if (spec) {
defaultsDeep(spec, this);
}
for (let key in spec) {
if (spec.hasOwnProperty(key) && typeof key === "string" && !!(<any>spec)[key]) {
(<any>this)[key] = (<any>spec)[key];
}
}
}
toXML(): string {
return serializeScoreHeader(this);
}
inspect() {
return this.toXML();
}
overwriteEncoding() {
let date = new Date;
this.identification = this.identification || (new ScoreHeaderModel(null)).identification;
this.identification.encoding = {
encodingDescriptions: [],
encodingDate: {
month: date.getMonth() + 1,
day: date.getDate(),
year: date.getFullYear()
},
supports: {
"satie-collaboration": {
element: "satie-collaboration",
value: null,
type: true,
attribute: null
}
},
encoders: [],
softwares: [
"Songhaus Satie"
]
};
}
private _getIdentificationOrCredit(type: string) {
if (this.identification && (this.identification.creators || []).length) {
let idComposer = this.identification.creators
.filter(c => c.type === type)
.map(c => c.creator)
.join(", ");
if (idComposer) {
return idComposer;
}
}
return this.credits.filter(c => c.creditTypes.indexOf(type) !== -1)
.map(m => m.creditWords)
.map(w => w.map(w => w.words).join(", "))
.join(", ");
}
private _setIdentification(type: string, val: string) {
this.identification = this.identification || {
miscellaneous: [],
creators: [],
encoding: [],
relations: [],
rights: [],
source: null
};
this.identification.creators = this.identification.creators || [];
forEach(this.identification.creators, c => {
if (c.type === type) {
c.creator = val;
}
});
if (!some(this.identification.creators, c => c.type === type)) {
// ...or add a val
this.identification.creators.push({
creator: val,
type: type
});
}
}
private _setCredits(type: string, val: string,
justification: LeftCenterRight, fontSize: string, top: number) {
const mm = this.defaults.scaling.millimeters;
const pageLayout = this.defaults.pageLayout;
this.credits = this.credits || [];
forEach(this.credits, (c, idx) => {
if (!c.creditWords) {
return false;
}
// Replace a credit...
let isComposer = c.creditTypes.indexOf(type) !== -1;
if (isComposer) {
if (!c.creditWords.length) {
delete this.credits[idx];
} else {
c.creditWords.length = 1;
c.creditWords[0].words = val;
}
}
});
if (!some(this.credits, c => Boolean(c.creditWords) && c.creditTypes.indexOf(type) !== -1)) {
let defaultX = NaN;
let margins = getPageMargins(this.defaults.pageLayout.pageMargins, 1);
// TODO: Throughout this file, use own instead of default values
switch (justification) {
case LeftCenterRight.Center:
defaultX = (margins.leftMargin - margins.rightMargin +
pageLayout.pageWidth) / 2;
break;
case LeftCenterRight.Right:
defaultX = pageLayout.pageWidth - margins.rightMargin;
break;
case LeftCenterRight.Left:
defaultX = margins.leftMargin;
break;
default:
defaultX = margins.leftMargin;
break;
};
this.credits.push({
// ... or add a credit
creditImage: null,
creditTypes: [type],
creditWords: [{
words: val,
defaultX: defaultX,
justify: justification,
defaultY: pageLayout.pageHeight - mmToTenths(mm, top),
fontSize: fontSize
}],
page: 1
});
}
}
}
export default ScoreHeaderModel;