tonus 0.10.2 → 0.10.3

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/CHANGELOG.md CHANGED
@@ -2,6 +2,26 @@
2
2
 
3
3
  All notable changes to tonus. Newest first.
4
4
 
5
+ ## 0.10.3 — 2026-09-04
6
+
7
+ The close ritards.
8
+
9
+ ### Changed
10
+
11
+ - **The close now ritards.** Two faults in the phrasing made every phrase
12
+ hurry into its cadence. The duration arch was a symmetric window, 0 at both
13
+ ends, so the second half of a phrase ran quicker than its middle; and the
14
+ cadence lengthened only the one note before the bar, by a term small enough
15
+ that the arch's fall ate it. A full close was held no longer than a comma,
16
+ and an ictus two notes earlier often outlasted the final note. The duration
17
+ arch now rises and holds — the subsiding is a lightening, not a hurrying —
18
+ and the `accentus` profile gains a rallentando: `ritard` (its depth),
19
+ `ritardNotes` (its width), `ritardCurve` (how late it gathers), graded by
20
+ the divisio ladder so a comma leans, a double bar broadens, a breath moves
21
+ nothing, with `finisBoost` on the piece's own last `::`. `shapedDuration`
22
+ changes on the second half of every phrase; `duration` and `velocity` do
23
+ not. Override `ritard: 0` to remove the window.
24
+
5
25
  ## 0.10.2 — 2026-09-02
6
26
 
7
27
  A stub is not a chant.
