tonus 0.1.6 → 0.5.0

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