UNPKG

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

964 lines (929 loc) 29.9 kB
// [Smoosic](https://github.com/AaronDavidNewman/Smoosic) // Copyright (c) Aaron David Newman 2021. /** * @module /smo/data/measureModifiers * **/ import { smoSerialize } from '../../common/serializationHelpers'; import { SmoMusic } from './music'; import { SmoAttrs, MeasureNumber, SmoObjectParams, SvgBox, SmoModifierBase, getId, SmoDynamicCtor, ElementLike } from './common'; import { SmoSelector } from '../xform/selections'; import { FontInfo } from '../../common/vex'; /** * Measure modifiers are attached to the measure itself. Each instance has a * `serialize()` method and a `ctor` attribute for deserialization. * @category SmoObject */ export abstract class SmoMeasureModifierBase implements SmoModifierBase { attrs: SmoAttrs; ctor: string; logicalBox: SvgBox | null = null; constructor(ctor: string) { this.ctor = ctor; this.attrs = { id: getId().toString(), type: ctor }; } static deserialize(jsonObj: SmoObjectParams) { const rv = SmoDynamicCtor[jsonObj.ctor](jsonObj); return rv; } abstract serialize(): any; } export type SmoMeasureFormatNumberAttributes = 'customStretch' | 'proportionality' | 'padLeft' | 'measureIndex'; export const SmoMeasureFormatNumberKeys: SmoMeasureFormatNumberAttributes[] = ['customStretch', 'proportionality', 'padLeft', 'measureIndex']; export type SmoMeasueFormatBooleanAttributes = 'autoJustify' | 'systemBreak' | 'skipMeasureCount' | 'pageBreak' | 'padAllInSystem' | 'restBreak' | 'forceRest'; export const SmoMeasureFormatBooleanKeys: SmoMeasueFormatBooleanAttributes[] = ['autoJustify','skipMeasureCount', 'systemBreak', 'pageBreak', 'padAllInSystem', 'restBreak', 'forceRest']; /** * Constructor parameter for measure formatting object * @category SmoObject */ export interface SmoMeasureFormatParams { /** * additional pixels to a measure (plus or minus) */ customStretch: number | null, /** * softmax factor, controls how tightly rhythms are formatted */ proportionality: number | null, /** * break justification for this column */ autoJustify: boolean | null, /** * create a new system before this measure */ systemBreak: boolean | null, /** * create a new system before this page */ pageBreak: boolean | null, /** * force a break in multi-measure rest */ restBreak: boolean | null, /** * treat this measure like a whole rest */ forceRest: boolean | null, /** * if score is grouping measures per system, skip this measure in the count * (used for short measures, or pickups) */ skipMeasureCount: boolean | null, /** * pad left, e.g. for the first stave in a system */ padLeft: number | null, /** * if padding left, pad all the measures in the column */ padAllInSystem: boolean | null, /** * renumber measures */ measureIndex: number | null, } /** * Serialization for measure formatting customization, like system break * @category serialization */ export interface SmoMeasureFormatParamsSer extends SmoMeasureFormatParams{ /** * class name for deserialization */ ctor: string } function isSmoMeasureParamsSer(params: Partial<SmoMeasureFormatParamsSer>):params is SmoMeasureFormatParamsSer { return typeof(params.ctor) === 'string'; } /** * ISmoMeasureFormatMgr is the DI interface to the * format manager. Measure formats are often the same to multiple measures * so we don't serialize each one - instead we map them with this interface * @category SmoObject */ export interface ISmoMeasureFormatMgr { format: SmoMeasureFormatParams, measureNumber: MeasureNumber } /** * Measure format holds parameters about the automatic formatting of the measure itself, such as the witch and * how the durations are proportioned. Note that measure formatting is also controlled by the justification * between voices and staves. For instance, 2 measures in different staves will have to have the same width * @category SmoObject */ export class SmoMeasureFormat extends SmoMeasureModifierBase implements SmoMeasureFormatParams { static get attributes() { return ['customStretch', 'proportionality', 'autoJustify', 'systemBreak', 'pageBreak', 'padLeft', 'measureIndex', 'padAllInSystem', 'skipMeasureCount', 'restBreak', 'forceRest']; } static get formatAttributes() { return ['customStretch', 'skipMeasureCount', 'proportionality', 'autoJustify', 'systemBreak', 'pageBreak', 'padLeft']; } static get defaultProportionality() { return 0; } static get legacyProportionality() { return 0; } static fromLegacyMeasure(measure: any) { const o: any = {}; SmoMeasureFormat.formatAttributes.forEach((attr: string | number) => { if (typeof (measure[attr]) !== 'undefined') { o[attr] = measure[attr]; } else { const rhs = (SmoMeasureFormat.defaults as any)[attr]; o[attr] = rhs; } o.measureIndex = measure.measureNumber.measureIndex; }); return new SmoMeasureFormat(o); } static get defaults(): SmoMeasureFormatParams { return JSON.parse(JSON.stringify({ customStretch: 0, proportionality: SmoMeasureFormat.defaultProportionality, systemBreak: false, pageBreak: false, restBreak: false, forceRest: false, padLeft: 0, padAllInSystem: true, skipMeasureCount: false, autoJustify: true, measureIndex: 0, })); } customStretch: number = SmoMeasureFormat.defaultProportionality; proportionality: number = 0; systemBreak: boolean = false; pageBreak: boolean = false; restBreak: boolean = false; skipMeasureCount: boolean = false; forceRest: boolean = false; padLeft: number = 0; padAllInSystem: boolean = true; autoJustify: boolean = true; measureIndex: number = 0; eq(o: SmoMeasureFormatParams) { let rv = true; SmoMeasureFormatBooleanKeys.forEach((attr) => { if (o[attr] !== this[attr]) { rv = false; } }); SmoMeasureFormatNumberKeys.forEach((attr) => { if (o[attr] !== this[attr] && attr !== 'measureIndex') { rv = false; } }); return rv; } get isDefault() { return this.eq(SmoMeasureFormat.defaults); } constructor(parameters: SmoMeasureFormatParams) { super('SmoMeasureFormat'); const def = SmoMeasureFormat.defaults; SmoMeasureFormatNumberKeys.forEach((param) => { this[param] = parameters[param] ? parameters[param] : (def as any)[param]; }); SmoMeasureFormatBooleanKeys.forEach((param) => { this[param] = parameters[param] ? parameters[param] : (def as any)[param]; }); } formatMeasure(mm: ISmoMeasureFormatMgr) { mm.format = new SmoMeasureFormat(this); mm.format.measureIndex = mm.measureNumber.measureIndex; } serialize(): SmoMeasureFormatParamsSer { const params: Partial<SmoMeasureFormatParamsSer> = { ctor: 'SmoMeasureFormat' }; smoSerialize.serializedMergeNonDefault(SmoMeasureFormat.defaults, SmoMeasureFormat.attributes, this, params); if (!isSmoMeasureParamsSer(params)) { throw('bad type SmoMeasureFormatParamsSer'); } return params; } } /** * Used to create a {@link SmoBarline} * @category SmoObject */ export interface SmoBarlineParams { position: number | null, barline: number | null } /** * @category serialization */ export interface SmoBarlineParamsSer extends SmoBarlineParams { ctor: string, position: number | null, barline: number | null } /** * Barline is just that, there is a start and end in each measure, which defaults to 'single'. * @category SmoObject */ export class SmoBarline extends SmoMeasureModifierBase { static readonly positions: Record<string, number> = { start: 0, end: 1 }; static readonly barlines: Record<string, number> = { singleBar: 0, doubleBar: 1, endBar: 2, startRepeat: 3, endRepeat: 4, noBar: 5 } static get _barlineToString() { return ['singleBar', 'doubleBar', 'endBar', 'startRepeat', 'endRepeat', 'noBar']; } static barlineString(inst: SmoBarline) { return SmoBarline._barlineToString[inst.barline]; } static get defaults(): SmoBarlineParams { return JSON.parse(JSON.stringify({ position: SmoBarline.positions.end, barline: SmoBarline.barlines.singleBar })); } static get attributes() { return ['position', 'barline']; } serialize(): SmoBarlineParamsSer { const params: any = {}; smoSerialize.serializedMergeNonDefault(SmoBarline.defaults, SmoBarline.attributes, this, params); params.ctor = 'SmoBarline'; return params; } constructor(parameters: SmoBarlineParams | null) { super('SmoBarline'); let ops = parameters as any; if (typeof (parameters) === 'undefined' || parameters === null) { ops = {}; } smoSerialize.serializedMerge(SmoBarline.attributes, SmoBarline.defaults, this); smoSerialize.serializedMerge(SmoBarline.attributes, ops, this); } barline: number = SmoBarline.barlines.singleBar; position: number = SmoBarline.positions.start; } /** * Constructor for SmoRepeatSymbol * @category SmoObject */ export interface SmoRepeatSymbolParams { /** * The symbol enumeration */ symbol: number, /** * x offset for DC, sign etc. */ xOffset: number, /** * y offset for DC, sign etc. */ yOffset: number, /** * position, above or below */ position: number } /** * @category serialization */ export interface SmoRepeatSymbolParamsSer extends SmoRepeatSymbolParams { /** * constructor */ ctor: string } function isSmoRepeatSymbolParamsSer(params: Partial<SmoRepeatSymbolParamsSer>):params is SmoRepeatSymbolParamsSer { return typeof(params.ctor) === 'string' && params.ctor === 'SmoRepeatSymbol'; } /** * Repeat symbols like DC, Fine etc. Note: voltas are their own thing, * and repeats are types of barlines. * @category SmoObject */ export class SmoRepeatSymbol extends SmoMeasureModifierBase { static readonly symbols: Record<string, number> = { None: 0, Coda: 1, Segno: 2, ToCoda: 10, DcAlCoda: 4, DcAlFine: 5, DsAlCoda: 7, DsAlFine: 8, Fine: 9 } static readonly _repeatSymbolStrings: Record<number, string> = { 0: 'None', 1: 'Coda', 2: 'Segno', 10: 'ToCoda', 4: 'DcAlCoda', 5: 'DcAlFine', 7: 'DsAlCoda', 8: 'DsAlFine', 9: 'Fine' }; static repeatSymbolString(symbol: SmoRepeatSymbol): string { return SmoRepeatSymbol._repeatSymbolStrings[symbol.symbol]; } static readonly positions: Record<string, number> = { start: 0, end: 1 } static get defaults(): SmoRepeatSymbolParams { return JSON.parse(JSON.stringify({ symbol: SmoRepeatSymbol.symbols.Coda, xOffset: 0, yOffset: 30, position: SmoRepeatSymbol.positions.end })); } static get attributes() { return ['symbol', 'xOffset', 'yOffset', 'position']; } symbol: number = SmoRepeatSymbol.symbols.Coda; xOffset: number = 0; yOffset: number = 30; position: number = SmoRepeatSymbol.positions.end; serialize(): SmoRepeatSymbolParamsSer { const params: Partial<SmoRepeatSymbolParamsSer> = {}; smoSerialize.serializedMergeNonDefault(SmoRepeatSymbol.defaults, SmoRepeatSymbol.attributes, this, params); params.ctor = 'SmoRepeatSymbol'; if (!isSmoRepeatSymbolParamsSer(params)) { throw 'bad type SmoRepeatSymbolParamsSer'; } return params; } constructor(parameters: SmoRepeatSymbolParams) { super('SmoRepeatSymbol'); if (typeof(parameters.symbol) !== 'number') { parameters.symbol = SmoRepeatSymbol.symbols.Coda; } smoSerialize.serializedMerge(SmoRepeatSymbol.attributes, SmoRepeatSymbol.defaults, this); smoSerialize.serializedMerge(SmoRepeatSymbol.attributes, parameters, this); } } /** * Constructor parameters for {@link SmoVolta} (2nd ending) * @category SmoObject */ export interface SmoVoltaParams { /** * start bar of ending */ startBar: number, /** * end bar (how long it stretches) */ endBar: number, /** * xoffset for start, for collisions */ xOffsetStart: number, /** * xoffset for end, for collisions */ xOffsetEnd: number, /** * yOffset, for collisions */ yOffset: number, /** * 2nd ending, 3rd etc. */ number: number } /** * serializable bits of volta/endings * @category serialization */ export interface SmoVoltaParamsSer extends SmoVoltaParams { /** * constructor */ ctor: string; } /** * Voltas (2nd endings) behave more like staff modifiers, but they are associated with the measure * since each measure has it's own rules for displaying part of the volta. * @category SmoObject */ export class SmoVolta extends SmoMeasureModifierBase { startBar: number = 1; endBar: number = 1; xOffsetStart: number = 0; xOffsetEnd: number = 0; yOffset: number = 20; number: number = 1; endingId: string | null = null; startSelector: SmoSelector | null = null; endSelector: SmoSelector | null = null; elements: ElementLike[] = []; constructor(parameters: SmoVoltaParams) { super('SmoVolta'); smoSerialize.serializedMerge(SmoVolta.attributes, SmoVolta.defaults, this); smoSerialize.serializedMerge(SmoVolta.attributes, parameters, this); } get id() { return this.attrs.id; } get type() { return this.attrs.type; } static get attributes() { return ['startBar', 'endBar', 'endingId', 'startSelector', 'endSelector', 'xOffsetStart', 'xOffsetEnd', 'yOffset', 'number']; } static get editableAttributes() { return ['xOffsetStart', 'xOffsetEnd', 'yOffset', 'number']; } serialize(): SmoVoltaParamsSer { const params: any = {}; smoSerialize.serializedMergeNonDefault(SmoVolta.defaults, SmoVolta.attributes, this, params); params.ctor = 'SmoVolta'; return params; } static get defaults(): SmoVoltaParams { return JSON.parse(JSON.stringify({ startBar: 1, endBar: 1, xOffsetStart: 0, xOffsetEnd: 0, yOffset: 20, number: 1 })); } } /** * Constructor parameters for {@link SmoMeasureText} * @category SmoObject */ export interface SmoMeasureTextParams { position: number, fontInfo: FontInfo, text: string, adjustX: number, adjustY: number, justification: number } /** * Serialized fields of SmoMeasureTextParams * @category serialization */ export interface SmoMeasureTextParamsSer extends SmoMeasureTextParams { /** * constructor */ ctor: string } /** * Measure text is just that. Now that score text can be associated with musical elements, this * class has falled into disrepair. It may be used for part notations in the score later. * @category SmoObject */ export class SmoMeasureText extends SmoMeasureModifierBase { static readonly positions: Record<string, number> = { above: 0, below: 1, left: 2, right: 3, none: 4 } static readonly justifications: Record<string, number> = { left: 0, right: 1, center: 2 } static readonly _positionToString: string[] = ['above', 'below', 'left', 'right'] static get attributes() { return ['position', 'fontInfo', 'text', 'adjustX', 'adjustY', 'justification']; } static readonly defaults: SmoMeasureTextParams = { position: SmoMeasureText.positions.above, fontInfo: { size: 9, family: 'times', style: 'normal', weight: 'normal' }, text: 'Smo', adjustX: 0, adjustY: 0, justification: SmoMeasureText.justifications.center } justification: number = SmoMeasureText.justifications.center; position: number = SmoMeasureText.positions.above; text: string = ''; adjustX: number = 0; adjustY: number = 0; fontInfo: FontInfo = { size: 9, family: 'times', style: 'normal', weight: 'normal' }; serialize(): SmoMeasureTextParamsSer { var params: Partial<SmoMeasureTextParamsSer> = {}; smoSerialize.serializedMergeNonDefault(SmoMeasureText.defaults, SmoMeasureText.attributes, this, params); params.ctor = 'SmoMeasureText'; return params as SmoMeasureTextParamsSer; // trivial class, no 'is' } constructor(parameters: SmoMeasureTextParams | null) { super('SmoMeasureText'); let pobj = parameters as any; if (pobj === null) { pobj = SmoMeasureText.defaults; } smoSerialize.serializedMerge(SmoMeasureText.attributes, SmoMeasureText.defaults, this); smoSerialize.serializedMerge(SmoMeasureText.attributes, pobj, this); // right-justify left text and left-justify right text by default if (!pobj.justification) { // eslint-disable-next-line this.justification = (this.position === SmoMeasureText.positions.left) ? SmoMeasureText.justifications.right : (this.position === SmoMeasureText.positions.right ? SmoMeasureText.justifications.left : this.justification); } } } /** * Used to construct {@link SmoRehearsalMark} * @internal * */ export interface SmoRehearsalMarkParams { /** * cardinal position */ position: number, /** * Symbol. by default, letters that auto-increment */ symbol: string, /** * future, define how increment works */ cardinality: string, /** * disable to make your own symbols for each new one. */ increment: boolean } /** * Serialized fields for rehearsal mark * @category serialization */ export interface SmoRehearsalMarkParamsSer extends SmoRehearsalMarkParams { /** * constructor */ ctor: string; } /** * Rehearsal marks are some type of auto-incrementing markers on a measure index. * @category SmoObject */ export class SmoRehearsalMark extends SmoMeasureModifierBase { static readonly cardinalities: Record<string, string> = { capitals: 'capitals', lowerCase: 'lowerCase', numbers: 'numbers' } static readonly positions: Record<string, number> = { above: 0, below: 1, left: 2, right: 3 } static get _positionToString(): string[] { return ['above', 'below', 'left', 'right']; } // TODO: positions don't work. static get defaults(): SmoRehearsalMarkParams { return JSON.parse(JSON.stringify({ position: SmoRehearsalMark.positions.above, cardinality: SmoRehearsalMark.cardinalities.capitals, symbol: 'A', increment: true })); } static get attributes() { return ['cardinality', 'symbol', 'position', 'increment']; } position: number = SmoRehearsalMark.positions.above; cardinality: string = SmoRehearsalMark.cardinalities.capitals; symbol: string = 'A'; increment: boolean = true; getIncrement() { if (this.cardinality !== 'number') { const code = this.symbol.charCodeAt(0) + 1; const symbol = String.fromCharCode(code); return symbol; } else { return (parseInt(this.symbol, 10) + 1).toString(); } } getInitial() { // eslint-disable-next-line return this.cardinality === SmoRehearsalMark.cardinalities.capitals ? 'A' : (this.cardinality === SmoRehearsalMark.cardinalities.lowerCase ? 'a' : '1'); } serialize(): SmoRehearsalMarkParamsSer { var params: Partial<SmoRehearsalMarkParamsSer> = {}; smoSerialize.serializedMergeNonDefault(SmoRehearsalMark.defaults, SmoRehearsalMark.attributes, this, params); params.ctor = 'SmoRehearsalMark'; return params as SmoRehearsalMarkParamsSer; } constructor(parameters: SmoRehearsalMarkParams) { super('SmoRehearsalMark'); let pobj = parameters; if (typeof (pobj) === 'undefined' || pobj === null) { pobj = SmoRehearsalMark.defaults; } smoSerialize.serializedMerge(SmoRehearsalMark.attributes, SmoRehearsalMark.defaults, this); smoSerialize.serializedMerge(SmoRehearsalMark.attributes, pobj, this); if (!pobj.symbol) { this.symbol = this.getInitial(); } } } export type SmoTempoNumberAttribute = 'bpm' | 'beatDuration' | 'yOffset'; export type SmoTempoStringAttribute = 'tempoMode' | 'tempoText' | 'customText'; export type SmoTempoBooleanAttribute = 'display'; export type SmoTempoMode = 'duration' | 'text' | 'custom'; /** * constructor parameters for {@link SmoTempoText} * @category SmoObject */ export interface SmoTempoTextParams { /** * text (e.g. Allegro) or bpm */ tempoMode: string, /** * playback bpm */ bpm: number, /** * note type for a metronome beat */ beatDuration: number, /** * if text mode, the text */ tempoText: string, /** * move the text to keep it from colliding with other things */ yOffset: number, /** * indicate if we are displaying, false if only affects playback */ display: boolean, /** * text taht is not a standards notation */ customText: string } /** * serialized tempo parameters * @category serialization */ export interface SmoTempoTextParamsSer extends SmoTempoTextParams { ctor: string; } /** * @internal */ export interface VexTempoTextParams { duration?: string, dots?: number, bpm?: number, name?: string } /** * Information about both playback tempo and how the tempo is notated. * @category SmoObject */ export class SmoTempoText extends SmoMeasureModifierBase implements SmoTempoTextParams { static get tempoModes(): Record<string, SmoTempoMode> { return { durationMode: 'duration', textMode: 'text', customMode: 'custom' }; } static get tempoTexts(): Record<string, string> { return { larghissimo: 'Larghissimo', grave: 'Grave', lento: 'Lento', largo: 'Largo', larghetto: 'Larghetto', adagio: 'Adagio', adagietto: 'Adagietto', andante_moderato: 'Andante moderato', andante: 'Andante', andantino: 'Andantino', moderator: 'Moderato', allegretto: 'Allegretto', allegro: 'Allegro', vivace: 'Vivace', presto: 'Presto', prestissimo: 'Prestissimo' }; } /** * create defaults for tempo initialization */ static get defaults(): SmoTempoTextParams { return JSON.parse(JSON.stringify({ tempoMode: SmoTempoText.tempoModes.durationMode, bpm: 120, beatDuration: 4096, tempoText: SmoTempoText.tempoTexts.allegro, yOffset: 0, display: false, customText: '' })); } static get attributes() { return ['tempoMode', 'bpm', 'display', 'beatDuration', 'tempoText', 'yOffset', 'customText']; } tempoMode: SmoTempoMode = SmoTempoText.tempoModes.durationMode bpm: number = 120; beatDuration: number = 4096; tempoText: string = 'Allegro'; yOffset: number = 0; display: boolean = false; customText: string = ''; _toVexTextTempo(): VexTempoTextParams { return { name: this.tempoText }; } /** * Return equality wrt the tempo marking, e.g. 2 allegro in textMode will be equal but * an allegro and duration 120bpm will not. * @param t1 * @param t2 * @returns */ static eq(t1: SmoTempoText, t2: SmoTempoText) { if (t1.tempoMode !== t2.tempoMode) { return false; } if (t1.tempoMode === SmoTempoText.tempoModes.durationMode) { return t1.bpm === t2.bpm && t1.beatDuration === t2.beatDuration; } if (t1.tempoMode === SmoTempoText.tempoModes.textMode) { return t1.tempoText === t2.tempoText; } else { return t1.bpm === t2.bpm && t1.beatDuration === t2.beatDuration && t1.tempoText === t2.tempoText; } } static get bpmFromText(): Record<string, number> { const rv: any = {}; rv[SmoTempoText.tempoTexts.larghissimo] = 24; rv[SmoTempoText.tempoTexts.grave] = 40; rv[SmoTempoText.tempoTexts.lento] = 45; rv[SmoTempoText.tempoTexts.largo] = 40; rv[SmoTempoText.tempoTexts.larghetto] = 60; rv[SmoTempoText.tempoTexts.adagio] = 72; rv[SmoTempoText.tempoTexts.adagietto] = 72; rv[SmoTempoText.tempoTexts.andante_moderato] = 72; rv[SmoTempoText.tempoTexts.andante] = 84; rv[SmoTempoText.tempoTexts.andantino] = 92; rv[SmoTempoText.tempoTexts.moderator] = 96; rv[SmoTempoText.tempoTexts.allegretto] = 96; rv[SmoTempoText.tempoTexts.allegro] = 120; rv[SmoTempoText.tempoTexts.vivace] = 144; rv[SmoTempoText.tempoTexts.presto] = 168; rv[SmoTempoText.tempoTexts.prestissimo] = 240; return rv as Record<string, number>; } _toVexDurationTempo(): VexTempoTextParams { var vd = SmoMusic.ticksToDuration[this.beatDuration]; var dots = (vd.match(/d/g) || []).length; vd = vd.replace(/d/g, ''); const rv: any = { duration: vd, dots, bpm: this.bpm }; if (this.customText.length) { rv.name = this.customText; } return rv; } toVexTempo(): VexTempoTextParams { if (this.tempoMode === SmoTempoText.tempoModes.durationMode || this.tempoMode === SmoTempoText.tempoModes.customMode) { return this._toVexDurationTempo(); } return this._toVexTextTempo(); } serialize(): SmoTempoTextParamsSer { var params: Partial<SmoTempoTextParamsSer> = {}; smoSerialize.serializedMergeNonDefault(SmoTempoText.defaults, SmoTempoText.attributes, this, params); params.ctor = 'SmoTempoText'; return params as SmoTempoTextParamsSer; } constructor(parameters: SmoTempoTextParams | null) { super('SmoTempoText'); let pobj: any = parameters; if (typeof (pobj) === 'undefined' || pobj === null) { pobj = {}; } smoSerialize.serializedMerge(SmoTempoText.attributes, SmoTempoText.defaults, this); smoSerialize.serializedMerge(SmoTempoText.attributes, pobj, this); } } /** * Constructor parameters for a time signature * @category SmoObject */ export interface TimeSignatureParameters { /** * numerator */ actualBeats: number, /** * denominator, always power of 2 */ beatDuration: number, /** * indicates cut time/common time */ useSymbol: boolean, /** * display, else just affects measure lengths. */ display: boolean, /** * for pickups, display the non-pickup value */ displayString: string } /** * serialized time signature * @category serialization */ export interface TimeSignatureParametersSer extends TimeSignatureParameters { /** * constructor */ ctor: string; } /** * Time signatures contain duration information for a measure, and information * about the display of the time signature. * @category SmoObject */ export class TimeSignature extends SmoMeasureModifierBase { static get defaults(): TimeSignatureParameters { return { actualBeats: 4, beatDuration: 4, useSymbol: false, display: true, displayString: '' }; } static equal(ts1: TimeSignature, ts2: TimeSignature): boolean { return (ts1.actualBeats === ts2.actualBeats && ts1.beatDuration === ts2.beatDuration); } static createFromPartial(value: Partial<TimeSignatureParameters>) { const params = TimeSignature.defaults; smoSerialize.serializedMerge(TimeSignature.parameters, value, params); return new TimeSignature(params); } // timeSignature: string = '4/4'; actualBeats: number = 4; beatDuration: number = 4; useSymbol: boolean = false; display: boolean = true; displayString: string = ''; get timeSignature() { return this.actualBeats.toString() + '/' + this.beatDuration.toString(); } static get parameters() { return ['actualBeats', 'beatDuration', 'useSymbol', 'display', 'displayString']; } static get boolParameters() { return []; } set timeSignature(value: string) { const ar = value.split('/'); this.actualBeats = parseInt(ar[0], 10); this.beatDuration = parseInt(ar[1], 10); } serialize(): TimeSignatureParametersSer { const rv: Partial<TimeSignatureParametersSer> = {}; smoSerialize.serializedMergeNonDefault(TimeSignature.defaults, TimeSignature.parameters, this, rv); rv.ctor = 'TimeSignature'; return rv as TimeSignatureParametersSer; } constructor(params: TimeSignatureParameters) { super('TimeSignature'); this.actualBeats = params.actualBeats; this.beatDuration = params.beatDuration; this.useSymbol = params.useSymbol; this.display = params.display; this.displayString = params.displayString; } } export const measureModifierDynamicCtorInit = () => { SmoDynamicCtor['SmoMeasureFormat'] = (params: SmoMeasureFormatParams) => new SmoMeasureFormat(params); SmoDynamicCtor['SmoBarline'] = (params: SmoBarlineParams) => new SmoBarline(params); SmoDynamicCtor['SmoRepeatSymbol'] = (params: SmoRepeatSymbolParams) => new SmoRepeatSymbol(params); SmoDynamicCtor['SmoVolta'] = (params: SmoVoltaParams) => new SmoVolta(params); SmoDynamicCtor['SmoMeasureText'] = (params: SmoMeasureTextParams) => new SmoMeasureText(params); SmoDynamicCtor['SmoRehearsalMark'] = (params: SmoRehearsalMarkParams) => new SmoRehearsalMark(params); SmoDynamicCtor['SmoTempoText'] = (params: SmoTempoTextParams) => new SmoTempoText(params); SmoDynamicCtor['TimeSignature'] = (params: TimeSignatureParameters) => new TimeSignature(params); }