tonus 0.1.4 → 0.1.6

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 (59) hide show
  1. package/BIBLIOGRAPHY.md +143 -96
  2. package/CHANGELOG.md +62 -0
  3. package/README.md +7 -9
  4. package/dist/data/am.d.ts +5 -0
  5. package/dist/data/am.js +14299 -0
  6. package/dist/data/office-monastic.d.ts +3 -0
  7. package/dist/data/office-monastic.js +6956 -0
  8. package/dist/data/office-psalms-monastic.d.ts +3 -0
  9. package/dist/data/office-psalms-monastic.js +21 -0
  10. package/dist/data/{office-psalms.d.ts → office-psalms-roman.d.ts} +1 -1
  11. package/dist/data/{office-psalms.js → office-psalms-roman.js} +3 -3
  12. package/dist/engines/cal/calendar.js +16 -1
  13. package/dist/engines/cal/date.js +5 -0
  14. package/dist/engines/chant/chant.js +2 -0
  15. package/dist/{data → engines/chant/data}/compline.d.ts +1 -1
  16. package/dist/{data → engines/chant/data}/masses.d.ts +1 -1
  17. package/dist/{data → engines/chant/data}/masses.js +1 -1
  18. package/dist/{data → engines/chant/data}/prime.d.ts +1 -1
  19. package/dist/{data → engines/chant/data}/prime.js +1 -1
  20. package/dist/engines/chant/hour.js +37 -22
  21. package/dist/engines/chant/intone.js +6 -1
  22. package/dist/engines/chant/ordinary.js +1 -1
  23. package/dist/engines/chant/psalm.d.ts +4 -4
  24. package/dist/engines/chant/psalm.js +6 -4
  25. package/dist/engines/chant/types.d.ts +5 -1
  26. package/dist/engines/harmonia/api.js +11 -0
  27. package/dist/engines/harmonia/data/doctrines.js +27 -1
  28. package/dist/engines/imprint.js +7 -1
  29. package/dist/engines/planet/position.js +51 -5
  30. package/dist/engines/score/articulation.js +60 -3
  31. package/dist/engines/score/cadence.js +3 -3
  32. package/dist/engines/score/emitters/midi.js +6 -2
  33. package/dist/engines/score/infer.js +13 -1
  34. package/dist/engines/score/ir.d.ts +2 -1
  35. package/dist/engines/score/ir.js +54 -12
  36. package/dist/engines/score/modulation.js +2 -2
  37. package/dist/engines/score/parse.js +16 -8
  38. package/dist/engines/score/phrasing.js +26 -0
  39. package/dist/engines/score/prosody.js +5 -0
  40. package/dist/engines/score/tabula.d.ts +2 -0
  41. package/dist/engines/score/tabula.js +1 -0
  42. package/dist/engines/score/types.d.ts +11 -2
  43. package/dist/engines/temper/api.js +1 -1
  44. package/dist/engines/temper/data/guido.d.ts +3 -2
  45. package/dist/engines/temper/data/guido.js +30 -25
  46. package/dist/engines/temper/data/modes.js +3 -0
  47. package/dist/{data → engines/temper/data}/tones.js +1 -1
  48. package/dist/engines/temper/gabc.js +25 -2
  49. package/dist/engines/temper/guido.d.ts +3 -2
  50. package/dist/engines/temper/interval.js +12 -0
  51. package/dist/engines/temper/modality.js +5 -4
  52. package/dist/engines/temper/scale.js +33 -2
  53. package/dist/engines/temper/step.d.ts +2 -2
  54. package/dist/index.d.ts +3 -3
  55. package/package.json +2 -1
  56. package/dist/data/office.d.ts +0 -12
  57. package/dist/data/office.js +0 -13052
  58. /package/dist/{data → engines/chant/data}/compline.js +0 -0
  59. /package/dist/{data → engines/temper/data}/tones.d.ts +0 -0
@@ -1,15 +1,35 @@
1
1
  // ---------------------------------------------------------------------------
2
2
  // engines/temper/gabc — GABC pitch letter utilities
3
3
  // ---------------------------------------------------------------------------
4
+ // GABC pitch letters (a–m) are DIATONIC STAFF POSITIONS, not pitch classes: the
5
+ // thirteen letters are the thirteen slots of the four-line staff and its ledger
6
+ // space, spanning roughly two octaves of white-key steps [biblio: gregorio-gabc].
7
+ // A letter has no fixed pitch on its own — the clef fixes it. So converting to
8
+ // and from MIDI is staff-position arithmetic, done in two moduli: 7 for the
9
+ // diatonic staff (steps per octave) and 12 for MIDI (semitones per octave).
10
+ //
11
+ // DIATONIC maps a diatonic step (0–6, do re mi fa sol la si) to its pitch class.
12
+ // LETTERS is the staff-slot alphabet, low to high.
4
13
  const DIATONIC = [0, 2, 4, 5, 7, 9, 11];
