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.
- package/BIBLIOGRAPHY.md +132 -108
- package/CHANGELOG.md +598 -1
- package/LICENSE +133 -29
- package/README.md +106 -83
- package/dist/data/am.js +2666 -11196
- package/dist/data/ams.d.ts +5 -0
- package/dist/data/ams.js +122 -0
- package/dist/data/attestation.d.ts +19 -0
- package/dist/data/attestation.js +15716 -0
- package/dist/data/attestation.json +15711 -0
- package/dist/data/cadentiae.d.ts +43 -0
- package/dist/data/cadentiae.js +174 -0
- package/dist/data/cal.js +48 -0
- package/dist/data/census.d.ts +12 -0
- package/dist/data/census.js +36 -0
- package/dist/data/commune-office.d.ts +4 -0
- package/dist/data/commune-office.js +2371 -0
- package/dist/data/commune-office.json +2365 -0
- package/dist/data/commune.js +181 -11
- package/dist/data/corpus-overlap.d.ts +17 -0
- package/dist/data/corpus-overlap.js +299 -5
- package/dist/data/cot.d.ts +5 -0
- package/dist/data/cot.js +172 -0
- package/dist/data/cse.d.ts +5 -0
- package/dist/data/cse.js +122 -0
- package/dist/data/gabc-glyphs.d.ts +45 -0
- package/dist/data/gabc-glyphs.js +122 -0
- package/dist/data/gr.js +754 -6394
- package/dist/data/kyriale.js +116 -116
- package/dist/data/la.js +799 -13419
- package/dist/data/lh.js +113 -3473
- package/dist/data/lu.js +931 -17631
- package/dist/data/nocturnale-romanum.js +1659 -10411
- package/dist/data/office-ferial.d.ts +4 -0
- package/dist/data/office-ferial.js +396 -0
- package/dist/data/office-ferial.json +391 -0
- package/dist/data/office-monastic.d.ts +17 -1
- package/dist/data/office-monastic.js +1403 -466
- package/dist/data/office-psalms-monastic.d.ts +13 -1
- package/dist/data/office-psalms-monastic.js +9 -0
- package/dist/data/propers.js +1 -1
- package/dist/data/psalms.js +22919 -5
- package/dist/data/psm.d.ts +5 -0
- package/dist/data/psm.js +122 -0
- package/dist/data/seasonal-respbreve.d.ts +5 -0
- package/dist/data/seasonal-respbreve.js +41 -0
- package/dist/data/seasonal-respbreve.json +35 -0
- package/dist/data/smufl-glyphs.d.ts +17 -0
- package/dist/data/smufl-glyphs.js +1546 -0
- package/dist/data/smufl-glyphs.json +1530 -0
- package/dist/engines/cal/calendar.d.ts +3 -2
- package/dist/engines/cal/calendar.js +105 -29
- package/dist/engines/cal/data/eras.d.ts +35 -0
- package/dist/engines/cal/data/eras.js +128 -0
- package/dist/engines/cal/date.js +44 -0
- package/dist/engines/cal/types.d.ts +15 -3
- package/dist/engines/cal/types.js +5 -5
- package/dist/engines/census/census.d.ts +7 -0
- package/dist/engines/census/census.js +179 -0
- package/dist/engines/census/types.d.ts +55 -0
- package/dist/engines/census/types.js +8 -0
- package/dist/engines/chant/attest.d.ts +39 -0
- package/dist/engines/chant/attest.js +90 -0
- package/dist/engines/chant/chant.d.ts +16 -4
- package/dist/engines/chant/chant.js +220 -30
- package/dist/engines/chant/data/compline.js +2 -1
- package/dist/engines/chant/data/masses.d.ts +56 -4
- package/dist/engines/chant/data/masses.js +305 -80
- package/dist/engines/chant/data/prime.js +1 -1
- package/dist/engines/chant/hour.js +279 -58
- package/dist/engines/chant/ordinary.d.ts +2 -0
- package/dist/engines/chant/ordinary.js +336 -56
- package/dist/engines/chant/propers.js +55 -5
- package/dist/engines/chant/psalm.d.ts +4 -4
- package/dist/engines/chant/psalm.js +25 -11
- package/dist/engines/chant/syllabify.d.ts +1 -0
- package/dist/engines/chant/syllabify.js +90 -17
- package/dist/engines/chant/types.d.ts +115 -12
- package/dist/engines/chant/types.js +38 -3
- package/dist/engines/harmonia/api.js +4 -0
- package/dist/engines/harmonia/data/doctrines.js +3 -1
- package/dist/engines/harmonia/tabula.d.ts +3 -0
- package/dist/engines/harmonia/tabula.js +1 -0
- package/dist/engines/harmonia/voice.d.ts +4 -0
- package/dist/engines/harmonia/voice.js +8 -4
- package/dist/engines/imprint.js +14 -1
- package/dist/engines/planet/orbital.js +4 -4
- package/dist/engines/planet/planet.d.ts +10 -0
- package/dist/engines/planet/planet.js +30 -3
- package/dist/engines/planet/position.js +13 -10
- package/dist/engines/planet/types.d.ts +1 -0
- package/dist/engines/score/api.d.ts +2 -13
- package/dist/engines/score/api.js +21 -8
- package/dist/engines/score/articulation.js +2 -2
- package/dist/engines/score/cadence.d.ts +76 -0
- package/dist/engines/score/cadence.js +96 -0
- package/dist/engines/score/emitters/accidentals.d.ts +21 -0
- package/dist/engines/score/emitters/accidentals.js +88 -0
- package/dist/engines/score/emitters/atramentum.d.ts +107 -0
- package/dist/engines/score/emitters/atramentum.js +239 -0
- package/dist/engines/score/emitters/breaking.d.ts +62 -0
- package/dist/engines/score/emitters/breaking.js +80 -0
- package/dist/engines/score/emitters/moderna.d.ts +38 -0
- package/dist/engines/score/emitters/moderna.js +612 -0
- package/dist/engines/score/emitters/svg.d.ts +143 -0
- package/dist/engines/score/emitters/svg.js +1328 -0
- package/dist/engines/score/emitters/tracks.d.ts +104 -0
- package/dist/engines/score/emitters/tracks.js +728 -0
- package/dist/engines/score/infer.d.ts +3 -3
- package/dist/engines/score/infer.js +2 -2
- package/dist/engines/score/inscriptio.d.ts +69 -0
- package/dist/engines/score/inscriptio.js +138 -0
- package/dist/engines/score/ir.d.ts +2 -2
- package/dist/engines/score/ir.js +59 -12
- package/dist/engines/score/lyric.d.ts +23 -0
- package/dist/engines/score/lyric.js +234 -0
- package/dist/engines/score/meta.d.ts +2 -2
- package/dist/engines/score/modulation.d.ts +12 -0
- package/dist/engines/score/modulation.js +49 -0
- package/dist/engines/score/neume.js +35 -4
- package/dist/engines/score/parse.js +150 -9
- package/dist/engines/score/phrasing.js +4 -3
- package/dist/engines/score/prosody.d.ts +38 -0
- package/dist/engines/score/prosody.js +70 -6
- package/dist/engines/score/tabula.d.ts +37 -5
- package/dist/engines/score/tabula.js +18 -0
- package/dist/engines/score/types.d.ts +88 -1
- package/dist/engines/temper/api.d.ts +4 -1
- package/dist/engines/temper/api.js +28 -5
- package/dist/engines/temper/data/guido.js +6 -2
- package/dist/engines/temper/data/modes.d.ts +6 -0
- package/dist/engines/temper/data/modes.js +42 -0
- package/dist/engines/temper/data/tones.d.ts +1 -1
- package/dist/engines/temper/data/tones.js +20 -11
- package/dist/engines/temper/interval.js +4 -3
- package/dist/engines/temper/modality.d.ts +11 -2
- package/dist/engines/temper/modality.js +74 -2
- package/dist/engines/temper/modes.d.ts +1 -1
- package/dist/engines/temper/pitch.d.ts +1 -1
- package/dist/engines/temper/pitch.js +12 -2
- package/dist/engines/temper/scale.d.ts +53 -0
- package/dist/engines/temper/scale.js +107 -8
- package/dist/index.d.ts +26 -8
- package/dist/index.js +37 -4
- package/docs/api/calendar.md +279 -0
- package/docs/api/census.md +288 -0
- package/docs/api/chant.md +657 -0
- package/docs/api/heavens.md +346 -0
- package/docs/api/index.md +263 -0
- package/docs/api/score.md +873 -0
- package/docs/api/tuning.md +619 -0
- package/package.json +11 -5
- package/dist/data/office-matins-roman.d.ts +0 -19
- package/dist/data/office-matins-roman.js +0 -4383
- package/dist/data/office-psalms-roman.d.ts +0 -15
- package/dist/data/office-psalms-roman.js +0 -28
- package/dist/data/office-roman.d.ts +0 -19
- package/dist/data/office-roman.js +0 -13792
- package/dist/engines/chant/matutinum.d.ts +0 -33
- package/dist/engines/chant/matutinum.js +0 -81
- package/dist/engines/score/emitters/midi.d.ts +0 -65
- package/dist/engines/score/emitters/midi.js +0 -162
- package/dist/engines/score/emitters/musicxml.d.ts +0 -18
- package/dist/engines/score/emitters/musicxml.js +0 -166
|
@@ -14,8 +14,9 @@ export declare function buildCalendar(year: number): Map<string, CalEntry[]>;
|
|
|
14
14
|
* Calendar lookup (`tonus.festum`). Returns matching feasts sorted
|
|
15
15
|
* `day asc, rank desc` — for a date, the primary feast plus concurrent
|
|
16
16
|
* feasts; for a `from`/`to` range, every day flattened; with no query,
|
|
17
|
-
* the
|
|
18
|
-
*
|
|
17
|
+
* the default-epoch day (Guido d'Arezzo's era); for a filter-only query,
|
|
18
|
+
* the liturgical year containing that epoch. Dates are UTC-canonical:
|
|
19
|
+
* build them from ISO strings or `Date.UTC`.
|
|
19
20
|
*/
|
|
20
21
|
export declare function getFeast(query?: FeastQuery): Feast[];
|
|
21
22
|
//# sourceMappingURL=calendar.d.ts.map
|
|
@@ -17,15 +17,12 @@
|
|
|
17
17
|
// noted as future work.) The rank system this data carries is documented at
|
|
18
18
|
// `ritus`/`Grade` in ./types.ts; Easter reckoning at pascha() in ./date.ts.
|
|
19
19
|
import { CAL } from "../../data/cal.js";
|
|
20
|
-
import {
|
|
20
|
+
import { feastKeptBy } from "./data/eras.js";
|
|
21
|
+
import { massesForRubric, CLASS_I_GRADES, CLASS_II_GRADES, CLASS_III_GRADES, } from "../chant/data/masses.js";
|
|
21
22
|
import { isoDate, startOfDay, addDays, subDays, firstSundayOnOrAfter, nextSunday, pascha, resolveEntryId, DEFAULT_EPOCH, } from "./date.js";
|
|
22
|
-
import {
|
|
23
|
+
import { TEMPORA, entryGrade, gradeOrder, BVM_FEAST_IDS, APOSTOLIC_FEAST_IDS, PENITENTIAL_SEASONS, } from "./types.js";
|
|
23
24
|
const _calCache = new Map();
|
|
24
25
|
const _anchorCache = new Map();
|
|
25
|
-
// Preferred mass order; ad libitum (mass 0) is handled separately in ordinary.ts.
|
|
26
|
-
const DEFAULT_MASSES = [
|
|
27
|
-
8, 9, 11, 1, 2, 3, 4, 5, 6, 7, 10, 12, 13, 14, 15, 16, 17, 18,
|
|
28
|
-
];
|
|
29
26
|
export function getAnchors(year) {
|
|
30
27
|
if (_anchorCache.has(year))
|
|
31
28
|
return _anchorCache.get(year);
|
|
@@ -142,29 +139,68 @@ function findSeason(date) {
|
|
|
142
139
|
return s("quad", a.ashWednesday, a.easter);
|
|
143
140
|
if (date >= a.easter && date < trinitySunday(a))
|
|
144
141
|
return s("pasc", a.easter, trinitySunday(a));
|
|
145
|
-
|
|
146
|
-
|
|
142
|
+
// Advent of THIS year closes the season after Pentecost — the `adv` branch
|
|
143
|
+
// above has already claimed those days, so reaching for next year's Advent
|
|
144
|
+
// put the end date sixteen months out and made the season 547 days long.
|
|
145
|
+
if (date >= trinitySunday(a) && date < a.adventFirstSunday)
|
|
146
|
+
return s("pent", trinitySunday(a), a.adventFirstSunday);
|
|
147
147
|
return s("epi", epiphanySunday(a), a.septuagesima);
|
|
148
148
|
}
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
149
|
+
// The Sunday-as-such grades. Per the canonical grade table, an ordinary Sunday is
|
|
150
|
+
// `semiduplex`, an Advent Sunday `semiduplex-ii`, a Lent Sunday `semiduplex-i` —
|
|
151
|
+
// so a day that is merely a Sunday is recognised by carrying one of these, while
|
|
152
|
+
// a feast that outranks the Sunday carries a duplex grade and takes its class.
|
|
153
|
+
const SUNDAY_GRADES = [
|
|
154
|
+
"semiduplex-i",
|
|
155
|
+
"semiduplex-ii",
|
|
156
|
+
"semiduplex",
|
|
157
|
+
];
|
|
158
|
+
/**
|
|
159
|
+
* The Kyriale rubric this day falls under — the book classifies by RANK and
|
|
160
|
+
* appoints one category per day [biblio: liber-usualis, Kyriale].
|
|
161
|
+
*
|
|
162
|
+
* Order matters. "In Paschal Time" is a season rubric and governs inside
|
|
163
|
+
* Paschaltide. The Sunday categories must be tested before the class tiers,
|
|
164
|
+
* because the Sunday grades are the semiduplex variants — which would otherwise
|
|
165
|
+
* be claimed by the I, II and III class tiers and hand a Lent Sunday a
|
|
166
|
+
* first-class mass.
|
|
167
|
+
*
|
|
168
|
+
* An open editorial question: should a I class feast inside Paschaltide
|
|
169
|
+
* (Ascension, Pentecost) reach for the I-class pair II/III instead of Lux et
|
|
170
|
+
* Origo? The book's heading is unqualified, so Paschaltide wins here.
|
|
171
|
+
*/
|
|
172
|
+
function rubricForDay(id, grade, season, date) {
|
|
173
|
+
const isSunday = date.getUTCDay() === 0;
|
|
174
|
+
const penitential = PENITENTIAL_SEASONS.has(season);
|
|
175
|
+
// The book's own subject category outranks the season: a BVM feast in
|
|
176
|
+
// Paschaltide is "For feasts of the Blessed Virgin", not "In Paschal Time" —
|
|
177
|
+
// paschal-first left Cum jubilo unreachable for the whole of Eastertide.
|
|
178
|
+
if (BVM_FEAST_IDS.has(id))
|
|
179
|
+
return "bvm";
|
|
180
|
+
if (season === "pasc")
|
|
181
|
+
return "paschal";
|
|
182
|
+
if (isSunday && SUNDAY_GRADES.includes(grade)) {
|
|
183
|
+
return penitential ? "sunday-penitential" : "sunday";
|
|
166
184
|
}
|
|
167
|
-
|
|
185
|
+
if (CLASS_I_GRADES.includes(grade))
|
|
186
|
+
return "class-i";
|
|
187
|
+
if (CLASS_II_GRADES.includes(grade))
|
|
188
|
+
return "class-ii";
|
|
189
|
+
if (CLASS_III_GRADES.includes(grade))
|
|
190
|
+
return "class-iii";
|
|
191
|
+
if (grade === "simplex")
|
|
192
|
+
return "commemoration";
|
|
193
|
+
return penitential ? "feria-penitential" : "feria";
|
|
194
|
+
}
|
|
195
|
+
// The masses the day may sing: those the Kyriale appoints under its rubric, in
|
|
196
|
+
// the book's own numbering. Where a rubric names several — II class 1–5 — that
|
|
197
|
+
// numbering IS the invitation to choose, and ordinary.ts rotates among them.
|
|
198
|
+
//
|
|
199
|
+
// This replaced a `seasons ∩ grades ∩ days` intersection that returned every
|
|
200
|
+
// mass not positively excluded, which is how Easter came to be offered masses IV
|
|
201
|
+
// and V (appointed for the II class). See the header of chant/data/masses.ts.
|
|
202
|
+
function selectMasses(id, grade, season, date) {
|
|
203
|
+
return massesForRubric(rubricForDay(id, grade, season, date)).map((m) => m.mass);
|
|
168
204
|
}
|
|
169
205
|
function calEntryToFeast(entry, season, d) {
|
|
170
206
|
const id = entry.id ?? "";
|
|
@@ -177,7 +213,7 @@ function calEntryToFeast(entry, season, d) {
|
|
|
177
213
|
ritus,
|
|
178
214
|
grade,
|
|
179
215
|
season: season.code,
|
|
180
|
-
tempus:
|
|
216
|
+
tempus: TEMPORA[season.code],
|
|
181
217
|
seasonStart: season.start,
|
|
182
218
|
seasonEnd: season.end,
|
|
183
219
|
date: d,
|
|
@@ -208,19 +244,41 @@ function feastsForDate(date) {
|
|
|
208
244
|
const season = findSeason(d);
|
|
209
245
|
return entries.map((e) => calEntryToFeast(e, season, d));
|
|
210
246
|
}
|
|
247
|
+
const FEAST_QUERY_KEYS = new Set([
|
|
248
|
+
"date", "from", "to", "nomen", "season", "grade", "marian", "apostolic",
|
|
249
|
+
"before",
|
|
250
|
+
]);
|
|
211
251
|
/**
|
|
212
252
|
* Calendar lookup (`tonus.festum`). Returns matching feasts sorted
|
|
213
253
|
* `day asc, rank desc` — for a date, the primary feast plus concurrent
|
|
214
254
|
* feasts; for a `from`/`to` range, every day flattened; with no query,
|
|
215
|
-
* the
|
|
216
|
-
*
|
|
255
|
+
* the default-epoch day (Guido d'Arezzo's era); for a filter-only query,
|
|
256
|
+
* the liturgical year containing that epoch. Dates are UTC-canonical:
|
|
257
|
+
* build them from ISO strings or `Date.UTC`.
|
|
217
258
|
*/
|
|
218
259
|
export function getFeast(query) {
|
|
260
|
+
// A bare Date is the natural guess, and it has no own enumerable keys — so
|
|
261
|
+
// without this it would read as "no query" and quietly return the default
|
|
262
|
+
// epoch, the same plausible-looking wrong answer the unknown-key guard below
|
|
263
|
+
// exists to prevent. Say what was meant instead.
|
|
264
|
+
if (query instanceof Date) {
|
|
265
|
+
throw new Error(`festum: pass the date as a query — festum({ date }), not festum(date)`);
|
|
266
|
+
}
|
|
219
267
|
if (!query || Object.keys(query).length === 0) {
|
|
220
268
|
return feastsForDate(DEFAULT_EPOCH);
|
|
221
269
|
}
|
|
270
|
+
// Reject unknown keys rather than silently falling through to the default
|
|
271
|
+
// epoch — `festum({ month: 12, day: 25 })` (a natural guess) would otherwise
|
|
272
|
+
// return a plausible-looking wrong answer. Fail loudly on the typo instead.
|
|
273
|
+
const unknown = Object.keys(query).filter((k) => !FEAST_QUERY_KEYS.has(k));
|
|
274
|
+
if (unknown.length > 0) {
|
|
275
|
+
throw new Error(`festum: unknown query key(s) ${unknown.map((k) => `"${k}"`).join(", ")} ` +
|
|
276
|
+
`(expected ${[...FEAST_QUERY_KEYS].join(", ")})`);
|
|
277
|
+
}
|
|
222
278
|
let results;
|
|
223
279
|
if (query.date) {
|
|
280
|
+
if (!(query.date instanceof Date) || Number.isNaN(query.date.getTime()))
|
|
281
|
+
throw new Error(`festum: date must be a Date — e.g. new Date("2026-12-25") (UTC-canonical)`);
|
|
224
282
|
results = feastsForDate(query.date);
|
|
225
283
|
}
|
|
226
284
|
else if (query.from != null || query.to != null) {
|
|
@@ -256,6 +314,24 @@ export function getFeast(query) {
|
|
|
256
314
|
d = addDays(d, 1);
|
|
257
315
|
}
|
|
258
316
|
}
|
|
317
|
+
// `before` resolves the day AS OF a year: feasts instituted later step aside,
|
|
318
|
+
// and whatever ranked behind them — usually the temporale or the feria — wins
|
|
319
|
+
// instead. The calendar DATA is untouched; this is a view over it, so a caller
|
|
320
|
+
// asking for 1350 and a caller asking for 1962 read the same shipped table.
|
|
321
|
+
// A day whose every candidate is later than `before` returns empty, which is
|
|
322
|
+
// the honest answer: that day had no feast yet.
|
|
323
|
+
if (query.before != null) {
|
|
324
|
+
if (!Number.isFinite(query.before)) {
|
|
325
|
+
throw new Error(`festum: before must be a year — e.g. festum({ date, before: 1350 })`);
|
|
326
|
+
}
|
|
327
|
+
results = results
|
|
328
|
+
.filter((f) => feastKeptBy(f.id, query.before))
|
|
329
|
+
// Stamp the view on the survivors. The chant verbs read it back, so one
|
|
330
|
+
// `before` at the calendar door carries through the whole day — the
|
|
331
|
+
// calendar as of 1100 serves the repertoire attested by 1100, without
|
|
332
|
+
// the caller saying the year twice. A `before` of their own overrides.
|
|
333
|
+
.map((f) => ({ ...f, before: query.before }));
|
|
334
|
+
}
|
|
259
335
|
if (query.nomen) {
|
|
260
336
|
const n = query.nomen.toLowerCase();
|
|
261
337
|
results = results.filter((f) => f.nomen.toLowerCase().includes(n));
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export interface FeastEra {
|
|
2
|
+
/** Year of universal Roman observance — the year it starts winning days. */
|
|
3
|
+
year: number;
|
|
4
|
+
/** Earlier local/regional observance, where one is well attested. */
|
|
5
|
+
local?: number;
|
|
6
|
+
/** Why the date is what it is, and where it is shaky. */
|
|
7
|
+
note?: string;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Feast id → when it entered the universal calendar. Ids are the calendar's own
|
|
11
|
+
* (`MM-DD` for the sanctorale, season-relative for the temporale).
|
|
12
|
+
*
|
|
13
|
+
* INTERNAL, ruled 2026-08-04. This table does not join the public appendix and
|
|
14
|
+
* is not re-exported from the index, though it is the last table that could
|
|
15
|
+
* plausibly have. The appendix carries measurements and vocabularies; these are
|
|
16
|
+
* forty-three liturgical-historical CLAIMS, and the distinction between `year`
|
|
17
|
+
* (universal Roman observance) and `local` is a judgement about what a caller
|
|
18
|
+
* is modelling rather than a fact about the calendar — see the banner above.
|
|
19
|
+
* Publishing it would invite callers to cite dates tonus is not in a position
|
|
20
|
+
* to defend. The behaviour it drives, `festum({ before })`, is public and
|
|
21
|
+
* documented; the reckoning behind it stays here.
|
|
22
|
+
*/
|
|
23
|
+
export declare const FEAST_ERAS: Readonly<Record<string, FeastEra>>;
|
|
24
|
+
/**
|
|
25
|
+
* Was this feast kept by `year`? A feast the table does not mention is treated
|
|
26
|
+
* as old enough — the table lists arrivals, not the whole calendar.
|
|
27
|
+
*/
|
|
28
|
+
export declare function feastKeptBy(feastId: string, year: number): boolean;
|
|
29
|
+
/**
|
|
30
|
+
* The same question asked of a LOCAL observance: a feast kept somewhere by
|
|
31
|
+
* `year`, even if Rome had not yet taken it up. Use this when modelling a house
|
|
32
|
+
* rather than the universal calendar.
|
|
33
|
+
*/
|
|
34
|
+
export declare function feastKeptLocallyBy(feastId: string, year: number): boolean;
|
|
35
|
+
//# sourceMappingURL=eras.d.ts.map
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// engines/cal/data/eras — when a feast entered the calendar
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
// The shipped calendar is Tridentine, so it carries feasts instituted centuries
|
|
5
|
+
// after the repertoire tonus models. This table dates the late arrivals so a
|
|
6
|
+
// caller can ask for the day AS OF a chosen year — `festum({ date, before })` —
|
|
7
|
+
// and let the temporale or the feria win where a later feast would have taken
|
|
8
|
+
// the day.
|
|
9
|
+
//
|
|
10
|
+
// tonus-corpus keeps shipping every calendar day; nothing is cut from the data.
|
|
11
|
+
// This is a VIEW over it. A feast absent from this table is treated as old
|
|
12
|
+
// enough to keep, so the table only ever needs the late arrivals.
|
|
13
|
+
//
|
|
14
|
+
// ── EVERY DATE HERE WANTS A HISTORIAN'S RED PEN ─────────────────────────────
|
|
15
|
+
// These are liturgical-historical claims, not measurements. `year` is the date
|
|
16
|
+
// of UNIVERSAL observance in the Roman calendar (the point at which the feast
|
|
17
|
+
// would displace a feria everywhere), because that is what a day-resolution
|
|
18
|
+
// question actually turns on. Where a local observance long predates the
|
|
19
|
+
// universal one, `local` records it — several feasts are genuinely medieval in
|
|
20
|
+
// one diocese and early-modern in Rome, and which one you want is a judgement
|
|
21
|
+
// about what you are modelling, not a fact.
|
|
22
|
+
//
|
|
23
|
+
// Deliberately NOT listed, though a name-pattern sweep flagged them:
|
|
24
|
+
// · 05-08 In Apparitione S. Michaëlis — the Monte Gargano apparition is 5th c.
|
|
25
|
+
// and the feast early medieval. Old; keep.
|
|
26
|
+
// · 02-01 S. Ignatii Episcopi et Martyris — Ignatius of ANTIOCH, d. c. 107.
|
|
27
|
+
// Not Ignatius Loyola. Keep.
|
|
28
|
+
// · 09-11 Ss. Proti et Hyacinthi — ancient Roman martyrs. Not Hyacinth of
|
|
29
|
+
// Poland (who is listed, at 08-17). Keep.
|
|
30
|
+
// Those three are the traps in dating this list by name.
|
|
31
|
+
//
|
|
32
|
+
// ── KEPT DELIBERATELY, AGAINST THE STRICT READING ───────────────────────────
|
|
33
|
+
// · 08-06 In Transfiguratione Domini — Callixtus III made it universal in 1457,
|
|
34
|
+
// but it is an ancient Eastern feast with local Western observance long
|
|
35
|
+
// before. Old school; keep.
|
|
36
|
+
// · 07-02 In Visitatione BMV — 1389 is Urban VI's UNIVERSAL extension (prayed
|
|
37
|
+
// for the end of the Great Schism). St Bonaventure had already instituted it
|
|
38
|
+
// for the Franciscans in 1263 — genuinely inside a pre-1350 line, so the
|
|
39
|
+
// keep is supported by the record, not only by instinct.
|
|
40
|
+
// · 02-08 S. Joannis de Matha and 11-20 S. Felicis de Valois — the Trinitarian
|
|
41
|
+
// founders, both dead c. 1212–13. Their cult was confirmed late, but the
|
|
42
|
+
// saints and their veneration are medieval. Keep.
|
|
43
|
+
// Each of these would come back under a stricter reading of "universal
|
|
44
|
+
// observance"; they are out because the feast, not the paperwork, is what a
|
|
45
|
+
// singing calendar is modelling.
|
|
46
|
+
/**
|
|
47
|
+
* Feast id → when it entered the universal calendar. Ids are the calendar's own
|
|
48
|
+
* (`MM-DD` for the sanctorale, season-relative for the temporale).
|
|
49
|
+
*
|
|
50
|
+
* INTERNAL, ruled 2026-08-04. This table does not join the public appendix and
|
|
51
|
+
* is not re-exported from the index, though it is the last table that could
|
|
52
|
+
* plausibly have. The appendix carries measurements and vocabularies; these are
|
|
53
|
+
* forty-three liturgical-historical CLAIMS, and the distinction between `year`
|
|
54
|
+
* (universal Roman observance) and `local` is a judgement about what a caller
|
|
55
|
+
* is modelling rather than a fact about the calendar — see the banner above.
|
|
56
|
+
* Publishing it would invite callers to cite dates tonus is not in a position
|
|
57
|
+
* to defend. The behaviour it drives, `festum({ before })`, is public and
|
|
58
|
+
* documented; the reckoning behind it stays here.
|
|
59
|
+
*/
|
|
60
|
+
export const FEAST_ERAS = Object.freeze({
|
|
61
|
+
// ── Devotional feasts of the Lord and of Our Lady ──────────────────────────
|
|
62
|
+
"Nat2-0": { year: 1721, local: 1530, note: "Holy Name of Jesus; Franciscan/local 16th c." },
|
|
63
|
+
"Epi1-0": { year: 1921, local: 1893, note: "Holy Family; Leo XIII then Benedict XV" },
|
|
64
|
+
"02-11": { year: 1907, note: "Apparition of the Immaculate Virgin (Lourdes)" },
|
|
65
|
+
"Quad5-5": { year: 1727, local: 1423, note: "Seven Sorrows, Friday of Passion week; Cologne 1423" },
|
|
66
|
+
"Pent02-5": { year: 1856, local: 1765, note: "Sacred Heart; Clement XIII grant 1765, Pius IX universal" },
|
|
67
|
+
"07-01": { year: 1849, note: "Precious Blood (Pius IX)" },
|
|
68
|
+
"07-16": { year: 1726, local: 1376, note: "Our Lady of Mount Carmel; Carmelite from c. 1376" },
|
|
69
|
+
"08-22": { year: 1944, note: "Immaculate Heart (Pius XII)" },
|
|
70
|
+
"09-15": { year: 1727, local: 1423, note: "Seven Sorrows (September)" },
|
|
71
|
+
"09-24": { year: 1696, note: "Our Lady of Ransom / de Mercede" },
|
|
72
|
+
"10-02": { year: 1670, local: 1608, note: "Guardian Angels (Clement X)" },
|
|
73
|
+
"10-07": { year: 1716, local: 1573, note: "Rosary; Gregory XIII 1573 after Lepanto, Clement XI universal" },
|
|
74
|
+
"10-11": { year: 1931, note: "Divine Maternity (Pius XI, Ephesus centenary)" },
|
|
75
|
+
"11-21": { year: 1585, local: 1372, note: "Presentation of Our Lady; Avignon papal court 1372" },
|
|
76
|
+
"09-17": { year: 1585, note: "Stigmata of St Francis" },
|
|
77
|
+
// ── Saints venerated after ~1350 ───────────────────────────────────────────
|
|
78
|
+
// Dated by canonization or by entry into the universal calendar, whichever is
|
|
79
|
+
// the later — again, these want checking against the sources.
|
|
80
|
+
"03-04": { year: 1521, note: "Casimir of Poland, d. 1484" },
|
|
81
|
+
"08-17": { year: 1594, note: "Hyacinth of Poland, d. 1257 — cult approved late" },
|
|
82
|
+
"05-05": { year: 1712, note: "Pius V, d. 1572" },
|
|
83
|
+
"10-15": { year: 1622, note: "Teresa of Ávila, d. 1582" },
|
|
84
|
+
"11-24": { year: 1726, note: "John of the Cross, d. 1591" },
|
|
85
|
+
"07-31": { year: 1622, note: "Ignatius Loyola, d. 1556" },
|
|
86
|
+
"12-03": { year: 1622, note: "Francis Xavier, d. 1552" },
|
|
87
|
+
"05-26": { year: 1622, note: "Philip Neri, d. 1595 — id verified against the calendar" },
|
|
88
|
+
"01-29": { year: 1665, note: "Francis de Sales, d. 1622" },
|
|
89
|
+
"07-19": { year: 1737, note: "Vincent de Paul, d. 1660" },
|
|
90
|
+
"08-02": { year: 1839, note: "Alphonsus Liguori, d. 1787" },
|
|
91
|
+
"06-21": { year: 1726, note: "Aloysius Gonzaga, d. 1591" },
|
|
92
|
+
"05-13": { year: 1930, note: "Robert Bellarmine, d. 1621" },
|
|
93
|
+
"04-27": { year: 1925, note: "Peter Canisius, d. 1597" },
|
|
94
|
+
"08-07": { year: 1671, note: "Cajetan, d. 1547" },
|
|
95
|
+
"03-08": { year: 1690, note: "John of God, d. 1550" },
|
|
96
|
+
"07-18": { year: 1746, note: "Camillus de Lellis, d. 1614" },
|
|
97
|
+
"08-27": { year: 1767, note: "Joseph Calasanz, d. 1648" },
|
|
98
|
+
"08-19": { year: 1925, note: "John Eudes, d. 1680" },
|
|
99
|
+
"04-28": { year: 1867, note: "Paul of the Cross, d. 1775" },
|
|
100
|
+
"05-17": { year: 1690, note: "Paschal Baylon, d. 1592" },
|
|
101
|
+
"10-20": { year: 1767, note: "John Cantius, d. 1473" },
|
|
102
|
+
"11-14": { year: 1867, note: "Josaphat, d. 1623" },
|
|
103
|
+
"10-17": { year: 1920, note: "Margaret Mary Alacoque, d. 1690" },
|
|
104
|
+
"01-31": { year: 1934, note: "John Bosco, d. 1888" },
|
|
105
|
+
"02-27": { year: 1920, note: "Gabriel of Our Lady of Sorrows, d. 1862" },
|
|
106
|
+
"09-03": { year: 1954, note: "Pius X, d. 1914" },
|
|
107
|
+
"03-24": { year: 1921, note: "St Gabriel Archangel as a separate feast; the archangel is of course ancient" },
|
|
108
|
+
});
|
|
109
|
+
/**
|
|
110
|
+
* Was this feast kept by `year`? A feast the table does not mention is treated
|
|
111
|
+
* as old enough — the table lists arrivals, not the whole calendar.
|
|
112
|
+
*/
|
|
113
|
+
export function feastKeptBy(feastId, year) {
|
|
114
|
+
const era = FEAST_ERAS[feastId];
|
|
115
|
+
return !era || era.year <= year;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* The same question asked of a LOCAL observance: a feast kept somewhere by
|
|
119
|
+
* `year`, even if Rome had not yet taken it up. Use this when modelling a house
|
|
120
|
+
* rather than the universal calendar.
|
|
121
|
+
*/
|
|
122
|
+
export function feastKeptLocallyBy(feastId, year) {
|
|
123
|
+
const era = FEAST_ERAS[feastId];
|
|
124
|
+
if (!era)
|
|
125
|
+
return true;
|
|
126
|
+
return Math.min(era.year, era.local ?? era.year) <= year;
|
|
127
|
+
}
|
|
128
|
+
//# sourceMappingURL=eras.js.map
|
package/dist/engines/cal/date.js
CHANGED
|
@@ -91,6 +91,50 @@ export function resolveEntryId(id, year, anchors) {
|
|
|
91
91
|
if (/^\d{2}-\d{2}$/.test(id)) {
|
|
92
92
|
return [place(startOfDay(parseMonthDay(year, id)))];
|
|
93
93
|
}
|
|
94
|
+
// The Christmas-octave days are DATE-stemmed, not week-stemmed:
|
|
95
|
+
// Nat26–Nat31 = Dec 26–31 ("Diei VI infra Octavam Nativitatis"). Dec 30
|
|
96
|
+
// has no saint, so without Nat30 the date resolved to nothing at all.
|
|
97
|
+
const nat = id.match(/^Nat(2[6-9]|3[01])$/);
|
|
98
|
+
if (nat) {
|
|
99
|
+
return [place(startOfDay(new Date(Date.UTC(year, 11, Number(nat[1])))))];
|
|
100
|
+
}
|
|
101
|
+
const m = id.match(/^([A-Za-z]+)(\d+)-(\d+)$/);
|
|
102
|
+
if (!m)
|
|
103
|
+
throw new Error(`Unrecognized tempora stem: ${id}`);
|
|
104
|
+
const prefix = m[1];
|
|
105
|
+
const week = parseInt(m[2], 10);
|
|
106
|
+
const weekday = parseInt(m[3], 10);
|
|
107
|
+
// The season after Pentecost is ELASTIC (23–28 Sundays), and the book's
|
|
108
|
+
// rule is not linear: DO's Pent24 is "Dominica XXIV et ultima" — ALWAYS
|
|
109
|
+
// the last Sunday before Advent — and the surplus Sundays between the 23rd
|
|
110
|
+
// and the last resume the post-Epiphany Sundays omitted in January, in
|
|
111
|
+
// their order, ending at the sixth. Linear placement stranded the November
|
|
112
|
+
// tail with no tempora at all (the 11-18 / 11-27 / 11-28 holes of 2026).
|
|
113
|
+
const lastSunday = addDays(anchors.adventFirstSunday, -7);
|
|
114
|
+
const sundaysAfterPentecost = Math.round((lastSunday.getTime() - anchors.pentecost.getTime()) / (7 * 86400000));
|
|
115
|
+
if (prefix === "Pent") {
|
|
116
|
+
// Dominica ultima: anchored to Advent, not to Pentecost.
|
|
117
|
+
if (week === 24)
|
|
118
|
+
return [place(addDays(lastSunday, weekday))];
|
|
119
|
+
// In a 23-Sunday year the ultima falls ON the 23rd slot and sings the
|
|
120
|
+
// 24th's mass — the 23rd steps aside rather than double-booking the day.
|
|
121
|
+
if (week >= sundaysAfterPentecost)
|
|
122
|
+
return [];
|
|
123
|
+
return [place(addDays(anchors.pentecost, week * 7 + weekday))];
|
|
124
|
+
}
|
|
125
|
+
if (prefix === "Epi") {
|
|
126
|
+
// Always the January placement…
|
|
127
|
+
const placements = [place(resolveTemporaStem(id, anchors))];
|
|
128
|
+
// …and, in a long year, the autumn resumption: with S Sundays after
|
|
129
|
+
// Pentecost, the surplus g = S − 24 weeks between the 23rd and the
|
|
130
|
+
// ultima take Epi(7−g) … Epi6, in their order.
|
|
131
|
+
const surplus = sundaysAfterPentecost - 24;
|
|
132
|
+
if (surplus > 0 && week >= 7 - surplus && week <= 6) {
|
|
133
|
+
const sundayIndex = 24 + (week - (7 - surplus));
|
|
134
|
+
placements.push(place(addDays(anchors.pentecost, sundayIndex * 7 + weekday)));
|
|
135
|
+
}
|
|
136
|
+
return placements;
|
|
137
|
+
}
|
|
94
138
|
return [place(resolveTemporaStem(id, anchors))];
|
|
95
139
|
}
|
|
96
140
|
function resolveTemporaStem(stem, anchors) {
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
export type Season = "adv" | "nat" | "epi" | "quadp" | "quad" | "pasc" | "pent";
|
|
2
|
-
export declare const
|
|
3
|
-
export declare const
|
|
2
|
+
export declare const SEASON_LABEL: Readonly<Record<Season, string>>;
|
|
3
|
+
export declare const TEMPORA: Readonly<Record<Season, string>>;
|
|
4
4
|
export declare const PENITENTIAL_SEASONS: ReadonlySet<Season>;
|
|
5
5
|
export type Grade = "triduum" | "duplex-i" | "duplex-majus-i" | "semiduplex-i" | "feria-privilegiata" | "duplex-ii" | "semiduplex-ii" | "duplex-majus" | "duplex" | "semiduplex" | "simplex" | "feria-major" | "vigilia" | "feria";
|
|
6
6
|
export declare const GRADE_ORDER: readonly Grade[];
|
|
7
|
-
export declare const
|
|
7
|
+
export declare const GRADUS: Readonly<Record<Grade, string>>;
|
|
8
8
|
export declare const RITUS_TO_GRADE: Readonly<Record<string, Grade>>;
|
|
9
9
|
/** Reduce a Tridentine ritus string to its canonical Grade. */
|
|
10
10
|
export declare function ritusToGrade(ritus: string): Grade;
|
|
@@ -34,6 +34,11 @@ export interface Feast {
|
|
|
34
34
|
masses: number[];
|
|
35
35
|
marian: boolean;
|
|
36
36
|
apostolic: boolean;
|
|
37
|
+
/** The era view this feast was resolved under — the `before` year given to
|
|
38
|
+
* festum. Carried on the feast so the chant verbs (proprium, ordinarium,
|
|
39
|
+
* officium) serve the SAME view without being told twice; a `before` on
|
|
40
|
+
* their own query overrides. Absent on a present-day resolution. */
|
|
41
|
+
before?: number;
|
|
37
42
|
}
|
|
38
43
|
export interface FeastQuery {
|
|
39
44
|
date?: Date;
|
|
@@ -44,6 +49,13 @@ export interface FeastQuery {
|
|
|
44
49
|
grade?: Grade;
|
|
45
50
|
marian?: boolean;
|
|
46
51
|
apostolic?: boolean;
|
|
52
|
+
/**
|
|
53
|
+
* Resolve the day AS OF this year: feasts instituted later step aside and
|
|
54
|
+
* whatever ranked behind them wins — usually the temporale or the feria. A
|
|
55
|
+
* view over the shipped calendar, which keeps every day either way. Dates
|
|
56
|
+
* live in cal/data/eras.ts.
|
|
57
|
+
*/
|
|
58
|
+
before?: number;
|
|
47
59
|
}
|
|
48
60
|
export interface Pascha {
|
|
49
61
|
year: number;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// ---------------------------------------------------------------------------
|
|
2
2
|
// engines/cal/types — calendar types and constants
|
|
3
3
|
// ---------------------------------------------------------------------------
|
|
4
|
-
export const
|
|
4
|
+
export const SEASON_LABEL = Object.freeze({
|
|
5
5
|
adv: "Advent",
|
|
6
6
|
nat: "Christmastide",
|
|
7
7
|
epi: "Time after Epiphany",
|
|
@@ -11,8 +11,8 @@ export const SEASON_LABELS = Object.freeze({
|
|
|
11
11
|
pent: "Time after Pentecost",
|
|
12
12
|
});
|
|
13
13
|
// Authentic Latin season names (the books' own headings). Feast.tempus
|
|
14
|
-
// carries these;
|
|
15
|
-
export const
|
|
14
|
+
// carries these; SEASON_LABEL above is the English reference map.
|
|
15
|
+
export const TEMPORA = Object.freeze({
|
|
16
16
|
adv: "Tempus Adventus",
|
|
17
17
|
nat: "Tempus Nativitatis",
|
|
18
18
|
epi: "Tempus post Epiphaniam",
|
|
@@ -46,12 +46,12 @@ export const GRADE_ORDER = [
|
|
|
46
46
|
"vigilia",
|
|
47
47
|
"feria",
|
|
48
48
|
];
|
|
49
|
-
// Canonical Latin name per grade (display reference map, like
|
|
49
|
+
// Canonical Latin name per grade (display reference map, like SEASON_LABEL).
|
|
50
50
|
// Feast objects don't carry this — `ritus` is the on-object Latin carrier;
|
|
51
51
|
// use this map when the canonical grade name is wanted instead (it differs
|
|
52
52
|
// from ritus only for octave compounds, the Triduum, and the privileged-
|
|
53
53
|
// Sunday overrides).
|
|
54
|
-
export const
|
|
54
|
+
export const GRADUS = Object.freeze({
|
|
55
55
|
triduum: "Triduum Sacrum",
|
|
56
56
|
"duplex-i": "Duplex I classis",
|
|
57
57
|
"duplex-majus-i": "Duplex majus I classis",
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Census, CensusQuery } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* The census of one chant: its profile against the corpus, where it is
|
|
4
|
+
* unusual, and what it is near.
|
|
5
|
+
*/
|
|
6
|
+
export declare function getCensus(query: CensusQuery): Census;
|
|
7
|
+
//# sourceMappingURL=census.d.ts.map
|