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
@@ -0,0 +1,396 @@
1
+ # Heavens
2
+
3
+ `tonus.caelum` computes the classical bodies' places for a single moment, from
4
+ JPL Keplerian elements valid 3000 BC – 3000 AD. `tonus.harmonia` voices those
5
+ bodies through a planetary-harmony doctrine, assigning each visible planet a
6
+ tuned pitch and a Greek vowel. The doctrines and their derived ratios are
7
+ summarized in [Theory & Context](#theory--context).
8
+
9
+ - [Heavens](#heavens)
10
+ - [The heavens — `caelum`](#the-heavens--caelum)
11
+ - [Aspects](#aspects)
12
+ - [The signs — `ZODIACA`](#the-signs--zodiaca)
13
+ - [The voiced heavens — `harmonia`](#the-voiced-heavens--harmonia)
14
+ - [The planetary vowels](#the-planetary-vowels)
15
+ - [The tabula](#the-tabula)
16
+ - [Theory \& Context](#theory--context)
17
+
18
+ ## The heavens — `caelum`
19
+
20
+ `caelum(query?)` returns a `Cosmos` for a single moment and `Cosmos[]` for
21
+ a `from`/`to` range. With `bodies` omitted, all eight are returned, and
22
+ aspects are computed only between requested bodies. A `feast` supplies its
23
+ date; an explicit `date` takes precedence.
24
+
25
+ ```js
26
+ tonus.caelum({ date: new Date("2026-12-25") });
27
+ ```
28
+
29
+ ```js
30
+ // bodies[6], Jupiter that morning — retrograde in Leo:
31
+ { name: "Jupiter", nomen: "Iuppiter", symbol: "♃",
32
+ helio: { lon: 137.9, lat: 0.79, dist: 5.33 },
33
+ geo: { lon: 146.6, … },
34
+ speed: -0.037, retrograde: true,
35
+ magnitude: -2.13, elongation: 125.1, phase: 0.99,
36
+ zodiac: 4, sign: "Leo" }
37
+ ```
38
+
39
+ ```js
40
+ tonus.caelum(); // the default epoch (991)
41
+ tonus.caelum({ feast: feasts[0] }); // the feast's date
42
+ tonus.caelum({ bodies: ["Sun", "Moon", "Jupiter"] });
43
+ tonus.caelum({ from: jan1, to: dec31, step: 7 }); // weekly snapshots
44
+ ```
45
+
46
+ The eight bodies:
47
+
48
+ | `name` | `nomen` | | `name` | `nomen` | |
49
+ | ------- | --------- | --- | ------- | -------- | --- |
50
+ | Sun | Sol | ☉ | Earth | Terra | ♁ |
51
+ | Moon | Luna | ☾ | Mars | Mars | ♂ |
52
+ | Mercury | Mercurius | ☿ | Jupiter | Iuppiter | ♃ |
53
+ | Venus | Venus | ♀ | Saturn | Saturnus | ♄ |
54
+
55
+ ```ts
56
+ interface CosmosQuery {
57
+ date?: Date;
58
+ feast?: Feast;
59
+ from?: Date;
60
+ to?: Date;
61
+ step?: number; // days, default 1
62
+ bodies?: BodyName[];
63
+ orbLimit?: number; // max orb for aspect detection, degrees (default 8)
64
+ }
65
+
66
+ interface Cosmos {
67
+ date: Date;
68
+ bodies: Body[];
69
+ aspects: Aspect[];
70
+ }
71
+
72
+ interface Body {
73
+ name: BodyName; // from the table above
74
+ nomen: string; // Latin name
75
+ symbol: string; // Unicode symbol
76
+ helio: HelioPos;
77
+ geo: GeoPos;
78
+ speed: number; // deg/day (negative = retrograde)
79
+ retrograde: boolean;
80
+ magnitude: number;
81
+ elongation: number; // deg from Sun (geocentric)
82
+ phase: number; // 0–1 illuminated fraction
83
+ apparentDiameter: number | { equ: number; pol: number }; // arcsec
84
+ zodiac: number; // sign 0–11 (Aries=0 … Pisces=11)
85
+ sign: string; // the code: "Aries", "Taurus", …
86
+ signum: string; // the Latin: "Scorpius", "Capricornus", …
87
+ distEarthRadii?: number; // Moon only
88
+ }
89
+
90
+ interface HelioPos {
91
+ lon: number; // ecliptic longitude, deg (0–360)
92
+ lat: number; // ecliptic latitude, deg
93
+ dist: number; // distance from Sun, AU
94
+ }
95
+
96
+ interface GeoPos {
97
+ lon: number;
98
+ lat: number;
99
+ dist: number;
100
+ equatorial: {
101
+ ra: number; // right ascension, deg
102
+ dec: number; // declination, deg
103
+ dist: number;
104
+ };
105
+ }
106
+ ```
107
+
108
+ ## Aspects
109
+
110
+ Aspects are angular relationships between geocentric body
111
+ longitudes, detected within the orb limit. Strength falls linearly from 1
112
+ at the exact angle to 0 at the limit. On Christmas 2026 the sky holds
113
+ eight, led by a Mercury–Jupiter trine 1.1° from exact.
114
+
115
+ | `type` | angle |
116
+ | ------------- | ----- |
117
+ | `conjunction` | 0° |
118
+ | `sextile` | 60° |
119
+ | `square` | 90° |
120
+ | `trine` | 120° |
121
+ | `opposition` | 180° |
122
+
123
+ ```ts
124
+ interface Aspect {
125
+ type: string; // from the table above
126
+ bodies: [string, string];
127
+ angle: number; // exact separation, deg
128
+ orb: number; // degrees from exact aspect angle
129
+ strength: number; // 0–1
130
+ }
131
+ ```
132
+
133
+ ## The signs — `ZODIACA`
134
+
135
+ `caelum` answers WHERE a body is. `ZODIACA` answers what that position MEANT:
136
+ the doctrine a medieval reader brought to a planet standing in a sign.
137
+
138
+ ```js
139
+ import tonus, { ZODIACA } from "tonus";
140
+
141
+ const [sun] = tonus.caelum({ date: new Date("2026-12-25") }).bodies;
142
+ const sign = ZODIACA[sun.zodiac];
143
+ // → { sign: "Capricorn", signum: "Capricornus", symbol: "♑",
144
+ // element: "earth", elementum: "terra", humor: "melancholia",
145
+ // quality: "cardinal", domicile: "Saturn", exaltation: "Mars",
146
+ // melothesia: { latin: "genua", english: "the knees" }, … }
147
+ ```
148
+
149
+ `ZODIACA[body.zodiac]` is the join. The array is in ecliptic order and each
150
+ entry knows its own `index`, so a body's sign is addressed directly rather than
151
+ searched for.
152
+
153
+ | Field | What it holds |
154
+ | ----------------------- | -------------------------------------------------------------------- |
155
+ | `sign` / `signum` | the code and the Latin — identical to `Body.sign` / `Body.signum` |
156
+ | `symbol` | the astronomical sign, U+2648–U+2653 |
157
+ | `element` / `elementum` | one of the four, and its Latin |
158
+ | `quality` / `qualitas` | cardinal, fixed, mutable — Ptolemy's tropic/solid/bicorporeal |
159
+ | `nature` | the two Aristotelian pairs: `calidum`/`frigidum`, `siccum`/`humidum` |
160
+ | `humor` / `temperament` | the Galenic humor the element carries, and its temperament |
161
+ | `domicile` | the planet that rules the sign, as a `BodyName` |
162
+ | `exaltation` | the planet exalted in it — `null` for the five that have none |
163
+ | `melothesia` | the member of the zodiac man the sign governs |
164
+ | `variant` | present only where the sources disagree |
165
+
166
+ **There are no dates.** When the Sun enters a sign is the ephemeris's business
167
+ and moves with precession; a table carrying "March 21" would be wrong for most
168
+ of the period this library models, and wrong differently every century.
169
+
170
+ **What is omitted, and why.** The exaltation degrees Ptolemy gives (the Sun at
171
+ 19° Arietis and the rest) are not carried — the sign is the resolution anything
172
+ here reads. Nor are the lunar nodes' exaltations, because the nodes are not
173
+ tonus bodies. Five signs exalt nobody: that silence is the tradition's, not a
174
+ gap in the table.
175
+
176
+ The `melothesia` is the *homo signorum* of medieval calendars — Aries at the
177
+ head down to Pisces at the feet. It was practice, not decoration: phlebotomy
178
+ was timed against it, and while the Moon stood in a sign its member was not to
179
+ be touched. Sourced from Ptolemy's *Tetrabiblos* I.17 and I.19
180
+ [biblio: ptolemy-tetrabiblos].
181
+
182
+ ## The voiced heavens — `harmonia`
183
+
184
+ `harmonia(cosmos, opts?)` gives each body a pitch and a Greek vowel according to
185
+ a **doctrina** (a named historical scheme for who sounds what: Pythagoras,
186
+ Boethius, Pliny, Ptolemy). The result
187
+ is pure data: each visible planet becomes a `VoicedBody` with a tuned pitch and
188
+ a Greek vowel; each aspect gains the `Interval` its angle sounds, with its
189
+ consonance grade. Range input populates `frames`, one per cosmos.
190
+
191
+ ```js
192
+ const sky = tonus.caelum({ date: new Date("2026-12-25") });
193
+ const h = tonus.harmonia(sky); // Boethius, pythagorean A440
194
+
195
+ h.auctor; // "Anicius Manlius Severinus Boethius"
196
+
197
+ h.bodies.find((b) => b.name === "Jupiter");
198
+ // { …Body, nota: { pitch: { spn: "F4", hz: 347.65, … },
199
+ // performance: { velocity: 0.55, … } },
200
+ // presence: 0.55, motion: 0.15,
201
+ // greekName: "parhypate meson", vowel: { name: "Upsilon", phonetic: "u", … } }
202
+
203
+ h.aspects[0];
204
+ // trine Mercury–Jupiter → interval: Quinta, consonance: "perfect"
205
+ ```
206
+
207
+ The doctrinae:
208
+
209
+ | `doctrina` | Source | Span | Notable |
210
+ | -------------- | -------------------------- | --------- | ------------------------------------------------------------- |
211
+ | `"pythagoras"` | via Plato, _Republic_ X | 1 octave | Disjunct diatonic tetrachords (B durum); includes Fixed Stars |
212
+ | `"boethius"` | _De institutione musica_ | major 7th | Conjunct diatonic tetrachords (B molle); medieval default |
213
+ | `"pliny"` | _Naturalis historia_ II.xx | 1 octave | Chromatic Dorian (distance-based); Earth = proslambanomenos |
214
+ | `"ptolemy"` | _Harmonics_ III | 2 octaves | Fixed tones of the Greater Perfect System |
215
+
216
+ Sphere pitches are computed directly from the doctrina's pure ratios,
217
+ anchored at the temperamentum's A4, so historical coherence holds:
218
+ `temperamentum("ptolemy-intense")` with `harmonia({ doctrina: "ptolemy" })`
219
+ gives pure Ptolemaic intervals throughout — Sun→Jupiter a pure 3/2,
220
+ Sun→Saturn a pure 2/1. The temperamentum's scale governs pitch naming and
221
+ the imprint.
222
+
223
+ Each voiced body's `nota` carries its velocity as a 0–1 factor: the body's
224
+ presence (visibility and brightness), on the same scale the score engine uses.
225
+ An aspect's interval is graded by class:
226
+
227
+ | interval class | consonance |
228
+ | -------------- | ---------- |
229
+ | P1, P5, P8 | perfect |
230
+ | m3, M3, m6, M6 | imperfect |
231
+ | all others | dissonant |
232
+
233
+ Bodies without a classical vowel are not voiced: Earth and the Fixed
234
+ Stars, Pliny's Earth-as-proslambanomenos among them.
235
+
236
+ ```js
237
+ tonus.harmonia(sky, { doctrina: "ptolemy" });
238
+ tonus.harmonia(sky, { temperamentum: tonus.temperamentum("ptolemy-intense") });
239
+ const h2 = tonus.harmonia(tonus.caelum({ from, to })); // h2.frames populated
240
+ ```
241
+
242
+ ```ts
243
+ interface HarmoniaOpts {
244
+ temperamentum?: Temperamentum; // default: pythagorean A440
245
+ doctrina?: string; // from the doctrinae table; default "boethius"
246
+ }
247
+
248
+ interface Harmony {
249
+ doctrina: string;
250
+ auctor: string; // full Latin name of the doctrina's author
251
+ date: Date; // first cosmos's date
252
+ bodies: VoicedBody[];
253
+ aspects: VoicedAspect[];
254
+ frames?: Frame[]; // only when input was an array of cosmos
255
+
256
+ tabula: HarmonyTabulaRow[]; // flat iterable view
257
+ imprint: Imprint; // shared analytical fingerprint — score.md
258
+ }
259
+
260
+ interface VoicedPitch {
261
+ pitch: Pitch;
262
+ performance: Performance; // velocity 0–1, presence-scaled
263
+ }
264
+
265
+ interface VoicedBody extends Body {
266
+ nota: VoicedPitch;
267
+ presence: number; // 0–1 (visibility + brightness)
268
+ motion: number; // 0–1 (normalized speed)
269
+ greekName: string; // position in the Greek tonal system
270
+ vowel: PlanetVowel;
271
+ }
272
+
273
+ interface VoicedAspect extends Aspect {
274
+ interval: Interval; // carries the consonance grade
275
+ }
276
+
277
+ interface Frame {
278
+ date: Date;
279
+ bodies: VoicedBody[];
280
+ aspects: VoicedAspect[];
281
+ }
282
+ ```
283
+
284
+ ## The planetary vowels
285
+
286
+ Each classical planet sounds one of the seven Greek vowels; the
287
+ Moon→Saturn ordering follows Nicomachus (_Excerpta ex Nicomacho_ 6), the
288
+ attestations Godwin's _The Mystery of the Seven Vowels_ (1991).
289
+
290
+ | Body | Greek | Name | Phonetic |
291
+ | ------- | ----- | ------- | -------- |
292
+ | Moon | Α / α | Alpha | a |
293
+ | Mercury | Ε / ε | Epsilon | e |
294
+ | Venus | Η / η | Eta | e |
295
+ | Sun | Ι / ι | Iota | i |
296
+ | Mars | Ο / ο | Omicron | o |
297
+ | Jupiter | Υ / υ | Upsilon | u |
298
+ | Saturn | Ω / ω | Omega | o |
299
+
300
+ ```ts
301
+ interface PlanetVowel {
302
+ greek: string; // "Α"
303
+ greekLower: string; // "α"
304
+ name: string; // "Alpha"
305
+ modern: string; // "A"
306
+ phonetic: "a" | "e" | "i" | "o" | "u";
307
+ ipa: string;
308
+ }
309
+ ```
310
+
311
+ ## The tabula
312
+
313
+ `harmony.tabula` is the flat iteration surface: one row per voiced body
314
+ (chant scores expose the same surface per note,
315
+ [score.md](score.md#the-tabula)).
316
+
317
+ ```js
318
+ h.tabula.find((r) => r.name === "Jupiter");
319
+ // { name: "Jupiter", nomen: "Iuppiter", greekName: "parhypate meson",
320
+ // ratio: [64, 81], spn: "F4", hz: 347.7, presence: 0.55, velocity: 0.55,
321
+ // vowelGreek: "Υ", sign: "Leo", aspectCount: 3, … }
322
+ ```
323
+
324
+ `ratio` is the doctrina's own fraction against the mese, the primary datum of
325
+ the whole scheme — `spn` and `hz` are that ratio sounded against A4, not
326
+ independent claims. Comparing doctrinae means comparing these: the table
327
+ under [Theory & Context](#theory--context) is what the field returns.
328
+
329
+ ```ts
330
+ interface HarmonyTabulaRow {
331
+ bodyIndex: number;
332
+ name: BodyName;
333
+ nomen: string;
334
+ greekName: string;
335
+
336
+ ratio: readonly [number, number]; // the doctrina's fraction against the mese
337
+ midi: number;
338
+ pc: number;
339
+ oct: number;
340
+ spn: string;
341
+ hz: number;
342
+
343
+ presence: number; // 0–1
344
+ motion: number; // 0–1
345
+ velocity: number; // 0–1, presence-scaled
346
+
347
+ vowelGreek: string;
348
+ vowelPhonetic: string;
349
+ vowelName: string;
350
+
351
+ zodiac: number; // 0–11
352
+ sign: string;
353
+ retrograde: boolean;
354
+ elongation: number; // deg from Sun
355
+ magnitude: number;
356
+
357
+ aspectCount: number; // aspects this body participates in
358
+ }
359
+ ```
360
+
361
+ ## Theory & Context
362
+
363
+ The doctrina ratios are reconstructed from the primary texts through
364
+ Joscelyn Godwin's syntheses, mapping each body to a Greek tone-name and
365
+ deriving its ratio by Pythagorean interval arithmetic normalized to the
366
+ mese (Sun = 1/1). The full method, the taxonomy, and the decisions taken
367
+ along the way are documented at the data — see `DOCTRINAE` in
368
+ [`harmonia/data/doctrines.ts`](https://github.com/jeffreypierce/tonus/blob/main/src/engines/harmonia/data/doctrines.ts).
369
+ The same arithmetic is laid out from the tuning side in
370
+ [tuning.md](tuning.md#theory--context).
371
+
372
+ The resulting ratios, by sphere from the outermost:
373
+
374
+ | body | `pythagoras` | `boethius` | `pliny` | `ptolemy` |
375
+ | ----------- | ------------ | ---------- | ----------- | --------- |
376
+ | Fixed Stars | 3/2 | — | — | — |
377
+ | Saturn | 3/4 | 3/4 | 4/3 | 2/1 |
378
+ | Jupiter | 64/81 | 64/81 | 65536/59049 | 3/2 |
379
+ | Mars | 8/9 | 8/9 | 256/243 | 9/8 |
380
+ | Sun | 1/1 | 1/1 | 1/1 | 1/1 |
381
+ | Venus | 9/8 | 256/243 | 16384/19683 | 3/4 |
382
+ | Mercury | 32/27 | 32/27 | 64/81 | 9/16 |
383
+ | Moon | 4/3 | 4/3 | 3/4 | 1/2 |
384
+ | Earth | — | — | 2/3 | — |
385
+
386
+ The single pitch separating Pythagoras from Boethius is Venus: a whole
387
+ tone above the Sun in the disjunct system (9/8, B durum), a semitone in
388
+ the conjunct (256/243, B molle) — the origin of the durum/molle
389
+ distinction that runs through all of medieval music theory.
390
+
391
+ ## Sources
392
+
393
+ Sources for this page are in the central [bibliography](https://github.com/jeffreypierce/tonus/blob/main/BIBLIOGRAPHY.md):
394
+ `godwin-harmonies`, `godwin-spheres`, `godwin-vowels`, `boethius-institutione`,
395
+ `doctrina-primaries` (Nicomachus, Plato, Pliny, Ptolemy, and the vowel
396
+ attestations), `standish-jpl`, `schlyter-positions`.
@@ -0,0 +1,265 @@
1
+ # tonus API
2
+
3
+ The technical center of tonus: the full public API, the conventions every method
4
+ obeys, and the error contract. The API is **fourteen methods on the `tonus`
5
+ namespace**, no sub-namespaces.
6
+
7
+ **[Interactive demo →](https://jeffreypierce.github.io/tonus/)**
8
+
9
+ ```js
10
+ import tonus from "tonus";
11
+ ```
12
+
13
+ - [The methods](#the-methods) — by engine
14
+ - [The appendix](#the-appendix) — the canonical constant tables
15
+ - [Full contents](#full-contents) — every method and section
16
+ - [Conventions](#conventions) — Latin/English, dates, determinism, error contracts, bibliography
17
+
18
+ ## The methods
19
+
20
+ | Method | Page | Returns |
21
+ | -------------------------------- | ----------------------- | ------------------------------------------------------- |
22
+ | `tonus.festum(query?)` | [calendar](calendar.md) | `Feast[]` — liturgical calendar lookup |
23
+ | `tonus.pascha(year)` | [calendar](calendar.md) | `Pascha` — the movable anchors of a liturgical year |
24
+ | `tonus.cantus(query?)` | [chant](chant.md) | `Chant[]` — cross-corpus chant retrieval / GABC parsing |
25
+ | `tonus.corpus(code?)` | [chant](chant.md) | `Corpus` — a book's ledger; no arg → the whole shelf |
26
+ | `tonus.proprium(query?)` | [chant](chant.md) | `Chant[]` — Mass propers |
27
+ | `tonus.ordinarium(query?)` | [chant](chant.md) | `OrdinaryChant[]` — Kyriale |
28
+ | `tonus.officium(query?)` | [chant](chant.md) | `Chant[]` — Divine Office hours |
29
+ | `tonus.psalmus(query?)` | [chant](chant.md) | `Chant[]` — intoned psalm verses |
30
+ | `tonus.temperamentum(input?)` | [tuning](tuning.md) | `Temperamentum` — tuning context |
31
+ | `tonus.notatio(chant, opts?)` | [score](score.md) | `Score` — GABC → musical score |
32
+ | `tonus.inscriptio(score, opts?)` | [score](score.md) | `Inscriptio` — `{ svg, geometry }`, the score drawn |
33
+ | `tonus.caelum(query?)` | [heavens](heavens.md) | `Cosmos \| Cosmos[]` — ephemeris |
34
+ | `tonus.harmonia(cosmos, opts?)` | [heavens](heavens.md) | `Harmony` — musica universalis |
35
+ | `tonus.census(query)` | [census](census.md) | `Census` — a chant against the corpus |
36
+
37
+ ### Query and builder functions
38
+
39
+ **Query functions** (`festum`, `pascha`, `cantus`,
40
+ `corpus`, `proprium`, `ordinarium`, `officium`, `psalmus`, `caelum`) name what you want and return arrays. Empty matches
41
+ return `[]`, never `null`. Calendar results sort `day asc, dignity desc`;
42
+ chant results sort by rank then incipit.
43
+
44
+ **Builder functions** (`temperamentum`,
45
+ `notatio`, `harmonia`, and `inscriptio`) construct and return context objects, and throw
46
+ `Error` on invalid input. Only `Temperamentum` carries methods; `Score` is a
47
+ plain data record, and rendering is the standalone `inscriptio`.
48
+
49
+ `census` takes the query form but answers for exactly one chant: an id with no
50
+ block throws rather than returning `[]`.
51
+
52
+ Context objects can be passed back into query functions as filters:
53
+
54
+ ```js
55
+ const feasts = tonus.festum({ season: "pasc" });
56
+ tonus.proprium({ feast: feasts, office: "an" });
57
+
58
+ const t = tonus.temperamentum({ tuning: "pythagorean" });
59
+ t.nota("D4");
60
+ ```
61
+
62
+ ## The appendix
63
+
64
+ The canonical constant tables ship as named exports beside the namespace.
65
+ Return values are plain data, and the appendix carries tables only, never
66
+ functions. It holds the tables a caller would otherwise transcribe: the mode
67
+ list, the hour list, the valid `by:` values.
68
+
69
+ ```js
70
+ import tonus, { SEASON_LABEL, HORAE, MODES } from "tonus";
71
+ ```
72
+
73
+ Names follow the register rule: a table of Latin values takes a Latin name, a
74
+ table of codes or English keeps English.
75
+
76
+ **Calendar**
77
+
78
+ | Export | What it holds |
79
+ | -------------- | --------------------------------------------------------------- |
80
+ | `SEASON_LABEL` | season code → English display label (`adv` → "Advent") |
81
+ | `TEMPORA` | season code → the Latin tempus name (`adv` → "Tempus Adventus") |
82
+ | `GRADE_ORDER` | the fourteen grades in precedence order (sort by `indexOf`) |
83
+ | `GRADUS` | grade code → the Latin rank name |
84
+
85
+ **Chant**
86
+
87
+ | Export | What it holds |
88
+ | ----------- | -------------------------------------------------------------------------- |
89
+ | `HORAE` | the eight canonical hours, Matins first — the order is the content |
90
+ | `OFFICIA` | office code → the Latin genus (`an` → "Antiphona") |
91
+ | `ORDINARIA` | ordinary code → the Latin name (`ky` → "Kyrie eleison") |
92
+ | `MODI` | mode number → the Latin name (`"1"` → "Modus I") |
93
+ | `SOURCES` | book code → its bibliographic record; the codes `cantus({ source })` takes |
94
+
95
+ **Tuning**
96
+
97
+ | Export | What it holds |
98
+ | ---------------------- | ----------------------------------------------------------------------------------------------------- |
99
+ | `MODES` | the eight modes' doctrine: final, tenor, ambitus, and the **received** cadence figures |
100
+ | `TONES` | the psalm tones (Graduale Romanum appendix), with their differentiae |
101
+ | `CADENTIAE` | the **mined** cadence families (`CadentiaFamilia`) — shape, arrival, share, finality, per-mode counts |
102
+ | `CADENTIAE_POPULATION` | the denominator behind every `share`: all phrase-ends, and the same total per mode |
103
+
104
+ The two cadence tables answer different questions. `MODES.cadences` is what the
105
+ treatises say a mode closes on (final cadences only); `CADENTIAE` is what the
106
+ corpus was measured doing (any target, so it is the one of the two that
107
+ accounts for medial closes). See [one spine, two
108
+ annotations](score.md#one-spine-two-annotations), and
109
+ [lift](tuning.md#lift--how-mode-bound-a-close-is) for what `CADENTIAE_POPULATION`
110
+ is for.
111
+
112
+ **Heavens**
113
+
114
+ | Export | What it holds |
115
+ | ------- | ---------------------------------------------------- |
116
+ | `SIGNS` | the twelve zodiac signs, English, ecliptic order |
117
+ | `SIGNA` | the same twelve in Latin (`Scorpius`, `Capricornus`) |
118
+
119
+ **Census**
120
+
121
+ | Export | What it holds |
122
+ | --------------- | ------------------------------------------------------------------------------------------------------ |
123
+ | `CENSUS_GROUPS` | the field groups → `{ offset, count }`; the keys are the valid `by:` values **and** the `profile` keys |
124
+ | `CENSUS_ORDER` | every censused chant id, in block order — so membership is a lookup, not a `try/catch` |
125
+
126
+ Use these to pool blocks without reproducing the distance rule — see [the census
127
+ contract](census.md#distance-is-cosine-per-field-group).
128
+
129
+ ## Full contents
130
+
131
+ Every method and every section, page by page, in dependency order. Pages later in
132
+ the list resolve their pitches through the ones before.
133
+
134
+ **[Tuning](tuning.md)** — the medieval pitch system; depends on nothing else.
135
+
136
+ - [The context — `temperamentum`](tuning.md#the-context--temperamentum)
137
+ - [Pitch input](tuning.md#pitch-input)
138
+ - [Pitches — `nota`](tuning.md#pitches--nota)
139
+ - [Steps — `gradus`](tuning.md#steps--gradus)
140
+ - [Intervals — `intervallum`](tuning.md#intervals--intervallum)
141
+ - [Neumes — `neuma`](tuning.md#neumes--neuma)
142
+ - [Ratios — `ratio`](tuning.md#ratios--ratio)
143
+ - [The gamut — `gamut`](tuning.md#the-gamut--gamut)
144
+ - [Modes — `modus`](tuning.md#modes--modus) · [Cadence figures](tuning.md#cadence-figures) · [The corpus catalogue](tuning.md#the-corpus-catalogue--cadentiae)
145
+ - [Psalm tones — `tonus`](tuning.md#psalm-tones--tonus)
146
+ - [Theory & Context](tuning.md#theory--context) · [Sources](tuning.md#sources)
147
+
148
+ **[Calendar](calendar.md)** — the Tridentine calendar against Julian/Gregorian-computus Easter.
149
+
150
+ - [The day's feasts — `festum`](calendar.md#the-days-feasts--festum)
151
+ - [Rank — `ritus` and `grade`](calendar.md#rank--ritus-and-grade)
152
+ - [Seasons — the temporale](calendar.md#seasons--the-temporale)
153
+ - [The year's anchors — `pascha`](calendar.md#the-years-anchors--pascha)
154
+ - [Theory & Context](calendar.md#theory--context) · [Sources](calendar.md#sources)
155
+
156
+ **[Chant](chant.md)** — the Solesmes books in GABC.
157
+
158
+ - [The corpora](chant.md#the-corpora)
159
+ - [The books — `corpus`](chant.md#the-books--corpus)
160
+ - [Retrieval — `cantus`](chant.md#retrieval--cantus) · [Reaching the ordinary](chant.md#reaching-the-ordinary--ordinary)
161
+ - [The repertoire as of a date — the era view](chant.md#the-repertoire-as-of-a-date--the-era-view)
162
+ - [The Mass propers — `proprium`](chant.md#the-mass-propers--proprium)
163
+ - [The ordinary — `ordinarium`](chant.md#the-ordinary--ordinarium)
164
+ - [The Office — `officium`](chant.md#the-office--officium)
165
+ - [Psalms — `psalmus`](chant.md#psalms--psalmus)
166
+ - [Theory & Context](chant.md#theory--context) · [Sources](chant.md#sources)
167
+
168
+ **[Score](score.md)** — GABC parsed into tuned, rhythmicized, drawable notes.
169
+
170
+ - [The score — `notatio`](score.md#the-score--notatio)
171
+ - [Interpretation — `pondus` and `accentus`](score.md#interpretation--pondus-and-accentus)
172
+ - [The note](score.md#the-note)
173
+ - [The tabula](score.md#the-tabula)
174
+ - [Rendering — `inscriptio`](score.md#rendering) · [theme](score.md#theme--faces-and-ink) · [The analysis tracks](score.md#the-analysis-tracks)
175
+ - [The imprint](score.md#the-imprint)
176
+ - [Prosody](score.md#prosody)
177
+ - [Cadences](score.md#cadences) · [One spine, two annotations](score.md#one-spine-two-annotations)
178
+ - [Modulations](score.md#modulations)
179
+ - [Theory & Context](score.md#theory--context) · [Sources](score.md#sources)
180
+
181
+ **[Heavens](heavens.md)** — an ephemeris voiced through the planetary doctrines.
182
+
183
+ - [The heavens — `caelum`](heavens.md#the-heavens--caelum)
184
+ - [Aspects](heavens.md#aspects)
185
+ - [The voiced heavens — `harmonia`](heavens.md#the-voiced-heavens--harmonia)
186
+ - [The planetary vowels](heavens.md#the-planetary-vowels)
187
+ - [The tabula](heavens.md#the-tabula)
188
+ - [Theory & Context](heavens.md#theory--context) · [Sources](heavens.md#sources)
189
+
190
+ **[Census](census.md)** — one chant measured against the corpus that holds it.
191
+
192
+ - [The method](census.md#the-method)
193
+ - [What a block holds](census.md#what-a-block-holds)
194
+ - [How the measurement works](census.md#how-the-measurement-works)
195
+ - [Distance is cosine per field group](census.md#distance-is-cosine-per-field-group)
196
+ - [Profile and typicality](census.md#profile-and-typicality)
197
+ - [Balance — distance and deviance](census.md#balance--distance-and-deviance)
198
+ - [Neighbors, and `by`](census.md#neighbors-and-by)
199
+ - [The era view](census.md#the-era-view)
200
+ - [What the census is not](census.md#what-the-census-is-not)
201
+
202
+ ## Conventions
203
+
204
+ ### Latin and English
205
+
206
+ The language of a key tells you the register of its value. A Latin key
207
+ returns authentic Latin content; an English key returns a machine code or
208
+ datum. Where a concept has both registers, they form a pair:
209
+
210
+ | English | Latin |
211
+ | -------- | -------- |
212
+ | `season` | `tempus` |
213
+ | `grade` | `ritus` |
214
+ | `mode` | `modus` |
215
+ | `name` | `nomen` |
216
+
217
+ Other fields carry only one register. Latin-only, _e.g._ `genus`, `ordinarium`,
218
+ `incipit`, `differentia`, `accentus`. English-only, _e.g._ `date`, `velocity`, `hz`.
219
+
220
+ Display strings live in exported maps (_e.g._ `SEASON_LABEL`),
221
+ never as label fields on objects — the maps are [the appendix](#the-appendix).
222
+
223
+ ### Dates
224
+
225
+ Dates are UTC-canonical. Local-time constructions like
226
+ `new Date(2026, 0, 6)` resolve to different days depending on the
227
+ machine's timezone. Prefer `new Date("2026-01-06")` instead.
228
+
229
+ `tonus.festum()` and `tonus.caelum()` default to **1 June 991**, the
230
+ symbolic birthday of Guido d'Arezzo. Pass an explicit `date` for any other
231
+ day.
232
+
233
+ ### Determinism
234
+
235
+ All pure transforms are deterministic for identical inputs and options.
236
+ No runtime network requests are made. Where variation is wanted (the scatter of
237
+ an ensemble) it is seeded, so the same seed yields byte-identical output.
238
+
239
+ ### Error contract
240
+
241
+ - Query functions return `[]` on no match, never throw — but an **empty or
242
+ unknown-key query** throws (a mistyped filter is a bug, not an empty result):
243
+ `festum({ month: 12 })` and `cantus({})` throw rather than silently resolving a
244
+ plausible-looking answer.
245
+ - Builder functions throw `Error` with a descriptive message on invalid input.
246
+ - `notatio` throws on invalid `Chant` input.
247
+ - `inscriptio` throws on a non-`Score` argument or an unknown notation species.
248
+ - `temperamentum.tonus()` throws if `mode` is `"auto"` — mode must be set
249
+ explicitly.
250
+ - Malformed `comma`, ratio, or Scala input throws `RangeError`; custom scales
251
+ must supply 7 or 12 steps, beginning at `1/1` (a degree list) or ending at
252
+ `2/1` (Scala convention).
253
+ - `pascha` throws `RangeError` on a non-finite year.
254
+ - `caelum` in range mode (`{ from, to, step }`) throws `RangeError` on a missing
255
+ bound, `to` before `from`, a non-positive step, or a range exceeding the frame cap.
256
+ - `harmonia` throws `RangeError` on an unknown doctrina.
257
+ - `census` throws on an id with no block (the census covers only the chants
258
+ tonus ships) and on an unknown query key or field group.
259
+
260
+ ### The bibliography — [`BIBLIOGRAPHY.md`](https://github.com/jeffreypierce/tonus/blob/main/BIBLIOGRAPHY.md)
261
+
262
+ The single source of truth for citations, each with a stable key. Code
263
+ cites by bracketed key (`[biblio: key]`); each page keeps a short `## Sources`
264
+ line pointing to the keys it draws on. Nothing outside `BIBLIOGRAPHY.md` restates
265
+ a full reference.