tonus 0.1.8 → 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 (164) hide show
  1. package/BIBLIOGRAPHY.md +132 -108
  2. package/CHANGELOG.md +598 -1
  3. package/LICENSE +133 -29
  4. package/README.md +106 -83
  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/tabula.d.ts +3 -0
  83. package/dist/engines/harmonia/tabula.js +1 -0
  84. package/dist/engines/harmonia/voice.d.ts +4 -0
  85. package/dist/engines/harmonia/voice.js +8 -4
  86. package/dist/engines/imprint.js +14 -1
  87. package/dist/engines/planet/orbital.js +4 -4
  88. package/dist/engines/planet/planet.d.ts +10 -0
  89. package/dist/engines/planet/planet.js +30 -3
  90. package/dist/engines/planet/position.js +13 -10
  91. package/dist/engines/planet/types.d.ts +1 -0
  92. package/dist/engines/score/api.d.ts +2 -13
  93. package/dist/engines/score/api.js +21 -8
  94. package/dist/engines/score/articulation.js +2 -2
  95. package/dist/engines/score/cadence.d.ts +76 -0
  96. package/dist/engines/score/cadence.js +96 -0
  97. package/dist/engines/score/emitters/accidentals.d.ts +21 -0
  98. package/dist/engines/score/emitters/accidentals.js +88 -0
  99. package/dist/engines/score/emitters/atramentum.d.ts +107 -0
  100. package/dist/engines/score/emitters/atramentum.js +239 -0
  101. package/dist/engines/score/emitters/breaking.d.ts +62 -0
  102. package/dist/engines/score/emitters/breaking.js +80 -0
  103. package/dist/engines/score/emitters/moderna.d.ts +38 -0
  104. package/dist/engines/score/emitters/moderna.js +612 -0
  105. package/dist/engines/score/emitters/svg.d.ts +143 -0
  106. package/dist/engines/score/emitters/svg.js +1328 -0
  107. package/dist/engines/score/emitters/tracks.d.ts +104 -0
  108. package/dist/engines/score/emitters/tracks.js +728 -0
  109. package/dist/engines/score/infer.d.ts +3 -3
  110. package/dist/engines/score/infer.js +2 -2
  111. package/dist/engines/score/inscriptio.d.ts +69 -0
  112. package/dist/engines/score/inscriptio.js +138 -0
  113. package/dist/engines/score/ir.d.ts +2 -2
  114. package/dist/engines/score/ir.js +59 -12
  115. package/dist/engines/score/lyric.d.ts +23 -0
  116. package/dist/engines/score/lyric.js +234 -0
  117. package/dist/engines/score/meta.d.ts +2 -2
  118. package/dist/engines/score/modulation.d.ts +12 -0
  119. package/dist/engines/score/modulation.js +49 -0
  120. package/dist/engines/score/neume.js +35 -4
  121. package/dist/engines/score/parse.js +150 -9
  122. package/dist/engines/score/phrasing.js +4 -3
  123. package/dist/engines/score/prosody.d.ts +38 -0
  124. package/dist/engines/score/prosody.js +70 -6
  125. package/dist/engines/score/tabula.d.ts +37 -5
  126. package/dist/engines/score/tabula.js +18 -0
  127. package/dist/engines/score/types.d.ts +88 -1
  128. package/dist/engines/temper/api.d.ts +4 -1
  129. package/dist/engines/temper/api.js +28 -5
  130. package/dist/engines/temper/data/guido.js +6 -2
  131. package/dist/engines/temper/data/modes.d.ts +6 -0
  132. package/dist/engines/temper/data/modes.js +42 -0
  133. package/dist/engines/temper/data/tones.d.ts +1 -1
  134. package/dist/engines/temper/data/tones.js +20 -11
  135. package/dist/engines/temper/interval.js +4 -3
  136. package/dist/engines/temper/modality.d.ts +11 -2
  137. package/dist/engines/temper/modality.js +74 -2
  138. package/dist/engines/temper/modes.d.ts +1 -1
  139. package/dist/engines/temper/pitch.d.ts +1 -1
  140. package/dist/engines/temper/pitch.js +12 -2
  141. package/dist/engines/temper/scale.d.ts +53 -0
  142. package/dist/engines/temper/scale.js +107 -8
  143. package/dist/index.d.ts +26 -8
  144. package/dist/index.js +37 -4
  145. package/docs/api/calendar.md +279 -0
  146. package/docs/api/census.md +288 -0
  147. package/docs/api/chant.md +657 -0
  148. package/docs/api/heavens.md +346 -0
  149. package/docs/api/index.md +263 -0
  150. package/docs/api/score.md +873 -0
  151. package/docs/api/tuning.md +619 -0
  152. package/package.json +11 -5
  153. package/dist/data/office-matins-roman.d.ts +0 -19
  154. package/dist/data/office-matins-roman.js +0 -4383
  155. package/dist/data/office-psalms-roman.d.ts +0 -15
  156. package/dist/data/office-psalms-roman.js +0 -28
  157. package/dist/data/office-roman.d.ts +0 -19
  158. package/dist/data/office-roman.js +0 -13792
  159. package/dist/engines/chant/matutinum.d.ts +0 -33
  160. package/dist/engines/chant/matutinum.js +0 -81
  161. package/dist/engines/score/emitters/midi.d.ts +0 -65
  162. package/dist/engines/score/emitters/midi.js +0 -162
  163. package/dist/engines/score/emitters/musicxml.d.ts +0 -18
  164. package/dist/engines/score/emitters/musicxml.js +0 -166
