tonus 0.1.8 → 0.6.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.
Files changed (166) hide show
  1. package/BIBLIOGRAPHY.md +138 -108
  2. package/CHANGELOG.md +664 -1
  3. package/LICENSE +133 -29
  4. package/README.md +107 -80
  5. package/dist/data/am.js +2666 -11196
  6. package/dist/data/ams.d.ts +5 -0
  7. package/dist/data/ams.js +122 -0
  8. package/dist/data/attestation.d.ts +19 -0
  9. package/dist/data/attestation.js +15716 -0
  10. package/dist/data/attestation.json +15711 -0
  11. package/dist/data/cadentiae.d.ts +43 -0
  12. package/dist/data/cadentiae.js +174 -0
  13. package/dist/data/cal.js +48 -0
  14. package/dist/data/census.d.ts +12 -0
  15. package/dist/data/census.js +36 -0
  16. package/dist/data/commune-office.d.ts +4 -0
  17. package/dist/data/commune-office.js +2371 -0
  18. package/dist/data/commune-office.json +2365 -0
  19. package/dist/data/commune.js +181 -11
  20. package/dist/data/corpus-overlap.d.ts +17 -0
  21. package/dist/data/corpus-overlap.js +299 -5
  22. package/dist/data/cot.d.ts +5 -0
  23. package/dist/data/cot.js +172 -0
  24. package/dist/data/cse.d.ts +5 -0
  25. package/dist/data/cse.js +122 -0
  26. package/dist/data/gabc-glyphs.d.ts +45 -0
  27. package/dist/data/gabc-glyphs.js +122 -0
  28. package/dist/data/gr.js +754 -6394
  29. package/dist/data/kyriale.js +116 -116
  30. package/dist/data/la.js +799 -13419
  31. package/dist/data/lh.js +113 -3473
  32. package/dist/data/lu.js +931 -17631
  33. package/dist/data/nocturnale-romanum.js +1659 -10411
  34. package/dist/data/office-ferial.d.ts +4 -0
  35. package/dist/data/office-ferial.js +396 -0
  36. package/dist/data/office-ferial.json +391 -0
  37. package/dist/data/office-monastic.d.ts +17 -1
  38. package/dist/data/office-monastic.js +1403 -466
  39. package/dist/data/office-psalms-monastic.d.ts +13 -1
  40. package/dist/data/office-psalms-monastic.js +9 -0
  41. package/dist/data/propers.js +1 -1
  42. package/dist/data/psalms.js +22919 -5
  43. package/dist/data/psm.d.ts +5 -0
  44. package/dist/data/psm.js +122 -0
  45. package/dist/data/seasonal-respbreve.d.ts +5 -0
  46. package/dist/data/seasonal-respbreve.js +41 -0
  47. package/dist/data/seasonal-respbreve.json +35 -0
  48. package/dist/data/smufl-glyphs.d.ts +17 -0
  49. package/dist/data/smufl-glyphs.js +1546 -0
  50. package/dist/data/smufl-glyphs.json +1530 -0
  51. package/dist/engines/cal/calendar.d.ts +3 -2
  52. package/dist/engines/cal/calendar.js +105 -29
  53. package/dist/engines/cal/data/eras.d.ts +35 -0
  54. package/dist/engines/cal/data/eras.js +128 -0
  55. package/dist/engines/cal/date.js +44 -0
  56. package/dist/engines/cal/types.d.ts +15 -3
  57. package/dist/engines/cal/types.js +5 -5
  58. package/dist/engines/census/census.d.ts +7 -0
  59. package/dist/engines/census/census.js +179 -0
  60. package/dist/engines/census/types.d.ts +55 -0
  61. package/dist/engines/census/types.js +8 -0
  62. package/dist/engines/chant/attest.d.ts +39 -0
  63. package/dist/engines/chant/attest.js +90 -0
  64. package/dist/engines/chant/chant.d.ts +16 -4
  65. package/dist/engines/chant/chant.js +220 -30
  66. package/dist/engines/chant/data/compline.js +2 -1
  67. package/dist/engines/chant/data/masses.d.ts +56 -4
  68. package/dist/engines/chant/data/masses.js +305 -80
  69. package/dist/engines/chant/data/prime.js +1 -1
  70. package/dist/engines/chant/hour.js +279 -58
  71. package/dist/engines/chant/ordinary.d.ts +2 -0
  72. package/dist/engines/chant/ordinary.js +336 -56
  73. package/dist/engines/chant/propers.js +55 -5
  74. package/dist/engines/chant/psalm.d.ts +4 -4
  75. package/dist/engines/chant/psalm.js +25 -11
  76. package/dist/engines/chant/syllabify.d.ts +1 -0
  77. package/dist/engines/chant/syllabify.js +90 -17
  78. package/dist/engines/chant/types.d.ts +115 -12
  79. package/dist/engines/chant/types.js +38 -3
  80. package/dist/engines/harmonia/api.js +4 -0
  81. package/dist/engines/harmonia/data/doctrines.js +3 -1
  82. package/dist/engines/harmonia/data/zodiac.d.ts +47 -0
  83. package/dist/engines/harmonia/data/zodiac.js +163 -0
  84. package/dist/engines/harmonia/tabula.d.ts +3 -0
  85. package/dist/engines/harmonia/tabula.js +1 -0
  86. package/dist/engines/harmonia/voice.d.ts +4 -0
  87. package/dist/engines/harmonia/voice.js +8 -4
  88. package/dist/engines/imprint.js +14 -1
  89. package/dist/engines/planet/orbital.js +4 -4
  90. package/dist/engines/planet/planet.d.ts +10 -0
  91. package/dist/engines/planet/planet.js +30 -3
  92. package/dist/engines/planet/position.js +13 -10
  93. package/dist/engines/planet/types.d.ts +1 -0
  94. package/dist/engines/score/api.d.ts +2 -13
  95. package/dist/engines/score/api.js +21 -8
  96. package/dist/engines/score/articulation.js +2 -2
  97. package/dist/engines/score/cadence.d.ts +76 -0
  98. package/dist/engines/score/cadence.js +96 -0
  99. package/dist/engines/score/emitters/accidentals.d.ts +21 -0
  100. package/dist/engines/score/emitters/accidentals.js +88 -0
  101. package/dist/engines/score/emitters/atramentum.d.ts +107 -0
  102. package/dist/engines/score/emitters/atramentum.js +239 -0
  103. package/dist/engines/score/emitters/breaking.d.ts +62 -0
  104. package/dist/engines/score/emitters/breaking.js +80 -0
  105. package/dist/engines/score/emitters/moderna.d.ts +38 -0
  106. package/dist/engines/score/emitters/moderna.js +612 -0
  107. package/dist/engines/score/emitters/svg.d.ts +143 -0
  108. package/dist/engines/score/emitters/svg.js +1335 -0
  109. package/dist/engines/score/emitters/tracks.d.ts +104 -0
  110. package/dist/engines/score/emitters/tracks.js +728 -0
  111. package/dist/engines/score/infer.d.ts +3 -3
  112. package/dist/engines/score/infer.js +2 -2
  113. package/dist/engines/score/inscriptio.d.ts +69 -0
  114. package/dist/engines/score/inscriptio.js +138 -0
  115. package/dist/engines/score/ir.d.ts +2 -2
  116. package/dist/engines/score/ir.js +59 -12
  117. package/dist/engines/score/lyric.d.ts +23 -0
  118. package/dist/engines/score/lyric.js +234 -0
  119. package/dist/engines/score/meta.d.ts +2 -2
  120. package/dist/engines/score/modulation.d.ts +12 -0
  121. package/dist/engines/score/modulation.js +49 -0
  122. package/dist/engines/score/neume.js +35 -4
  123. package/dist/engines/score/parse.js +150 -9
  124. package/dist/engines/score/phrasing.js +4 -3
  125. package/dist/engines/score/prosody.d.ts +38 -0
  126. package/dist/engines/score/prosody.js +70 -6
  127. package/dist/engines/score/tabula.d.ts +37 -5
  128. package/dist/engines/score/tabula.js +18 -0
  129. package/dist/engines/score/types.d.ts +88 -1
  130. package/dist/engines/temper/api.d.ts +4 -1
  131. package/dist/engines/temper/api.js +28 -5
  132. package/dist/engines/temper/data/guido.js +6 -2
  133. package/dist/engines/temper/data/modes.d.ts +6 -0
  134. package/dist/engines/temper/data/modes.js +42 -0
  135. package/dist/engines/temper/data/tones.d.ts +1 -1
  136. package/dist/engines/temper/data/tones.js +20 -11
  137. package/dist/engines/temper/interval.js +4 -3
  138. package/dist/engines/temper/modality.d.ts +11 -2
  139. package/dist/engines/temper/modality.js +74 -2
  140. package/dist/engines/temper/modes.d.ts +1 -1
  141. package/dist/engines/temper/pitch.d.ts +1 -1
  142. package/dist/engines/temper/pitch.js +12 -2
  143. package/dist/engines/temper/scale.d.ts +53 -0
  144. package/dist/engines/temper/scale.js +107 -8
  145. package/dist/index.d.ts +26 -8
  146. package/dist/index.js +40 -4
  147. package/docs/api/calendar.md +279 -0
  148. package/docs/api/census.md +288 -0
  149. package/docs/api/chant.md +657 -0
  150. package/docs/api/heavens.md +396 -0
  151. package/docs/api/index.md +265 -0
  152. package/docs/api/score.md +906 -0
  153. package/docs/api/tuning.md +619 -0
  154. package/package.json +13 -5
  155. package/dist/data/office-matins-roman.d.ts +0 -19
  156. package/dist/data/office-matins-roman.js +0 -4383
  157. package/dist/data/office-psalms-roman.d.ts +0 -15
  158. package/dist/data/office-psalms-roman.js +0 -28
  159. package/dist/data/office-roman.d.ts +0 -19
  160. package/dist/data/office-roman.js +0 -13792
  161. package/dist/engines/chant/matutinum.d.ts +0 -33
  162. package/dist/engines/chant/matutinum.js +0 -81
  163. package/dist/engines/score/emitters/midi.d.ts +0 -65
  164. package/dist/engines/score/emitters/midi.js +0 -162
  165. package/dist/engines/score/emitters/musicxml.d.ts +0 -18
  166. package/dist/engines/score/emitters/musicxml.js +0 -166