@@ -6,6 +6,10 @@ const PHRASING_PROFILES = {
6
6
  curve: 0.1,
7
7
  accent: 0.5,
8
8
  cadence: 0.1,
9
+ ritard: 0.25,
10
+ ritardNotes: 2,
11
+ ritardCurve: 1.6,
12
+ finisBoost: 1.5,
9
13
  tenor: 2.3,
10
14
  baseVelocity: 0.65,
11
15
  contourVel: 0.08,
@@ -19,6 +23,10 @@ const PHRASING_PROFILES = {
19
23
  curve: 0.35,
20
24
  accent: 0.8,
21
25
  cadence: 0.15,
26
+ ritard: 0.4,
27
+ ritardNotes: 3,
28
+ ritardCurve: 1.6,
29
+ finisBoost: 1.5,
22
30
  tenor: 1.2,
23
31
  baseVelocity: 0.68,
24
32
  contourVel: 0.18,
@@ -32,6 +40,10 @@ const PHRASING_PROFILES = {
32
40
  curve: 0.25,
33
41
  accent: 0.6,
34
42
  cadence: 0.12,
43
+ ritard: 0.35,
44
+ ritardNotes: 3,
45
+ ritardCurve: 1.6,
46
+ finisBoost: 1.5,
35
47
  tenor: 0.9,
36
48
  baseVelocity: 0.69,
37
49
  contourVel: 0.14,
@@ -45,6 +57,10 @@ const PHRASING_PROFILES = {
45
57
  curve: 0.5,
46
58
  accent: 1,
47
59
  cadence: 0.2,
60
+ ritard: 0.5,
61
+ ritardNotes: 4,
62
+ ritardCurve: 1.6,
63
+ finisBoost: 1.5,
48
64
  tenor: 1.7,
49
65
  baseVelocity: 0.71,
50
66
  contourVel: 0.22,
@@ -207,8 +223,8 @@ export function applyPhrasing(events, profile, tenorPc) {
207
223
  }
208
224
  if (currentPhrase.length > 0)
209
225
  phrases.push(currentPhrase);
210
- for (const phrase of phrases) {
211
- const noteEntries = phrase;
226
+ for (let p = 0; p < phrases.length; p++) {
227
+ const noteEntries = phrases[p];
212
228
  if (noteEntries.length === 0)
213
229
  continue;
214
230
  let minArsis = Infinity, maxArsis = -Infinity;
@@ -260,14 +276,36 @@ export function applyPhrasing(events, profile, tenorPc) {
260
276
  velocity *=
261
277
  1 - profile.cadence * divisioStrength * CADENCE_VELOCITY_FACTOR;
262
278
  const finalVelocity = Math.max(VELOCITY_MIN, Math.min(VELOCITY_MAX, velocity * profile.baseVelocity));
279
+ // The duration arch is one-sided: it rises with the velocity arch over
280
+ // the first half and then HOLDS. The grand rythme's subsiding is a
281
+ // lightening, not a hurrying — a symmetric window here made every phrase
282
+ // accelerate into its cadence.
283
+ const durArch = t < 0.5 ? arch : 1;
263
284
  const baseDur = note.performance.duration;
264
285
  let durFactor = DURATION_BASE_FACTOR +
265
286
  arsisRelative * DURATION_ARSIS_FACTOR +
266
- arch * DURATION_ARCH_FACTOR;
287
+ durArch * DURATION_ARCH_FACTOR;
267
288
  durFactor += (contourRelative - VELOCITY_CENTER) * profile.contourDur;
268
289
  if (note.context.ictus)
269
290
  durFactor *= profile.ictusBoost;
270
291
  durFactor += profile.cadence * divisioStrength * CADENCE_DURATION_FACTOR;
292
+ // The rallentando into the close. The cadence term above lengthens only
293
+ // the one note before the bar, so without a window a full close was held
294
+ // no longer than a comma, and an ictus two notes earlier often outlasted
295
+ // the final note. The window covers the phrase's last `ritardNotes` (or
296
+ // all of a shorter phrase), rising 0 → 1 toward the bar along a power
297
+ // curve; the divisio ladder grades it, so a comma leans, a double bar
298
+ // broadens, a breath moves nothing. The piece's own last bar is not just
299
+ // another double bar. A ritard is a tempo scaling, so it multiplies.
300
+ const closing = noteEntries[noteEntries.length - 1].nextDivisio;
301
+ const span = Math.min(Math.max(1, Math.round(profile.ritardNotes)), noteEntries.length);
302
+ const tail = order - (noteEntries.length - span);
303
+ if (tail >= 0) {
304
+ const ramp = ((tail + 1) / span) ** profile.ritardCurve;
305
+ const finis = p === phrases.length - 1 && closing === "::" ? profile.finisBoost : 1;
306
+ durFactor *=
307
+ 1 + profile.ritard * getDivisioStrength(closing) * finis * ramp;
308
+ }
271
309
  const shapedDuration = Math.max(DURATION_MIN, Math.min(DURATION_MAX, baseDur * durFactor));
272
310
  shaped.push({
273
311
  ...note,
@@ -132,6 +132,14 @@ export interface PhrasingProfile {
132
132
  curve: number;
133
133
  accent: number;
134
134
  cadence: number;
135
+ /** The rallentando into a close: how far its last notes broaden, graded by the bar. */
136
+ ritard: number;
137
+ /** How many closing notes the rallentando covers. */
138
+ ritardNotes: number;
139
+ /** The ramp's power: 1 is linear, above 1 gathers the broadening late, like a singer. */
140
+ ritardCurve: number;
141
+ /** A multiplier on the piece's own last `::` — the end of the chant over any other double bar. */
142
+ finisBoost: number;
135
143
  tenor: number;
136
144
  baseVelocity: number;
137
145
  contourVel: number;
package/docs/api/score.md CHANGED
@@ -208,6 +208,8 @@ The `accentus` profile (`PhrasingProfile`):
208
208
  | `curve` | depth of the phrase-level velocity arch |
209
209
  | `accent` | accent emphasis within the phrase |
210
210
  | `cadence` | weight given to phrase-final cadences |
211
+ | `ritard`, `ritardNotes` | depth and width, in notes, of the rallentando |
212
+ | `ritardCurve`, `finisBoost` | how late it gathers; the piece's own last `::` |
211
213
  | `tenor` | pull toward the reciting tone |
212
214
  | `baseVelocity`, `velSpread` | the velocity floor and the dynamic range above it |
213
215
  | `contourVel`, `contourDur` | melodic-contour influence on velocity and duration |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tonus",
3
- "version": "0.10.2",
3
+ "version": "0.10.3",
4
4
  "type": "module",
5
5
  "description": "Medieval music analysis: GABC plainchant exports, liturgical calendar, tuning systems, ephemeris, and the harmony of the spheres",
6
6
  "author": "Jeffrey Pierce <jeffrey@jeffreypierce.net>",