@@ -0,0 +1,657 @@
1
+ # Chant
2
+
3
+ The chant engines retrieve the sung repertoire. `cantus` searches the
4
+ corpora at large and parses raw GABC; `proprium` supplies the Mass propers
5
+ of a feast; `ordinarium` the Kyriale settings appropriate to it;
6
+ `officium` the chants of the canonical hours; `psalmus` psalm and canticle
7
+ verses intoned to the psalm tones. Every melody is GABC-encoded and
8
+ carries page-level provenance back to its book.
9
+
10
+ - [Chant](#chant)
11
+ - [The corpora](#the-corpora)
12
+ - [The cut](#the-cut)
13
+ - [The books — `corpus`](#the-books--corpus)
14
+ - [The ledger of the cut — `full`](#the-ledger-of-the-cut--full)
15
+ - [Retrieval — `cantus`](#retrieval--cantus)
16
+ - [Reaching the ordinary — `ordinary`](#reaching-the-ordinary--ordinary)
17
+ - [On chant ids](#on-chant-ids)
18
+ - [The repertoire as of a date — the era view](#the-repertoire-as-of-a-date--the-era-view)
19
+ - [The Mass propers — `proprium`](#the-mass-propers--proprium)
20
+ - [The ordinary — `ordinarium`](#the-ordinary--ordinarium)
21
+ - [The Office — `officium`](#the-office--officium)
22
+ - [One cursus, the Benedictine](#one-cursus-the-benedictine)
23
+ - [Psalms — `psalmus`](#psalms--psalmus)
24
+ - [Theory \& Context](#theory--context)
25
+ - [The Solesmes restoration](#the-solesmes-restoration)
26
+ - [GABC: neumes as text](#gabc-neumes-as-text)
27
+ - [The Mass: proper and ordinary](#the-mass-proper-and-ordinary)
28
+ - [The Office: the daily cursus](#the-office-the-daily-cursus)
29
+ - [Psalm tones](#psalm-tones)
30
+
31
+ ## The corpora
32
+
33
+ Nine Solesmes books, extracted from
34
+ [GregoBase](https://gregobase.selapa.net/), joined by the Divinum Officium
35
+ propers, office, and psalter, plus the Nocturnale Romanum for the night office:
36
+
37
+ | Source | Book | Edition | Chants |
38
+ | ------ | --------------------------------- | ------------------ | ------ |
39
+ | `gr` | Graduale Romanum | Solesmes, 1961 | 780 |
40
+ | `lu` | The Liber Usualis | Solesmes, 1961 | 707 |
41
+ | `la` | Liber antiphonarius | Solesmes, 1960 | 160 |
42
+ | `lh` | Liber Hymnarius | Solesmes, 1983 | 25 |
43
+ | `am` | Antiphonale Monasticum | Solesmes, 1934 | 576 |
44
+ | `ams` | Antiphonale Monasticum Solesmense | Solesmes, 1935 | 11 |
45
+ | `psm` | Psalterium Monasticum | Solesmes, 1981 | 11 |
46
+ | `cse` | Cantus selecti | Solesmes, 1957 | 11 |
47
+ | `cot` | Chants of the Church | Solesmes, 1956 | 16 |
48
+ | `nr` | Nocturnale Romanum | Sandhofe, 2002 | 470 |
49
+
50
+ **2,187 chants in all**, plus the Mass ordinary (120 settings, reached by
51
+ [`ordinary`](#reaching-the-ordinary--ordinary) rather than by book). The books
52
+ hold 10,156 between them; tonus ships only what the calendar calls for on some
53
+ day of the year, so a chant with no day to be sung on is not here. See
54
+ [The cut](#the-cut) below.
55
+
56
+ The ten books list 2,767 rows for those 2,187 chants: a melody printed in two
57
+ books is stored once and listed under both.
58
+
59
+ `am`, `ams`, and `psm` are the monastic (Benedictine) books; the rest are
60
+ Roman. Every book here bears the rhythmic markings the score engine reads; that
61
+ is the admission rule. `nr` is the night-office repertoire (responsories,
62
+ antiphons) from the
63
+ [Nocturnale Romanum](https://github.com/Nocturnale-Romanum/nocturnale-romanum)
64
+ community restitution, the one non-Solesmes source, admitted because it carries
65
+ those marks too.
66
+
67
+ ### The cut
68
+
69
+ The corpus is **assignment-driven**: a chant ships when some day of the
70
+ liturgical year calls for it. The calendar is walked year by year until it stops
71
+ finding new assignments (39 years, in the event), and what it never reaches is
72
+ not shipped — 10,156 book chants become 2,187.
73
+
74
+ Everything here answers "what was sung on this day". A query for a chant the
75
+ calendar never calls for returns nothing.
76
+
77
+ ## The books — `corpus`
78
+
79
+ `corpus(code)` returns one book's bibliographic identity and a breakdown of what
80
+ it holds — how many chants, in what genres, in what modes. `corpus({ book })` is
81
+ the same question in the query form every other verb uses; both spellings return
82
+ one answer.
83
+
84
+ `corpus()` with no argument returns **the whole shelf** — the rollup plus every
85
+ book's ledger:
86
+
87
+ ```js
88
+ tonus.corpus();
89
+ // { count: 2187, // chants tonus holds, each counted once
90
+ // listings: 2767, // rows on the shelf — a chant in two books appears twice
91
+ // total: 10156, // what the books hold, before the cut
92
+ // genera: [ { office: "an", genus: "Antiphona", count: 693 }, … ],
93
+ // modes: [ { mode: "1", modus: "Modus I", count: 392 }, … ],
94
+ // books: [ …10 Corpus entries, in registry order ] }
95
+ ```
96
+
97
+ **`count` is the number of chants** — the one to quote. `listings` is how long
98
+ the shelf is, and `listings - count` is 580 extra rows, over the 683 chants
99
+ printed in more than one book. The breakdowns describe the same population
100
+ `count` does, so `genera` and `modes` sum to it.
101
+
102
+ ```js
103
+ tonus.corpus("am");
104
+ // { code: "am", book: "Antiphonale Monasticum", fullTitle: null,
105
+ // edition: "Pro Diurnis Horis", year: 1934, editor: "Solesmes",
106
+ // scanSource: "Scans courtesy of Corpus Christi Watershed", count: 576,
107
+ // genera: [ { office: "an", genus: "Antiphona", count: 458 }, … ],
108
+ // modes: [ { mode: "1", modus: "Modus I", count: 101 }, …,
109
+ // { mode: null, modus: null, count: 24 } ] }
110
+ ```
111
+
112
+ ```ts
113
+ interface Corpus {
114
+ code: ChantSource;
115
+ book: string; // short title
116
+ fullTitle: string | null; // full Latin title, where the edition records one
117
+ edition: string | null; // edition note, else null
118
+ year: number | null;
119
+ editor: string | null;
120
+ scanSource: string | null; // scan attribution
121
+ count: number; // chants tonus stores (after dedup)
122
+ total: number | null; // chants the book holds (before dedup); null if unmeasured
123
+ unique: number | null; // chants in this book alone; null if unmeasured
124
+ shared: { code: ChantSource; count: number }[] | null; // shared per book, desc; null if unmeasured
125
+ genera: { office: OfficeCode; genus: string; count: number }[]; // descending
126
+ modes: { mode: string | null; modus: string | null; count: number }[];
127
+ full: { total: number; genera: GenusCount[]; modes: ModeCount[] } | null; // the pre-cut tally (below); null if unmeasured
128
+ }
129
+ ```
130
+
131
+ ### The ledger of the cut — `full`
132
+
133
+ Every `Corpus` carries `full`: what the book HELD, before the keep set ran, in
134
+ the same genera/modes shape as the shipped counts.
135
+
136
+ ```js
137
+ const am = tonus.corpus("am");
138
+ am.count; // 576 — antiphons and the rest tonus kept
139
+ am.full.total; // 1456 — what the Antiphonale Monasticum holds
140
+ am.genera[0]; // { office: "an", genus: "Antiphona", count: 458 }
141
+ am.full.genera[0]; // { office: "an", genus: "Antiphona", count: 1049 }
142
+ ```
143
+
144
+ Reading the two tallies side by side names what was left out — 1,049 antiphons
145
+ in the book, 458 sung.
146
+
147
+ Only the extractor can measure this. By the time tonus loads, the keep set has
148
+ already run, so the pre-cut tally is read from an artifact rather than derived.
149
+ Every shelved book reports one, including the Nocturnale, whose tally comes
150
+ from its own extract rather than from GregoBase.
151
+
152
+ The metadata is drawn from GregoBase's own catalogue. The `genera` list is the
153
+ office distribution (descending by count); `modes` counts modes I–VIII, with a
154
+ final `mode: null` bucket for chants outside the eight modes (psalm tones and the
155
+ like) so the counts reconcile with `count`.
156
+
157
+ **Overlap.** tonus keeps one copy of each chant (the Liber Usualis is the primary
158
+ source; the Antiphonarius and Hymnarius fill gaps), so a book's stored `count`
159
+ undercounts what it holds. `total` is the full pre-dedup count, `unique` the
160
+ chants a book alone has, and `shared` how many it holds in common with each other
161
+ book (by GregoBase chant id). These reveal, for instance, that the Liber Usualis
162
+ is largely the Graduale and the Antiphonarius bound together (it shares hundreds
163
+ of chants with each), while the Antiphonale Monasticum is almost entirely its own.
164
+
165
+ The Nocturnale (`nr`) is compared differently, because it has no GregoBase
166
+ catalogue: its counts come from its own extract, and it shares **nothing** —
167
+ `unique` is all 1,564 chants it holds. That is a measurement, not a gap. The
168
+ nocturnale–GregoBase crosswalk is a route to metadata, not a claim that the two
169
+ books print the same chant, so it does not count as sharing.
170
+
171
+ The `null` those fields can still carry means **unmeasured**, distinct from a
172
+ measured zero, so a consumer never mistakes "not compared" for "shares
173
+ nothing".
174
+
175
+ ## Retrieval — `cantus`
176
+
177
+ `cantus(query?)` searches across the corpora by id, incipit, mode, genre,
178
+ and source. Results sort by rank, then incipit; `limit` and `offset` page
179
+ through them.
180
+
181
+ ```js
182
+ tonus.cantus({ mode: 1, office: "an", source: "am", limit: 1 });
183
+ ```
184
+
185
+ ```js
186
+ [
187
+ {
188
+ id: "gregobase:10082",
189
+ incipit: "Ait latro",
190
+ gabc: "(c4) A(d)it(f') la(d)tro(dc) ad(f) la(g)tró(f_h)nem:(h'_) *(,)…",
191
+ office: "an",
192
+ genus: "Antiphona",
193
+ mode: "1",
194
+ modus: "Modus I",
195
+ pages: [{ page: "439", sequence: 2, extent: 2 }],
196
+ source: {
197
+ book: "Antiphonale Monasticum",
198
+ fullTitle: null,
199
+ edition: "Pro Diurnis Horis",
200
+ year: 1934,
201
+ editor: "Solesmes",
202
+ scanSource: "Scans courtesy of Corpus Christi Watershed",
203
+ code: "am",
204
+ },
205
+ },
206
+ ];
207
+ ```
208
+
209
+ The Graduale is the Mass book and holds four antiphons in the shipped corpus,
210
+ so the same query against `source: "gr"` returns `[]`.
211
+
212
+ `cantus` also accepts raw GABC through the `gabc` field. The corpus is
213
+ bypassed and a single user `Chant` returns. The input may be a notation
214
+ body or a full GABC file (headers, `%%`, body); header values for `name`,
215
+ `mode`, and `office-part` are read automatically, and the `incipit`,
216
+ `mode`, and `office` query fields override them.
217
+
218
+ ```js
219
+ // notation body
220
+ tonus.cantus({
221
+ gabc: "(c4) Sán(g)ctus(h) Sán(g)ctus(h)",
222
+ incipit: "Sanctus",
223
+ mode: 1,
224
+ });
225
+
226
+ // full GABC file
227
+ tonus.cantus({
228
+ gabc: "name: Sanctus;\nmode: 1;\n%%\n(c4) Sán(g)ctus(h) Sán(g)ctus(h)",
229
+ });
230
+ ```
231
+
232
+ The `office` field is the genre code; `genus` carries the genre's Latin
233
+ name:
234
+
235
+ | `office` | `genus` | `office` | `genus` | `office` | `genus` |
236
+ | -------- | --------- | -------- | ------------ | -------- | ------------------ |
237
+ | `an` | Antiphona | `hy` | Hymnus | `rb` | Responsorium Breve |
238
+ | `al` | Alleluia | `in` | Introitus | `se` | Sequentia |
239
+ | `ca` | Canticum | `of` | Offertorium | `tr` | Tractus |
240
+ | `co` | Communio | `ps` | Psalmus | `tp` | Tonus Peregrinus |
241
+ | `gr` | Graduale | `re` | Responsorium | `or` | Ordinarium |
242
+
243
+ ```ts
244
+ interface Chant {
245
+ id: string; // "gregobase:1210", "nocturnale:E1F2R3" — see below
246
+ incipit: string;
247
+ gabc: string;
248
+ office: OfficeCode; // genre code
249
+ genus: string; // Latin genre name, "Antiphona", "Introitus" …
250
+ mode: string | null; // raw from source: "1"–"8", differentia forms ("2d", "8g"), "p"/"d"/"e" …
251
+ modus: string | null; // Latin mode name, "Modus I"–"Modus VIII"
252
+ pages: { page: string; sequence: number; extent: number }[];
253
+ source: {
254
+ book: string;
255
+ year: number | null;
256
+ editor: string | null;
257
+ code?: ChantSource | "user";
258
+ fullTitle?: string; // the book's full Latin title
259
+ edition?: string;
260
+ scanSource?: string; // scan attribution (GregoBase catalogue)
261
+ };
262
+ }
263
+
264
+ interface CantusQuery {
265
+ id?: string | string[];
266
+ gabc?: string;
267
+ incipit?: string;
268
+ mode?: number | string | (number | string)[];
269
+ office?: OfficeCode | OfficeCode[];
270
+ source?: ChantSource | ChantSource[];
271
+ ordinary?: OrdinaryCode | OrdinaryCode[]; // a part of the Mass ordinary
272
+ before?: number; // only chants ATTESTED by this year (the era view)
273
+ cursus?: "monastic" | "secular"; // transmission; `both` satisfies either
274
+ limit?: number;
275
+ offset?: number;
276
+ sort?: "incipit" | "mode" | "id";
277
+ }
278
+ ```
279
+
280
+ ### Reaching the ordinary — `ordinary`
281
+
282
+ The Mass ordinary is addressable but not shelved. `ordinary` is the door:
283
+
284
+ ```js
285
+ tonus.cantus({ ordinary: "ky" }); // all 31 Kyries
286
+ tonus.cantus({ ordinary: "gl", mode: 4 }); // mode-4 Glorias
287
+ tonus.cantus({ ordinary: ["as", "va"] }); // the sprinkle antiphons
288
+ ```
289
+
290
+ The Kyriale is a **partition of the Graduale**, so it is not a `source` and not
291
+ a row in the shelf. It stays nameable by `id` and by the part of the Mass it
292
+ belongs to.
293
+
294
+ A plain search does not sweep it in: `{ mode: 5 }` returns the shelf. Ask for a
295
+ Kyrie and you get Kyries.
296
+
297
+ For the setting a given DAY calls for, [`ordinarium`](#the-ordinary--ordinarium)
298
+ is the verb — it applies the Kyriale's own rubrics. This is flat retrieval.
299
+
300
+ ### On chant ids
301
+
302
+ An id's prefix names **the catalogue the identifier came from** — not the book
303
+ the chant is printed in, and not a claim about who the melody belongs to. A
304
+ chant carrying `gregobase:1210` is a Solesmes book chant that GregoBase happens
305
+ to have catalogued; the corpus is assembled from ten books, and GregoBase is
306
+ one source among several.
307
+
308
+ The prefix is therefore **not a namespace you can query against**. GregoBase
309
+ holds 18,148 chants; tonus ships 1,717 of them — 9.5% — because the corpus is
310
+ assignment-driven, so an id copied from the GregoBase site will usually return
311
+ `[]` here. That is not a lookup failure; it means no day of the calendar calls
312
+ for that chant. The two prefixes in the shipped corpus are `gregobase:` (1,717)
313
+ and `nocturnale:` (470), the latter carrying the Nocturnale's own alphanumeric
314
+ keys rather than numbers.
315
+
316
+ Within tonus an id is exactly one chant. A melody printed in several books —
317
+ 683 of them are — is stored once, under the record `cantus({ id })` returns, so
318
+ `id` is a stable key to a chant rather than to a printing.
319
+
320
+ ## The repertoire as of a date — the era view
321
+
322
+ `before: 1098` keeps only chants a manuscript of the 10th century or earlier
323
+ already holds. This is **evidence, not existence**: the dates come from
324
+ CANTUS's manuscript index, a terminus ante quem, so the filter answers "what
325
+ is attested by then," never "what existed then" — and a chant with no dated
326
+ witness is excluded rather than assumed old. CANTUS dates only to the century,
327
+ so a year admits the centuries that have CLOSED before it (`before: 1098` →
328
+ through the 900s).
329
+
330
+ The view is the analogue of
331
+ [`festum({ before })`](calendar.md#the-day-as-of-a-year--before) over the
332
+ calendar, and the two **compose**: a feast resolved under a view carries it,
333
+ and every day verb serves the same view unasked.
334
+
335
+ ```js
336
+ const [easter] = tonus.festum({ date: new Date("2026-04-05"), before: 1100 });
337
+ tonus.proprium({ feast: easter }); // only propers attested by 1100
338
+ tonus.ordinarium({ feast: easter }); // the ordinary the view attests
339
+ ```
340
+
341
+ What happens to a slot the view excludes differs by verb, on the rubric's
342
+ own logic: `ordinarium` **re-picks** — the Kyriale offers ranked
343
+ alternatives by design, so the rotation runs over the admissible pool and
344
+ the day still sings. `proprium` and `officium` have no pool
345
+ of alternatives, so an excluded chant **falls silent**. A `before` given to a
346
+ day verb directly overrides the feast's view; an invalid one throws at every
347
+ door.
348
+
349
+ ## The Mass propers — `proprium`
350
+
351
+ `proprium(query?)` retrieves the chants whose texts change with the day:
352
+ Introitus, Graduale, Alleluia or Tractus, Offertorium, Communio. A feast
353
+ narrows the result to its own propers.
354
+
355
+ ```js
356
+ const [feast] = tonus.festum({ date: new Date("2026-12-25") });
357
+ tonus.proprium({ feast, office: "in" });
358
+ // Puer natus est — Introitus, Modus VII, Liber Usualis p. 408
359
+ ```
360
+
361
+ Coverage is 689 proper formularies. When a feast has no dedicated proper for a
362
+ slot, the Commune Sanctorum (formularies for classes of saints) supplies it
363
+ through 48 commune sets and 254 feast-to-commune mappings.
364
+
365
+ ```ts
366
+ interface PropriumQuery extends CantusQuery {
367
+ feast?: Feast | Feast[];
368
+ }
369
+ ```
370
+
371
+ ## The ordinary — `ordinarium`
372
+
373
+ `ordinarium(query?)` retrieves the fixed chants of the Mass from the
374
+ Kyriale. A feast drives mass selection through its `masses` list — the
375
+ masses the day's Kyriale RUBRIC appoints, derived as described in
376
+ [calendar.md](calendar.md#the-days-feasts--festum); `mass` pins a kyriale
377
+ number directly. Where the rubric names several masses, the year rotates
378
+ through them (same feast, same year → same answer, every time), and sibling
379
+ printings under one number (Mass I prints two dismissals; Mass XVII prints
380
+ Kyrie A/B/C) rotate with it. Slots resolve independently, which the book
381
+ licenses outright — "chants from one Mass may be used together with those
382
+ from others" — with one exception, the book's own: **"the Ferial Masses
383
+ excepted."** Under a ferial rubric the sung ordinary is not gathered from
384
+ several masses; only the dismissal travels.
385
+
386
+ ```js
387
+ const [easter] = tonus.festum({ date: new Date("2026-04-05") });
388
+ tonus.ordinarium({ feast: easter });
389
+ // ky Kyrie I (mass 1) — Lux et origo, Paschal time, every year
390
+ // gl Gloria I (mass 1)
391
+ // cr Credo III (mass 3) — the credo rotates on its own cycle
392
+ // sa Sanctus I (mass 1)
393
+ // ag Agnus Dei I (mass 1)
394
+ // it Ite Ia (mass 1)
395
+ // va Vidi aquam (mass 0) — the Paschaltide sprinkling antiphon rides along
396
+ ```
397
+
398
+ The **Gloria follows the day's rank rubric, not its season**: the ferial
399
+ masses print none (XVI, XVIII) and the penitential-Sunday mass none (XVII),
400
+ while a I-class feast inside Advent or Lent — the Immaculate Conception —
401
+ keeps its Gloria. At a Gloria-less Mass the dismissal is the Benedicamus
402
+ Domino, and a mass with no dismissal of its own borrows one exactly as the
403
+ book directs: "Benedicamus Domino **as in Mass II**" — so a green feria
404
+ sings Mass XVI whole with the Mass II Benedicamus. The ad libitum appendix
405
+ is a **solemnity boost**, reachable only under the festal rubrics (it takes
406
+ its turn in the rotation once every _n + 1_ years); it never reaches a
407
+ penitential day or a feria, and the Requiem settings stay out of every
408
+ calendar-driven pick (reachable by `ordinarium({ mass: 102 })` only). The
409
+ Triduum returns no ordinary at all: Good Friday has no Mass, and the
410
+ Vigil's ordinary belongs to Easter. A pinned `mass` overrides the Triduum
411
+ rule and the rotation both.
412
+
413
+ **Maundy Thursday** (In Cena Domini) is the Triduum's exception: it keeps a
414
+ full Mass with the Gloria, its ordinary fixed to Mass I (Lux et origo).
415
+
416
+ The sprinkle rite (aspersion before the principal Sunday Mass) is appended
417
+ and selected by season: **Vidi aquam** (`va`) in Paschaltide, **Asperges
418
+ me** (`as`) otherwise.
419
+
420
+ ```js
421
+ tonus.ordinarium({ ordinary: "ky" }); // every Kyrie
422
+ tonus.ordinarium({ mass: 9, ordinary: "gl" }); // Gloria of Cum jubilo
423
+ ```
424
+
425
+ | `ordinary` | `ordinarium` |
426
+ | ---------- | --------------------------------------------- |
427
+ | `ky` | Kyrie eleison |
428
+ | `gl` | Gloria |
429
+ | `cr` | Credo |
430
+ | `sa` | Sanctus |
431
+ | `ag` | Agnus Dei |
432
+ | `be` | Benedicamus |
433
+ | `it` | Ite missa est |
434
+ | `as` | Asperges (sprinkle rite, outside Paschaltide) |
435
+ | `va` | Vidi aquam (sprinkle rite, Paschaltide) |
436
+
437
+ ```ts
438
+ interface OrdinaryChant extends Chant {
439
+ ordinary: OrdinaryCode; // movement code
440
+ ordinarium: string; // Latin movement name, "Kyrie eleison" …
441
+ mass: number;
442
+ }
443
+
444
+ interface OrdinariumQuery extends CantusQuery {
445
+ feast?: Feast | Feast[];
446
+ ordinary?: OrdinaryCode;
447
+ mass?: number;
448
+ }
449
+ ```
450
+
451
+ ## The Office — `officium`
452
+
453
+ `officium(query?)` retrieves the chants of a canonical hour; a feast acts
454
+ as a filter. Without an hour, every available hour returns.
455
+
456
+ ```js
457
+ const christmas = tonus.festum({ date: new Date("2026-12-25") });
458
+ tonus.officium({ feast: christmas, hora: "laudes" });
459
+ // 7 chants: the Lauds antiphons, the Benedictus antiphon, and the hymn
460
+ ```
461
+
462
+ | Hour | Content |
463
+ | --------------------------- | ---------------------------------------------------------------------------- |
464
+ | `matutinum` | Invitatory, antiphons, hymn, responsories |
465
+ | `laudes` | Antiphons, Benedictus antiphon, hymn |
466
+ | `tertia` / `sexta` / `nona` | The gradual psalms (Terce 119–121, Sext 122–124, None 125–127; Sunday and Monday take portions of Ps 118) + responsory breve |
467
+ | `vesperae` | Antiphons, Magnificat antiphon, hymn |
468
+ | `prima` | The Prime ordo (sung parts) — see below |
469
+ | `completorium` | The full Compline ordo — see below |
470
+
471
+ **Prime and Compline are ordos, not chant sets.** These two hours are
472
+ almost invariable: the same
473
+ sequence each day, varying only by season. They are assembled from a small
474
+ seasonal ordo and returned in liturgical order. With no feast, each resolves
475
+ for the [default epoch](index.md#dates).
476
+
477
+ **Matins is returned flat.** The night office answers like any other hour,
478
+ its responsories drawn from the Nocturnale Romanum (`nr`) — but the
479
+ three-nocturn, twelve-psalm division is not modelled: the chants are right,
480
+ their grouping into nocturns is not expressed.
481
+
482
+ ```js
483
+ tonus.officium({ feast: christmas, hora: "completorium" });
484
+ // Deus in adjutorium → Ps 4, 90, 133 → Te lucis → In manus tuas
485
+ // → Nunc dimittis → Alma Redemptoris (simple tone)
486
+ ```
487
+
488
+ ```ts
489
+ interface OfficiumQuery extends CantusQuery {
490
+ feast?: Feast | Feast[];
491
+ hora?: CanonicalHour;
492
+ }
493
+ ```
494
+
495
+ The eight hours ship as [`HORAE`](index.md#the-appendix), Matins first — read
496
+ them from there rather than transcribing them, and an unrecognised `hora`
497
+ throws rather than matching nothing, so a misspelling cannot read as an empty
498
+ hour.
499
+
500
+ ```js
501
+ import { HORAE } from "tonus";
502
+ // ["matutinum", "laudes", "prima", "tertia", "sexta", "nona",
503
+ // "vesperae", "completorium"]
504
+
505
+ tonus.officium({ hora: "vespers" }); // throws: unknown hora "vespers"
506
+ ```
507
+
508
+ ### One cursus, the Benedictine
509
+
510
+ tonus assembles a single office — the monastic cursus — with no option to
511
+ choose another. The chants come from the Antiphonale Monasticum (`am`) and its
512
+ companions; the psalmody follows the Benedictine distribution — the little
513
+ hours take the gradual psalms (Terce 119–121, Sext 122–124, None 125–127),
514
+ with Sunday and Monday walking their portions of Ps 118 instead; Prime walks
515
+ Pss 1–19 across the week (Sunday opens Ps 118); and Compline is the fixed
516
+ three, 4, 90 and 133.
517
+
518
+ ```js
519
+ tonus.officium({ feast: benedict, hora: "vesperae" });
520
+ // the monastic Vespers antiphons, sourced from the Antiphonale Monasticum
521
+ ```
522
+
523
+ ## Psalms — `psalmus`
524
+
525
+ `psalmus(query?)` returns psalm and canticle verses as intoned chant:
526
+ GABC pointed to a psalm tone, modes 1–8 or the tonus peregrinus (mode 0).
527
+ `differentia` selects the cadential variant; `intonatio` controls whether
528
+ the opening formula is included, as it is for a psalm's first verse.
529
+ `inDirectum` recites a verse straight through to the termination with no
530
+ mediant, as a psalm sung without an antiphon; `solemn` uses a tone's
531
+ ornamented mediant where it has one. Canticles are addressed by name:
532
+ `benedictus`, `magnificat`, `nunc dimittis`, `benedicite`. (The Te Deum
533
+ is not psalmody — it carries its own melody and is not addressable here.)
534
+
535
+ ```js
536
+ tonus.psalmus({ psalm: 109, verse: "1a", mode: 1 });
537
+ ```
538
+
539
+ ```js
540
+ [
541
+ {
542
+ incipit: "Dixit Dóminus Dómino meo:",
543
+ modus: "Modus I",
544
+ gabc: "(c4) (f)Di(h)xit (j)Dó(h)mi(h)nus (h)Dó(j)mi(h)no (g)me(h)o:(:) …",
545
+ },
546
+ ];
547
+ ```
548
+
549
+ ```js
550
+ tonus.psalmus({ psalm: 109, mode: 2, differentia: "6F" });
551
+ tonus.psalmus({ psalm: "benedictus", mode: 8, intonatio: false });
552
+ ```
553
+
554
+ The tones and their differentiae follow the Graduale Romanum appendix; the
555
+ tone's anatomy as tuned pitches is available from
556
+ [`temperamentum.tonus()`](tuning.md#psalm-tones--tonus).
557
+
558
+ ```ts
559
+ interface PsalmusQuery {
560
+ psalm?: number | string;
561
+ verse?: string;
562
+ mode?: number;
563
+ differentia?: string; // differentia code, e.g. "6F", "4e"
564
+ intonatio?: boolean; // include opening intonation formula, default true
565
+ inDirectum?: boolean; // recite straight through, no mediant
566
+ solemn?: boolean; // use the ornamented solemn mediant where the tone has one
567
+ }
568
+ ```
569
+
570
+ ## Theory & Context
571
+
572
+ ### The Solesmes restoration
573
+
574
+ The melodies in tonus are the Solesmes editions: the scholarly restoration
575
+ produced from the mid-19th century onward and matured into the books listed
576
+ under [The corpora](#the-corpora). The 1961 Graduale, the last complete edition
577
+ before the post-conciliar reforms, covers the full Tridentine cycle the
578
+ calendar in [calendar.md](calendar.md) expects.
579
+
580
+ Every reading reflects editorial judgment (no single medieval church sang
581
+ precisely these books), and the Solesmes books are a complete, internally
582
+ consistent edition of the Tridentine cycle, available machine-readable through
583
+ GregoBase.
584
+
585
+ ### GABC: neumes as text
586
+
587
+ All melodies are encoded in GABC, the plain-text notation of the
588
+ [Gregorio](https://gregorio-project.github.io/) project: lyric syllables
589
+ each followed by a parenthesized note group, with pitch letters (`a`–`m`)
590
+ read against a clef declaration such as `(c4)`.
591
+
592
+ ```
593
+ (c4) Pu(g)er(gh) na(hj)tus(j) est(j)
594
+ ```
595
+
596
+ Melodic shapes are implicit in the letter groupings and define the neume
597
+ vocabulary in [tuning.md](tuning.md#neumes--neuma). Other marks carry the
598
+ Solesmes performance layer (episemas, the quilisma, liquescents, dots,
599
+ and divisiones) which the score engine reads into performance data and
600
+ phrase punctuation ([score.md](score.md#the-tabula)).
601
+
602
+ Because the encoding is textual, lyrics and neumes stay aligned syllable
603
+ by syllable, which is what lets `tonus.notatio` reconstruct syllables,
604
+ neumes, and prosody without images.
605
+
606
+ ### The Mass: proper and ordinary
607
+
608
+ Two layers of chant make up a sung Mass, and tonus separates them exactly
609
+ as the books do:
610
+
611
+ - **The proper** (`tonus.proprium`) supplies the five processional and
612
+ interlectionary chants whose texts change with the day: Introitus,
613
+ Graduale, Alleluia (or Tractus in penitential seasons), Offertorium,
614
+ Communio. When a feast lacks its own proper, the rite supplies one from
615
+ the Commune Sanctorum, the shared formularies for classes of saints;
616
+ this is why `proprium` falls back to commune sets.
617
+ - **The ordinary** (`tonus.ordinarium`) supplies the fixed texts sung at
618
+ every Mass: Kyrie, Gloria, Credo, Sanctus, Agnus Dei, Ite or Benedicamus.
619
+ Their melodies live in the **Kyriale**, eighteen numbered mass-settings
620
+ plus ad libitum chants, each conventionally assigned to a class of day
621
+ (Lux et origo for Paschaltide, Orbis factor for Sundays throughout the year, the
622
+ Missa de Angelis everywhere). Feast-aware mass selection follows those
623
+ assignments.
624
+
625
+ ### The Office: the daily cursus
626
+
627
+ The Divine Office (`tonus.officium`) supplies the eight canonical hours
628
+ that structure the liturgical day: Matutinum (the night office), Laudes
629
+ at dawn, the little hours of Prima, Tertia, Sexta, and Nona, Vesperae at
630
+ evening, and Completorium before sleep. The backbone of every hour is
631
+ psalmody: psalms and canticles framed by antiphons, with hymns and
632
+ responsories proper to the hour and the day. The eight-hour cursus is a
633
+ medieval inheritance intact in the Tridentine books.
634
+
635
+ ### Psalm tones
636
+
637
+ Psalm verses are not through-composed; they are _intoned_ on recitation
638
+ formulas (`tonus.psalmus`): one tone per mode, plus the wandering
639
+ **tonus peregrinus** with its two tenors (sung to _In exitu Israel_).
640
+ Each tone has a fixed anatomy: an **intonatio** (the opening rise, sung
641
+ for the first verse), recitation on the **tenor**, a **mediatio** cadence
642
+ at the verse's colon, recitation again, and a **terminatio** cadence.
643
+
644
+ Terminations come in variants, the **differentiae** (`"6F"`, `"4e"`, …),
645
+ whose purpose is practical: ending the verse on a pitch that leads
646
+ smoothly back into the antiphon's opening. The tones and differentiae in
647
+ tonus follow the Graduale Romanum appendix (Toni Communes), keyed by the
648
+ same codes Divinum Officium uses. The gamut-level mechanics, tenor and
649
+ finalis per mode, are on the tuning page
650
+ ([tuning.md](tuning.md#modes--modus)).
651
+
652
+ ## Sources
653
+
654
+ Sources for this page are in the central [bibliography](https://github.com/jeffreypierce/tonus/blob/main/BIBLIOGRAPHY.md):
655
+ `gregobase` (the ten Solesmes books), `nocturnale-romanum`, `divinum-officium`,
656
+ `graduale-toni-communes`, `bloomfield-compline`, `gregorio-gabc`, `apel-chant`,
657
+ `hiley-plainchant`, `saulnier-guide`.