smoosic
Version:
<sub>[Github site](https://github.com/Smoosic/smoosic) | [source documentation](https://smoosic.github.io/Smoosic/release/docs/modules.html) | [change notes](https://aarondavidnewman.github.io/Smoosic/changes.html) | [application](https://smoosic.github.i
295 lines (290 loc) • 9.91 kB
text/typescript
// [Smoosic](https://github.com/AaronDavidNewman/Smoosic)
// Copyright (c) Aaron David Newman 2021.
/**
* Classes to support partsInfo class, for part extraction.
* Parts is parts.
* @module /smo/data/partInfo
*/
import { smoSerialize } from '../../common/serializationHelpers';
import { createXmlAttribute } from './common';
import { SmoMeasureFormat, SmoMeasureFormatParamsSer, SmoMeasureModifierBase } from './measureModifiers';
import { SmoLayoutManager, SmoLayoutManagerParamsSer, SmoLayoutManagerParams, SmoPageLayout } from './scoreModifiers';
import { SmoTextGroup, SmoTextGroupParamsSer } from './scoreText';
import { StaffModifierBase } from './staffModifiers';
export type SmoPartInfoStringType = 'partName' | 'partAbbreviation';
export const SmoPartInfoStringTypes: SmoPartInfoStringType[] = ['partName', 'partAbbreviation'];
export type SmoPartInfoNumType = 'stavesAfter' | 'stavesBefore';
export const SmoPartInfoNumTypes: SmoPartInfoNumType[] = ['stavesAfter', 'stavesBefore'];
export type SmoPartInfoBooleanType = 'preserveTextGroups' | 'cueInScore' | 'expandMultimeasureRests';
export const SmoPartInfoBooleanTypes: SmoPartInfoBooleanType[] = ['preserveTextGroups', 'cueInScore', 'expandMultimeasureRests'];
export const SmoPartAttributesBasic = ['partName', 'partAbbreviation', 'stavesAfter', 'stavesBefore', 'preserveTextGroups', 'cueInScore', 'expandMultimeasureRests'];
/**
* @category SmoObject
*/
export interface SmoMidiInstrument {
channel: number,
program: number,
volume: number,
pan: number
}
/**
* Data contained in a part. A part has its own text, measure formatting and page layouts,
* and contains the notes from the score. It can be comprised of 1 or 2 adjacent staves.
* Usually you will call
* {@link SmoPartInfo.defaults}, and modify the parameters you need to change.
* @category SmoObject
*/
export interface SmoPartInfoParams {
/**
* Name of the part, can be used in headers
*/
partName: string,
/**
* abbrevation of part name
*/
partAbbreviation: string,
/**
* indicates that this part include the next stave (e.g. piano part)
*/
stavesAfter: number,
/**
* indicates that this part include the previous stave (e.g. piano part)
*/
stavesBefore: number,
/**
* parts can have their own page settings, zoom settings, etc.
*/
layoutManager?: SmoLayoutManager;
/**
* parts can have their own measure formatting
*/
measureFormatting?: Record<number, SmoMeasureFormat>,
/**
* for part-specific text
*/
textGroups: SmoTextGroup[],
/**
* indicates a part has its own text, not inherited from the score
*/
preserveTextGroups: boolean,
/**
* indicates the part appears as cue size in the score
*/
cueInScore: boolean,
/**
* future, for playback. TODO: Note staves contain instruments that compete with this.
* maybe this will be removed
*/
midiDevice: string | null,
/**
* see midiDevice
*/
midiInstrument: SmoMidiInstrument | null,
/**
* indicates multimeasure rests in parts should be expanded.
*/
expandMultimeasureRests: boolean
}
/**
* Serialized part information
* @category serialization
*/
export interface SmoPartInfoParamsSer {
/** constructor */
ctor: string;
/**
* Name of the part, can be used in headers
*/
partName: string,
/**
* abbrevation of part name
*/
partAbbreviation: string,
/**
* indicates that this part include the next stave (e.g. piano part)
*/
stavesAfter: number,
/**
* indicates that this part include the previous stave (e.g. piano part)
*/
stavesBefore: number,
/**
* parts can have their own page settings, zoom settings, etc.
*/
layoutManager?: SmoLayoutManagerParamsSer;
/**
* parts can have their own measure formatting
*/
measureFormatting?: Record<number, SmoMeasureFormatParamsSer>,
/**
* for part-specific text
*/
textGroups: SmoTextGroupParamsSer[],
/**
* indicates a part has its own text, not inherited from the score
*/
preserveTextGroups: boolean,
/**
* indicates the part appears as cue size in the score
*/
cueInScore: boolean,
/**
* future, for playback. TODO: Note staves contain instruments that compete with this.
* maybe this will be removed
*/
midiDevice: string | null,
/**
* see midiDevice
*/
midiInstrument: SmoMidiInstrument | null,
/**
* indicates multimeasure rests in parts should be expanded.
*/
expandMultimeasureRests: boolean
}
function isSmoPartInfoParamsSer(params: Partial<SmoPartInfoParamsSer>): params is SmoPartInfoParamsSer {
if (params.ctor && params.ctor === 'SmoPartInfo') {
return true;
}
return false;
}
/**
* Part info contains information that group 1 or 2 adjacent staves.
* Parts can have formatting that is indepenedent of the score
* @category SmoObject
*/
export class SmoPartInfo extends StaffModifierBase {
partName: string = '';
partAbbreviation: string = '';
layoutManager: SmoLayoutManager;
measureFormatting: Record<number, SmoMeasureFormat> = {};
textGroups: SmoTextGroup[] = [];
stavesAfter: number = 0;
stavesBefore: number = 0;
preserveTextGroups: boolean = false;
cueInScore: boolean = false;
displayCues: boolean = false;
expandMultimeasureRests: boolean = false;
midiInstrument: SmoMidiInstrument| null;
midiDevice: string | null;
static get defaults(): SmoPartInfoParams {
return JSON.parse(JSON.stringify({
partName: 'Staff ',
partAbbreviation: '',
globalLayout: SmoLayoutManager.defaultLayout,
textGroups: [],
preserveTextGroups: false,
pageLayoutMap: {},
stavesAfter: 0,
stavesBefore: 0,
cueInScore: false,
midiDevice: null,
midiInstrument: null,
expandMultimeasureRests: false
}));
}
constructor(params: SmoPartInfoParams) {
super('SmoPartInfo');
if (!params.layoutManager) {
this.layoutManager = new SmoLayoutManager(SmoLayoutManager.defaults);
} else {
this.layoutManager = new SmoLayoutManager(params.layoutManager);
}
if (typeof(params.measureFormatting) !== 'undefined') {
const formatKeys = Object.keys(params.measureFormatting);
formatKeys.forEach((key) => {
const numKey = parseInt(key, 10);
this.measureFormatting[numKey] = new SmoMeasureFormat(params.measureFormatting![numKey]);
});
}
if (params.textGroups) {
this.textGroups = params.textGroups;
}
SmoPartInfoStringTypes.forEach((st) => {
this[st] = params[st];
});
SmoPartInfoNumTypes.forEach((st) => {
this[st] = params[st];
});
SmoPartInfoBooleanTypes.forEach((st) => {
this[st] = params[st] ?? false;
});
this.midiDevice = params.midiDevice;
if (params.midiInstrument) {
this.midiInstrument = JSON.parse(JSON.stringify(params.midiInstrument));
} else {
this.midiInstrument = null;
}
}
static deserialize(jsonObj: SmoPartInfoParamsSer): SmoPartInfo {
const params = SmoPartInfo.defaults;
smoSerialize.serializedMerge(SmoPartAttributesBasic, jsonObj, params);
jsonObj.textGroups.forEach((tg) => {
params.textGroups.push(SmoTextGroup.deserializePreserveId(tg));
});
params.midiInstrument = jsonObj.midiInstrument;
params.midiDevice = jsonObj.midiDevice;
params.measureFormatting = {};
if (jsonObj.layoutManager) {
const layoutManagerParams: SmoLayoutManagerParams = {
globalLayout: jsonObj.layoutManager.globalLayout,
/**
* page margins for each page
*/
pageLayouts: []
}
jsonObj.layoutManager.pageLayouts.forEach((pl) => {
const pageLayout = new SmoPageLayout(pl);
layoutManagerParams.pageLayouts.push(pageLayout);
});
params.layoutManager = new SmoLayoutManager(layoutManagerParams);
}
if (jsonObj.measureFormatting) {
const mfkeys = Object.keys(jsonObj.measureFormatting);
mfkeys.forEach((mfkey) => {
const mfnum = parseInt(mfkey, 10);
params.measureFormatting![mfnum] = SmoMeasureModifierBase.deserialize(jsonObj.measureFormatting![mfnum]);
});
}
return new SmoPartInfo(params);
}
serialize(): SmoPartInfoParamsSer {
const rv: Partial<SmoPartInfoParamsSer> = { ctor: 'SmoPartInfo' };
smoSerialize.serializedMergeNonDefault(SmoPartInfo.defaults, SmoPartAttributesBasic, this, rv);
rv.layoutManager = this.layoutManager.serialize();
rv.textGroups = [];
this.textGroups.forEach((tg) => {
rv.textGroups!.push(tg.serialize());
});
rv.measureFormatting = {};
if (this.midiInstrument) {
rv.midiInstrument = JSON.parse(JSON.stringify(this.midiInstrument));
}
if (this.midiDevice) {
rv.midiDevice = this.midiDevice;
}
Object.keys(this.measureFormatting).forEach((key) => {
const numKey = parseInt(key, 10);
rv.measureFormatting![numKey] = this.measureFormatting[numKey];
});
if (!isSmoPartInfoParamsSer(rv)) {
throw 'bad part info ' + JSON.stringify(rv);
}
return rv;
}
updateTextGroup(textGroup: SmoTextGroup, toAdd: boolean) {
const tgid = typeof (textGroup) === 'string' ? textGroup :
textGroup.attrs.id;
const ar = this.textGroups.filter((tg) => tg.attrs.id !== tgid);
this.textGroups = ar;
if (toAdd) {
this.textGroups.push(textGroup);
}
}
removeTextGroup(textGroup: SmoTextGroup) {
this.updateTextGroup(textGroup, false);
}
addTextGroup(textGroup: SmoTextGroup) {
this.updateTextGroup(textGroup, true);
}
}