calliope-ts 0.0.1 → 0.0.2

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 CHANGED
@@ -1,31 +1,31 @@
1
1
  # Calliope TS
2
2
 
3
- **Calliope TS** is a phonological poetry-scansion toolkit for TypeScript / Node.js.
4
- Give it a poem (or a single line of verse) and it will tell you — and *show*
5
- you how the poem sounds: which syllables carry stress and how much, where the
6
- metrical beats fall, what meter each line is written in, how certain that
7
- reading is, what the rhyme scheme is, and what poetic form the stanzas add up
8
- to.
9
-
10
- It is not a syllable-counter with a rulebook bolted on. Calliope TS scans verse
11
- the way a trained reader does: it first works out how the words would actually
12
- be *spoken* dictionary stress, phrase rhythm, sentence intonation — and only
13
- then asks which metrical pattern that spoken contour fits best.
3
+ **Calliope TS** is a phonological poetry scansion toolkit for TypeScript / Node.js.
4
+
5
+ Its name, of course, recalls a muse.
6
+ Hand to this muse a song or verse,
7
+ And she might show you how it curves,
8
+ Which syllables hold weight in time,
9
+ How much of it, and where a rhyme,
10
+ Which meter dances through each line,
11
+ And where should land each beat
12
+ Your voice might drawl, or music drawn...
13
+ She even knows poetic form!
14
+
15
+ The premise of this toolkit is that rhythm, and therefore also meter, is much more reliably *recovered from pronunciation* than from text as such. What?! But there's no Text-to-Speech here?! (Sorry!) A lapse? A paradox? Nah. Instead, Calliope parses a stanza or verse, reproducing roughly how an attentive (but silent) reader might do so: first, working out how the words are likely to be said aloud — deduced by following contextual patterns, cohering a syntax, wielding phonotactics, evading constraints, gathering prosody, accounting for the ways phrases clump or bounce, for how a voice might subtly perk over clause-ends, and all the rest of it; and only thereafter scanning for what sort of rhythms a phrase such as that might be spectrally reaching for, or settling into. The scholars dwelling on Generative Metrics, a liminal little field somewhere in-between linguistics, phonetics, and literary studies, call this sort of approach **phonological scansion**, and the bulk of this README is about what that means and how the toolkit puts it to work.
14
16
 
15
17
  ```
16
18
  $ calliope-ts --reading exile.txt
17
19
 
18
20
  Reading View — stress gradient over the original text
19
21
 
20
- He happens to be a French poet, that thin, every syllable colour-coded
21
- book-carrying man with a bristly gray chin; by its stress level
22
+ He happens to be a French poet, that thin, each syllable is tinted by how
23
+ book-carrying man with a bristly gray chin; much stress it carries (see below)
22
24
  ...
23
25
 
24
- Stress Maps & Meter — top-3 fit scores per line
25
- S1L1 nsw|xwx|msw ‖ xs amphibrachic tetrameter (amph 1.20 · iamb 1.14 ·
26
- anap 1.13) A
27
- S1L2 m|-mww|sxx|mwm|s amphibrachic tetrameter ≈ continuity; standalone:
28
- dactylic tetrameter A(perfect)
26
+ Stress Maps & Meter — top-3 candidate meters per line
27
+ S1L1 nsw|xwx|msw ‖ xs amphibrachic tetrameter (amph 1.20 · iamb 1.14 · anap 1.13) A
28
+ S1L2 m-mw|wsx|xmw|ms amphibrachic tetrameter ≈ continuity; standalone: dactylic tetrameter A
29
29
  ...
30
30
  ```
31
31
 