5
14
  const LETTERS = "abcdefghijklm";
15
+ // A GABC clef names the staff line that carries "do" (c-clefs) or "fa" (f-clefs)
16
+ // and thereby anchors every letter. `doIdx` is the LETTERS slot that line falls
17
+ // on; `doMidi` is the MIDI pitch of "do" there (60 = middle C; 53 = the F below).
18
+ // A higher c-clef (c4 vs c1) moves "do" up the staff, so the same letter reads a
19
+ // lower pitch — hence doIdx climbs 3→5→7→9 across c1→c4. The f-clefs anchor on
20
+ // fa (MIDI 53) and are used for lower-tessitura chant.
6
21
  const CLEFS = {
7
22
  c1: { doMidi: 60, doIdx: 3 },
8
23
  c2: { doMidi: 60, doIdx: 5 },
9
24
  c3: { doMidi: 60, doIdx: 7 },
10
25
  c4: { doMidi: 60, doIdx: 9 },
11
- f3: { doMidi: 53, doIdx: 5 },
12
- f4: { doMidi: 53, doIdx: 3 },
26
+ // f-clefs anchor fa on the named line. Staff lines (bottom→top) sit at
27
+ // letters d/f/h/j (per the Gregorio spec: 2-line staff = a–i, 3-line = a–k,
28
+ // 4-line = a–m, pinning the lines at slots 3/5/7/9), so f3 puts fa at 'h'
29
+ // (7) and f4 at 'j' (9). Previous values (5 and 3) were off by a third and
30
+ // read every f-clef chant at the wrong staff position.
31
+ f3: { doMidi: 53, doIdx: 7 },
32
+ f4: { doMidi: 53, doIdx: 9 },
13
33
  };
