Spaces:
Running
Running
| // Faithful port of Scandroid.py's DeduceParameters (the "loaded whole | |
| // document" path: sample up to a dozen real lines, decide Metron by summed | |
| // complexity, then decide a consistent line length via _setLineLengthIfPossible) | |
| // and of ShowTextLine's "typed single line" path (decide Metron for just that | |
| // one line, line length stays variable). Calliope has two real analogues of | |
| // these: a multi-line poem (deduce once over the whole text) and a single | |
| // verse line typed/pasted alone (per-line decision, matching OnTypeBtn). | |
| import { ScansionMachine } from './machine.js'; | |
| export type Metron = 'iambic' | 'anapestic'; | |
| export interface ScandroidParams { | |
| metron: Metron; | |
| lineFeet: number; | |
| lineFeetSet: boolean; | |
| } | |
| const LINES_TO_SAMPLE = 12; | |
| /** Mirrors Scandroid.py's _setLineLengthIfPossible: an average with a small | |
| * enough fractional remainder rounds to a fixed length; otherwise length | |
| * stays variable (lineFeetSet=false) and lineFeet keeps its prior/default value. */ | |
| function setLineLengthIfPossible(lengths: number[], priorLineFeet: number): { lineFeet: number; lineFeetSet: boolean } { | |
| const total = lengths.reduce((a, b) => a + b, 0); | |
| if (!total || lengths.length === 0) return { lineFeet: priorLineFeet, lineFeetSet: false }; | |
| const avg = total / lengths.length; | |
| const integ = Math.trunc(avg); | |
| const frac = avg - integ; | |
| if (frac > 0.8) return { lineFeet: integ + 1, lineFeetSet: true }; | |
| if (frac < 0.2) return { lineFeet: integ, lineFeetSet: true }; | |
| return { lineFeet: priorLineFeet, lineFeetSet: false }; | |
| } | |
| /** DeduceParameters: samples up to 12 non-title-like lines from the whole | |
| * poem, tries each as iambic (ChooseAlgorithm, deducingParams) and as | |
| * anapestic (GetBestAnapLexes, deducingParams), sums complexity per metron, | |
| * and picks whichever metron scored lower overall. */ | |
| export function deduceParametersForPoem(rawLines: string[]): ScandroidParams { | |
| const machine = new ScansionMachine(); | |
| machine.SetLineFeet(5, false); | |
| let iambCompTotal = 0; | |
| let anapCompTotal = 0; | |
| const iambLens: number[] = []; | |
| const anapLens: number[] = []; | |
| let sampled = 0; | |
| for (const line of rawLines) { | |
| if (sampled >= LINES_TO_SAMPLE) break; | |
| if (line.length < 5 || line.startsWith('\t')) continue; // skip titles/short lines | |
| sampled += 1; | |
| machine.ParseLine(line); | |
| const iamb = machine.ChooseAlgorithmComplexityOnly(); | |
| iambCompTotal += iamb.score; | |
| if (iamb.score < 100) iambLens.push(iamb.length); | |
| const anap = machine.GetBestAnapLexesComplexityOnly(); | |
| anapCompTotal += anap.score; | |
| if (anap.score < 100) anapLens.push(anap.length); | |
| } | |
| const metron: Metron = iambCompTotal < anapCompTotal ? 'iambic' : 'anapestic'; | |
| const { lineFeet, lineFeetSet } = setLineLengthIfPossible(metron === 'iambic' ? iambLens : anapLens, 5); | |
| return { metron, lineFeet, lineFeetSet }; | |
| } | |
| /** ShowTextLine's typed-single-line path: decide Metron for just this one | |
| * line by comparing its own iambic vs anapestic complexity; line length | |
| * stays variable (lineFeetSet=false), matching OnTypeBtn's own reset. */ | |
| export function deduceParametersForSingleLine(line: string): ScandroidParams { | |
| const machine = new ScansionMachine(); | |
| machine.SetLineFeet(5, false); | |
| machine.ParseLine(line); | |
| const iamb = machine.ChooseAlgorithmComplexityOnly(); | |
| const anap = machine.GetBestAnapLexesComplexityOnly(); | |
| const metron: Metron = iamb.score < anap.score ? 'iambic' : 'anapestic'; | |
| return { metron, lineFeet: 5, lineFeetSet: false }; | |
| } | |