@@ -36,9 +36,12 @@ Stress Maps & Meter — top-3 fit scores per line
36
36
  - [Installation](#installation)
37
37
  - [Quick start (CLI)](#quick-start-cli)
38
38
  - [Reading the output](#reading-the-output)
39
- - [How it works: the scansion pipeline](#how-it-works-the-scansion-pipeline)
39
+ - [What "phonological scansion" means](#what-phonological-scansion-means)
40
+ - [The pipeline, stage by stage](#the-pipeline-stage-by-stage)
41
+ - [A note on rules vs. constraints](#a-note-on-rules-vs-constraints)
40
42
  - [Programmatic usage (API)](#programmatic-usage-api)
41
43
  - [Examples](#examples)
44
+ - [How it compares to other scanners](#how-it-compares-to-other-scanners)
42
45
  - [Background and lineage](#background-and-lineage)
43
46
  - [Scope, strengths, and honest limitations](#scope-strengths-and-honest-limitations)
44
47
  - [Development](#development)
@@ -48,15 +51,13 @@ Stress Maps & Meter — top-3 fit scores per line
48
51
 
49
52
  ## Installation
50
53
 
51
- Requires **Node.js ≥ 20** (≥ 22 recommended — one optional analysis stage, the
52
- dependency-tree repair, needs Node's `require(esm)` support and degrades
53
- gracefully without it).
54
+ Requires **Node.js ≥ 20** (≥ 22 recommended — one optional stage, the dependency-tree repair, uses Node's `require(esm)` support and simply switches itself off on older runtimes).
54
55
 
55
56
  ```bash
56
57
  # As a global CLI tool
57
58
  npm install -g calliope-ts
58
59
 
59
- # Or as a library in your project
60
+ # Or as a library inside your own project
60
61
  npm install calliope-ts
61
62
  ```
62
63
 
@@ -79,14 +80,14 @@ calliope-ts "Shall I compare thee to a summer's day?"
79
80
  # 2. Scan a whole poem from a file (blank lines separate stanzas)
80
81
  calliope-ts poem.txt
81
82
 
82
- # 3. The Reading View — the poem itself, colour-coded by stress (recommended
83
- # for whole poems; add -r or --reading)
84
- calliope-ts --reading poem.txt
83
+ # 3. Reading view — the poem itself, syllables tinted by stress
84
+ # (the nicest way to read a whole poem; first option in the CLI;
85
+ or add -r or --reading)
85
86
 
86
87
  # 4. Pipe text in
87
88
  cat poem.txt | calliope-ts --reading
88
89
 
89
- # 5. Interactive menu (run with no arguments in a terminal)
90
+ # 5. Interactive menu (run without typing arguments in a terminal each time)
90
91
  calliope-ts
91
92
  ```
92
93
 
@@ -119,31 +120,34 @@ core representation everything else is built on):
119
120
  | `s` | strong | primary stresses, phrase peaks, line-final nuclei |
120
121
 
121
122
  A line's **stress map** is simply its syllables in order, e.g.
122
- `xs|wxm|wxs|xws` — with `|` marking foot boundaries, `‖` a strong caesura
123
- (major punctuation pause), `¦` a lighter phrase break, and `-` a "silent beat"
124
- inside a stress clash.
123
+ `xs|wxm|wxs|xws` — with:
124
+ `|` marking foot boundaries;
125
+ `‖` a strong caesura (aka syntactic pause) at major phonological/clausal or punctuation breaks;
126
+ `¦` a lighter phrase break;
127
+ `-` a "silent beat" inserted to neutralize a stress clash wherever two strongly-stressed syllables would otherwise collide, it forces the second to await its turn.
125
128
 
126
129
  ### Meter lines
127
130
 
131
+ In the reading view each verse-line gets a single summary line.
132
+ Reading one such line left to right:
133
+
128
134
  ```
129
135
  S1L2 m|-mww|sxx|mwm|s amphibrachic tetrameter (dact 1.21 · amph 1.17 ·
130
136
  anap 1.16) ≈ continuity; standalone: dactylic tetrameter A(perfect)
131
137
  ```
132
138
 
133
- - **`amphibrachic tetrameter`** — the line's meter: foot type + number of feet.
139
+ - **`amphibrachic tetrameter`** — the line's meter: foot type + count.
134
140
  - **`(dact 1.21 · amph 1.17 · anap 1.16)`** — the top three candidate meters
135
141
  with their raw fit scores. The named meter need not be the numerically
136
142
  first candidate: ties between sibling meters are resolved by principled
137
143
  criteria (word integrity, caesura alignment, stanza context — see below).
138
144
  - **`≈ continuity; standalone: dactylic tetrameter`** — this line, taken
139
- alone, fits dactylic a hair better; but the stanza is amphibrachic and the
140
- line fits amphibrachic nearly as well, so the continuity reading is
141
- promoted to the base reading and the standalone result is kept as a note.
145
+ alone, fits dactylic a hair better; but if most of the other lines in the same stanza are amphibrachic and the line fits amphibrachic nearly as well as it fits dactylic, the primary reading is promoted to the stanza-dominant base meter, while the line-prevalent meter is co-registered as the "standalone" top fit via an added note.
142
146
  - **`↔ aligns w/ stanza …`** — a weaker version of the same: the line stays
143
147
  with its own meter but is flagged as compatible with the stanza's.
144
- - **`♪ 4-beat accentual`** / `♪ 3-ictus dolnik` — a *rhythm note*: the stanza
145
- is not accentual-syllabic at all but keeps a constant count of strong beats
146
- with varying syllable counts (see "Beyond classical meters" below).
148
+ - **`♪ 4-beat accentual`** / `♪ 3-ictus dolnik` — a *rhythm note*: if the stanza
149
+ does not appear to be accentual-syllabic, but keeps a constant count of strong beats
150
+ with varying syllable counts (see "Beyond classical meters" below), it is marked as acentual.
147
151
  - **`A`, `B(perfect)`, `·`** — the rhyme-scheme letter for the line's end
148
152
  word, with the rhyme type when the line rhymes with an earlier one
149
153
  (`perfect`, `rich`, `family`, `assonant`, `consonant`, `augmented`,
@@ -151,7 +155,7 @@ S1L2 m|-mww|sxx|mwm|s amphibrachic tetrameter (dact 1.21 · amph 1.17 ·
151
155
  - **`❡ ballad stanza (ABCB, 4·3)`** — a stanza-level *form* verdict (shown in
152
156
  the stanza header): ballad stanza, blank verse, couplet, limerick,
153
157
  Shakespearean/Petrarchan sonnet, terza rima, etc.
154
- - **Certainty / fit %** (detailed view) — the share of the line realised by
158
+ - **Certainty / fit %** (detailed view) — the share of the line realized by
155
159
  clean, unsubstituted feet, tempered by phrase-edge agreement. A perfectly
156
160
  regular line reads 100%; real verse usually lands between 50 and 90.
157
161
 
@@ -159,99 +163,52 @@ S1L2 m|-mww|sxx|mwm|s amphibrachic tetrameter (dact 1.21 · amph 1.17 ·
159
163
 
160
164
  ## How it works: the scansion pipeline
161
165
 
162
- Most automatic scanners pattern-match syllable counts against meter templates.
163
- Calliope TS instead follows the **phonological scansion** method: derive the
164
- line's natural spoken prominence first, then fit meters to it. The pipeline
165
- has eight stages.
166
-
167
- **1. Parsing (who does what in the sentence).**
168
- The line is tokenized, part-of-speech tagged, and dependency-parsed (which
169
- word grammatically governs which) using the FinNLP family of libraries
170
- (`lexed`, `en-pos`, `en-parse`). Two correction layers sit inside this stage,
171
- because verse breaks taggers in predictable ways: a *tag-repair* pass fixes
172
- systematic errors before the dependency tree is built (archaic forms like
173
- *thou/thy/doth/wherefore*, the pronoun *I*, perfect-tense participles like
174
- *had quit*), and a *tree-repair* pass (built on the
175
- [depedits](https://www.npmjs.com/package/depedits) rule engine) fixes
176
- systematic attachment errors after it (e.g. noun compounds parsed as double
177
- objects). Hyphenated compounds and contractions (*we'll*, *don't*, archaic
178
- *fix'd*) are re-merged into single metrical words.
179
-
180
- **2. Lexical stress (how each word is pronounced).**
181
- Every word is looked up in an augmented CMU pronouncing dictionary
182
- ([nounsing-pro](https://www.npmjs.com/package/nounsing-pro)): syllable count,
183
- stress pattern (primary / secondary / unstressed), syllable weights, and
184
- vowel quantities. Words not in the dictionary go through a morphological
185
- fallback (strip a productive suffix, look up the stem, restore) and finally a
186
- quantity-sensitive English Stress Rule. Poetic elisions are honoured:
187
- *heav'n* is one syllable, *o'er* one, *th'expense* two, *'tis/'twas* reduce,
188
- and archaic *-'d* / *-'st* forms (*fix'd*, *stopp'st*) keep their elided
189
- syllable counts.
190
-
191
- **3. The prosodic hierarchy (how the line phrases).**
192
- Words are grouped the way speech groups them: each content word attracts its
193
- function-word satellites into a **clitic group** (CP); clitic groups join into
194
- **phonological phrases** (PP) along the syntactic dependencies; phrases join
195
- into **intonational units** (IU) bounded by major punctuation. Crucially, the
196
- **line is the scansion domain**: a verse line containing several grammatical
197
- sentences is still ONE metrical unit (the internal full stops become strong
198
- caesurae), and quotation marks are *not* treated as prosodic breaks.
166
+ Most automatic scanners pattern-match syllable counts against meter templates, or "from the outside in": count syllables, then match the count and a guessed stress pattern against a list of meter templates. Calliope TS instead follows the **phonological scansion** method. Phonological scansion works "from the inside out": it derives the line's normative spoken prominence first, then fits meters to it.
167
+
168
+ Two simple principles carry a lot of the work here, both articulated in Bruce Hayes and Abigail Kaun's study of how words are set to music (*The Role of Phonological Phrasing in Sung and Chanted Verse*, 1996):
169
+
170
+ 1. **The ends of phonological units is what matters the most (in English, at least).**: Stresses at the *right edges* of prosodic units are more reliable metrical evidence. Generative metrics has long summarized this as **"beginnings free, endings strict."**
171
+
172
+ 2. **Bigger units matter more.**
173
+ So, a stress at the end of a whole intonational unit is stronger evidence than one at the end of a phonological phrase, which is stronger evidence than the distribution of a given clitic phrase, which is more significant than the pattern of an individual word (however longer polysyllabic words may likewise hold more "weight" here). And in English as a whole, the pattern generally leans to the right (with certain significant exceptions, like numerous classes of compound expressions). But this bias as such aligns with the infamous iambic inclination.
174
+
175
+ Bruce Hayes himself (2005) and Gareth McAleese (2007/2008) built the first faithful algorithmic/computational implementations of a verse scansion procedure around those two ideas. Calliope TS re-implements that architecture on a modern JavaScript/TypeScript stack, while attempting to push it into distinctive directions. Among these: deliberately looser pre-attunement to standard iamb-centered English canons (while preserving and seeking to refine English phonological alignment accuracy); better accommodation of World poetry in meter-matching English translation (in other words, English canon levels of iambic base meter predominance should not be pre-assumed for Englished World (aka "Worldish") poetry canons); likewise, the mechanics of Calliope TS proceed from a choice not to essentialize ternary meters as inherently rare solely from English canon distributions (statistical fatalism); we, furthermore, aim to extend scansion accuracy over accentual verse forms; and, beyond scansion itself, hope to gradually incorporate nuanced and diversified identification of a broad range of poetic forms and devices (currently supported: rhyme types; in the future: alliterations, anaphoras, trope/cliche identifications, and more).
176
+
177
+
178
+ The current pipeline has eight stages.
179
+
180
+ **1. Grammatical parsing.**
181
+ The line is tokenized, part-of-speech tagged, and dependency-parsed — that is, the engine works out which word grammatically governs which (subject of what verb, object of what preposition) — using the FinNLP family of libraries (`lexed`, `en-pos`, `en-parse`). Two correction layers sit inside this stage,
182
+ First, because poetry tends to break part-of-speech and grammatical dependency taggers in predictable ways, a *tag-repair* pass fixes systematic errors before the dependency tree is built (this appropriately accounts for rare exotics and awkward/shifty commonplaces alike: from archaic forms like *thou/thy/doth/wherefore*, to the pronoun *I*, to perfect-tense participles like *had quit*).
183
+ Then we leverage a *tree-repair* pass (using the [depedits](https://www.npmjs.com/package/depedits) rule engine, our TypeScript port of the DepEdit library, originally in Python), which fixes systematic phrasal role attachment errors (e.g. noun compounds parsed as double objects, and the like). Hyphenated compounds and contractions (like *we'll*, *don't*, archaic *fix'd*, etc) are re-merged into single metrical words.
184
+
185
+ **2. Lexical stress.**
186
+ Every word is looked up for its pronunciation — syllable count, primary/secondary/unstressed pattern, syllable weights, vowel quantities — via our [nounsing-pro](https://www.npmjs.com/package/nounsing-pro) NLP toolkit, build over a full-scope CMU dictionary augmented with phonological and morphological data: syllable count, stress pattern (primary / secondary / unstressed), syllable weights, consonant types, morphological complexity and
187
+ vowel quantities. Words not in the dictionary go through a morphological fallback (strip a productive suffix, look up the stem, restore) coupled with a quantity-sensitive English Stress Rule. Poetic elisions are honored: *heav'n* is parsed as one syllable, so is *o'er*, *th'expense* as two, *'tis/'twas* reduces, while archaic *-'d* / *-'st* forms (*fix'd*, *stopp'st*) retain their elided syllable counts.
188
+
189
+ **3. The prosodic hierarchy.**
190
+ Words are grouped the way speech groups them, in the nested structure linguists call the prosodic hierarchy (Selkirk 1978; Hayes 1989):
191
+ Each content word attracts its function-word satellites into a **clitic group** (CP); clitic groups are joined into **phonological phrases** (PP) in accordance with syntactic inter-dependencies and primacies parsed earlier; phrases are organized into **intonational units** (IU) bounded by major punctuation or/and delineation. Crucially, the **line is the scansion domain**: a verse line containing several grammatical sentences is still parsed as a singular metrical unit (the internal full stops are treated as strong caesurae).
199
192
 
200
193
  **4. Phrase-level stress rules.**
201
- Three classic rules adjust the dictionary stresses in context: the **compound
202
- rule** (left element stressed: *CITY hall*), the **nuclear stress rule** (the
203
- last content word of an intonational unit gets the phrase's peak), and a set
204
- of **clash resolutions** (two adjacent strong syllables cannot both keep full
205
- prominence — one yields, chosen by syntactic direction). The result is mapped
206
- onto the five-tier `x w n m s` scale.
207
-
208
- **5. Key stresses, "beginnings free, endings strict."**
209
- Following McAleese's central insight, the stresses at the *right edges* of
210
- prosodic units are the reliable ones — speakers may start a phrase loosely
211
- but they land its ending. Each unit contributes its right-edge "key stress"
212
- with a weight (intonational unit > phrase > long word > clitic group), and
213
- meters that place beats on those key stresses are rewarded.
214
-
215
- **6. Meter fitting (dynamic programming).**
216
- For each of seven candidate meters — iambic, trochaic, anapestic, dactylic,
217
- **amphibrachic**, bacchic, spondaic — the engine finds the optimal division of
218
- the line into feet, using that meter's full inventory of legitimate
219
- variations: inversions at line-start or after a caesura, pyrrhic and spondaic
220
- substitutions, catalexis (truncated final foot), anacrusis (extrametrical
221
- upbeat), feminine endings, acephalous openings. Each syllable is scored
222
- against the metrical position it lands in (a strong syllable in a beat slot
223
- is ideal; a reduced clitic forced onto a beat is the cardinal violation), and
224
- an **Attridge promotion** rule lets a weak syllable carry a beat when it is
225
- flanked by even weaker ones (*"happens to BE a French poet"*) — which is how
226
- real readers actually deliver such lines.
194
+ Several well-established rules of *English stress* mediate dictionary-derived lexical stresses within the consolidated phonological/phrasal context: primarily, the **compound stress rule** (left element generally stressed, *CITY hall*, with various exceptions), the **nuclear stress rule** (the last content word of an intonational unit receives a stress boost), and a set of **clash resolutions** (two adjacent strong syllables cannot both keep full prominence — one yields, chosen by syntactic direction). The result is mapped onto the five-tier `x w n m s` scale.
195
+
196
+ **5. Key stresses.**
197
+ Following generative metrics, the stresses at the *right edges* of prosodic units are treated as more reliable — speakers may start a phrase loosely but they land its ending. Each unit contributes its right-edge "key stress" with a weight (intonational unit > phrase > long word / clitic group > short word ), and meters that place beats on those key stresses are promoted.
198
+
199
+ **6. Meter fitting.**
200
+ For each of seven candidate metrical foot types — iambic, trochaic, anapestic, dactylic, amphibrachic, bacchic, spondaic — the engine finds the optimal division of the line into feet, using that meter's inventory of most plausible variations/divergences: inversions at line-start or after a caesura, pyrrhic and spondaic foot substitutions, catalexis (truncated final foot), anacrusis (extrametrical upbeat), feminine endings, acephalous openings, and so forth. Each syllable is scored against the metrical position it lands in (a strong syllable in a beat slot
201
+ is ideal; while a reduced clitic forced onto a beat is perhaps the heaviest cardinal violation). An additional **promotion** rule (drawn from Derik Attridge) lets a weak syllable carry a beat when it is flanked by even weaker ones (*"happens to BE a French poet"*).
227
202
 
228
203
  **7. Arbitration and context.**
229
- The winning meter is not just the top raw score:
230
- - **Ternary siblings** (anapest / amphibrach / dactyl) frequently fit a line
231
- with the *identical* beat pattern then the name is decided by which
232
- division avoids splitting words across feet and aligns with the line's
233
- pauses, not by score noise.
234
- - **Stanza consensus**: each stanza's dominant meter is identified (for
235
- ternary verse, by the stanza's *anacrusis profile* — how many slack
236
- syllables open the lines, following Russian metrics). A line that fits the
237
- dominant meter nearly as well as its own standalone winner **adopts the
238
- dominant meter** as its base reading, with the standalone reading preserved
239
- as a note. Metrical continuity outranks a hair of fit score, exactly as it
240
- does for a human scander.
204
+ The winning meter is not simply the top raw score:
205
+ - **Ternary siblings** (anapest / amphibrach / dactyl meters) may sometimes fit a line via an *identical* distribution of beats — the difference being only where one might draw the foot boundaries. In such a situation, the base meter determination is decided by which foot division avoids slicing through words and best aligns with the line's pauses.
206
+ - **Stanza consensus**: each stanza's dominant meter is identified. An additional consideration is taken into account for ternary meters at this point: *anacrusis profile* how many unstressed syllables are found at the beginning of a given line, and the stanza as a whole (a sensible heuristic suggested by Russian metrics).
241
207
 
242
208
  **8. Beyond classical meters.**
243
- If a stanza's syllable counts vary while its strong-beat counts stay constant
244
- — and no classical meter dominates — the stanza is read as **accentual verse**
245
- (`4-beat accentual`), or more precisely as a **dolnik** (1–2 slack syllables
246
- between beats) or **taktovik** (1–3), the categories of Russian verse theory
247
- that also fit much English song, ballad, and modernist verse. Free verse is
248
- recognised both line-by-line and stanza-wide. Independently, a **rhyme
249
- layer** classifies every line-end pair (perfect / rich / family / slant /
250
- eye / … rhymes, masculine / feminine / dactylic), detects the rhyme scheme,
251
- and a **form layer** names what the stanzas amount to: ballad stanza (rhyme
252
- scheme *and* alternating 4·3 beats), blank verse (unrhymed iambic
253
- pentameter), couplets, quatrains, limerick, rhyme royal, Shakespearean or
254
- Petrarchan sonnet, terza rima.
209
+ If a stanza's syllable counts vary significantly while its strong-beat count stays constant, the stanza is read as **accentual verse** , rather than classically footed accentual-syllabic (aka syllabotonic), and labeled as (`n-beat accentual`).
210
+
211
+ Alongside this, a **rhyme layer** classifies every line-end pair (perfect / rich / family / slant /eye / … rhymes, in masculine / feminine / dactylic shapes), detects the poem's rhyme scheme (if discernible), as well as its apparent **poetic form** (if documented by us). For now, the range of supported forms is very limited and the mechanics of discernment leave much to be desired. As of this version, the engine may (when particularly inspired) identify a ballad stanza, blank verse (unrhymed iambic pentameter), couplets, quatrains, limericks, rhyme royal, Shakespearean or Petrarchan sonnets, and Dante-style terza rima.
255
212
 
256
213
  ---
257
214
 
@@ -353,20 +310,15 @@ interface PhonologicalScansionDetail {
353
310
  }
354
311
  ```
355
312
 
356
- Lower-level functions (`parseDocument`, `assignLexicalStress`,
357
- `buildPhonologicalHierarchy`, `scoreMeters`, …) are exported from their
358
- modules under `calliope-ts/dist/*` for users who want to run or modify
359
- individual pipeline stages. The optional Scandroid comparison engines
360
- (Charles Hartman's "Corral the Weird" and "Maximize the Normal") are exported
361
- from `calliope-ts/dist/scandroid.js`.
313
+ Lower-level functions (`parseDocument`, `assignLexicalStress`, `buildPhonologicalHierarchy`, `scoreMeters`, …) are exported from their modules under `calliope-ts/dist/*` for users who want to run or modify individual pipeline stages.
314
+
315
+ The optional Scandroid comparison engines (Charles Hartman's "Corral the Weird" and "Maximize the Normal") are exported from `calliope-ts/dist/scandroid.js`.
362
316
 
363
317
  ---
364
318
 
365
319
  ## Examples
366
320
 
367
- **A Shakespeare sonnet** (`calliope-ts --reading sonnet130.txt`) — every line
368
- identified as iambic pentameter; the scheme letters spell ABAB CDCD EFEF GG;
369
- the stanza header reads `❡ Shakespearean Sonnet`.
321
+ **A Shakespeare sonnet** (`calliope-ts --reading sonnet130.txt`) — every line identified as iambic pentameter; the scheme letters spell ABAB CDCD EFEF GG; the stanza header reads `❡ Shakespearean Sonnet`.
370
322
 
371
323
  **A ballad quatrain:**
372
324
 
@@ -377,82 +329,42 @@ And he stoppeth one of three.
377
329
  Now wherefore stopp'st thou me?
378
330
  ```
379
331
 
380
- → lines of iambic tetrameter / trimeter, scheme `·A·A`, and the form verdict
381
- `❡ ballad stanza (ABCB, 4·3)` — the rhyme scheme *and* the alternating
382
- 4-beat/3-beat design both check out.
332
+ → lines of iambic tetrameter / trimeter, scheme `·A·A`, and the form verdict `❡ ballad stanza (ABCB, 4·3)` — the rhyme scheme *and* the alternating 4-beat/3-beat design both check out.
383
333
 
384
- **Accentual verse** (Wyatt, *They flee from me*) — no classical meter
385
- dominates and syllable counts vary, but every line carries four strong beats:
386
- each line is annotated `♪ 4-beat accentual`.
334
+ **Accentual verse** (Wyatt, *They flee from me*) — no classical meter dominates and syllable counts vary, but every line carries four strong beats: each line is annotated `♪ 4-beat accentual`.
387
335
 
388
- **Amphibrachic verse** (Nabokov wrote his poem "Exile" as a demonstration
389
- of English amphibrachs — the `x S x` foot): stanza consensus reads the poem's
390
- constant one-syllable anacrusis as amphibrachic, names near-tie lines
391
- accordingly with `≈ continuity` notes, and reports the `aabccb` rhyme
392
- envelope.
336
+ **Amphibrachic verse** (Nabokov wrote his poem "Exile" as a demonstration of English amphibrachs — the `x S x` foot): stanza consensus reads the poem's constant one-syllable anacrusis as amphibrachic, names near-tie lines accordingly with `≈ continuity` notes, and reports the `aabccb` rhyme envelope.
393
337
 
394
- **Blank verse** (Frost, *Mending Wall*) — unrhymed lines, dominant iambic
395
- pentameter: `❡ blank verse`.
338
+ **Blank verse** (Frost, *Mending Wall*) — unrhymed lines, dominant iambic pentameter: `❡ blank verse`.
396
339
 
397
340
  ---
398
341
 
399
342
  ## Background and lineage
400
343
 
401
- **Calliope (2008).** The method implemented here originates with Gareth
402
- McAleese's *Calliope*, developed for his M.Sc. at the Open University
403
- (*"Improving Scansion with Syntax: an Investigation into the Effectiveness of
404
- a Syntactic Analysis of Poetry by Computer using Phonological Scansion
405
- Theory"*, Technical Report 2007/26, submitted 2008). McAleese's central idea:
406
- scansion should be computed from a line's *phonology* — the prosodic
407
- hierarchy and the stresses at the right edges of its units ("beginnings free,
408
- endings strict") — rather than from orthographic pattern-matching. Calliope TS
409
- re-implements that architecture on a modern Node.js stack and extends it.
410
-
411
- **Scandroid (1996/2005).** Charles O. Hartman's Scandroid, a classic
412
- foot-by-foot scanner (GNU GPL), is included as an optional comparison engine:
413
- its "Corral the Weird" and "Maximize the Normal" algorithms can be run side
414
- by side with the phonological scansion.
415
-
416
- **Extensions beyond McAleese.** The five-tier stress gradient with a
417
- zero-provision level; a unified dynamic-programming meter fitter with full
418
- substitution inventories (and first-class **amphibrach** support); beat
419
- promotion after Derek Attridge's beat/offbeat analysis; the dolnik/taktovik
420
- classification from Russian verse theory (Gasparov); stanza-consensus and
421
- anacrusis-profile naming; the rhyme and form layers; and the parse-correction
422
- layers for verse-specific tagging.
423
-
424
- **Data.** Lexical phonology comes from the augmented CMU dictionary published
425
- as `nounsing-pro` (syllabification, stress transcription, syllable structure,
426
- morphological stress classes). Parsing uses the FinNLP family. Dependency
427
- repairs use `depedits`, a TypeScript port of the DepEdit rule engine.
428
-
429
- Calliope TS is developed by **Aleksey Calvin** /
430
- [SilverAgePoets.com](https://www.SilverAgePoets.com), with particular care for
431
- verse traditions that standard English-centric tools marginalize: ternary
432
- meters, Russian Silver-Age forms in translation, song lyrics, and accentual
433
- verse.
344
+ The method implemented here substantially follows the example set by Gareth McAleese's *Calliope* (2007.2008), developed for his M.Sc. at the Open University (*"Improving Scansion with Syntax: an Investigation into the Effectiveness of a Syntactic Analysis of Poetry by Computer using Phonological Scansion Theory"*, Technical Report 2007/26, submitted 2008). For it, McAleese devises a computational scansion framework substantially grounded in the phonological scansion methodologies of UCLA's renowned linguist and phonologist **Bruce Hayes**, developed by him across the 1980s, 90s, and 2000s — most relevantly in *Extrametricality and English Stress* (1982), *The Phonology of Rhythm in English* (1984), *The Prosodic Hierarchy in Meter* (1989), the *Metrical Stress Theory* (1995), and *The Role of Phonological Phrasing in Sung and Chanted Verse* with Abigail Kaun (1996). Hayes in turn built on the groundwork laid by Halle & Chomsky, Liberman & Prince, and Kiparsky (some of the seminal Generative Metrics, Optimality Theory, and related domains. Most pertinent to approach taken up by McAleese (as well as ourselves presently) is Hayes's work from the early 1990s onward, in which he drew increasingly on the nascent **Optimality Theory** (OT) (much credit to Prince & Smolensky, whose *Optimality Theory: Constraint Interaction in Generative Grammar*, 1993/2002, much enriched the empirical toolkit of generative grammar-adjacent theory domains). In the 2000s Hayes would contribute to placing OT onto the more precise rails of **MaxEnt** (Maximum Entropy) methods. McAleese's work, however, preceded this turn. Backing up somewhat to peruse the field, it is worth acknowledging the remarkable breadth of synthesis McAleese draws on, which beyond those above-named, harnesses the influence of Paul Kiparsky (e.g. *The Rhythmic Structure of English Verse*, 1977), Kristin Hanson (*A Parametric Theory of Poetic Meter*, 1996), the prosodic-phrasing and stress-shift related work of Elisabeth Selkirk (1978), as well as studies by Richard Cureton (1992), Peter Groves (1998), and many others (see McAleese's paper at https://oro.open.ac.uk/90197/ for a full bibliography).
434
345
 
435
- ---
346
+ | Source | What it grounds in Calliope |
347
+ |---|---|
348
+ | Hayes & Kaun (1996) | the core method: right-edge key stresses, weighted by prosodic-unit size (stage 5) |
349
+ | Hayes (1989), *Prosodic Hierarchy in Meter* | the CP → PP → IU phrasing (stage 3) |
350
+ | Hayes (1982), *Extrametricality* | extrametrical syllables and the OOV English Stress Rule (stage 2) |
351
+ | Chomsky & Halle (1968); Liberman & Prince (1977); Hayes (1984) | the lexical and phrasal stress rules — compound, nuclear, clash (stage 4) |
352
+ | Prince & Smolensky (1993/2002); Hayes et al., MaxEnt | the constraint-based, weighted meter fitter (stages 5–7) |
353
+ | Selkirk (1978) | the phonological phrase as a prosodic constituent (stage 3) |
354
+ | Attridge, *The Rhythms of English Poetry* / *The Rhythms of the English Dolnik* | beat/offbeat promotion (stage 6) and the dolnik (stage 8) |
355
+ | Gasparov, *A History of European Versification* | the dolnik / taktovik / accentual taxonomy (stage 8) |
356
+ | McAleese (2008) | the core outline for the scansion procedure, with further solutions drawn by us from Hayes 2008 & Prosodic (MaxEnt weighing), as well as original extrapolations |
436
357
 
437
- ## Scope, strengths, and honest limitations
358
+ **Scandroid (1996/2005).** Charles O. Hartman's Scandroid, a classic foot-by-foot scanner (GNU GPL), is included as an optional comparison engine: its "Corral the Weird" and "Maximize the Normal" algorithms can be run side by side with the phonological scansion.
438
359
 
439
- **Strengths.** Line-level stress mapping is the core competence the Reading
440
- View is usable as a reading aid in itself. Ternary meters (anapest /
441
- amphibrach / dactyl) are first-class citizens, not afterthoughts. Accentual
442
- and dolnik verse get a real classification instead of a "free verse" shrug.
443
- Whole-poem context (stanza consensus, rhyme, form) informs line verdicts the
444
- way it does for human readers.
360
+ Calliope TS is developed by **Aleksey Calvin** / [SilverAgePoets.com](https://www.SilverAgePoets.com)
361
+
362
+ ---
445
363
 
446
364
  **Limitations to know about.**
447
- - English only (the dictionary and parser are English; other languages will
448
- produce nonsense rather than errors).
449
- - Stress-doublet words (*rebel*, *content*, names like *Hugo*) are read with
450
- their dictionary stress; a rhyme-driven correction is on the roadmap.
365
+ - English only (the dictionary and parser are English; other languages will produce nonsense rather than errors).
366
+ - Stress-doublet words (*rebel*, *content*, names like *Hugo*) are read with their dictionary stress; a correction is on the roadmap.
451
367
  - Rare or foreign proper names fall back to rule-based stress guesses.
452
- - The form layer recognises the common stanza forms; it does not yet attempt
453
- villanelle/pantoum repetition checking or syllabic forms.
454
- - Meter identification on single lines without context is genuinely harder
455
- than with stanza context — when possible, scan whole poems.
456
368
 
457
369
  ---
458
370
 
@@ -468,18 +380,12 @@ node trials/mcaleese_benchmark.mjs # McAleese's own trial poems + expert keys
468
380
  node trials/corpus_benchmark.mjs # litlab / prosodic / epg64 meter corpora
469
381
  ```
470
382
 
471
- The repository's `AGENTS.md` carries a dated engineering log of every
472
- substantive change with its rationale and verification.
473
-
474
383
  ---
475
384
 
476
385
  ## License and credits
477
386
 
478
387
  Apache-2.0. © Aleksey Calvin Tsukanov / SilverAgePoets.com.
388
+ My email: alekseycalvin@gmail.com
479
389
 
480
- Methodological debts: Gareth McAleese (Calliope, the phonological scansion
481
- procedure); Charles O. Hartman (Scandroid); Derek Attridge (beat/offbeat
482
- rhythm and the English dolnik); M. L. Gasparov (dolnik/taktovik taxonomy);
483
- Bruce Hayes, Paul Kiparsky, and the generative-metrics tradition (stress
484
- theory, syllable quantity, extrametricality); the CMU Pronouncing Dictionary
485
- and its `nounsing-pro` augmentation.
390
+ Methodological and conceptual debts: Bruce Hayes (1982/1984/1995/1996 with Abigail Kaun/2005) (the phonological scansion procedure as such, extrametricality insights, text-setting methodologies, MaxEnt OT, and who knows what else), Gareth McAleese (for a single 2008 paper, for detailing the original Calliope implementation, for exhibiting a remarkable field-spanning purview, an uncanny industriousness, and an uncommon – perhaps a tad obsessive – dedication to testing, refining, fusing, and extending all sorts of methodologies in a single-minded pursuit of bringing constraint-based computational scansion far beyond the best documented practices and results at that time; and for so obviously succeeding, if only to seemingly vanish from the field as abruptly and unreservedly as he entered and absorbed it*); Charles O. Hartman (Scandroid); Claire Moore Cantwell (morphological/phonological tagging algorithms), Austin Pursley (implementing finer-grained rhyme-matching heuristics over a corpus), Allison Parrish (Pronouncing-py and being a real life computational poet hero), Derek Attridge (beat/offbeat rhythm theory and insightful writings on the English dolnik); M. L. Gasparov (dolnik/taktovik taxonomy); the compilers of the CMU Pronouncing Dictionary; the makers of Prosodic (Heuser et al, for establishing an admirable state-of-the-art to compare against, differentiate from, and hopefully surpass in due time, in select ways), as well as broader generative-metrics, constraint-based metrics, and OT traditions, including Kiparsky, Prince & Smolensky, Groves, Blumenfeld, Lilja, Chomsky & Halle, Fabb & Halle (rule-based grid scansion theory), Einarsson (Metremic theory), Russom (Universalist metrics), K. M. Ryan (gradient syllable weight), big daddy Jakobson who had once roped the whole world with subtle strings and often hung out with Mayakovsky, and many others.
391
+ *Gareth McAleese: If you're reading this, please do email me!
package/dist/index.js CHANGED
@@ -495,7 +495,15 @@ async function main() {
495
495
  // No arguments: launch interactive REPL
496
496
  await replMode();
497
497
  }
498
- const isMain = process.argv[1] === fileURLToPath(import.meta.url);
498
+ let isMain = false;
499
+ if (process.argv[1]) {
500
+ try {
501
+ isMain = fs.realpathSync(process.argv[1]) === fs.realpathSync(fileURLToPath(import.meta.url));
502
+ }
503
+ catch {
504
+ isMain = false;
505
+ }
506
+ }
499
507
  if (isMain) {
500
508
  main().catch(err => {
501
509
  console.error(chalk.red('Fatal error:'), err);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "calliope-ts",
3
- "version": "0.0.1",
4
- "description": "A phonological poetry scansion framework in TypeScript, attempting to retrace and extend the works of G. McAleese (Calliope, 2008) and the makers of Scandroid (2005)",
3
+ "version": "0.0.2",
4
+ "description": "A phonological poetry scansion framework in TypeScript, attempting to retrace and extend the work of (primarily) Bruce Hayes (1996/2005), Gareth McAleese (2008), and the makers of Scandroid (2005)",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
7
7
  "bin": {
package/src/index.ts CHANGED
@@ -532,10 +532,15 @@ async function main(): Promise<void> {
532
532
  await replMode();
533
533
  }
534
534
 
535
- const isMain = process.argv[1] === fileURLToPath(import.meta.url);
535
+ let isMain = false;
536
+ if (process.argv[1]) {
537
+ try {
538
+ isMain = fs.realpathSync(process.argv[1]) === fs.realpathSync(fileURLToPath(import.meta.url));
539
+ } catch { isMain = false; }
540
+ }
536
541
  if (isMain) {
537
542
  main().catch(err => {
538
543
  console.error(chalk.red('Fatal error:'), err);
539
544
  process.exit(1);
540
545
  });
541
- }
546
+ }