14
34
  export function midiToGabc(midi, clef = "c4") {
15
35
  const def = CLEFS[clef];
@@ -34,6 +54,9 @@ export function gabcToMidi(letter, clef = "c4") {
34
54
  const staffPos = LETTERS.indexOf(letter.toLowerCase());
35
55
  if (staffPos === -1)
36
56
  throw new Error(`Unknown GABC letter: ${letter}`);
57
+ // Diatonic steps from "do", split into whole octaves (÷7) and the step within
58
+ // the octave. The `((x % 7) + 7) % 7` form keeps the step in 0–6 for letters
59
+ // below "do", where stepsFromDo is negative and JS `%` would return negative.
37
60
  const stepsFromDo = staffPos - def.doIdx;
38
61
  const octOffset = Math.floor(stepsFromDo / 7);
39
62
  const diatStep = ((stepsFromDo % 7) + 7) % 7;
@@ -1,3 +1,4 @@
1
+ import type { Finger, Region } from "./step.js";
1
2
  export type { GuidonianVariant, GuidonianEntry } from "./data/guido.js";
2
3
  export declare function lookupGuido(midi: number, mode?: number): {
3
4
  name: string | null;
@@ -9,8 +10,8 @@ export declare function lookupGuido(midi: number, mode?: number): {
9
10
  solmization: string;
10
11
  }[];
11
12
  hand: {
12
- finger: string;
13
- region: string;
13
+ finger: Finger;
14
+ region: Region;
14
15
  } | null;
15
16
  };
16
17
  //# sourceMappingURL=guido.d.ts.map
@@ -2,6 +2,12 @@
2
2
  // engines/temper/interval — interval classification between pitches
3
3
  // ---------------------------------------------------------------------------
4
4
  import { INTERVAL, UNISONUS } from "./data/constants.js";
5
+ // The three-tier consonance taxonomy [biblio: schulter-harmony] (the same table
6
+ // stated at docs/heavens.md and docs/tuning.md). Note that the perfect fourth is
7
+ // deliberately NOT perfect here: in medieval counterpoint the P4 above the bass
8
+ // is treated as a dissonance, unlike the melodic P4. So P1/P5/P8 are perfect,
9
+ // the thirds and sixths imperfect, and everything else — including P4 and the
10
+ // tritone — dissonant.
5
11
  const PERFECT_CLASSES = new Set(["P1", "P5", "P8"]);
6
12
  const IMPERFECT_CLASSES = new Set(["m3", "M3", "m6", "M6"]);
7
13
  function classifyConsonance(intervalClass) {
@@ -16,6 +22,9 @@ export function classifyInterval(a, b) {
16
22
  const abs = Math.abs(semitones);
17
23
  const simple = abs % 12;
18
24
  const direction = semitones > 0 ? "up" : semitones < 0 ? "down" : "unison";
25
+ // A true zero-distance unison (abs === 0) is UNISONUS; a compound octave
26
+ // (simple === 0 but abs a nonzero multiple of 12) folds to INTERVAL[0], the
27
+ // octave entry. The two share simple === 0 but name different intervals.
19
28
  const entry = abs === 0 ? UNISONUS : (simple === 0 ? INTERVAL[0] : INTERVAL[simple]);
20
29
  return {
21
30
  nomen: entry.latin,
@@ -24,6 +33,9 @@ export function classifyInterval(a, b) {
24
33
  class: entry.class,
25
34
  direction,
26
35
  semitones,
36
+ // Nominal equal-tempered cents (100 per semitone), NOT the tuned distance —
37
+ // the actual sounding interval depends on the temperament (see the ratio a
38
+ // temperamentum reports for a nota, and docs/tuning.md on nominal vs tuned).
27
39
  cents: semitones * 100,
28
40
  consonance: classifyConsonance(entry.class),
29
41
  };
@@ -13,10 +13,11 @@ const TENOR_WEIGHT = 2;
13
13
  const REGULAR_MOD_WEIGHT = 1;
14
14
  const CONCEDED_MOD_WEIGHT = 0.5;
15
15
  // A chant's opening note is a modal signal: each mode lists its valid initials
16
- // in rank order (the first is the most characteristic). Opening on a mode's
17
- // primary initial boosts it more than opening on a lower-ranked one — which is
18
- // what separates an authentic mode from its plagal partner, since the two share
19
- // a finalis but rank the same opening pitch differently.
16
+ // in rank order, most characteristic first (Rockstro's Grove ordering
17
+ // [biblio: rockstro-grove]). Opening on a mode's primary initial boosts it more
18
+ // than opening on a lower-ranked one — which is what separates an authentic mode
19
+ // from its plagal partner, since the two share a finalis but rank the same
20
+ // opening pitch differently.
20
21
  const INITIAL_BONUS = 0.3;
21
22
  /**
22
23
  * Rank a pitch-class distribution against the eight modes, best fit first.
@@ -1,6 +1,24 @@
1
1
  // ---------------------------------------------------------------------------
2
2
  // engines/temper/scale — tuning ratio builder
3
3
  // ---------------------------------------------------------------------------
4
+ // Builds the 12 pitch-class ratios for a temperament. The default is pure
5
+ // Pythagorean — all intervals from the 3/2 fifth and the octave — because that
6
+ // is the tuning of medieval theory from Boethius through the Guidonian gamut
7
+ // [biblio: boethius-institutione, guidonian-gamut], and it is correct for
8
+ // unaccompanied chant: melodic fifths/fourths are perfect, the narrow limma
9
+ // (256/243) gives half-steps a keen leading quality, and the wide Pythagorean
10
+ // third (81/64) never has to serve as a consonance.
11
+ //
12
+ // `comma` tempers the fifth toward meantone by narrowing it by a fraction of
13
+ // the syntonic comma (81/80, the gap between the Pythagorean and pure 5/4
14
+ // third): comma "1/4" stacks four fifths to a pure major third — quarter-comma
15
+ // meantone, the 16th-century sound.
16
+ //
17
+ // The `steps` presets supply just-intonation genera in place of the tempered
18
+ // fifth: the three Ptolemaic diatonics (intense/soft/equable, his χρόαι
19
+ // "shades") come straight from Ptolemy's tetrachord divisions [biblio:
20
+ // ptolemy-harmonics, Harmonics I.15–16]. See expandDiatonicSteps for how a
21
+ // 7-ratio genus is laid onto the fixed gamut, and why.
4
22
  import { MODES } from "./modes.js";
5
23
  // Stern-Brocot rational approximation — finds nearest simple fraction
6
24
  function approximate(value, maxDen = 1000) {
@@ -110,7 +128,14 @@ export function parseScala(input) {
110
128
  }
111
129
  const PURE_FIFTH = 3 / 2;
112
130
  const SYNTONIC_COMMA = 81 / 80;
131
+ // The circle of fifths, as chromatic pitch classes: C G D A E B F♯ … stacking
132
+ // twelve 3/2s. buildPythagoreanRatios walks this and octave-folds each.
113
133
  const FIFTH_TO_CHROM = [0, 7, 2, 9, 4, 11, 6, 1, 8, 3, 10, 5];
134
+ // Ptolemy's three diatonic genera [biblio: ptolemy-harmonics, Harmonics I.15–16],
135
+ // each a tetrachord (1/1 … 4/3) doubled up a 3/2 to fill the octave:
136
+ // intense (syntonon) — classical just intonation: pure 5/4 major, 6/5 minor.
137
+ // soft (malakon) — septimal: the 7th harmonic gives a large 8/7 whole tone.
138
+ // equable (homalon) — undecimal: near-equal ~150–182¢ steps.
114
139
  const PTOLEMAIC = {
115
140
  "ptolemy-intense": ["1/1", "9/8", "5/4", "4/3", "3/2", "5/3", "15/8"],
116
141
  "ptolemy-soft": ["1/1", "8/7", "80/63", "4/3", "3/2", "12/7", "40/21"],
@@ -143,8 +168,14 @@ const NATURAL_PCS = [0, 2, 4, 5, 7, 9, 11];
143
168
  // pitch order — NOT onto the mode's scalePcs. A church mode is an octave
144
169
  // species of this one gamut, so its interval qualities emerge from *where the
145
170
  // final sits within the fixed tuning*, handled downstream by normalizeToRoot.
146
- // (Mapping degree-per-mode instead would force major-scale qualities — e.g.
147
- // a 5/4 major third above every final — onto every mode; see docs/tuning.md.)
171
+ // This yields the authentic per-mode qualities (a Dorian minor third, a
172
+ // Mixolydian ♭7) and the honest syntonic wolf (D–A = 40/27 under
173
+ // ptolemy-intense).
174
+ //
175
+ // Mapping the genus degree-per-mode onto scalePcs instead would force
176
+ // major-scale qualities — a 5/4 major third above *every* final — onto every
177
+ // mode. Re-deriving a per-mode just intonation would attribute a modern just
178
+ // tuning to Ptolemy, who described tetrachord divisions, not modal scales.
148
179
  function expandDiatonicSteps(diatonic) {
149
180
  const base = buildPythagoreanRatios(0);
150
181
  const out = base.slice();
@@ -1,6 +1,6 @@
1
1
  import type { Scale } from "./scale.js";
2
- export type Finger = "wrist" | "palm" | "thumb" | "index" | "middle" | "ring" | "pinky";
3
- export type Region = "base" | "mid" | "tip" | "top";
2
+ export type Finger = "thumb" | "index" | "middle" | "ring" | "pinky";
3
+ export type Region = "base" | "mid" | "top" | "tip" | "super";
4
4
  export interface StepVariant {
5
5
  hexachord: "durum" | "naturale" | "molle";
6
6
  solmization: string;
package/dist/index.d.ts CHANGED
@@ -9,7 +9,7 @@ import { buildScore } from "./engines/score/api.js";
9
9
  import { getCosmos } from "./engines/planet/planet.js";
10
10
  import { buildHarmonia } from "./engines/harmonia/api.js";
11
11
  import type { FeastQuery, Feast, Pascha, Season, Grade } from "./engines/cal/types.js";
12
- import type { CantusQuery, Chant, OrdinaryChant, PropriumQuery, OrdinariumQuery, OfficiumQuery, PsalmusQuery } from "./engines/chant/types.js";
12
+ import type { CantusQuery, Chant, OrdinaryChant, PropriumQuery, OrdinariumQuery, OfficiumQuery, PsalmusQuery, Rite } from "./engines/chant/types.js";
13
13
  import type { TemperamentumInput, Temperamentum, Tuning, TemperamentumOpts, Pitch, PitchInput, Step, Neume, NeumeShape, Interval, ModeData, CadenceFigure, Modus, TunedNote, GamutOptions, Tonus, TonusOpts } from "./engines/temper/api.js";
14
14
  import type { Score, ScoreOpts, PondusInput, PondusOpts, AccentusInput, AccentusOpts, Cadence, CadenceTarget, CadenceApproach, Modulation, MidiOpts, MidiEmitResult, MidiJsonResult, MidiJsonEvent, MusicXmlOpts, MusicXmlEmitResult } from "./engines/score/api.js";
15
15
  import type { ChantTabulaRow } from "./engines/score/tabula.js";
@@ -18,7 +18,7 @@ import type { Prosody, RhythmicProfile, NoteRange, CadenceDistribution } from ".
18
18
  import type { Harmony, HarmoniaOpts, VoicedBody, VoicedAspect, Frame, Author } from "./engines/harmonia/api.js";
19
19
  import type { HarmonyTabulaRow } from "./engines/harmonia/tabula.js";
20
20
  import type { PlanetVowel } from "./engines/harmonia/data/vowels.js";
21
- import type { Note, Performance, Phrase, Syllable, RestEvent, ParseError, ArsisThesis } from "./engines/score/types.js";
21
+ import type { Note, Performance, Phrase, Syllable, RestEvent, ParseError, ArsisThesis, RhythmicType, CompoundBeat } from "./engines/score/types.js";
22
22
  import type { VoicedPitch } from "./engines/harmonia/voice.js";
23
23
  import type { Cosmos, CosmosQuery, Body, BodyName, Aspect } from "./engines/planet/types.js";
24
24
  declare const tonus: {
@@ -36,5 +36,5 @@ declare const tonus: {
36
36
  };
37
37
  export default tonus;
38
38
  export { SEASON_LABELS, TEMPUS_NAMES, GRADE_ORDER, GRADE_NAMES, gradeOrder, compareGrade, ritusToGrade, } from "./engines/cal/types.js";
39
- export type { Feast, FeastQuery, Pascha, Season, Grade, Chant, CantusQuery, OrdinaryChant, PropriumQuery, OrdinariumQuery, OfficiumQuery, PsalmusQuery, Temperamentum, TemperamentumInput, TemperamentumOpts, Tuning, Pitch, PitchInput, Step, Neume, NeumeShape, Interval, ModeData, CadenceFigure, Modus, TunedNote, GamutOptions, Tonus, TonusOpts, Score, ScoreOpts, PondusInput, PondusOpts, AccentusInput, AccentusOpts, Cadence, CadenceTarget, CadenceApproach, Modulation, MidiOpts, MidiEmitResult, MidiJsonResult, MidiJsonEvent, MusicXmlOpts, MusicXmlEmitResult, ChantTabulaRow, Note, Performance, Phrase, Syllable, RestEvent, ParseError, ArsisThesis, VoicedPitch, Cosmos, CosmosQuery, Body, BodyName, Aspect, Imprint, Attractor, VowelAttractor, ModalAffinity, Prosody, RhythmicProfile, NoteRange, CadenceDistribution, Harmony, HarmoniaOpts, VoicedBody, VoicedAspect, Frame, Author, HarmonyTabulaRow, PlanetVowel, };
39
+ export type { Feast, FeastQuery, Pascha, Season, Grade, Chant, CantusQuery, OrdinaryChant, PropriumQuery, OrdinariumQuery, OfficiumQuery, PsalmusQuery, Rite, Temperamentum, TemperamentumInput, TemperamentumOpts, Tuning, Pitch, PitchInput, Step, Neume, NeumeShape, Interval, ModeData, CadenceFigure, Modus, TunedNote, GamutOptions, Tonus, TonusOpts, Score, ScoreOpts, PondusInput, PondusOpts, AccentusInput, AccentusOpts, Cadence, CadenceTarget, CadenceApproach, Modulation, MidiOpts, MidiEmitResult, MidiJsonResult, MidiJsonEvent, MusicXmlOpts, MusicXmlEmitResult, ChantTabulaRow, Note, Performance, Phrase, Syllable, RestEvent, ParseError, ArsisThesis, RhythmicType, CompoundBeat, VoicedPitch, Cosmos, CosmosQuery, Body, BodyName, Aspect, Imprint, Attractor, VowelAttractor, ModalAffinity, Prosody, RhythmicProfile, NoteRange, CadenceDistribution, Harmony, HarmoniaOpts, VoicedBody, VoicedAspect, Frame, Author, HarmonyTabulaRow, PlanetVowel, };
40
40
  //# sourceMappingURL=index.d.ts.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tonus",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
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",
@@ -42,6 +42,7 @@
42
42
  "!dist/**/*.js.map",
43
43
  "!dist/**/*.d.ts.map",
44
44
  "LICENSE",
45
+ "CHANGELOG.md",
45
46
  "BIBLIOGRAPHY.md"
46
47
  ],
47
48
  "scripts": {
@@ -1,12 +0,0 @@
1
- export interface OfficeDay {
2
- feastId: string;
3
- doFile: string;
4
- antLaudes: string[];
5
- antBenedictus: string | null;
6
- antVespera: string[];
7
- antMagnificat: string | null;
8
- hymnLaudes: string | null;
9
- hymnVespera: string | null;
10
- }
11
- export declare const OFFICE: OfficeDay[];
12
- //# sourceMappingURL=office.d.ts.map