package/CHANGELOG.md CHANGED
@@ -2,6 +2,669 @@
2
2
 
3
3
  All notable changes to tonus. Newest first.
4
4
 
5
+ ## Unreleased
6
+
7
+ ## 0.6.0 — 2026-08-16
8
+
9
+ The zodiac learns what it means, and the hand stops sitting on its own floor.
10
+
11
+ ### Added
12
+
13
+ - **`ZODIACA` — the doctrine of the twelve signs.** `caelum` answers WHERE a
14
+ body is; this answers what a medieval reader brought to that position: the
15
+ element and its Galenic humor, the quality, the ruling and exalted planets,
16
+ and the member of the zodiac man the sign governs. `ZODIACA[body.zodiac]` is
17
+ the join, so a sky and its meaning are one lookup apart.
18
+
19
+ Deliberately no dates. When the Sun enters a sign is the ephemeris's business
20
+ and moves with precession — a table carrying "March 21" would be wrong for
21
+ most of the period this library models, and wrong differently every century.
22
+
23
+ The exaltation degrees are omitted (the sign is the resolution anything here
24
+ reads) and so are the lunar nodes, which are not tonus bodies. Five signs
25
+ exalt nobody, and that silence is the tradition's.
26
+
27
+ ### Changed — breaking
28
+
29
+ - **`SIGNS` and `SIGNA` are no longer exported.** `ZODIACA` carries each sign's
30
+ own `sign` and `signum` beside its doctrine, so it answers both what a sign
31
+ is called and what it means. The bare name arrays go back to being internal
32
+ to the engine that computes a longitude, which is the only place they were a
33
+ lookup rather than a listing.
34
+
35
+ ```js
36
+ // before
37
+ import tonus, { SIGNS, SIGNA } from "tonus";
38
+ SIGNS[body.zodiac]; // "Capricorn"
39
+ SIGNA[body.zodiac]; // "Capricornus"
40
+
41
+ // after
42
+ import tonus, { ZODIACA } from "tonus";
43
+ ZODIACA[body.zodiac].sign; // "Capricorn"
44
+ ZODIACA[body.zodiac].signum; // "Capricornus"
45
+ ```
46
+
47
+ ### Fixed
48
+
49
+ - **The Manus line reads the note that was clicked.** The subheader found its
50
+ row by pitch, and `find(r => r.spn === spn)` returns the FIRST note of the
51
+ chant at that pitch — so clicking the second E of a porrectus flexus
52
+ described an E in a torculus eleven notes earlier, with that note's neume,
53
+ joint and Guidonian name.
54
+
55
+ ### Site
56
+
57
+ - **The chant is lifted onto the hand automatically, by whole octaves.** Almost
58
+ nothing fits the gamut as printed: measured over 400 chants, 32 sit inside
59
+ their mode's gamut and 346 sit below it, so a piece read straight lands on
60
+ the bottom joint and stays there. The lift is computed from the chant's own
61
+ lowest note, which takes notes landing on a joint from 68.8% to 99.9%. The
62
+ `octava` button was the manual version of this and is gone.
63
+ - **The three sung hours become one Officium segment**, with all eight hours in
64
+ its popover — Matins and Vespers on, the little hours and Compline there to
65
+ be asked for.
66
+ - **The address bar keeps `cantus` and nothing else.** Eleven parameters were
67
+ mirrored there, each written the moment it moved off its default, which made
68
+ the bar a running transcript of a session.
69
+ - All three analysis tracks open together; the Similes subheader reports the
70
+ company a melody keeps rather than describing the list; the temperament
71
+ slider steps a tenth of a cent, the precision its readout prints.
72
+
73
+ ## 0.5.0 — 2026-08-12
74
+
75
+ Rendering, mostly — a day of looking at real chant on a real page and fixing
76
+ what the page showed.
77
+
78
+ ### Added
79
+
80
+ - **A third analysis track: `"prosodia"`** — how the melody treats the word.
81
+ One tent per word (the hairpin's top edge), its apex over the accented
82
+ syllable, the landing at the peak in the liturgical red — filled for arsic
83
+ (struck), an open ring for thetic (deferred). Below it a fence on a rail:
84
+ a stem per spoken syllable, a flat dash for a syllable recited on the tenor,
85
+ and a block per melisma — as wide as its real extent, as tall as its note
86
+ count, connected melismas joining into one ridge. It rides first in the
87
+ stack, directly under the lyric line it reads, and the site's vestigia strip
88
+ and tracks key carry it. Shaped across eight lab rounds
89
+ (`working/review/diagram-word-track-01…08.html`).
90
+ - **The rubrica reservation, amended.** Ruled 2026-07-29 as the mode line's
91
+ alone, the liturgical red now belongs to the claims: the tonarium's mode
92
+ line and the prosodia's accent dots. The precedent is the score's own text
93
+ apparatus — the dropcap and annotations were always rubricated, and in the
94
+ books red is the word's colour.
95
+
96
+ ### Changed — breaking
97
+
98
+ - **The layout options are `width` and `scale`.** `padding`, `noteScale`,
99
+ `systemGap`, and `custos` are gone. Nothing ever set them — not the docs site,
100
+ not the 28 lab plates, not the 13 stress pieces — so they were surface without
101
+ use. They are now constants chosen to look right at every scale, and the
102
+ custos appears whenever a system wraps, which is what a chant book does.
103
+
104
+ `scale` replaces `staffHeight`: `"small"`, `"normal"` (default), `"large"`, or
105
+ a staff height in px for fitting a known column. A caller decides how big the
106
+ chant should be, not how tall its staff is in pixels.
107
+
108
+ ```js
109
+ inscriptio(score, { width: 900, scale: "large" })
110
+ ```
111
+
112
+ The page margin deliberately does NOT scale with it — a margin belongs to the
113
+ page, not the notation, and scaling it gave a large chant *less* usable width
114
+ than a small one (89% of a 900px canvas against 93%). The air between systems
115
+ does scale, since flat 24px held the system pitch at 135px whether the staff
116
+ was 30 or 56.
117
+
118
+ - **`inscriptio`'s look options are one `theme` object.** `fonts`, `noteColor`,
119
+ `staffLineColor`, and `rubricaColor` are replaced by
120
+ `theme: { fonts, colors }`. These travel
121
+ together — a caller setting a lyric face is usually setting a whole look — and
122
+ a house style is worth naming once and passing everywhere.
123
+
124
+ ```js
125
+ // before
126
+ inscriptio(score, { fonts: { lyric: "Junicode" }, rubricaColor: "#801", staffHeight: 48 })
127
+ // after
128
+ inscriptio(score, { scale: 48, theme: {
129
+ fonts: { lyric: "Junicode" }, colors: { rubrica: "#801" },
130
+ } })
131
+ ```
132
+
133
+ `fonts` keeps its four roles unchanged, `dropcap` among them: a book's
134
+ initial is very often not its lyric face, and the two stay separate.
135
+
136
+ ### Added
137
+
138
+ - **A note knows the diphthong it belongs to.** `Context.diphthong` and
139
+ `ChantTabulaRow.diphthong` report the pair a sung vowel is part of — `ae` ·
140
+ `oe` · `au`, and `ui` in the cui/hui stems — or null. `vowel` is unchanged: it
141
+ stays the NUCLEUS, the single vowel a singer sustains, because that is what
142
+ the analysis passes key on. The pair rides beside it for anyone rendering the
143
+ off-glide.
144
+
145
+ It has to come from here, because **only the syllabifier can tell a diphthong
146
+ from a hiatus: the accent decides.** `cae` is one syllable — nucleus `a`,
147
+ gliding toward `e`; `sa-é` is two, the same letters with the accent on the
148
+ second and no glide at all. Downstream, given only the lyric, the two are
149
+ indistinguishable. A scan for the letter pair also misreads `quae`, where `qu`
150
+ is a consonantal glide: the nucleus is `u`, and the `ae` is not the sung pair.
151
+ Over the Graduale that is 71 distinct syllables — 1,526 sung notes — a naive
152
+ reading gets wrong, and 192 syllables over the whole corpus. The detection
153
+ reuses the syllabifier's own `DIPHTHONGS` set, so the word means the same
154
+ thing here as it does when a word is split.
155
+
156
+ - **The ink is themable from CSS.** Colours now reach the SVG as custom
157
+ properties with the render's own value as the fallback —
158
+ `fill="var(--tonus-note, #111)"` — so a host stylesheet can retheme a drawn
159
+ chant without re-rendering it, while a file opened on its own still shows the
160
+ ink it was drawn with. Three properties: `--tonus-note`, `--tonus-staff-line`,
161
+ `--tonus-rubrica`. The emitter already carried semantic classes (`note`,
162
+ `lyric`, `dropcap`, `custos`, `episema`, `divisio`, `clef`, `mora`, `ictus`)
163
+ but an inline `fill` beats any stylesheet rule, so none of them could be
164
+ styled. `scale` deliberately stays outside the theme for the same reason it
165
+ cannot be a CSS property: line breaking consumes it long before a stylesheet
166
+ sees the output.
167
+
168
+ ### Fixed
169
+
170
+ - **An accidental was emitted as a sounding note.** GABC's `fx` means "F is flat
171
+ from here" — a mark drawn on the staff, not a pitch to sing. The parser set the
172
+ state correctly and then fell through and pushed a note anyway, so `A(fxfg)`
173
+ returned three notes for two and duplicated the pitch. 1337 markers across 426
174
+ Graduale chants: 0.97% of every note in the book was a phantom, at a wrong
175
+ pitch, inventing a unison before each one and inflating every count, interval
176
+ and analysis downstream.
177
+
178
+ The SIGN still draws, and now on the note that follows it — where the books
179
+ print it. That needed a new field: `accidentalSign` is what to draw, separate
180
+ from `accidental`, which is the note's own alteration. In `fe(jx)cit(ih)` the
181
+ flat is printed before the I while it governs J; the emitter had been reading
182
+ the alteration and so drew nothing once the phantom was gone.
183
+
184
+ - **A written flat could come back spelled as a sharp.** Pitch spelling was
185
+ derived from the pitch class alone, against a preferred-flat set that omitted
186
+ pc 1 and pc 6 — so a GABC `x` (a flat) landing on those degrees returned
187
+ `C#`/`F#` rather than `Db`/`Gb`, and reported `accidental: +1` for a source
188
+ that wrote a flat. The pitch was always right; only the spelling and its sign
189
+ were wrong. `toPitch` now takes an optional spelling preference and `notatio`
190
+ passes what the source wrote. The Graduale writes only the bmolle (1821 B-flats
191
+ and no sharps at all), so the corpus never reached the gap.
192
+
193
+ - **Moderna honoured no note colour at all.** It hardcoded `#111` in seventeen
194
+ places while quadrata threaded the option, so a caller theming the ink saw one
195
+ species change and the other not.
196
+
197
+ - **`staffHeight` means the same thing in both species.** Moderna's staff was
198
+ a hardcoded constant, so the option moved quadrata and did nothing here: a
199
+ request of 30, 40 or 60 left moderna at 7.4px every time. Every geometric
200
+ constant now derives from it through one metrics factory.
201
+ - **The canvas is the width you asked for.** It was `max(systemMaxX)` —
202
+ whatever the widest system happened to reach — so a requested 900 came back
203
+ 915, 986, 1074, 1203 by chant, and a host applying `max-width` shrank each
204
+ differently. That is why the same page showed one chant's notation a third
205
+ smaller than the next's.
206
+ - **Lyrics count toward a system's extent.** A syllable is centred on its
207
+ note, so half of a final wide syllable always hung past the canvas and was
208
+ cut off. The layout already tracked the lyric's right edge for its collision
209
+ check; the width calculation never asked.
210
+ - **The custos is a custos.** It was drawn as a shrunken punctum on the
211
+ authority of a comment saying no custos glyph was baked — Bravura's have
212
+ been baked all along. The real glyph is a hooked note whose stem points
213
+ toward the pitch it announces. It also floated 41px past the last note,
214
+ having been placed after the divisio's trailing air; and it is now
215
+ suppressed after a full stop, where drawing it put two marks in one place
216
+ and read as a heavy double barline.
217
+ - **Analysis tracks scale with the staff in moderna**, and a track that SPANS
218
+ notes now reaches their ink rather than their anchors — the emitters had
219
+ measured the ink all along and the mapping dropped it.
220
+ - **Capitals are measured as capitals.** The lyric width estimate was a flat
221
+ per-character average, and chant sets its opening word in capitals; "CAn"
222
+ was estimated at 22.9px against a real 27.9, so opening syllables collided
223
+ with what followed.
224
+
225
+ ### Changed
226
+
227
+ - **More air.** Syllable and word spacing widened in both species, the
228
+ staff-to-lyric gap from 21px to 28, and moderna's note advance retuned so a
229
+ chant takes about one system more than quadrata rather than twice as many.
230
+
231
+ ## 0.4.5 — 2026-08-04
232
+
233
+ The rubric true-up, the era view, and a long pass of making the library say
234
+ what it means. The 2026-07-27 review found the Kyriale selection chain leaking
235
+ past its own gates; the fixes landed with regression tests, the corpus pipeline
236
+ was corrected and regenerated behind them, and the calendar's and the corpus's
237
+ `before` arguments were reconciled into one composable view. The shelf widened,
238
+ the office settled on one cursus, the cadence catalogue was re-mined over the
239
+ sung corpus, and a new verb — `census` — measures every chant against the
240
+ corpus that holds it.
241
+
242
+ Then the tidying, most of it prompted by things that turned out to be measured
243
+ rather than assumed. The salicus took Cardine's correction and, in the process,
244
+ stopped being confused with the Solesmes ictus — two nearly disjoint sets under
245
+ one name. The cadence catalogue learned its own denominator and joined itself
246
+ once instead of per consumer. The appendix roughly doubled and was renamed to
247
+ follow the register rule. The corpus ledger stopped needing three numbers to
248
+ say how much chant there is. And the Apel formula matcher came out: what
249
+ shipped could not use the catalogue it shipped with, and saying so was better
250
+ than leaving a public field that answered `null`.
251
+
252
+ `docs/` is a published site now, which is where 0.5 is headed.
253
+
254
+ ### Removed
255
+
256
+ - **BREAKING — `Score.formulas` and the Apel centonization matcher are out.**
257
+ The catalogue of standard phrases, the per-phrase matcher, `FormulaMatch`,
258
+ `Formula`, `FormulaSlot`, and `formulaeFor` are gone from the library. What
259
+ shipped was one mode's alphabet (mode-5 Graduals) behind a matcher whose
260
+ per-phrase window structurally could not fire on 40 of its 53 formulae —
261
+ Apel tabulates a chant as a sequence across a whole respond, and matching at
262
+ that scale is a different algorithm that was never built. The honest state
263
+ was a public field that answered `null` for nearly every chant, and an
264
+ appendix of research the library could not yet use.
265
+ The census keeps its `formulas` field group: those four floats are baked
266
+ upstream in tonus-corpus and describe the corpus as measured, whatever tonus
267
+ does with them. The transcriptions, photographs, tooling and verification
268
+ reports are kept in the workshop, not deleted — the reading of Apel stands on
269
+ its own, and the pipeline is there if the matcher is ever built properly.
270
+
271
+ ### Changed
272
+
273
+ - **BREAKING — the shelf reports ONE number, and the Kyriale leaves it.**
274
+ `corpus()` reported `count` (book listings, a chant in two books counted
275
+ twice) beside `distinct` (chants), which made the headline figure depend on
276
+ how many books happened to print the same melody — a reader had to know that
277
+ to read either. Now `count` is **how many chants tonus holds**, each counted
278
+ once, and `listings` is the shelf's length; `listings - count` is the
279
+ overlap. `genera` and `modes` sum to `count`, describing the same population
280
+ the headline does.
281
+ The Kyriale is no longer an eleventh book. There is no Kyriale in GregoBase:
282
+ `ky` and `gr` are the same source, partitioned by the extractor so the
283
+ ordinary can be routed to per-ordinary codes, so listing it beside its parent
284
+ counted the Graduale twice and its `total: null` read as "unmeasured" when
285
+ there was no separate book to measure. `ky` is not a `ChantSource` and not a
286
+ row in the shelf — which is 10 books, 2,187 chants, 2,767 listings.
287
+ Nothing became unreachable. `ordinarium({ feast })` is untouched, and the
288
+ ordinary is now queryable directly: **`cantus({ ordinary: "ky" })`** returns
289
+ all 31 Kyries, composes with the other filters, and the Kyriale's
290
+ bibliographic record still rides every one of its chants. A plain search does
291
+ not sweep the ordinary in — you ask for a Kyrie rather than stumbling onto
292
+ one.
293
+ - **BEHAVIOUR — the salicus takes Cardine's correction, and stops being
294
+ confused with the Solesmes ictus.** Two changes, each ruled and each
295
+ measured separately over the sung corpus (2,887 chants, 28,498 phrases).
296
+ The second — redefining what counts as a salicus at all — was ratified on
297
+ its own terms, since it decides what the word denotes everywhere in tonus,
298
+ not just how one note is weighted.
299
+ **Detection.** A salicus is "at least three ascending notes in which the
300
+ next-to-last is an oriscus" [biblio: cardine-semiology, ch. 16]. tonus had
301
+ been calling any ascending run with the editorial ictus a salicus — 2,795
302
+ groups, of which **36 (1.3%) actually carried an oriscus** — while missing
303
+ 188 of the corpus's 226 real salici, which classified as `scandicus`. Two
304
+ nearly disjoint sets under one name. Now: `salicus` 2,795 → **226**, exactly
305
+ the chants that meet Cardine's definition; `scandicus` 498 → 2,501, and the
306
+ 4-and-5-note ictus ascents (566) join `compound`. An ictus-marked ascent
307
+ with no oriscus is a scandicus that Solesmes marked for rhythm, and the mark
308
+ is still readable on `context.ictus`.
309
+ **Weighting.** The printed editions lengthen the oriscus; the manuscripts
310
+ show the principal note is the one immediately following it. The 1.3×
311
+ prolongation moves from the next-to-last note to the **summit** — which is
312
+ the last note at any length, since the oriscus is next-to-last by
313
+ definition, so the 4- and 5-note forms need no separate rule. The salicus
314
+ stays arsic: tension toward the summit is the arsic gesture, so Cardine
315
+ strengthens that rule rather than contradicting it.
316
+ **Downstream:** 208 of 28,498 phrases (0.73%) change `rhythmicType`,
317
+ overwhelmingly toward VIII. This is the one point where tonus's rhythmic
318
+ layer departs from Mocquereau and Suñol, and `ir.ts` now says so at its
319
+ header.
320
+ **The limit, stated plainly:** tonus sees only what the transcription marks.
321
+ Bevenot's own example — the mode-6 _Requiem_ introit's fa-sol-la — carries
322
+ the ictus and no oriscus in GregoBase, so it still reads as a scandicus. He
323
+ is reading the manuscripts; the corpus is a printed edition that resolved
324
+ the oriscus away. Recovering those wants the sources, not a looser rule.
325
+ - **The tonarium's cadence label says how typical a close is, not which
326
+ family it belongs to.** The bracket read `"2,0,-2 @0"` — the family's name,
327
+ which a reader could not weigh. It now reads `"×2.1"`: the family's share
328
+ within this chant's mode over its share of the corpus at large. Measured
329
+ across four books the figure spans ×0.46 to ×10.56, median ×2.22; a lift
330
+ below 1.0 prints too, since an atypical close is information. A mode-less
331
+ chant, or a family with fewer than ten occurrences in the chant's mode,
332
+ falls back to the plain corpus share. The key is not lost — each cadence
333
+ now draws inside a group carrying `data-cadentia`, the join back to
334
+ `CADENTIAE` and the provenance a margin gloss can print.
335
+ - **BREAKING — five appendix tables renamed to match the register rule.**
336
+ `TEMPUS_NAME` → `TEMPORA`, `GRADE_NAME` → `GRADUS`, and the internal
337
+ `OFFICE_LABELS` / `ORDINARY_LABELS` / `MODE_LABELS` → `OFFICIA` /
338
+ `ORDINARIA` / `MODI`, the last three now public. The house rule is that
339
+ Latin names carry Latin content and English names carry codes or English;
340
+ the appendix had three spellings of "code → display string" and five tables
341
+ whose Latin values sat under English names. Names now follow the register of
342
+ their values, so `SEASON_LABEL` ("Advent") and `TEMPORA` ("Tempus
343
+ Adventus") are distinguishable by name rather than by memory. Only the two
344
+ `cal` tables were public before; the rest is new surface.
345
+ - **`officium` throws on an unrecognised `hora`.** It returned `[]`, which
346
+ read as "no chants at this hour" rather than "there is no such hour" — the
347
+ same silent-nothing the unknown-query-key guard beside it already refused.
348
+
349
+ ### Added
350
+
351
+ - **Every shelved book reports what it HOLDS.** The Nocturnale was the last
352
+ one answering `full: null`, which read as "not yet measured" — but the tally
353
+ had existed all along in its own extract. It holds **1,564** chants against
354
+ the 470 tonus ships, and it shares nothing: `unique` is all of them. That is
355
+ a measurement, not a gap. The crosswalk pairing a nocturnale chant with a
356
+ GregoBase twin is enrichment, a route to metadata, not a claim that two books
357
+ print the same chant, so counting those as shared would invent a
358
+ relationship. The shelf now holds 10,156 chants before the cut.
359
+ - **Three genera that are reported but not shipped now have names.**
360
+ `Improperia` (the Good Friday Reproaches), `Antiphona Mariana` (Marian
361
+ antiphons outside the office cycle) and `Supplicatio` (litanies) appear in a
362
+ book's pre-cut `full` tally, where they had been printing as bare codes —
363
+ `genus: "su"` — while every other row carried a Latin genus. Being outside
364
+ the cut is not a reason to be nameless in a table tonus publishes.
365
+ - **The cadence catalogue carries its own denominator, and joins itself.**
366
+ Every `CadentiaFamilia` gains `share` — its occurrences over ALL 28,481
367
+ phrase-ends, not over the 58.7% that cleared the table's floor, which would
368
+ have flattered every family in it. `CADENTIAE_POPULATION` ships beside it
369
+ with the same total per mode digit, so a family's **lift** in a mode is one
370
+ division: `(modes[m] / byMode[m]) / share`. The ratio itself is not baked —
371
+ export the vocabulary, not the arithmetic.
372
+ A `Cadence` now carries `finality`, the share of ITS family's corpus
373
+ occurrences that land at a final close, joined once in `notatio` instead of
374
+ by every caller rebuilding the index. It rides the cadence while `familia`
375
+ still does not, because the two differ in kind: the signature already IS the
376
+ family's name, but how often that family closes cannot be read off it — of
377
+ the 55 families landing on the final, 31 do not close, and their finality
378
+ spans 0.054 to 1.000. Detection stays pure; the corpus table meets detected
379
+ data in the score builder, where `MODES` already does.
380
+ Both cadence catalogues are now documented as what they are — _tradita_
381
+ (the treatises' figures, final cadences only) and _inventa_ (the corpus
382
+ tally, any target, and so the only account of medial closes) — with the
383
+ measured coverage of each and a worked lift example that was run before it
384
+ was printed.
385
+ - **The appendix widened, so callers stop transcribing the library's own
386
+ vocabulary.** `HORAE` (the eight canonical hours, Matins first — the order
387
+ is the content, and `officium`'s validation reads the same list, so the two
388
+ cannot drift), `OFFICIA`, `ORDINARIA`, `MODI`, `SOURCES` (the book codes
389
+ `cantus({ source })` takes, with their bibliographic records), and
390
+ `CENSUS_GROUPS` / `CENSUS_ORDER` — the census field groups and the block
391
+ index, so asking whether a chant is censused stops needing a `try/catch`.
392
+ A table is admitted when a caller would otherwise type it out, because a
393
+ transcribed copy drifts and fails as wrong answers rather than as an error.
394
+ The appendix is now grouped by engine.
395
+ - **The census distance rule is documented as a contract.** Similarity is
396
+ cosine per field group, never over the flat 225; `by: "all"` is the
397
+ equal-weight mean. Grouping is userland, so a caller pooling blocks is
398
+ computing a distance and must reproduce the rule or silently disagree with
399
+ `census()`. `census.md` now states it for callers with the three ways to get
400
+ a plausible wrong answer, and a worked example — pooling the 178 Communions
401
+ — that was run before it was printed, and reproduces `census()` exactly.
402
+ - **The analysis tracks ship with `inscriptio`.** `tracks: ["chironomia"]`
403
+ draws the conducting hand's wave — arsic crests, thetic
404
+ troughs, pick-up loops, Pierik letters. `tracks: ["tonarium"]`
405
+ draws the melodic lane: the four maneriae rails, the melody
406
+ compressed to its ambitus, the mode line in rubrica (solid inflection,
407
+ dashed transposition). A cadence is the melody's own ending re-inked
408
+ at full strength — the same curve at the same width — landing on a
409
+ terminal node (filled when the family's measured finality in CADENTIAE
410
+ closes, open when it suspends), labelled beneath by its signature on a
411
+ light end-ticked bracket spanning the figure. One governing ink system
412
+ runs through both tracks: one black graded by stratum opacity (rubrica
413
+ is the mode line's alone), and one nib law — velocity as stroke
414
+ width — for every pressure-bearing line. Either track rides either
415
+ species, and both may ride one score: they stack in a fixed order, the
416
+ chironomia above the tonarium, whichever order they are asked for. The
417
+ two-register principle — the rhythmic band under the square notation,
418
+ the melodic band under the transcription — is the house default, not a
419
+ rule the renderer enforces. Confidence is
420
+ opacity; claims under 0.45 draw nothing. The tracks consume the same
421
+ note anchors the geometry contract exports — the score's own ink is
422
+ untouched, and the geometry is unchanged with tracks on.
423
+ - **The duae species share one lyric setting and one opening.**
424
+ Quadrata's staff→lyric gap now equals moderna's (21px at the default
425
+ staff height, scaling with it), and both species default the lyric
426
+ weight to 518; an explicit `fonts.lyric.weight` still overrides.
427
+ Moderna now honours the front matter (`title`, `rubric`,
428
+ `annotation: "auto"`) as quadrata does; the official opening of a
429
+ tonus score is the incipit as `title` plus the auto genus/mode mark,
430
+ with no dropcap.
431
+ - **The era view composes end to end.** `festum({ date, before })` stamps
432
+ the view year on the returned `Feast` (`feast.before`), and every day
433
+ verb — `proprium`, `ordinarium`, `officium` — serves the
434
+ same view without being told the year twice; an own `before`
435
+ overrides. Previously the day verbs' types promised `before`/`century`/
436
+ `cursus` (they extend `CantusQuery`) while the implementations diverged
437
+ three ways: `proprium` threw "unknown query key," `officium` and
438
+ `ordinarium` silently ignored them.
439
+ - One admissibility rule for every door: `engines/chant/attest.ts`, a leaf
440
+ module shared by `cantus`, the day verbs, and the census. One spelling
441
+ at the door — `before`, a year.
442
+ - Under a view, `ordinarium` **re-picks** over the admissible pool (the
443
+ Kyriale offers ranked alternatives by design); `proprium` and `officium`
444
+ degrade to silence — the corpus cut's evidence law.
445
+ - **The shelf widens to eleven books.** Four more marked Solesmes office
446
+ books join — Antiphonale Monasticum Solesmense (`ams`), Psalterium
447
+ Monasticum (`psm`), Cantus selecti (`cse`), Chants of the Church
448
+ (`cot`) — admitted by the stated rule: Solesmes and rhythmically marked.
449
+ The shipped corpus is 2,887 rows over 2,187 distinct chants.
450
+ - **`tonus.census({ id })`** — one chant measured against the corpus that
451
+ holds it: a per-group profile with typicality, `balance` (distance and
452
+ deviant groups), and nearest neighbours by per-group cosine (`k`, `by`,
453
+ `before`). One block of 225 float32s per shipped chant; see
454
+ `docs/census.md`.
455
+ - **`corpus()` with no argument returns the whole shelf** — the rollup
456
+ plus every book's ledger — and every book carries `full`, the pre-cut
457
+ tally: the ledger of the cut, auditable rather than asserted.
458
+ - **`CADENTIAE` joins the appendix** (with `CadentiaFamilia`): the
459
+ corpus-grounded cadence catalogue, mined from every phrase-end in the
460
+ sung corpus — the same population the census counts — keyed by shape
461
+ and arrival.
462
+ - Test suites: `tests/ordinarium.test.mjs` (the selection chain end to
463
+ end) and `tests/era-view.test.mjs` (the view's whole contract).
464
+
465
+ ### Removed
466
+
467
+ - **The `rite` option.** tonus assembles one cursus, the Benedictine: the
468
+ Roman office table carried no chant at all on 63.4% of its days, and its
469
+ little-hours psalmody had no other consumer, so `rite: "romanum"`
470
+ returned a cursus nobody sang. A stale `rite` key now throws
471
+ (`officium` rejects unknown keys) rather than being silently ignored.
472
+ - **`matutinum` as a separate verb.** Matins is an hour of `officium`
473
+ (`hora: "matutinum"`), returned flat like every other hour; the
474
+ three-nocturn ordo shape remains unmodelled.
475
+ - **`century`.** It was `before: N * 100` in different clothes; the two
476
+ spellings converged on the one that is a year.
477
+
478
+ ### Fixed
479
+
480
+ - **"Requiescant in pace" was the dismissal on every green feria** (~20
481
+ days/year): `isRequiem` missed the bare "Requiescant" incipit AND the
482
+ last-resort appendix return was ungated. The same hole handed ferias an
483
+ ad libitum Gloria. The appendix now never reaches a day whose rubric
484
+ does not admit it; the Requiem stays reachable via
485
+ `ordinarium({ mass: 102 })`.
486
+ - **The Gloria follows the day's rank rubric, not its season**: ferias
487
+ print none; a I-class feast inside Advent (Immaculate Conception) keeps
488
+ hers. The "Benedicamus Domino as in Mass II" borrow the book directs is
489
+ now real — ferias and penitential Sundays sing Mass II's dismissal.
490
+ - **The paschal rubric is a time, not a rank**: removed from the solemn
491
+ set (an Eastertide Tuesday no longer leads with an appendix Kyrie;
492
+ Easter keeps Lux et origo every year), and BVM outranks paschal in
493
+ `rubricForDay`, so Cum jubilo is reachable in Eastertide.
494
+ - **Credo V was never sung**: the off-year rotation's parity was coupled
495
+ to the two-year bias (2 divides 6). The off-years now advance their own
496
+ cycle — all six credos are heard across twelve years.
497
+ - The leftover masses-1–9 preference on high feasts (precedence measuring
498
+ what the rubric should) — deleted; it defeated the appendix solemnity
499
+ boost on the class feasts it was built for and pinned high BVM feasts to
500
+ mass IX.
501
+ - `centuryOf` off-by-one at exact century multiples: `before: 1100` now
502
+ admits the closed 11th century, as its own doc always claimed.
503
+ - "Kyrie XVII C" refiled from a synthetic mass 100 to mass 17 (corpus-side
504
+ incipit parsing read the variant letter C as roman 100) — it now rotates
505
+ as Mass XVII's sibling printing.
506
+
507
+ ### Data
508
+
509
+ - Regenerated from the corrected pipeline: `office-roman` (canticle
510
+ antiphons split positionally — the last SURVIVOR of matching is no
511
+ longer promoted to the Benedictus/Magnificat slot), `office-monastic`
512
+ (464 days; rubric-year variant files excluded from the merge; stub
513
+ redirects followed to their actual targets), `office-ferial` (honestly
514
+ monastic rows only; the tier-then-book tiebreak; Matins 94 → 98 of 128),
515
+ `commune-office` (rubric-variant sections no longer blended; the Introit
516
+ genre-cap hole closed), `kyriale`.
517
+
518
+ ## 0.3.0
519
+
520
+ The vox-ectomy. The voice engine (`vox`, `chorus`, the formant tables,
521
+ personae, consortia, liquescentia coda tables, spectrum, accordatio) leaves
522
+ the public library for orreliquum, the private instrument it was always
523
+ being built toward. tonus returns to what it is: theory, calendar, chant,
524
+ notation, and the heavens — the algorithms, publishable and citable.
525
+
526
+ ### Added
527
+
528
+ - **`accent` on `ChantTabulaRow`** — a boolean marking whether a note's
529
+ syllable bears the Latin tonic word-accent. The value already existed inside
530
+ the parse (`detectVowelAccent` drives articulation stress); it is now surfaced
531
+ as data on the tabula, so downstream analysis can read accent placement
532
+ without re-deriving it. Threaded `ParsedNote → Context → ChantTabulaRow`.
533
+
534
+ ### Removed
535
+
536
+ - **`tonus.vox()` and `tonus.chorus()`** and every voice type
537
+ (`Vox`, `VoxInput`, `VoxParams`, `Vowel`, `Formant`, `Locus`, `Latinitas`,
538
+ `Persona`, `PersonaName`, `Coda`, `TuningLike`, `Chorus`, `ChorusOpts`,
539
+ `Dispersio`, `ConsortiumName`, `Voces`). The namespace holds fourteen
540
+ verbs again. `docs/voice.md` emigrates with the code.
541
+ - Voice test suites (`voice-vox`, `voice-chorus`, `voice-features`,
542
+ `voice-spectrum`) move with their subject.
543
+
544
+ ### Unchanged
545
+
546
+ - `harmonia`'s planetary voices (`VoicedPitch`, `VoicedBody`, the Greek
547
+ vowel tables) are its own and remain.
548
+ - Every other engine, table, and contract.
549
+
550
+ ## 0.2.0
551
+
552
+ Rendering becomes a standalone engine (`inscriptio`), the per-chant analysis
553
+ chart is completed, a voice engine is added, and the emitter surface settles on
554
+ one format (SVG).
555
+
556
+ ### Added
557
+
558
+ - **`inscriptio(score, opts?)`** — the standalone SVG renderer. Draws a `Score`
559
+ and returns `{ svg, geometry }`. Two notation species, each with its own
560
+ spacing pass: `"quadrata"` (square-note, SMuFL glyphs baked inline) and
561
+ `"moderna"` (modern round-note transcription — treble-8 clef, engraved slurs,
562
+ after the Lomer practice). A multi-system **layout engine** (`width`,
563
+ `systemGap`, `custos`) and **front matter** (`title`, `rubric` /
564
+ `annotation: "auto"`, `dropcap`, `rubricaColor`).
565
+ - **The geometry contract** — `geometry: NoteGeometry[]`, one entry per note in
566
+ tabula order (system, x, y, systemY). A public API: downstream analysis tracks
567
+ build on it instead of scraping the SVG.
568
+ - **The intonation channel** — `accidentals: "standard" | "heji" | "cents"`, a
569
+ **moderna** overlay. HEJI comma accidentals are baselined on the Pythagorean
570
+ chain (tonus's default tuning), so a Pythagorean chant renders clean and
571
+ syntonic-comma arrows bloom only under a just preset; meantone is not just, so
572
+ `heji` throws under it. `cents` labels signed deviations against `"pythagorean"`
573
+ (default) or `"et"`. Square notation (`quadrata`) carries only the accidentals
574
+ GABC itself expresses (flat / natural / sharp) — the HEJI and cents overlays are
575
+ modern analytical marks, so quadrata throws when asked for them.
576
+ - **Score metrics** on `prosody`: interval statistics (histogram, maxLeap,
577
+ leapRate, step/skip/leap `motus`), `tessitura`, the melodic `arcus`
578
+ (initial/peak/final + arch index), `melismaCadential`; and conveniences
579
+ `phrase.noteCount` / `phrase.syllableCount` / `syllable.melisma`.
580
+ - **`vox` and `chorus`** — a singing voice modelled as formant and spectrum
581
+ data, and seeded ensembles. `formantes` tunes to a temperament directly:
582
+ `tenor.formantes("a", temper, vis?)` — `vis` weights the pull, 0 (phonetic
583
+ truth) to 1 (fully tuned, the default).
584
+ - **The appendix.** The export law is settled: verbs live on the namespace,
585
+ return values are plain data, and the named exports are canonical constant
586
+ tables only — `SEASON_LABEL`, `TEMPUS_NAME`, `GRADE_ORDER`, `GRADE_NAME`,
587
+ `MODES`, `TONES` (types `PsalmTone`/`Differentia` ride with their table).
588
+ The grade helper functions (`gradeOrder`, `compareGrade`, `ritusToGrade`)
589
+ retire from the surface: `gradeOrder(g)` is `GRADE_ORDER.indexOf(g)`.
590
+ - **`docs/` ships in the package** — the documentation renders from the
591
+ installed tarball, pinned to the version it describes.
592
+ - **GABC lyric markup decodes.** The angle-bracket text tags — excluded since
593
+ the MIDI-only days — are parsed at intake: `<sp>` specials become real
594
+ characters (℣ ℟ † ǽ œ, the raised *), style tags (`<i>`, `<b>`, `<sc>`,
595
+ `<c>` rubric color, `<e>` elision) survive as styled `runs` on `Syllable`
596
+ and the tabula row, and both notation species draw them as `<tspan>`s.
597
+ Layout hints (`<clear>`, `<nlba>`, centering braces) and `\pageref`
598
+ cross-references vanish. Before this, quadrata printed `<sp>V/</sp>` as
599
+ literal lyric text and syllable widths were measured tags-and-all.
600
+ - **Per-role text faces** — `inscriptio`'s `fonts` option assigns a face to
601
+ the `dropcap`, `title`, `annotation`, and `lyric` roles (family, optional
602
+ weight, optional size scale). By default the SVG carries references and
603
+ the host page supplies the face; a slot may instead `embed` the caller's
604
+ own font bytes (base64) into the SVG's `<style>` for a self-contained
605
+ file. tonus bundles no font files either way.
606
+
607
+ ### Changed
608
+
609
+ - **License: PolyForm Noncommercial 1.0.0** (was MIT through 0.1.x, which
610
+ remain MIT). Free for any noncommercial purpose; commercial use by
611
+ arrangement with the author.
612
+ - **Corpus double-escape fixed.** Every `gabc` field stored its non-ASCII as a
613
+ literal `\uXXXX` escape, which had silently disabled accent detection across
614
+ the whole corpus — so note weights, prosody, rhythm, and imprint were computed
615
+ accent-blind. The extractor now decodes correctly; **computed accent weights
616
+ shift corpus-wide** as a result. A guard test asserts no gabc carries an escape.
617
+ - **`cantus({})` throws.** An empty or unknown-key chant query is a caller bug,
618
+ not an empty result; it throws with guidance (matching the `festum` contract).
619
+ - NABC pipes stripped from the corpus (`(notes|nabc)` → `(notes)`), with a
620
+ corpus-wide guard test.
621
+
622
+ ### Fixed (pre-release review)
623
+
624
+ - **The Pythagorean chain is the medieval dodecachord (E♭–G♯).** The chain had
625
+ stacked twelve fifths ascending from C, spelling F as E♯ — a 521.5¢ wolf
626
+ ut–fa — and b molle as A♯, so `temperamentum` disagreed with `harmonia`'s own
627
+ pure F. The naturals now sit F–B with b molle and E♭ on the flat side, the
628
+ ficta sharps on the sharp side; the heji/cents baseline derives from the same
629
+ chain constant, so a flatted chant under the default tuning renders clean
630
+ (it had thrown).
631
+ - **Quadrata figure grouping is phrase-aware.** A single-syllable phrase
632
+ followed by another phrase merged figures across the divisio, silently
633
+ dropping the second lyric and the divisio itself. An accidental inflecting
634
+ any note of a ligature now prints before the whole figure (Solesmes
635
+ practice) instead of vanishing on non-initial notes.
636
+ - **Canticles by name resolve their own texts.** `magnificat` and
637
+ `nunc dimittis` pointed at the wrong psalter rows (one returned the Symbolum
638
+ Athanasium); `te deum` is withdrawn — the Te Deum is not psalmody.
639
+ - **Formula step-skeletons count Apel's degrees.** Octave-aware relative to the
640
+ final's register: the mode-5 tenor reads +4, where the old ±half-octave fold
641
+ read −3. (Cadence figures keep their deliberate local-contour fold.)
642
+ - **The register doctrine at the seams.** `Matins.nomen` / `Matins.ritus`
643
+ (were `name` / `rank`), `PsalmTone.nomen`, and `rubricaColor` (was `rubrica`,
644
+ a Latin key that took a CSS color string).
645
+ - **The error contract enforced across the surface.** `notatio`, `festum`,
646
+ `caelum`, `matutinum`, `harmonia`, `proprium`, `temperamentum`, and `vox`
647
+ throw with guidance on junk input instead of raw TypeErrors or silent
648
+ fallbacks; a bare `chorus()` sings (the schola is the default); `psalmus`
649
+ no longer stamps the wall-clock year into `source.year` (determinism);
650
+ `iter` honours the `latinitas` shift; a user-GABC `office-part` header
651
+ normalizes to its OfficeCode.
652
+
653
+ ### Removed
654
+
655
+ - **The MusicXML and MIDI emitters** (`score.musicxml()`, `score.midi()`).
656
+ tonus emits one format now: SVG. Microtuning still lives on the tabula
657
+ (`bend`/`hz`/`offset`) for a Web-Audio player to read directly — microtonally
658
+ exact, which MIDI never was.
659
+
660
+ ## 0.1.8
661
+
662
+ - **Roman Matins.** `matutinum({ feast })` assembles the structured Roman night
663
+ office — the nocturns with their great responsories — from the community
664
+ *Nocturnale Romanum* (new `nr` chant source, 1,564 chants). Sanctorale feasts
665
+ draw Matins from their commune; coverage is the sanctorale and Advent today
666
+ (see COVERAGE.md). A separate accessor; the flat `officium` path is unchanged.
667
+
5
668
  ## 0.1.7
6
669
 
7
670
  - **`corpus(code)`** — metadata and analytics for a corpus book. Returns its
@@ -44,7 +707,7 @@ All notable changes to tonus. Newest first.
44
707
 
45
708
  - **Modulation detection** (`score.modulations`) — where the tonal centre leans
46
709
  away from the home mode, calibrated against Suñol.
47
- - **Richer modal affinity** — degree-, ictus-, and cadence-weighted, with ranked
710
+ - **Modal affinity** — degree-, ictus-, and cadence-weighted, with ranked
48
711
  initials after Rockstro.
49
712
  - **`modus()`** tunes its finalis, tenor, and ambitus through the temperamentum.
50
713