tonus 0.10.0 → 0.10.1

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/CHANGELOG.md CHANGED
@@ -2,6 +2,31 @@
2
2
 
3
3
  All notable changes to tonus. Newest first.
4
4
 
5
+ ## 0.10.1 — 2026-08-31
6
+
7
+ A capital ligature is a vowel like any other.
8
+
9
+ ### Fixed
10
+
11
+ - **`Æ` and `Œ` reached the nucleus unexpanded.** `selectVowel` expanded the
12
+ ligatures case-sensitively, and neither has an NFD decomposition (unlike
13
+ `á` → `a`), so a capital survived the scan and was reported AS the vowel:
14
+ `vowel: "æ"`, a two-letter answer to a one-letter promise. Consumers that
15
+ hold tonus to that contract throw on it. 136 chants carry one, every case
16
+ opening a word — 94 at the start of the lyric, the other 42 inside an
17
+ all-capital opening word (`SÆpe`, `PRÆ`, `HÆC`), which is exactly where a
18
+ capital lives. The gated shelf held almost none of them, so 0.10.0's re-cut
19
+ is what surfaced it; the defect is older.
20
+ - **An accented ligature accented the off-glide.** `œ́` is `œ` + combining
21
+ acute, with no precomposed form, so expanding it to `oe` + accent marked the
22
+ `e`: `selectVowel("œ́")` returned nucleus `"e"` where it should return `"o"`.
23
+ The pair is now rebuilt as `o` + accent + `e`. `ǽ`, which IS precomposed,
24
+ was always correct — the two agree now.
25
+
26
+ Note for consumers baking off `vowel`: 136 chants change nucleus, and `Æ`/`Œ`
27
+ now report `diphthong: "ae"`/`"oe"` where they reported `null`. Anything
28
+ derived from the vowel plane wants a re-bake.
29
+
5
30
  ## 0.10.0 — 2026-08-31
6
31
 
7
32
  The corpus stores a melody once instead of once per book, the genus labels say
@@ -18,7 +18,8 @@
18
18
  // cl, gl, fl) and the Greek digraphs th, ph, ch stay with the following
19
19
  // vowel; all other clusters split after the first consonant
20
20
  //
21
- // Diacritics (áéíóúàèìòùâêîôû etc.) are treated as their base vowel throughout.
21
+ // Diacritics (áéíóúàèìòùâêîôû etc.) are treated as their base vowel throughout,
22
+ // and the ligatures æ/œ expand to their pairs in either case (see selectVowel).
22
23
  // ── Character classes ──
23
24
  const VOWELS = new Set("aeiouyáéíóúàèìòùâêîôûäëïöüæœý");
24
25
  const SOFT_HYPHEN = "\u00ad";
@@ -203,7 +204,25 @@ export function hyphenateWord(word) {
203
204
  }
204
205
  const ACCENTED = /[\u0301]/; // combining acute accent (NFD form)
205
206
  export function selectVowel(text) {
206
- const expanded = text.replace(/ǽ/g, "áe").replace(/æ/g, "ae").replace(/œ/g, "oe");
207
+ // THE LIGATURES EXPAND BEFORE THE SCAN, IN EITHER CASE. æ and œ have no
208
+ // NFD decomposition (unlike á → a), so a ligature that survives to the loop
209
+ // below is reported AS the nucleus — a two-letter vowel, which breaks the
210
+ // one-letter contract `vowel` promises and throws downstream. This was
211
+ // case-sensitive until 2026-08-31 and so missed every capital: 135 chants,
212
+ // all of them word-initial (`Ægýptum`, the Nocturnale's Æ responsories),
213
+ // invisible while the shelf was gated because it carried almost none.
214
+ //
215
+ // The accent rides the NUCLEUS, not the glide. ǽ is precomposed, but œ́ is
216
+ // not — it is œ + combining acute — so the pair is rebuilt as o + accent + e
217
+ // rather than oe + accent, which would accent the off-glide and invert what
218
+ // `accent` reports. NFC first, so a decomposed source still matches here.
219
+ const expanded = text
220
+ .normalize("NFC")
221
+ .replace(/ǽ/gi, "áe")
222
+ .replace(/([æœ])(́?)/gi, (_m, lig, acc) => {
223
+ const pair = lig.toLowerCase() === "æ" ? "ae" : "oe";
224
+ return acc ? pair[0] + acc + pair[1] : pair;
225
+ });
207
226
  const nfd = expanded.normalize("NFD");
208
227
  let firstVowel = "";
209
228
  let accentedVowel = "";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tonus",
3
- "version": "0.10.0",
3
+ "version": "0.10.1",
4
4
  "type": "module",
5
5
  "description": "Medieval music analysis: GABC plainchant exports, liturgical calendar, tuning systems, ephemeris, and the harmony of the spheres",
6
6
  "author": "Jeffrey Pierce <jeffrey@jeffreypierce.net>",