calliope-ts 0.0.4 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +69 -3
- package/dist/calliope/bracketing.d.ts.map +1 -1
- package/dist/calliope/bracketing.js +59 -5
- package/dist/calliope/deps.d.ts +5 -1
- package/dist/calliope/deps.d.ts.map +1 -1
- package/dist/calliope/deps.js +53 -3
- package/dist/calliope/engine.d.ts.map +1 -1
- package/dist/calliope/engine.js +6 -1
- package/dist/calliope/relstress.d.ts.map +1 -1
- package/dist/calliope/relstress.js +411 -15
- package/dist/calliope/syntax.d.ts +33 -0
- package/dist/calliope/syntax.d.ts.map +1 -1
- package/dist/calliope/syntax.js +199 -0
- package/dist/data/en_common_words.json +1 -0
- package/dist/display.d.ts +15 -1
- package/dist/display.d.ts.map +1 -1
- package/dist/display.js +112 -10
- package/dist/fabbhalle.d.ts +54 -0
- package/dist/fabbhalle.d.ts.map +1 -0
- package/dist/fabbhalle.js +404 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +86 -33
- package/dist/phonological.d.ts.map +1 -1
- package/dist/phonological.js +7 -1
- package/dist/rewriteEn.d.ts +14 -0
- package/dist/rewriteEn.d.ts.map +1 -0
- package/dist/rewriteEn.js +392 -0
- package/dist/rhyme.d.ts +35 -3
- package/dist/rhyme.d.ts.map +1 -1
- package/dist/rhyme.js +176 -11
- package/dist/russian/accentuator.d.ts +31 -0
- package/dist/russian/accentuator.d.ts.map +1 -0
- package/dist/russian/accentuator.js +546 -0
- package/dist/russian/collocations.d.ts +7 -0
- package/dist/russian/collocations.d.ts.map +1 -0
- package/dist/russian/collocations.js +38 -0
- package/dist/russian/compounds.d.ts +6 -0
- package/dist/russian/compounds.d.ts.map +1 -0
- package/dist/russian/compounds.js +81 -0
- package/dist/russian/download.d.ts +23 -0
- package/dist/russian/download.d.ts.map +1 -0
- package/dist/russian/download.js +117 -0
- package/dist/russian/engine.d.ts +6 -0
- package/dist/russian/engine.d.ts.map +1 -0
- package/dist/russian/engine.js +1424 -0
- package/dist/russian/parser.d.ts +8 -0
- package/dist/russian/parser.d.ts.map +1 -0
- package/dist/russian/parser.js +92 -0
- package/dist/russian/paths.d.ts +55 -0
- package/dist/russian/paths.d.ts.map +1 -0
- package/dist/russian/paths.js +114 -0
- package/dist/russian/rewrite.d.ts +6 -0
- package/dist/russian/rewrite.d.ts.map +1 -0
- package/dist/russian/rewrite.js +400 -0
- package/dist/russian/rhyme.d.ts +18 -0
- package/dist/russian/rhyme.d.ts.map +1 -0
- package/dist/russian/rhyme.js +230 -0
- package/dist/russian/syllabifier.d.ts +14 -0
- package/dist/russian/syllabifier.d.ts.map +1 -0
- package/dist/russian/syllabifier.js +475 -0
- package/dist/russian/types.d.ts +94 -0
- package/dist/russian/types.d.ts.map +1 -0
- package/dist/russian/types.js +27 -0
- package/dist/scandroidNative/constants.d.ts +16 -0
- package/dist/scandroidNative/constants.d.ts.map +1 -0
- package/dist/scandroidNative/constants.js +60 -0
- package/dist/scandroidNative/deduceParameters.d.ts +16 -0
- package/dist/scandroidNative/deduceParameters.d.ts.map +1 -0
- package/dist/scandroidNative/deduceParameters.js +69 -0
- package/dist/scandroidNative/dictionary-data.d.ts +2 -0
- package/dist/scandroidNative/dictionary-data.d.ts.map +1 -0
- package/dist/scandroidNative/dictionary-data.js +744 -0
- package/dist/scandroidNative/dictionary.d.ts +12 -0
- package/dist/scandroidNative/dictionary.d.ts.map +1 -0
- package/dist/scandroidNative/dictionary.js +50 -0
- package/dist/scandroidNative/engine.d.ts +41 -0
- package/dist/scandroidNative/engine.d.ts.map +1 -0
- package/dist/scandroidNative/engine.js +119 -0
- package/dist/scandroidNative/machine.d.ts +93 -0
- package/dist/scandroidNative/machine.d.ts.map +1 -0
- package/dist/scandroidNative/machine.js +795 -0
- package/dist/scandroidNative/positioner.d.ts +32 -0
- package/dist/scandroidNative/positioner.d.ts.map +1 -0
- package/dist/scandroidNative/positioner.js +208 -0
- package/dist/scandroidNative/syllabizer.d.ts +3 -0
- package/dist/scandroidNative/syllabizer.d.ts.map +1 -0
- package/dist/scandroidNative/syllabizer.js +215 -0
- package/dist/scandroidNative/utilities.d.ts +37 -0
- package/dist/scandroidNative/utilities.d.ts.map +1 -0
- package/dist/scandroidNative/utilities.js +86 -0
- package/dist/scansion.d.ts.map +1 -1
- package/dist/scansion.js +118 -14
- package/dist/stress.d.ts +1 -0
- package/dist/stress.d.ts.map +1 -1
- package/dist/stress.js +502 -42
- package/dist/types.d.ts +3 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +27 -6
- package/tools/fetch-russian-data.mjs +30 -0
- package/webapp/public/app.js +2418 -0
- package/webapp/public/assets/GithubLogo.png +0 -0
- package/webapp/public/assets/NPM2.png +0 -0
- package/webapp/public/assets/fonts/MurenaBold-webfont.woff2 +0 -0
- package/webapp/public/assets/fonts/MurenaItalic-webfont.woff2 +0 -0
- package/webapp/public/assets/fonts/MurenaRegular-webfont.woff2 +0 -0
- package/webapp/public/assets/fonts/Myra_4F_Caps_Bold-webfont.woff2 +0 -0
- package/webapp/public/assets/fonts/SinkinSans-700Bold.otf +0 -0
- package/webapp/public/assets/fonts/butterflies.ttf +0 -0
- package/webapp/public/assets/fonts/midcase_blackline-webfont.woff2 +0 -0
- package/webapp/public/assets/fonts/neueral-bold-webfont.woff2 +0 -0
- package/webapp/public/assets/fonts/neueral-regular-webfont.woff2 +0 -0
- package/webapp/public/assets/fonts/popelka-webfont.woff2 +0 -0
- package/webapp/public/assets/fonts/vremenagrotesk-webfont.woff +0 -0
- package/webapp/public/assets/fonts/wicky_javick_bold-webfont.woff2 +0 -0
- package/webapp/public/assets/logos/Logo_Var_10_Celtic_Knot_SVG.svg +116 -0
- package/webapp/public/assets/logos/Logo_Var_11_Graph_SVG.svg +313 -0
- package/webapp/public/assets/logos/Logo_Var_12_Limacon_Knot_SVG.svg +3942 -0
- package/webapp/public/assets/logos/Logo_Var_13_Crescents_SVG.svg +1030 -0
- package/webapp/public/assets/logos/Logo_Var_1_Spiral_SVG.svg +1491 -0
- package/webapp/public/assets/logos/Logo_Var_2_Tunnel_SVG.svg +12 -0
- package/webapp/public/assets/logos/Logo_Var_3_Triskelion_SVG.svg +10 -0
- package/webapp/public/assets/logos/Logo_Var_4_Quinquetra_PNG.png +0 -0
- package/webapp/public/assets/logos/Logo_Var_5_Penrose_Pentagram_SVG.svg +37 -0
- package/webapp/public/assets/logos/Logo_Var_6_Triskelion2_SVG.svg +10 -0
- package/webapp/public/assets/logos/Logo_Var_7_SixfoldSpirals_SVG.svg +20 -0
- package/webapp/public/assets/logos/Logo_Var_8_Radial_Labyrinth_SVG.svg +163 -0
- package/webapp/public/assets/logos/Logo_Var_9_Radial_Labyrinth_2_SVG.svg +106 -0
- package/webapp/public/index.html +316 -0
- package/webapp/public/styles.css +1114 -0
- package/webapp/server.mjs +680 -0
- package/src/caesura.ts +0 -201
- package/src/calliope/boundaries.ts +0 -190
- package/src/calliope/bracketing.ts +0 -390
- package/src/calliope/deps.ts +0 -160
- package/src/calliope/engine.ts +0 -77
- package/src/calliope/feats.ts +0 -46
- package/src/calliope/names.ts +0 -44
- package/src/calliope/postag.ts +0 -253
- package/src/calliope/prosodic.ts +0 -262
- package/src/calliope/relstress.ts +0 -645
- package/src/calliope/stressrules.ts +0 -147
- package/src/calliope/syntax.ts +0 -218
- package/src/calliope/udpos.ts +0 -152
- package/src/calliope_src_contents.md +0 -19049
- package/src/clio/caesura.ts +0 -145
- package/src/clio/depfix.ts +0 -88
- package/src/clio/display.ts +0 -1042
- package/src/clio/engine.ts +0 -38
- package/src/clio/parser.ts +0 -845
- package/src/clio/phonological.ts +0 -849
- package/src/clio/phrasestress.ts +0 -108
- package/src/clio/pipeline.ts +0 -154
- package/src/clio/rhyme.ts +0 -740
- package/src/clio/scandroid.ts +0 -434
- package/src/clio/scansion.ts +0 -1130
- package/src/clio/semantics.ts +0 -134
- package/src/clio/stress.ts +0 -1731
- package/src/clio/tagfix.ts +0 -104
- package/src/depfix.ts +0 -88
- package/src/display.ts +0 -1138
- package/src/engine.ts +0 -22
- package/src/index.ts +0 -690
- package/src/parser.ts +0 -501
- package/src/phonological.ts +0 -917
- package/src/phrasestress.ts +0 -108
- package/src/rhyme.ts +0 -748
- package/src/scandroid.ts +0 -434
- package/src/scansion.ts +0 -1174
- package/src/semantics.ts +0 -134
- package/src/stress.ts +0 -2111
- package/src/tagfix.ts +0 -104
- package/src/types.ts +0 -265
- package/tests/DataForHayesLinesOnly.txt +0 -364
- package/tests/DataForHayesStressSymbolsRevised.txt +0 -728
- package/tests/basic.test.ts +0 -1102
- package/tests/bench-hayes.mjs +0 -72
- package/tests/epg64.meter.train.txt +0 -8139
- package/tests/litlab-sample-2016.txt +0 -1738
- package/tests/prosodic.meter.train.txt +0 -13192
- package/tests/wagner-stress.test.ts +0 -188
- package/tsconfig.json +0 -29
- package/vitest.config.d.ts +0 -2
- package/vitest.config.js +0 -14
package/dist/rhyme.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { LineResult, StressLevel } from './types.js';
|
|
2
|
-
export type RhymeTypeName = 'identical' | 'rich' | 'perfect' | 'family' | 'assonant' | 'consonant' | 'augmented' | 'diminished' | 'wrenched' | 'eye';
|
|
2
|
+
export type RhymeTypeName = 'identical' | 'rich' | 'perfect' | 'family' | 'assonant' | 'consonant' | 'augmented' | 'diminished' | 'grammatical' | 'mosaic' | 'wrenched' | 'eye';
|
|
3
3
|
export interface RhymePair {
|
|
4
4
|
type: RhymeTypeName;
|
|
5
5
|
/** masculine (stress on final syllable) / feminine (penult) / dactylic (antepenult) */
|
|
@@ -7,6 +7,34 @@ export interface RhymePair {
|
|
|
7
7
|
}
|
|
8
8
|
/** Classify the rhyme relation between two line-end words (or null). */
|
|
9
9
|
export declare function classifyRhymePair(a: string, b: string): RhymePair | null;
|
|
10
|
+
/** A BEAT-BEARING end-rhyme between two line-end words — the bar the stanza
|
|
11
|
+
* rhyme-fellow pass (index.ts markRhymeFellows) requires before a weak
|
|
12
|
+
* line-final monosyllable may inherit a beat from its rhyme partner. Beyond
|
|
13
|
+
* the STRONG tier (perfect/rich/family) it admits AUGMENTED and DIMINISHED
|
|
14
|
+
* pairs: their stressed vowel is identical and they differ by ONE terminal
|
|
15
|
+
* consonant, so the correspondence is still carried audibly by the final
|
|
16
|
+
* beat ("me"/"dream", "sea"/"seed"). Identical repetition is excluded
|
|
17
|
+
* (epistrophe, not a prominence template), as are the looser slant tiers
|
|
18
|
+
* (assonant/consonant/grammatical/wrenched/eye) — a hunch is not a beat. */
|
|
19
|
+
export declare function isBeatTransferRhyme(a: string, b: string): boolean;
|
|
20
|
+
/**
|
|
21
|
+
* MOSAIC (multi-word) end-rhyme — Byron's "intellectual / hen-pecked you all";
|
|
22
|
+
* the maintainer's "TENderly / SLENder — SEE?". One line's POLYSYLLABIC rhyme
|
|
23
|
+
* part (its final word's last stressed vowel + everything after, ≥2 syllables)
|
|
24
|
+
* is covered by a SEQUENCE of words at the other line's end, the rhyme domain
|
|
25
|
+
* re-segmented across the word boundary. Detection is nucleus-aligned:
|
|
26
|
+
* • the covering span opens on a STRESSED vowel exactly n syllables from the
|
|
27
|
+
* line end (n = the anchor's rhyme-part syllable count) and must CROSS a
|
|
28
|
+
* word boundary (a one-word cover is ordinary rhyme, handled elsewhere);
|
|
29
|
+
* • every nucleus must match its opposite number;
|
|
30
|
+
* • the consonant runs between nuclei must match, except AT MOST ONE run may
|
|
31
|
+
* differ (by substitution or one phone of length) — the re-segmentation
|
|
32
|
+
* fudge that makes tender-LY ~ slender-SEE (L→S) and ~ slender-BE (L→B)
|
|
33
|
+
* ring true, while "tenderly / slender seed" (two differing runs) fails.
|
|
34
|
+
* This is inherently beyond a one-word-at-a-time rhyme dictionary (Nounsing);
|
|
35
|
+
* only the line-holding layer can see it.
|
|
36
|
+
*/
|
|
37
|
+
export declare function classifyMosaicPair(tailA: string[], tailB: string[]): RhymePair | null;
|
|
10
38
|
export interface LineRhyme {
|
|
11
39
|
endWord: string;
|
|
12
40
|
letter: string;
|
|
@@ -14,8 +42,12 @@ export interface LineRhyme {
|
|
|
14
42
|
matchedLine?: number;
|
|
15
43
|
}
|
|
16
44
|
/** Detect a stanza's rhyme scheme from its line-end words. Strong rhymes
|
|
17
|
-
* bind; slant-tier rhymes bind only when no strong candidate exists.
|
|
18
|
-
|
|
45
|
+
* bind; slant-tier rhymes bind only when no strong candidate exists. When
|
|
46
|
+
* `endTails` (each line's last few words) is supplied, a MOSAIC multi-word
|
|
47
|
+
* rhyme is tried wherever the single-word pair yields nothing strong — the
|
|
48
|
+
* mosaic verdict outranks a looser single-word slant reading ("tenderly /
|
|
49
|
+
* slender — see?" is mosaic, not a wrenched hunch on tenderly/see). */
|
|
50
|
+
export declare function detectScheme(endWords: string[], endTails?: string[][]): LineRhyme[];
|
|
19
51
|
/**
|
|
20
52
|
* Annotate rhyme letters/types (`detail.rhyme`) and form verdicts
|
|
21
53
|
* (`detail.formNote`) across a poem. Stanza forms are per-stanza; a poem-level
|
package/dist/rhyme.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rhyme.d.ts","sourceRoot":"","sources":["../src/rhyme.ts"],"names":[],"mappings":"AAgBA,OAAO,EAAW,UAAU,EAA8B,WAAW,EAAE,MAAM,YAAY,CAAC;AAK1F,MAAM,MAAM,aAAa,GACrB,WAAW,GAAG,MAAM,GAAG,SAAS,GAAG,QAAQ,GAC3C,UAAU,GAAG,WAAW,GAAG,WAAW,GAAG,YAAY,GACrD,UAAU,GAAG,KAAK,CAAC;AAEvB,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,aAAa,CAAC;IACpB,uFAAuF;IACvF,SAAS,CAAC,EAAE,WAAW,GAAG,UAAU,GAAG,UAAU,CAAC;CACnD;AA6DD,wEAAwE;AACxE,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,
|
|
1
|
+
{"version":3,"file":"rhyme.d.ts","sourceRoot":"","sources":["../src/rhyme.ts"],"names":[],"mappings":"AAgBA,OAAO,EAAW,UAAU,EAA8B,WAAW,EAAE,MAAM,YAAY,CAAC;AAK1F,MAAM,MAAM,aAAa,GACrB,WAAW,GAAG,MAAM,GAAG,SAAS,GAAG,QAAQ,GAC3C,UAAU,GAAG,WAAW,GAAG,WAAW,GAAG,YAAY,GACrD,aAAa,GAAG,QAAQ,GACxB,UAAU,GAAG,KAAK,CAAC;AAEvB,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,aAAa,CAAC;IACpB,uFAAuF;IACvF,SAAS,CAAC,EAAE,WAAW,GAAG,UAAU,GAAG,UAAU,CAAC;CACnD;AA6DD,wEAAwE;AACxE,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CA8ExE;AASD;;;;;;;;6EAQ6E;AAC7E,wBAAgB,mBAAmB,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAIjE;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,SAAS,GAAG,IAAI,CAErF;AAwED,MAAM,WAAW,SAAS;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,aAAa,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;;wEAKwE;AACxE,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,EAAE,QAAQ,CAAC,EAAE,MAAM,EAAE,EAAE,GAAG,SAAS,EAAE,CAiDnF;AAmND;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,UAAU,EAAE,EAAE,GAAG,IAAI,CAkB/D;AAsCD,MAAM,WAAW,QAAQ;IACvB,QAAQ,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IACpC,MAAM,EAAE,MAAM,CAAC;IAAG,OAAO,EAAE,MAAM,CAAC;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,aAAa,CAAC;IACrB,IAAI,EAAE,KAAK,GAAG,UAAU,GAAG,MAAM,CAAC;IAClC,SAAS,EAAE,WAAW,CAAC;CACxB;AACD,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,GAAG,EAAE,QAAQ,EAAE,CAAC;IAChB,QAAQ,EAAE,QAAQ,EAAE,CAAC;IACrB,IAAI,EAAE,QAAQ,EAAE,CAAC;IACjB,YAAY,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,EAAE,CAAA;KAAE,EAAE,CAAC;IACnD,SAAS,EAAE;QAAE,MAAM,EAAE,MAAM,EAAE,CAAC;QAAC,MAAM,EAAE,MAAM,EAAE,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CACnE;AAED;0EAC0E;AAC1E,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,UAAU,EAAE,EAAE,GAAG,YAAY,CAsIzE;AAgCD,mEAAmE;AACnE,MAAM,WAAW,WAAW;IAAG,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;CAAE;AA2B9D;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,UAAU,EAAE,EAAE,GAAG,WAAW,EAAE,CA0DpE"}
|
package/dist/rhyme.js
CHANGED
|
@@ -120,15 +120,42 @@ export function classifyRhymePair(a, b) {
|
|
|
120
120
|
if (vowelSame) {
|
|
121
121
|
// Same stressed vowel. Matching-length codas whose consonants pair up
|
|
122
122
|
// within one phonetic family (wet/deck, dame/grain) → family rhyme.
|
|
123
|
-
|
|
123
|
+
// The segments must ALSO be the same length overall — the coda test
|
|
124
|
+
// filters vowels out, so without the length gate an extra tail syllable
|
|
125
|
+
// vanishes and fun/funny reads "family" (it is grammatical, below).
|
|
126
|
+
if (na.length === nb.length && codaA.length === codaB.length && codaA.length > 0
|
|
124
127
|
&& codaA.every((p, i) => p === codaB[i] || sameFamily(p, codaB[i]))) {
|
|
125
128
|
return { type: 'family', structure };
|
|
126
129
|
}
|
|
127
|
-
//
|
|
128
|
-
|
|
130
|
+
// AUGMENTED / DIMINISHED (poemshape's Dickinson taxonomy): the rhyme is
|
|
131
|
+
// "extended by a consonant" — ONE extra TERMINAL consonant on one side,
|
|
132
|
+
// compared over the FULL rhyme segments, vowels included (bray/brave,
|
|
133
|
+
// grow/sown, stained/rain; manner/manners feminine). The previous
|
|
134
|
+
// coda-only comparison filtered the tail's unstressed VOWELS away before
|
|
135
|
+
// comparing, so a whole extension syllable could masquerade as a terminal
|
|
136
|
+
// consonant (pun/running "augmented", floating/float "diminished") — the
|
|
137
|
+
// same corruption the maintainer traced in Nounsing's search lineage.
|
|
138
|
+
const extraTerminalConsonant = (shorter, longer) => longer.length === shorter.length + 1 &&
|
|
139
|
+
shorter.every((p, i) => p === longer[i]) &&
|
|
140
|
+
!isVowelPhone(longer[longer.length - 1]);
|
|
141
|
+
if (extraTerminalConsonant(na, nb))
|
|
129
142
|
return { type: 'augmented', structure };
|
|
130
|
-
if (
|
|
143
|
+
if (extraTerminalConsonant(nb, na))
|
|
131
144
|
return { type: 'diminished', structure };
|
|
145
|
+
// GRAMMATICAL rhyme: matching stressed vowel at the roots, distinct
|
|
146
|
+
// INFLECTIONAL tails (pun/running, fun/funny, float/floating). One full
|
|
147
|
+
// segment is a proper prefix of the other, the excess is SYLLABIC
|
|
148
|
+
// (contains a vowel — a bare terminal consonant was aug/dim above), and
|
|
149
|
+
// the longer word's spelling ends in a closed inflectional suffix.
|
|
150
|
+
const shorterFirst = na.length <= nb.length;
|
|
151
|
+
const segS = shorterFirst ? na : nb;
|
|
152
|
+
const segL = shorterFirst ? nb : na;
|
|
153
|
+
const wordL = shorterFirst ? wb : wa;
|
|
154
|
+
if (segL.length > segS.length && segS.every((p, i) => p === segL[i])
|
|
155
|
+
&& segL.slice(segS.length).some(p => isVowelPhone(p))
|
|
156
|
+
&& GRAMMATICAL_TAILS.some(t => wordL.endsWith(t))) {
|
|
157
|
+
return { type: 'grammatical', structure };
|
|
158
|
+
}
|
|
132
159
|
return { type: 'assonant', structure };
|
|
133
160
|
}
|
|
134
161
|
if (codaEq && codaA.length > 0)
|
|
@@ -136,20 +163,153 @@ export function classifyRhymePair(a, b) {
|
|
|
136
163
|
const t = orthographicTier(a, b, pa, pb);
|
|
137
164
|
return t ? { type: t } : null;
|
|
138
165
|
}
|
|
166
|
+
/** Closed inflectional/light-derivational tails for the GRAMMATICAL rhyme
|
|
167
|
+
* test (spelling-level; the phone-level prefix condition does the real work). */
|
|
168
|
+
const GRAMMATICAL_TAILS = [
|
|
169
|
+
'ings', 'ing', "in'", 'ied', 'ies', 'ed', 'es', 'ers', 'er', 'est', 'eth',
|
|
170
|
+
'ly', 'y', 's', "'d", "'st",
|
|
171
|
+
];
|
|
172
|
+
/** A BEAT-BEARING end-rhyme between two line-end words — the bar the stanza
|
|
173
|
+
* rhyme-fellow pass (index.ts markRhymeFellows) requires before a weak
|
|
174
|
+
* line-final monosyllable may inherit a beat from its rhyme partner. Beyond
|
|
175
|
+
* the STRONG tier (perfect/rich/family) it admits AUGMENTED and DIMINISHED
|
|
176
|
+
* pairs: their stressed vowel is identical and they differ by ONE terminal
|
|
177
|
+
* consonant, so the correspondence is still carried audibly by the final
|
|
178
|
+
* beat ("me"/"dream", "sea"/"seed"). Identical repetition is excluded
|
|
179
|
+
* (epistrophe, not a prominence template), as are the looser slant tiers
|
|
180
|
+
* (assonant/consonant/grammatical/wrenched/eye) — a hunch is not a beat. */
|
|
181
|
+
export function isBeatTransferRhyme(a, b) {
|
|
182
|
+
const r = classifyRhymePair(a, b);
|
|
183
|
+
if (!r || r.type === 'identical')
|
|
184
|
+
return false;
|
|
185
|
+
return STRONG.has(r.type) || r.type === 'augmented' || r.type === 'diminished';
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* MOSAIC (multi-word) end-rhyme — Byron's "intellectual / hen-pecked you all";
|
|
189
|
+
* the maintainer's "TENderly / SLENder — SEE?". One line's POLYSYLLABIC rhyme
|
|
190
|
+
* part (its final word's last stressed vowel + everything after, ≥2 syllables)
|
|
191
|
+
* is covered by a SEQUENCE of words at the other line's end, the rhyme domain
|
|
192
|
+
* re-segmented across the word boundary. Detection is nucleus-aligned:
|
|
193
|
+
* • the covering span opens on a STRESSED vowel exactly n syllables from the
|
|
194
|
+
* line end (n = the anchor's rhyme-part syllable count) and must CROSS a
|
|
195
|
+
* word boundary (a one-word cover is ordinary rhyme, handled elsewhere);
|
|
196
|
+
* • every nucleus must match its opposite number;
|
|
197
|
+
* • the consonant runs between nuclei must match, except AT MOST ONE run may
|
|
198
|
+
* differ (by substitution or one phone of length) — the re-segmentation
|
|
199
|
+
* fudge that makes tender-LY ~ slender-SEE (L→S) and ~ slender-BE (L→B)
|
|
200
|
+
* ring true, while "tenderly / slender seed" (two differing runs) fails.
|
|
201
|
+
* This is inherently beyond a one-word-at-a-time rhyme dictionary (Nounsing);
|
|
202
|
+
* only the line-holding layer can see it.
|
|
203
|
+
*/
|
|
204
|
+
export function classifyMosaicPair(tailA, tailB) {
|
|
205
|
+
return mosaicDirected(tailA, tailB) ?? mosaicDirected(tailB, tailA);
|
|
206
|
+
}
|
|
207
|
+
function mosaicDirected(anchorTail, coverTail) {
|
|
208
|
+
if (anchorTail.length === 0 || coverTail.length < 2)
|
|
209
|
+
return null;
|
|
210
|
+
const anchorWord = anchorTail[anchorTail.length - 1];
|
|
211
|
+
const pa = phonesOf(anchorWord);
|
|
212
|
+
if (!pa)
|
|
213
|
+
return null;
|
|
214
|
+
const ia = lastStressedIdx(pa);
|
|
215
|
+
if (ia < 0)
|
|
216
|
+
return null;
|
|
217
|
+
const ra = pa.slice(ia);
|
|
218
|
+
const nA = ra.filter(p => isVowelPhone(p)).length;
|
|
219
|
+
if (nA < 2)
|
|
220
|
+
return null; // needs a feminine/dactylic anchor
|
|
221
|
+
const perWord = [];
|
|
222
|
+
for (const w of coverTail) {
|
|
223
|
+
const p = phonesOf(w);
|
|
224
|
+
if (!p)
|
|
225
|
+
return null;
|
|
226
|
+
perWord.push(p);
|
|
227
|
+
}
|
|
228
|
+
const combined = perWord.flat();
|
|
229
|
+
const lastWordStart = combined.length - perWord[perWord.length - 1].length;
|
|
230
|
+
// The covering span opens at the vowel sitting nA nuclei from the line end.
|
|
231
|
+
let seen = 0;
|
|
232
|
+
let p0 = -1;
|
|
233
|
+
for (let i = combined.length - 1; i >= 0; i--) {
|
|
234
|
+
if (isVowelPhone(combined[i])) {
|
|
235
|
+
seen++;
|
|
236
|
+
if (seen === nA) {
|
|
237
|
+
p0 = i;
|
|
238
|
+
break;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
if (p0 < 0)
|
|
243
|
+
return null;
|
|
244
|
+
if (!/[12]$/.test(combined[p0]))
|
|
245
|
+
return null; // span opens on a stressed vowel
|
|
246
|
+
if (p0 >= lastWordStart)
|
|
247
|
+
return null; // must cross a word boundary
|
|
248
|
+
const rb = combined.slice(p0);
|
|
249
|
+
// Nucleus-by-nucleus alignment with the one-run fudge.
|
|
250
|
+
const decompose = (seg) => {
|
|
251
|
+
const nuclei = [];
|
|
252
|
+
const runs = [];
|
|
253
|
+
let cur = [];
|
|
254
|
+
for (const p of seg) {
|
|
255
|
+
if (isVowelPhone(p)) {
|
|
256
|
+
if (nuclei.length)
|
|
257
|
+
runs.push(cur);
|
|
258
|
+
cur = [];
|
|
259
|
+
nuclei.push(base(p));
|
|
260
|
+
}
|
|
261
|
+
else {
|
|
262
|
+
cur.push(base(p));
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
runs.push(cur); // final coda
|
|
266
|
+
return { nuclei, runs };
|
|
267
|
+
};
|
|
268
|
+
const A = decompose(ra);
|
|
269
|
+
const B = decompose(rb);
|
|
270
|
+
if (A.nuclei.length !== B.nuclei.length)
|
|
271
|
+
return null;
|
|
272
|
+
if (A.nuclei.some((v, i) => v !== B.nuclei[i]))
|
|
273
|
+
return null;
|
|
274
|
+
let fudged = 0;
|
|
275
|
+
for (let i = 0; i < A.runs.length; i++) {
|
|
276
|
+
const x = A.runs[i];
|
|
277
|
+
const y = B.runs[i];
|
|
278
|
+
if (x.length === y.length && x.every((p, k) => p === y[k]))
|
|
279
|
+
continue;
|
|
280
|
+
if (Math.abs(x.length - y.length) <= 1) {
|
|
281
|
+
fudged++;
|
|
282
|
+
continue;
|
|
283
|
+
}
|
|
284
|
+
return null;
|
|
285
|
+
}
|
|
286
|
+
if (fudged > 1)
|
|
287
|
+
return null;
|
|
288
|
+
return { type: 'mosaic', structure: nA === 2 ? 'feminine' : 'dactylic' };
|
|
289
|
+
}
|
|
139
290
|
// Strength tiers for scheme detection.
|
|
140
291
|
const STRONG = new Set(['identical', 'rich', 'perfect', 'family']);
|
|
141
|
-
const SLANT = new Set(['assonant', 'consonant', 'augmented', 'diminished', 'wrenched', 'eye']);
|
|
292
|
+
const SLANT = new Set(['assonant', 'consonant', 'augmented', 'diminished', 'grammatical', 'mosaic', 'wrenched', 'eye']);
|
|
142
293
|
// Full rhyme only — the stricter bar a pre-caesural INTERNAL rhyme must clear.
|
|
143
294
|
const STRICT_INTERNAL = new Set(['identical', 'rich', 'perfect']);
|
|
144
295
|
/** Detect a stanza's rhyme scheme from its line-end words. Strong rhymes
|
|
145
|
-
* bind; slant-tier rhymes bind only when no strong candidate exists.
|
|
146
|
-
|
|
296
|
+
* bind; slant-tier rhymes bind only when no strong candidate exists. When
|
|
297
|
+
* `endTails` (each line's last few words) is supplied, a MOSAIC multi-word
|
|
298
|
+
* rhyme is tried wherever the single-word pair yields nothing strong — the
|
|
299
|
+
* mosaic verdict outranks a looser single-word slant reading ("tenderly /
|
|
300
|
+
* slender — see?" is mosaic, not a wrenched hunch on tenderly/see). */
|
|
301
|
+
export function detectScheme(endWords, endTails) {
|
|
147
302
|
const out = [];
|
|
148
303
|
let nextLetter = 0;
|
|
149
304
|
for (let i = 0; i < endWords.length; i++) {
|
|
150
305
|
let best = null;
|
|
151
306
|
for (let j = i - 1; j >= 0; j--) {
|
|
152
|
-
|
|
307
|
+
let pair = classifyRhymePair(endWords[j], endWords[i]);
|
|
308
|
+
if ((!pair || !STRONG.has(pair.type)) && endTails?.[j]?.length && endTails?.[i]?.length) {
|
|
309
|
+
const m = classifyMosaicPair(endTails[j], endTails[i]);
|
|
310
|
+
if (m)
|
|
311
|
+
pair = m;
|
|
312
|
+
}
|
|
153
313
|
if (!pair)
|
|
154
314
|
continue;
|
|
155
315
|
if (STRONG.has(pair.type)) {
|
|
@@ -327,6 +487,11 @@ function lineEndWord(line) {
|
|
|
327
487
|
const ws = lineWords(line);
|
|
328
488
|
return ws.length ? surfaceOf(ws[ws.length - 1]) : '';
|
|
329
489
|
}
|
|
490
|
+
/** The line's last few syllable-bearing words — the material a MOSAIC
|
|
491
|
+
* multi-word rhyme may re-segment across (classifyMosaicPair). */
|
|
492
|
+
function lineEndTail(line, maxWords = 4) {
|
|
493
|
+
return lineWords(line).slice(-maxWords).map(surfaceOf);
|
|
494
|
+
}
|
|
330
495
|
// ─── INTERNAL (PRE-CAESURAL) RHYME — additive layer ─────────────────
|
|
331
496
|
//
|
|
332
497
|
// The per-stanza END-rhyme scheme (detectScheme) is the primary, UNTOUCHED
|
|
@@ -444,7 +609,7 @@ export function applyRhymeAndForm(stanzas) {
|
|
|
444
609
|
// stanza) — that is what couplet/quatrain/sonnet patterns are defined over.
|
|
445
610
|
const analyzed = stanzas.map(lines => {
|
|
446
611
|
const details = lines.map(l => l.phonologicalScansion);
|
|
447
|
-
const rhymes = detectScheme(lines.map(lineEndWord));
|
|
612
|
+
const rhymes = detectScheme(lines.map(lineEndWord), lines.map(l => lineEndTail(l)));
|
|
448
613
|
return { lines, details, rhymes };
|
|
449
614
|
});
|
|
450
615
|
for (const { details, rhymes } of analyzed) {
|
|
@@ -514,7 +679,7 @@ export function analyzePhonopoetics(stanzas) {
|
|
|
514
679
|
};
|
|
515
680
|
// ── Phase 1: END rhymes (poem-wide) ──
|
|
516
681
|
const endWords = PLs.map(pl => lineEndWord(pl.line));
|
|
517
|
-
const es = detectScheme(endWords);
|
|
682
|
+
const es = detectScheme(endWords, PLs.map(pl => lineEndTail(pl.line)));
|
|
518
683
|
const endScheme = canonicalScheme(es);
|
|
519
684
|
const byLetter = new Map();
|
|
520
685
|
es.forEach((r, i) => { if (r.letter !== '·') {
|
|
@@ -740,7 +905,7 @@ export function summarizePoem(stanzas) {
|
|
|
740
905
|
// ── Form — the form layer's verdict(s) + registry-scheme alignment. (The
|
|
741
906
|
// detailed rhyme listing now lives in the Phonopoetics section; the synopsis
|
|
742
907
|
// carries only the canonical End-Rhyme Scheme, below.) ──
|
|
743
|
-
const endRhymes = N >= 2 ? detectScheme(lines.map(lineEndWord)) : [];
|
|
908
|
+
const endRhymes = N >= 2 ? detectScheme(lines.map(lineEndWord), lines.map(l => lineEndTail(l))) : [];
|
|
744
909
|
const endScheme = endRhymes.length ? canonicalScheme(endRhymes) : '';
|
|
745
910
|
const hasEndRhyme = endRhymes.some(r => r.letter !== '·');
|
|
746
911
|
const forms = [...new Set(details.map(d => d.formNote).filter((x) => !!x))];
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
interface AccentsData {
|
|
2
|
+
wordAccents: Map<string, number>;
|
|
3
|
+
ambiguousAccents: Record<string, Record<string, string[]>>;
|
|
4
|
+
ambiguousAccents2: Record<string, number[]>;
|
|
5
|
+
yoWords: Record<string, string>;
|
|
6
|
+
yoByGram: Record<string, any>;
|
|
7
|
+
secondaryStress: Record<string, number[]>;
|
|
8
|
+
rhymedWords: Set<[string, string]>;
|
|
9
|
+
rhymingDict: Record<string, string[]>;
|
|
10
|
+
derivationData: any;
|
|
11
|
+
}
|
|
12
|
+
declare function loadJson<T>(filename: string): T;
|
|
13
|
+
/** Load a JSON data file (used by other modules). */
|
|
14
|
+
export { loadJson };
|
|
15
|
+
export interface AccentResult {
|
|
16
|
+
stressPos: number;
|
|
17
|
+
secondaryStress: number[] | null;
|
|
18
|
+
ambiguous: boolean;
|
|
19
|
+
allStressPositions: number[];
|
|
20
|
+
}
|
|
21
|
+
/** Apply ёфикация: replace 'е' with 'ё' where appropriate. */
|
|
22
|
+
export declare function yoficate(word: string, feats?: Record<string, string>, upos?: string): string;
|
|
23
|
+
/** Get all accentuations for a word (multi-variant, like original get_accents). */
|
|
24
|
+
export declare function getAccents(word: string, feats?: Record<string, string>, upos?: string): AccentResult[];
|
|
25
|
+
/** Get stress position for a word, using dictionary + neural MLP fallback. */
|
|
26
|
+
export declare function getAccent(word: string, feats?: Record<string, string>, upos?: string): AccentResult;
|
|
27
|
+
/** Render a word with stress mark (U+0301 for primary, U+0300 for secondary). */
|
|
28
|
+
export declare function renderStressedWord(word: string, accent: AccentResult): string;
|
|
29
|
+
/** Get the data handle (for rhyme module). */
|
|
30
|
+
export declare function getAccentsData(): AccentsData;
|
|
31
|
+
//# sourceMappingURL=accentuator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"accentuator.d.ts","sourceRoot":"","sources":["../../src/russian/accentuator.ts"],"names":[],"mappings":"AAuBA,UAAU,WAAW;IACnB,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC;IAC3D,iBAAiB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IAC5C,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC9B,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IAC1C,WAAW,EAAE,GAAG,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACnC,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IACtC,cAAc,EAAE,GAAG,CAAC;CACrB;AAID,iBAAS,QAAQ,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,GAAG,CAAC,CAGxC;AAED,qDAAqD;AACrD,OAAO,EAAE,QAAQ,EAAE,CAAC;AAuMpB,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,eAAe,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;IACjC,SAAS,EAAE,OAAO,CAAC;IACnB,kBAAkB,EAAE,MAAM,EAAE,CAAC;CAC9B;AAED,8DAA8D;AAC9D,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CA+ChG;AAwCD,mFAAmF;AACnF,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,YAAY,EAAE,CA8E1G;AAED,8EAA8E;AAC9E,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,YAAY,CAqHvG;AAsED,iFAAiF;AACjF,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,GAAG,MAAM,CAe7E;AAED,8CAA8C;AAC9C,wBAAgB,cAAc,IAAI,WAAW,CAE5C"}
|