Spaces:
Sleeping
Sleeping
| import {SimpleClass} from "./jsonRecovery"; | |
| enum LayoutType { | |
| Ordinary = "ordinary", | |
| Full = "full", | |
| Conservative = "conservative", | |
| Once = "once", | |
| }; | |
| interface MeasureLayout { | |
| serialize (type: LayoutType): number[]; | |
| seq: MeasureSeq; | |
| code: string; | |
| }; | |
| export type MeasureSeq = MeasureLayout[]; | |
| const spreadMeasureSeq = (seq: MeasureSeq, type: LayoutType = LayoutType.Ordinary): number[] => [].concat(...seq.map(layout => layout.serialize(type))); | |
| const seqToCode = (seq: MeasureSeq, {withBrackets = false}: {withBrackets?: boolean} = {}): string => { | |
| //const code = seq.map(layout => layout.code).join(", "); | |
| let code = ""; | |
| let inRange = false; | |
| for (let i = 0; i < seq.length; ++i) { | |
| const middle = seq[i - 1] instanceof SingleMLayout && seq[i] instanceof SingleMLayout && seq[i + 1] instanceof SingleMLayout; | |
| if (middle) { | |
| if (!inRange) { | |
| code += ".."; | |
| inRange = true; | |
| } | |
| } | |
| else { | |
| if (i > 0 && !inRange) | |
| code += ", "; | |
| inRange = false; | |
| code += seq[i].code; | |
| } | |
| } | |
| return withBrackets ? `[${code}]` : code; | |
| }; | |
| class SingleMLayout extends SimpleClass implements MeasureLayout { | |
| static className = "SingleMLayout"; | |
| measure: number; | |
| static from (measure: number) { | |
| const layout = new SingleMLayout(); | |
| layout.measure = measure; | |
| return layout; | |
| } | |
| serialize (): number[] { | |
| return [this.measure]; | |
| } | |
| get seq (): MeasureSeq { | |
| return [this]; | |
| } | |
| get code (): string { | |
| return this.measure.toString(); | |
| } | |
| }; | |
| class BlockMLayout extends SimpleClass implements MeasureLayout { | |
| static className = "BlockMLayout"; | |
| seq: MeasureSeq; | |
| static trimSeq (seq: MeasureSeq): MeasureSeq { | |
| const seq2 = []; | |
| for (const layout of seq) { | |
| if (layout instanceof BlockMLayout) { | |
| for (const sub of layout.seq) | |
| seq2.push(sub); | |
| } | |
| else | |
| seq2.push(layout); | |
| } | |
| // reduce duplicated or backwards single measures | |
| const seq3 = []; | |
| let measure = null; | |
| for (const layout of seq2) { | |
| if (layout instanceof SingleMLayout) { | |
| if (layout.measure > measure) { | |
| seq3.push(layout); | |
| measure = layout.measure; | |
| } | |
| } | |
| else | |
| seq3.push(layout); | |
| } | |
| return seq3; | |
| } | |
| static fromSeq (seq: MeasureSeq): BlockMLayout { | |
| const layout = new BlockMLayout(); | |
| layout.seq = BlockMLayout.trimSeq(seq); | |
| return layout; | |
| } | |
| serialize (type: LayoutType): number[] { | |
| return spreadMeasureSeq(this.seq, type); | |
| } | |
| get code (): string { | |
| return seqToCode(this.seq, {withBrackets: true}); | |
| } | |
| }; | |
| class VoltaMLayout extends SimpleClass implements MeasureLayout { | |
| static className = "VoltaMLayout"; | |
| times: number; | |
| body: MeasureSeq; | |
| alternates: MeasureSeq[]; | |
| serialize (type: LayoutType): number[] { | |
| const bodySeq = spreadMeasureSeq(this.body); | |
| if (this.alternates) { | |
| const alternateSeqs = this.alternates.map(seq => spreadMeasureSeq(seq)); | |
| const lastAlternateSeq = alternateSeqs[alternateSeqs.length - 1]; | |
| switch (type) { | |
| case LayoutType.Ordinary: | |
| return bodySeq.concat(...alternateSeqs); | |
| case LayoutType.Conservative: | |
| case LayoutType.Full: { | |
| const priorSeq = [].concat(...Array(this.times - 1).fill(null).map((_, i) => [ | |
| ...bodySeq, | |
| ...alternateSeqs[i % (this.times - 1)], | |
| ])); | |
| return [ | |
| ...priorSeq, | |
| ...bodySeq, | |
| ...lastAlternateSeq, | |
| ]; | |
| } | |
| case LayoutType.Once: | |
| return [ | |
| ...bodySeq, | |
| ...lastAlternateSeq, | |
| ]; | |
| } | |
| } | |
| else { | |
| switch (type) { | |
| case LayoutType.Ordinary: | |
| case LayoutType.Conservative: | |
| case LayoutType.Once: | |
| return bodySeq; | |
| case LayoutType.Full: | |
| return [].concat(...Array(this.times).fill(null).map(() => bodySeq)); | |
| } | |
| } | |
| console.warn("the current case not handled:", type, this); | |
| } | |
| get seq (): MeasureSeq { | |
| const alternates = this.alternates ? this.alternates[this.alternates.length - 1] : []; | |
| return [ | |
| ...this.body, | |
| ...alternates, | |
| ]; | |
| } | |
| get code (): string { | |
| const body = seqToCode(this.body, {withBrackets: true}); | |
| let code = `${this.times}*${body}`; | |
| if (this.alternates) | |
| code += "{" + this.alternates.map(seq => seqToCode(seq, {withBrackets: seq.length > 1})).join(", ") +"}"; | |
| return code; | |
| } | |
| }; | |
| class ABAMLayout extends SimpleClass implements MeasureLayout { | |
| static className = "ABAMLayout"; | |
| main: MeasureLayout; | |
| rest: MeasureSeq; | |
| serialize (type: LayoutType): number[] { | |
| const seqA = this.main.serialize(type); | |
| const seqA_ = spreadMeasureSeq(this.main.seq, LayoutType.Once); | |
| const seqB = spreadMeasureSeq(this.rest, type); | |
| switch (type) { | |
| case LayoutType.Ordinary: // A B | |
| return [ | |
| ...seqA, | |
| ...seqB, | |
| ]; | |
| case LayoutType.Once: // B A' | |
| return [ | |
| ...seqB, | |
| ...seqA_, | |
| ]; | |
| case LayoutType.Conservative: // A B A' | |
| case LayoutType.Full: // A B A' | |
| return [ | |
| ...seqA, | |
| ...seqB, | |
| ...seqA_, | |
| ]; | |
| default: | |
| console.warn("the current case not handled:", type, this); | |
| } | |
| } | |
| get seq (): MeasureSeq { | |
| return [ | |
| this.main, | |
| ...this.rest, | |
| ]; | |
| } | |
| get code (): string { | |
| return "<" + this.main.code + ", " + seqToCode(this.rest) + ">"; | |
| } | |
| }; | |
| export { | |
| LayoutType, | |
| MeasureLayout, | |
| SingleMLayout, | |
| BlockMLayout, | |
| VoltaMLayout, | |
| ABAMLayout, | |
| }; | |