tonus 0.1.1 → 0.1.2
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/dist/engines/chant/hour.js +36 -22
- package/package.json +1 -1
|
@@ -14,6 +14,17 @@ function romanMap() {
|
|
|
14
14
|
_roman = new Map(OFFICE_ROMAN.map((d) => [d.feastId, d]));
|
|
15
15
|
return _roman;
|
|
16
16
|
}
|
|
17
|
+
// Hours whose result is an ordered sequence (an ordo) rather than a set of
|
|
18
|
+
// chants — they keep assembly order instead of being sorted by incipit.
|
|
19
|
+
const ORDERED_ORDO_HOURS = new Set([
|
|
20
|
+
"prima", "tertia", "sexta", "nona", "completorium",
|
|
21
|
+
]);
|
|
22
|
+
// The purely seasonal/fixed hours — identical for every feast of a day, so
|
|
23
|
+
// concurrent feasts collapse to one and a no-feast query resolves the default
|
|
24
|
+
// epoch. (Terce/Sext/None are NOT here: their responsory breve is per-feast.)
|
|
25
|
+
const SEASONAL_ORDO_HOURS = new Set([
|
|
26
|
+
"prima", "completorium",
|
|
27
|
+
]);
|
|
17
28
|
// Compline is fixed and seasonal, not per-feast: it does not use the OfficeDay
|
|
18
29
|
// tables at all. The ordo is assembled from the season (Te lucis, In manus
|
|
19
30
|
// tuas), the fixed psalms (from the extracted DO scheme), the invariable spine
|
|
@@ -92,18 +103,21 @@ function chantsForFeastHour(feast, hour) {
|
|
|
92
103
|
if (hy)
|
|
93
104
|
results.push(hy);
|
|
94
105
|
}
|
|
95
|
-
else if (hour === "tertia") {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
106
|
+
else if (hour === "tertia" || hour === "sexta" || hour === "nona") {
|
|
107
|
+
// The little hours: their portion of Ps 118 (Terce vv. 33–80, Sext 81–128,
|
|
108
|
+
// None 129–176, from the extracted DO scheme), then the responsory breve.
|
|
109
|
+
// The psalmody belongs to a specific day, so it is only included for a real
|
|
110
|
+
// feast query — not the all-days survey scan (which has no date and would
|
|
111
|
+
// repeat the psalms once per feast).
|
|
112
|
+
if (feast.date) {
|
|
113
|
+
const hourName = hour === "tertia" ? "Tertia" : hour === "sexta" ? "Sexta" : "Nona";
|
|
114
|
+
for (const p of officePsalmPortions(hourName, feast.weekday)) {
|
|
115
|
+
results.push(...intonePortion(p));
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
const rb = resolveChant(hour === "tertia" ? day.respBreveTertia
|
|
119
|
+
: hour === "sexta" ? day.respBreveSexta
|
|
120
|
+
: day.respBreveNona);
|
|
107
121
|
if (rb)
|
|
108
122
|
results.push(rb);
|
|
109
123
|
}
|
|
@@ -138,10 +152,9 @@ export function getHour(query) {
|
|
|
138
152
|
// Prime and Compline are seasonal/weekday ordos, identical for every feast
|
|
139
153
|
// of the day — so concurrent feasts collapse to a single ordo rather than
|
|
140
154
|
// repeating it. The other hours are genuinely per-feast.
|
|
141
|
-
results =
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
: feasts.flatMap((f) => chantsForFeastHour(f, hour));
|
|
155
|
+
results = SEASONAL_ORDO_HOURS.has(hour)
|
|
156
|
+
? feasts[0] ? chantsForFeastHour(feasts[0], hour) : []
|
|
157
|
+
: feasts.flatMap((f) => chantsForFeastHour(f, hour));
|
|
145
158
|
}
|
|
146
159
|
else if (feasts) {
|
|
147
160
|
const hours = [
|
|
@@ -150,14 +163,15 @@ export function getHour(query) {
|
|
|
150
163
|
];
|
|
151
164
|
results = feasts.flatMap((f) => hours.flatMap((h) => chantsForFeastHour(f, h)));
|
|
152
165
|
}
|
|
153
|
-
else if (hour
|
|
166
|
+
else if (hour && SEASONAL_ORDO_HOURS.has(hour)) {
|
|
154
167
|
// Prime and Compline are seasonal ordos, not per-feast. With no feast,
|
|
155
168
|
// resolve for the default epoch (Guido d'Arezzo's era) — festum()'s anchor.
|
|
156
169
|
const [feast] = getFeast();
|
|
157
170
|
results = feast ? chantsForFeastHour(feast, hour) : [];
|
|
158
171
|
}
|
|
159
172
|
else if (hour) {
|
|
160
|
-
// Hour without feast —
|
|
173
|
+
// Hour without feast — survey per-feast content across all office entries.
|
|
174
|
+
// mockFeast has no date, so the little hours return only their responsories.
|
|
161
175
|
results = OFFICE_ROMAN.flatMap((day) => {
|
|
162
176
|
const mockFeast = { id: day.feastId };
|
|
163
177
|
return chantsForFeastHour(mockFeast, hour);
|
|
@@ -190,10 +204,10 @@ export function getHour(query) {
|
|
|
190
204
|
const ids = new Set(toArray(query.id));
|
|
191
205
|
results = results.filter((c) => ids.has(c.id));
|
|
192
206
|
}
|
|
193
|
-
//
|
|
194
|
-
// they keep assembly order unless the caller explicitly asks for
|
|
195
|
-
//
|
|
196
|
-
const isOrderedOrdo = query.hora
|
|
207
|
+
// The little hours and Compline are ordered ordos — their sequence IS the
|
|
208
|
+
// content — so they keep assembly order unless the caller explicitly asks for
|
|
209
|
+
// a sort. The other hours return a set of chants, sorted by incipit.
|
|
210
|
+
const isOrderedOrdo = query.hora != null && ORDERED_ORDO_HOURS.has(query.hora);
|
|
197
211
|
if (query.sort || !isOrderedOrdo) {
|
|
198
212
|
const sort = query.sort ?? "incipit";
|
|
199
213
|
results.sort((a, b) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tonus",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "Medieval music analysis and performance: GABC plainchant exports, liturgical calendar, tuning systems, ephemeris, and the harmony of the spheres",
|