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
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ScoreIR, Neume, LyricRun } from "./types.js";
|
|
2
2
|
import type { Cadence } from "./cadence.js";
|
|
3
|
-
import type { ChantType, InterpretationOptions } from "./types.js";
|
|
3
|
+
import type { ChantType, InterpretationOptions, WrittenShape } from "./types.js";
|
|
4
4
|
export type NoteRole = "finalis" | "tenor" | "other" | null;
|
|
5
5
|
export interface ChantTabulaRow {
|
|
6
6
|
phraseIndex: number;
|
|
@@ -11,7 +11,25 @@ export interface ChantTabulaRow {
|
|
|
11
11
|
/** 0-based position of this note within its neume figure. */
|
|
12
12
|
neumeIndex: number;
|
|
13
13
|
lyric: string;
|
|
14
|
+
/** Styled lyric spans; present only when GABC markup styled this syllable. */
|
|
15
|
+
runs?: LyricRun[];
|
|
14
16
|
vowel: string;
|
|
17
|
+
/**
|
|
18
|
+
* The diphthong the sung vowel belongs to — `ae` · `oe` · `au`, and `ui` in the
|
|
19
|
+
* cui/hui stems — or null. `vowel` is its NUCLEUS (what the singer sustains);
|
|
20
|
+
* the second element is a late off-glide. Only the syllabifier can tell a
|
|
21
|
+
* diphthong from a hiatus, since the accent decides: `cae` is one syllable,
|
|
22
|
+
* `sa-é` is two.
|
|
23
|
+
*/
|
|
24
|
+
diphthong: string | null;
|
|
25
|
+
/** True when this note's syllable bears the Latin tonic word-accent. */
|
|
26
|
+
accent: boolean;
|
|
27
|
+
/** True when this note's syllable is the first of its word. */
|
|
28
|
+
wordStart: boolean;
|
|
29
|
+
/** The engraver asked for a line break BEFORE this note (GABC `z`). */
|
|
30
|
+
lineBreak: boolean;
|
|
31
|
+
/** Inside `<nlba>` — the engraver forbade a break before this note. */
|
|
32
|
+
keepWithPrev: boolean;
|
|
15
33
|
/** MIDI pitch number (after transpose, clamped 0–127) */
|
|
16
34
|
midi: number;
|
|
17
35
|
/** Pitch class 0–11 (C=0) */
|
|
@@ -22,8 +40,16 @@ export interface ChantTabulaRow {
|
|
|
22
40
|
degree: number | null;
|
|
23
41
|
hz: number;
|
|
24
42
|
offset: number;
|
|
25
|
-
/** Scientific pitch name, e.g. "D4", "Bb3" — the
|
|
43
|
+
/** Scientific pitch name, e.g. "D4", "Bb3" — the step + octave. */
|
|
26
44
|
spn: string;
|
|
45
|
+
/** Raw GABC pitch letter a–m — the square-note staff slot. */
|
|
46
|
+
staffLetter: string;
|
|
47
|
+
/** Staff position in half-spaces from the bottom line (a=−2 … g=4 … m=10). */
|
|
48
|
+
staffPosition: number;
|
|
49
|
+
/** Active GABC clef when this note sounded, e.g. "c4", "fb3". */
|
|
50
|
+
clef: string;
|
|
51
|
+
/** Written note shape (punctum, virga, inclinatum, …). */
|
|
52
|
+
shape: WrittenShape;
|
|
27
53
|
/** 14-bit MIDI pitch bend for this note's microtuning (8192 = center). */
|
|
28
54
|
bend: number;
|
|
29
55
|
/** Solesmes quality of this note's compound beat (shared across group). */
|
|
@@ -35,9 +61,15 @@ export interface ChantTabulaRow {
|
|
|
35
61
|
velocity: number | null;
|
|
36
62
|
shapedDuration: number;
|
|
37
63
|
ictus: boolean;
|
|
64
|
+
/** Written vertical episema / ictus mark ('), distinct from the analytical `ictus`. */
|
|
65
|
+
ictusSign: boolean;
|
|
66
|
+
/** Written horizontal episema (_), distinct from `mora`. */
|
|
67
|
+
episema: boolean;
|
|
38
68
|
accidental: -1 | 0 | 1;
|
|
39
69
|
/** How this note's accidental arose — only "explicit" prints a glyph. */
|
|
40
70
|
accidentalSource: "none" | "state" | "explicit";
|
|
71
|
+
/** The sign to draw before this note, if any. */
|
|
72
|
+
accidentalSign?: -1 | 0 | 1;
|
|
41
73
|
quilisma: boolean;
|
|
42
74
|
liquescent: boolean;
|
|
43
75
|
strophicus: boolean;
|
|
@@ -47,7 +79,7 @@ export interface ChantTabulaRow {
|
|
|
47
79
|
divisio: string | null;
|
|
48
80
|
/** Index into score.cadences[] when this note forms a cadence; null otherwise. */
|
|
49
81
|
cadenceRef: number | null;
|
|
50
|
-
/** Modal role: "
|
|
82
|
+
/** Modal role: "finalis" | "tenor" | "other" | null if no mode or no match */
|
|
51
83
|
role: NoteRole;
|
|
52
84
|
/** Guidonian short name (e.g. "g", "aa") — null for chromatic pitches with no gamut entry */
|
|
53
85
|
name: string | null;
|
|
@@ -74,5 +106,5 @@ export interface TabulaOptions {
|
|
|
74
106
|
/** Detected cadences; used to stamp each row's cadenceRef. */
|
|
75
107
|
cadences?: Cadence[];
|
|
76
108
|
}
|
|
77
|
-
export declare function computeTabula(ir:
|
|
109
|
+
export declare function computeTabula(ir: ScoreIR, options?: TabulaOptions): ChantTabulaRow[];
|
|
78
110
|
//# sourceMappingURL=tabula.d.ts.map
|
|
@@ -2,6 +2,7 @@ import { MODES } from "../temper/modes.js";
|
|
|
2
2
|
import { buildPhrasing, shapePhrasingForMode, applyPhrasing, } from "./phrasing.js";
|
|
3
3
|
import { inferMode } from "./infer.js";
|
|
4
4
|
import { CHROMA_TO_SOLFEGE as SOLFEGE_BY_PC } from "../temper/data/constants.js";
|
|
5
|
+
import { staffPositionForLetter } from "../../data/gabc-glyphs.js";
|
|
5
6
|
export function computeTabula(ir, options = {}) {
|
|
6
7
|
const modeNum = options.mode ?? inferMode(ir);
|
|
7
8
|
const modeData = modeNum !== undefined ? MODES.get(modeNum) : undefined;
|
|
@@ -83,7 +84,17 @@ export function computeTabula(ir, options = {}) {
|
|
|
83
84
|
neumeGroup: n.context.neumeGroup,
|
|
84
85
|
neumeIndex: neumeIndices[i],
|
|
85
86
|
lyric: n.context.lyric,
|
|
87
|
+
runs: n.context.runs,
|
|
86
88
|
vowel: n.context.vowel,
|
|
89
|
+
diphthong: n.context.diphthong,
|
|
90
|
+
accent: n.context.accent,
|
|
91
|
+
// wordStart reads the note's CONTEXT syllable index (the parser's, which
|
|
92
|
+
// resets to 0 at each word boundary — so 0 = word start), NOT the row's
|
|
93
|
+
// own `syllableIndex` above (a global per-phrase counter). Two indices,
|
|
94
|
+
// two meanings; the per-word one is what marks words.
|
|
95
|
+
wordStart: n.context.syllableIndex === 0,
|
|
96
|
+
lineBreak: n.context.lineBreak === true,
|
|
97
|
+
keepWithPrev: n.context.keepWithPrev === true,
|
|
87
98
|
midi: n.pitch.midi,
|
|
88
99
|
pc: n.pitch.pc,
|
|
89
100
|
octave: n.pitch.oct,
|
|
@@ -91,6 +102,10 @@ export function computeTabula(ir, options = {}) {
|
|
|
91
102
|
hz: n.pitch.hz,
|
|
92
103
|
offset: n.pitch.offset,
|
|
93
104
|
spn: n.pitch.spn,
|
|
105
|
+
staffLetter: n.context.staffLetter,
|
|
106
|
+
staffPosition: staffPositionForLetter(n.context.staffLetter) ?? 0,
|
|
107
|
+
clef: n.context.clef,
|
|
108
|
+
shape: n.context.shape,
|
|
94
109
|
bend: n.pitch.bend,
|
|
95
110
|
rhythmicShape: n.performance.rhythmicShape,
|
|
96
111
|
rhythmicIndex: n.performance.rhythmicIndex,
|
|
@@ -98,8 +113,11 @@ export function computeTabula(ir, options = {}) {
|
|
|
98
113
|
velocity: velocities[i],
|
|
99
114
|
shapedDuration: shapedDurations[i],
|
|
100
115
|
ictus: n.context.ictus,
|
|
116
|
+
ictusSign: n.context.ictusSign,
|
|
117
|
+
episema: n.context.episema,
|
|
101
118
|
accidental: n.pitch.acc,
|
|
102
119
|
accidentalSource: n.context.accidentalSource,
|
|
120
|
+
accidentalSign: n.context.accidentalSign,
|
|
103
121
|
quilisma: n.context.quilisma,
|
|
104
122
|
liquescent: n.context.liquescent,
|
|
105
123
|
strophicus: n.context.strophicus,
|
|
@@ -14,14 +14,59 @@ export interface Performance {
|
|
|
14
14
|
rhythmicShape: ArsisThesis;
|
|
15
15
|
rhythmicIndex: number;
|
|
16
16
|
}
|
|
17
|
+
export type WrittenShape = "punctum" | "inclinatum" | "virga" | "virgaReversa" | "quilisma" | "oriscus" | "strophicus" | "cavum" | "linea";
|
|
18
|
+
/**
|
|
19
|
+
* One styled span of a syllable's lyric — the decoded form of GABC's text
|
|
20
|
+
* markup (<i>, <b>, <sc>, <c>, elisions). The spans concatenate to the
|
|
21
|
+
* syllable's `lyric`; style flags are absent when a span is plain.
|
|
22
|
+
*/
|
|
23
|
+
export interface LyricRun {
|
|
24
|
+
text: string;
|
|
25
|
+
italic?: boolean;
|
|
26
|
+
bold?: boolean;
|
|
27
|
+
smallCaps?: boolean;
|
|
28
|
+
/** Rendered in the rubric color (GABC's <c>). */
|
|
29
|
+
rubric?: boolean;
|
|
30
|
+
}
|
|
17
31
|
export interface Context {
|
|
18
32
|
lyric: string;
|
|
33
|
+
/** Styled lyric spans; present only when GABC markup styled this syllable. */
|
|
34
|
+
runs?: LyricRun[];
|
|
19
35
|
vowel: string;
|
|
36
|
+
/**
|
|
37
|
+
* The diphthong this syllable's sung vowel belongs to (`ae` · `oe` · `au`, and
|
|
38
|
+
* `ui` in the cui/hui stems), or null. `vowel` above is its NUCLEUS — what the
|
|
39
|
+
* singer sustains; the pair's second element is a late off-glide. Reported
|
|
40
|
+
* here because only the syllabifier can tell a diphthong from a hiatus: the
|
|
41
|
+
* accent decides (`cae` is one syllable, `sa-é` is two).
|
|
42
|
+
*/
|
|
43
|
+
diphthong: string | null;
|
|
20
44
|
syllableIndex: number;
|
|
45
|
+
/** True when this note's syllable bears the Latin tonic word-accent. */
|
|
46
|
+
accent: boolean;
|
|
21
47
|
/** 0-based index of the neume figure within the syllable (GABC break markers). */
|
|
22
48
|
neumeGroup: number;
|
|
49
|
+
/** The engraver asked for a line break BEFORE this note — GABC's `z` (and
|
|
50
|
+
* `Z`, its page-break sibling, honoured the same way since tonus paginates
|
|
51
|
+
* nothing). An editor who set a chant chose where its lines end, and that
|
|
52
|
+
* choice carries information a width cannot: a break at the right place is
|
|
53
|
+
* an editorial reading of the piece. Where it is absent the layout decides. */
|
|
54
|
+
lineBreak?: boolean;
|
|
55
|
+
keepWithPrev?: boolean;
|
|
56
|
+
/** Raw GABC pitch letter a–m — the staff slot for square-note rendering. */
|
|
57
|
+
staffLetter: string;
|
|
58
|
+
/** Active GABC clef when this note sounded, e.g. "c4", "fb3". */
|
|
59
|
+
clef: string;
|
|
60
|
+
/** Written note shape (punctum, virga, inclinatum, …). */
|
|
61
|
+
shape: WrittenShape;
|
|
23
62
|
ictus: boolean;
|
|
63
|
+
/** Written vertical episema / ictus mark ('), distinct from the analytical `ictus`. */
|
|
64
|
+
ictusSign: boolean;
|
|
65
|
+
/** Written horizontal episema (_), distinct from `mora`. */
|
|
66
|
+
episema: boolean;
|
|
24
67
|
accidentalSource: "none" | "state" | "explicit";
|
|
68
|
+
/** The sign to DRAW before this note (see parse.ts) — distinct from `accidental`. */
|
|
69
|
+
accidentalSign?: -1 | 0 | 1;
|
|
25
70
|
quilisma: boolean;
|
|
26
71
|
liquescent: boolean;
|
|
27
72
|
strophicus: boolean;
|
|
@@ -116,14 +161,37 @@ export interface ParsedNote {
|
|
|
116
161
|
type: "note";
|
|
117
162
|
step: number;
|
|
118
163
|
lyric: string;
|
|
164
|
+
/** Styled lyric spans; present only when GABC markup styled this syllable. */
|
|
165
|
+
runs?: LyricRun[];
|
|
119
166
|
syllableIndex: number;
|
|
167
|
+
/** True when this note's syllable bears the Latin tonic word-accent. */
|
|
168
|
+
accent: boolean;
|
|
120
169
|
/** 0-based index of the neume figure within the syllable (GABC break markers). */
|
|
121
170
|
neumeGroup: number;
|
|
171
|
+
/** The engraver asked for a line break BEFORE this note — GABC's `z` (and
|
|
172
|
+
* `Z`, its page-break sibling, honoured the same way since tonus paginates
|
|
173
|
+
* nothing). An editor who set a chant chose where its lines end, and that
|
|
174
|
+
* choice carries information a width cannot: a break at the right place is
|
|
175
|
+
* an editorial reading of the piece. Where it is absent the layout decides. */
|
|
176
|
+
lineBreak?: boolean;
|
|
177
|
+
keepWithPrev?: boolean;
|
|
178
|
+
/** Raw GABC pitch letter a–m — the staff slot for square-note rendering. */
|
|
179
|
+
staffLetter: string;
|
|
180
|
+
/** Active GABC clef when this note sounded, e.g. "c4", "fb3". */
|
|
181
|
+
clef: string;
|
|
182
|
+
/** Written note shape (punctum, virga, inclinatum, …). */
|
|
183
|
+
shape: WrittenShape;
|
|
122
184
|
ictus: boolean;
|
|
185
|
+
/** Written vertical episema / ictus mark ('), distinct from analytical `ictus`. */
|
|
186
|
+
ictusSign: boolean;
|
|
187
|
+
/** Written horizontal episema (_), distinct from `mora`. */
|
|
188
|
+
episema: boolean;
|
|
123
189
|
weight: number;
|
|
124
190
|
duration: number;
|
|
125
191
|
accidental: -1 | 0 | 1;
|
|
126
192
|
accidentalSource: "none" | "state" | "explicit";
|
|
193
|
+
/** The sign to DRAW before this note (see parse.ts) — distinct from `accidental`. */
|
|
194
|
+
accidentalSign?: -1 | 0 | 1;
|
|
127
195
|
quilisma: boolean;
|
|
128
196
|
liquescent: boolean;
|
|
129
197
|
strophicus: boolean;
|
|
@@ -150,18 +218,37 @@ export interface Neume {
|
|
|
150
218
|
}
|
|
151
219
|
export interface Syllable {
|
|
152
220
|
lyric: string;
|
|
221
|
+
/** Styled lyric spans; present only when GABC markup styled this syllable. */
|
|
222
|
+
runs?: LyricRun[];
|
|
153
223
|
notes: Note[];
|
|
154
224
|
neume: Neume;
|
|
225
|
+
/** Notes sung on this syllable — its melisma. 1 = syllabic, >1 = melismatic. */
|
|
226
|
+
melisma: number;
|
|
155
227
|
}
|
|
156
228
|
export interface Phrase {
|
|
157
229
|
syllables: Syllable[];
|
|
158
230
|
divisio?: RestEvent;
|
|
231
|
+
/** Total notes across this phrase's syllables. */
|
|
232
|
+
noteCount: number;
|
|
233
|
+
/** Sung syllables in this phrase (syllables carrying at least one note). */
|
|
234
|
+
syllableCount: number;
|
|
159
235
|
/** The phrase's compound beats in order — the A/T sequence of the incise. */
|
|
160
236
|
beats: CompoundBeat[];
|
|
161
237
|
/** Le Guennant/Carroll rhythmic type of the incise; null if none fits. */
|
|
162
238
|
rhythmicType: RhythmicType;
|
|
163
239
|
}
|
|
164
|
-
|
|
240
|
+
/**
|
|
241
|
+
* What `buildIR` returns: the parsed, phrased, rhythm-classified melody before
|
|
242
|
+
* any analysis rides on it. Every downstream pass (meta, infer, tabula) takes
|
|
243
|
+
* one of these and names its parameter `ir`.
|
|
244
|
+
*
|
|
245
|
+
* This was called `Score` and shadowed the real one in api.ts — same name, a
|
|
246
|
+
* strict subset of its fields, and a `chant` narrowed to three strings. Nothing
|
|
247
|
+
* distinguished them at an import site, so the wrong one could be imported and
|
|
248
|
+
* still typecheck against the fields it happened to share. The public Score is
|
|
249
|
+
* the nine-field record in api.ts; this is its input.
|
|
250
|
+
*/
|
|
251
|
+
export interface ScoreIR {
|
|
165
252
|
chant: {
|
|
166
253
|
incipit: string;
|
|
167
254
|
mode: string | null;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Scale, ScaleOpts, ScalaFile, RatioResult } from "./scale.js";
|
|
1
|
+
import type { Scale, ScaleOpts, ScalaFile, RatioResult, Lupus } from "./scale.js";
|
|
2
2
|
import type { Pitch, PitchInput } from "./pitch.js";
|
|
3
3
|
import type { Step, StepVariant, Finger, Region } from "./step.js";
|
|
4
4
|
import type { Interval, IntervalDirection, IntervalQuality } from "./interval.js";
|
|
@@ -60,6 +60,9 @@ export interface Temperamentum {
|
|
|
60
60
|
ratio(input: string): RatioResult & {
|
|
61
61
|
step: Step | null;
|
|
62
62
|
};
|
|
63
|
+
/** The wolf, or null when no fifths chain built this scale. UNDOCUMENTED:
|
|
64
|
+
* the site is its only caller and the shape is still settling. */
|
|
65
|
+
lupus(): Lupus | null;
|
|
63
66
|
gamut(opts?: GamutOptions): Pitch[];
|
|
64
67
|
modus(mode: number): Modus;
|
|
65
68
|
tonus(opts?: TonusOpts): Tonus;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// ---------------------------------------------------------------------------
|
|
2
2
|
// engines/temper/api — Temperamentum context builder
|
|
3
3
|
// ---------------------------------------------------------------------------
|
|
4
|
-
import { buildRatios, parseScala, getPtolemaicRatios, toRatio } from "./scale.js";
|
|
4
|
+
import { buildRatios, parseScala, getPtolemaicRatios, toRatio, wolfOf } from "./scale.js";
|
|
5
5
|
import { parsePitch, toPitch } from "./pitch.js";
|
|
6
6
|
import { toStep } from "./step.js";
|
|
7
7
|
import { classifyInterval } from "./interval.js";
|
|
@@ -10,11 +10,20 @@ import { buildGamut } from "./gamut.js";
|
|
|
10
10
|
import { getMode } from "./modes.js";
|
|
11
11
|
import { getTone, getDifferentia } from "./data/tones.js";
|
|
12
12
|
function resolveOpts(input) {
|
|
13
|
-
if (
|
|
13
|
+
if (input == null)
|
|
14
14
|
return {};
|
|
15
15
|
if (typeof input === "string")
|
|
16
16
|
return { tuning: input };
|
|
17
|
-
|
|
17
|
+
if (typeof input !== "object")
|
|
18
|
+
throw new Error(`temperamentum input must be a tuning name or an options object, got ${typeof input}`);
|
|
19
|
+
const opts = input;
|
|
20
|
+
if (opts.tuning != null && typeof opts.tuning !== "string")
|
|
21
|
+
throw new Error(`tuning must be a string, got ${typeof opts.tuning}`);
|
|
22
|
+
if (opts.mode != null &&
|
|
23
|
+
opts.mode !== "auto" &&
|
|
24
|
+
(!Number.isInteger(opts.mode) || opts.mode < 1 || opts.mode > 8))
|
|
25
|
+
throw new RangeError(`Unknown mode: ${opts.mode}. Supported: 1–8 or "auto".`);
|
|
26
|
+
return opts;
|
|
18
27
|
}
|
|
19
28
|
function resolveScale(opts) {
|
|
20
29
|
if (!opts.scale)
|
|
@@ -49,8 +58,10 @@ function tuningToScaleOpts(opts) {
|
|
|
49
58
|
break;
|
|
50
59
|
default: {
|
|
51
60
|
const ptolemaic = getPtolemaicRatios(tuning);
|
|
52
|
-
if (ptolemaic)
|
|
53
|
-
|
|
61
|
+
if (!ptolemaic)
|
|
62
|
+
throw new Error(`Unknown tuning: "${tuning}". Built-ins: pythagorean, meantone, equal, ` +
|
|
63
|
+
`ptolemy-intense, ptolemy-soft, ptolemy-equable — or pass a scale.`);
|
|
64
|
+
scalaOpts.steps = ptolemaic;
|
|
54
65
|
break;
|
|
55
66
|
}
|
|
56
67
|
}
|
|
@@ -93,6 +104,18 @@ export function buildTemper(input) {
|
|
|
93
104
|
neuma(inputs) {
|
|
94
105
|
return buildNeume(inputs, scala);
|
|
95
106
|
},
|
|
107
|
+
/** The wolf of THIS temperament.
|
|
108
|
+
*
|
|
109
|
+
* Null when the scale was supplied rather than stacked: a Ptolemaic
|
|
110
|
+
* genus and a Scala list arrive as ratios, so there is no chain of
|
|
111
|
+
* fifths and nothing is left over. Returning a number there would be
|
|
112
|
+
* inventing one.
|
|
113
|
+
*/
|
|
114
|
+
lupus() {
|
|
115
|
+
if (scalaOpts.steps != null)
|
|
116
|
+
return null;
|
|
117
|
+
return wolfOf(scala.comma);
|
|
118
|
+
},
|
|
96
119
|
ratio(input) {
|
|
97
120
|
const result = toRatio(input);
|
|
98
121
|
const folded = result.ratio >= 2 ? result.ratio / Math.pow(2, Math.floor(Math.log2(result.ratio))) : result.ratio;
|
|
@@ -4,8 +4,12 @@
|
|
|
4
4
|
// little→index, descend the index, then spiral inward; ee (76) floats above the
|
|
5
5
|
// middle fingertip (region "super"). The two b/♮ pairs (58/59, 70/71) share a
|
|
6
6
|
// locus by design — one joint, two readings (fa in molle, mi in durum), the
|
|
7
|
-
// tradition, not a duplication to fix.
|
|
8
|
-
//
|
|
7
|
+
// tradition, not a duplication to fix. They are one distinction, named for the
|
|
8
|
+
// two shapes of the letter: b rotundum (the round b) against b quadratum (the
|
|
9
|
+
// square b), which is where ♭ and ♮ come from. A RENDERER may draw them with
|
|
10
|
+
// the medieval glyphs (GLYPH.flat / GLYPH.natural); these names stay letters,
|
|
11
|
+
// because this table names joints rather than drawing them. After Suñol
|
|
12
|
+
// [biblio: sunol-textbook] and Apel [biblio: apel-chant].
|
|
9
13
|
// prettier-ignore
|
|
10
14
|
export const GUIDONIAN_DATA = new Map([
|
|
11
15
|
[43, { hand: { finger: "thumb", region: "tip" }, name: ["Γ", "Gammaut"], variants: [{ hexachord: "durum", solmization: "UT" }] }],
|
|
@@ -9,6 +9,11 @@ export interface CadenceFigure {
|
|
|
9
9
|
id: string;
|
|
10
10
|
steps: number[];
|
|
11
11
|
}
|
|
12
|
+
export interface RecitingNote {
|
|
13
|
+
pc: number;
|
|
14
|
+
rank: "principal" | "auxiliary" | "secondary" | "pseudo" | "rare";
|
|
15
|
+
label?: string;
|
|
16
|
+
}
|
|
12
17
|
export interface ModeData {
|
|
13
18
|
mode: number;
|
|
14
19
|
nomen: string;
|
|
@@ -17,6 +22,7 @@ export interface ModeData {
|
|
|
17
22
|
type: "authentic" | "plagal";
|
|
18
23
|
final: number;
|
|
19
24
|
tenor: number;
|
|
25
|
+
recitingNotes: RecitingNote[];
|
|
20
26
|
scalePcs: number[];
|
|
21
27
|
hexachords: ("durum" | "naturale" | "molle")[];
|
|
22
28
|
profile: ModeProfile;
|
|
@@ -12,6 +12,7 @@ export const MODES = new Map([
|
|
|
12
12
|
type: "authentic",
|
|
13
13
|
final: 2,
|
|
14
14
|
tenor: 9,
|
|
15
|
+
recitingNotes: [{ pc: 9, rank: "principal", label: "psalmodic tenor" }],
|
|
15
16
|
scalePcs: [2, 4, 5, 7, 9, 11, 0],
|
|
16
17
|
hexachords: ["naturale"],
|
|
17
18
|
profile: { mood: "serious", ethos: "gravis", phrasing: "lyrical", melodic: "falling", tendency: "melismatic" },
|
|
@@ -36,6 +37,11 @@ export const MODES = new Map([
|
|
|
36
37
|
type: "plagal",
|
|
37
38
|
final: 2,
|
|
38
39
|
tenor: 5,
|
|
40
|
+
recitingNotes: [
|
|
41
|
+
{ pc: 5, rank: "principal", label: "official dominant, psalmodic tenor, strong degree" },
|
|
42
|
+
{ pc: 2, rank: "principal", label: "final; also a principal note of composition per prose" },
|
|
43
|
+
{ pc: 7, rank: "secondary", label: "important; prose also names it a note of composition" },
|
|
44
|
+
],
|
|
39
45
|
scalePcs: [2, 4, 5, 7, 9, 11, 0],
|
|
40
46
|
hexachords: ["naturale"],
|
|
41
47
|
profile: { mood: "sad", ethos: "tristis", phrasing: "lyrical", melodic: "arch", tendency: "neumatic" },
|
|
@@ -63,6 +69,12 @@ export const MODES = new Map([
|
|
|
63
69
|
type: "authentic",
|
|
64
70
|
final: 4,
|
|
65
71
|
tenor: 0,
|
|
72
|
+
// No Degree Summary Table exists for mode III (prose only, see
|
|
73
|
+
// ../../../../BIBLIOGRAPHY.md [biblio: saulnier-modes]): "the primitive
|
|
74
|
+
// psalmodic tenor is b, but in numerous medieval manuscripts from northern Europe
|
|
75
|
+
// and in the modern editions, this tenor is raised to c." tonus already
|
|
76
|
+
// stored the raised/practical value; this just cites the confirmation.
|
|
77
|
+
recitingNotes: [{ pc: 0, rank: "principal", label: "historically raised from the primitive tenor b" }],
|
|
66
78
|
scalePcs: [4, 5, 7, 9, 11, 0, 2],
|
|
67
79
|
hexachords: ["naturale"],
|
|
68
80
|
profile: { mood: "mystic", ethos: "mysticus", phrasing: "solemn", melodic: "falling", tendency: "melismatic" },
|
|
@@ -86,6 +98,12 @@ export const MODES = new Map([
|
|
|
86
98
|
type: "plagal",
|
|
87
99
|
final: 4,
|
|
88
100
|
tenor: 9,
|
|
101
|
+
recitingNotes: [
|
|
102
|
+
{ pc: 9, rank: "principal", label: "psalmodic tenor" },
|
|
103
|
+
{ pc: 7, rank: "secondary", label: "note of composition for Responsory verses (deuterus-tritus)" },
|
|
104
|
+
{ pc: 4, rank: "secondary", label: "final; also usable as a recitation note per prose" },
|
|
105
|
+
{ pc: 5, rank: "pseudo", label: "pseudo-tenor; strong importance but not a true note of composition per prose" },
|
|
106
|
+
],
|
|
89
107
|
scalePcs: [4, 5, 7, 9, 11, 0, 2],
|
|
90
108
|
hexachords: ["naturale"],
|
|
91
109
|
profile: { mood: "harmonious", ethos: "harmonicus", phrasing: "lyrical", melodic: "arch", tendency: "neumatic" },
|
|
@@ -109,6 +127,10 @@ export const MODES = new Map([
|
|
|
109
127
|
type: "authentic",
|
|
110
128
|
final: 5,
|
|
111
129
|
tenor: 0,
|
|
130
|
+
recitingNotes: [
|
|
131
|
+
{ pc: 0, rank: "principal", label: "official dominant, psalmodic tenor" },
|
|
132
|
+
{ pc: 5, rank: "rare", label: "final; sometimes used as a recitation note" },
|
|
133
|
+
],
|
|
112
134
|
scalePcs: [5, 7, 9, 11, 0, 2, 4],
|
|
113
135
|
hexachords: ["molle"],
|
|
114
136
|
profile: { mood: "happy", ethos: "laetus", phrasing: "solemn", melodic: "rising", tendency: "melismatic" },
|
|
@@ -132,6 +154,11 @@ export const MODES = new Map([
|
|
|
132
154
|
type: "plagal",
|
|
133
155
|
final: 5,
|
|
134
156
|
tenor: 9,
|
|
157
|
+
// Prose: "Mode VI possesses only one note of composition: f, the final
|
|
158
|
+
// of the pieces. For the psalmody, it resorts to a, even if it has been
|
|
159
|
+
// infrequently heard in the piece." So the reciting note is real but
|
|
160
|
+
// structurally secondary — hence "auxiliary," not "principal."
|
|
161
|
+
recitingNotes: [{ pc: 9, rank: "auxiliary", label: "psalmodic tenor; the mode's only note of composition is the final f, itself not used for recitation" }],
|
|
135
162
|
scalePcs: [5, 7, 9, 11, 0, 2, 4],
|
|
136
163
|
hexachords: ["molle"],
|
|
137
164
|
profile: { mood: "devout", ethos: "devotus", phrasing: "lyrical", melodic: "arch", tendency: "neumatic" },
|
|
@@ -155,6 +182,11 @@ export const MODES = new Map([
|
|
|
155
182
|
type: "authentic",
|
|
156
183
|
final: 7,
|
|
157
184
|
tenor: 2,
|
|
185
|
+
recitingNotes: [
|
|
186
|
+
{ pc: 2, rank: "principal", label: "psalmodic tenor" },
|
|
187
|
+
{ pc: 0, rank: "auxiliary", label: "strong degree" },
|
|
188
|
+
{ pc: 11, rank: "auxiliary" },
|
|
189
|
+
],
|
|
158
190
|
scalePcs: [7, 9, 11, 0, 2, 4, 5],
|
|
159
191
|
hexachords: ["durum"],
|
|
160
192
|
profile: { mood: "angelical", ethos: "angelicus", phrasing: "solemn", melodic: "rising", tendency: "melismatic" },
|
|
@@ -179,6 +211,16 @@ export const MODES = new Map([
|
|
|
179
211
|
type: "plagal",
|
|
180
212
|
final: 7,
|
|
181
213
|
tenor: 0,
|
|
214
|
+
// Prose: "Mode VIII has two upper notes of recitation: c (psalmodic
|
|
215
|
+
// tenor) and b. The lower note of recitation, g, is also the final of
|
|
216
|
+
// the pieces. B and g are the notes of recitation for the verses of the
|
|
217
|
+
// Responsories." — the final doubling as a low reciting note is a real,
|
|
218
|
+
// distinct case (see also mode V's f).
|
|
219
|
+
recitingNotes: [
|
|
220
|
+
{ pc: 0, rank: "principal", label: "psalmodic tenor" },
|
|
221
|
+
{ pc: 11, rank: "auxiliary" },
|
|
222
|
+
{ pc: 7, rank: "secondary", label: "final; the mode's lower recitation note, used for Responsory verses alongside b" },
|
|
223
|
+
],
|
|
182
224
|
scalePcs: [7, 9, 11, 0, 2, 4, 5],
|
|
183
225
|
hexachords: ["durum"],
|
|
184
226
|
profile: { mood: "perfect", ethos: "perfectus", phrasing: "lyrical", melodic: "arch", tendency: "neumatic" },
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
//
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// engines/temper/data/tones — psalm tones (Graduale Romanum appendix)
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
2
4
|
// Pitches as MIDI numbers; tone codes follow DO convention: "1g", "6F", "4e".
|
|
3
5
|
// c4 clef reference: f=53 g=55 h=57 i=59 j=60 k=62 l=64 (F3–F4)
|
|
4
6
|
const f = 53, g = 55, h = 57, i = 59, j = 60, k = 62, l = 64;
|
|
@@ -6,7 +8,7 @@ const d3 = 50, e3 = 52; // below c4 clef range, needed for Tone II
|
|
|
6
8
|
export const TONES = [
|
|
7
9
|
{
|
|
8
10
|
mode: 1,
|
|
9
|
-
|
|
11
|
+
nomen: "Tonus I",
|
|
10
12
|
tenor: h, // A3
|
|
11
13
|
intonation: [f, h, j], // F-A-C
|
|
12
14
|
flex: [h, g], // A-G
|
|
@@ -22,7 +24,7 @@ export const TONES = [
|
|
|
22
24
|
},
|
|
23
25
|
{
|
|
24
26
|
mode: 2,
|
|
25
|
-
|
|
27
|
+
nomen: "Tonus II",
|
|
26
28
|
tenor: f, // F3
|
|
27
29
|
intonation: [d3, f], // D3-F3
|
|
28
30
|
flex: [f, e3], // F-E
|
|
@@ -37,7 +39,7 @@ export const TONES = [
|
|
|
37
39
|
},
|
|
38
40
|
{
|
|
39
41
|
mode: 3,
|
|
40
|
-
|
|
42
|
+
nomen: "Tonus III",
|
|
41
43
|
tenor: i, // B3
|
|
42
44
|
intonation: [g, i, j], // G-B-C
|
|
43
45
|
flex: [i, h], // B-A
|
|
@@ -51,7 +53,7 @@ export const TONES = [
|
|
|
51
53
|
},
|
|
52
54
|
{
|
|
53
55
|
mode: 4,
|
|
54
|
-
|
|
56
|
+
nomen: "Tonus IV",
|
|
55
57
|
tenor: h, // A3
|
|
56
58
|
intonation: [f, g, h], // F-G-A
|
|
57
59
|
flex: [h, f], // A-F
|
|
@@ -66,7 +68,7 @@ export const TONES = [
|
|
|
66
68
|
},
|
|
67
69
|
{
|
|
68
70
|
mode: 5,
|
|
69
|
-
|
|
71
|
+
nomen: "Tonus V",
|
|
70
72
|
tenor: j, // C4
|
|
71
73
|
intonation: [h, j], // A-C
|
|
72
74
|
flex: [j, i], // C-B
|
|
@@ -80,7 +82,7 @@ export const TONES = [
|
|
|
80
82
|
},
|
|
81
83
|
{
|
|
82
84
|
mode: 6,
|
|
83
|
-
|
|
85
|
+
nomen: "Tonus VI",
|
|
84
86
|
tenor: f, // F3
|
|
85
87
|
intonation: [d3, e3, f], // D-E-F
|
|
86
88
|
flex: [f, e3], // F-E
|
|
@@ -94,7 +96,7 @@ export const TONES = [
|
|
|
94
96
|
},
|
|
95
97
|
{
|
|
96
98
|
mode: 7,
|
|
97
|
-
|
|
99
|
+
nomen: "Tonus VII",
|
|
98
100
|
tenor: j, // C4
|
|
99
101
|
intonation: [h, i, j], // A-B-C
|
|
100
102
|
flex: [j, i], // C-B
|
|
@@ -109,7 +111,7 @@ export const TONES = [
|
|
|
109
111
|
},
|
|
110
112
|
{
|
|
111
113
|
mode: 8,
|
|
112
|
-
|
|
114
|
+
nomen: "Tonus VIII",
|
|
113
115
|
tenor: j, // C4
|
|
114
116
|
intonation: [h, j], // A-C
|
|
115
117
|
flex: [j, h], // C-A
|
|
@@ -125,7 +127,7 @@ export const TONES = [
|
|
|
125
127
|
// ── Tonus Peregrinus ── irregular: two different tenors
|
|
126
128
|
{
|
|
127
129
|
mode: 0,
|
|
128
|
-
|
|
130
|
+
nomen: "Tonus Peregrinus",
|
|
129
131
|
tenor: j, // C4 (first half tenor)
|
|
130
132
|
intonation: [h, i, j], // A-B-C
|
|
131
133
|
flex: [j, i],
|
|
@@ -138,7 +140,14 @@ export const TONES = [
|
|
|
138
140
|
];
|
|
139
141
|
/** Look up a PsalmTone by mode number (1–8, or 0 for Peregrinus) */
|
|
140
142
|
export function getTone(mode) {
|
|
141
|
-
|
|
143
|
+
const tone = TONES.find(t => t.mode === mode);
|
|
144
|
+
// A bad mode throws rather than silently singing Tonus VIII: a
|
|
145
|
+
// plausible-looking wrong psalm tone is worse than no answer, and getMode
|
|
146
|
+
// (temper/modes.ts) already holds this line for the modes themselves.
|
|
147
|
+
if (!tone) {
|
|
148
|
+
throw new Error(`getTone: no psalm tone for mode ${mode} (expected 1–8, or 0 for the tonus peregrinus).`);
|
|
149
|
+
}
|
|
150
|
+
return tone;
|
|
142
151
|
}
|
|
143
152
|
/** Look up a differentia by code, falling back to the tone's defaultDiff */
|
|
144
153
|
export function getDifferentia(tone, code) {
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
// ---------------------------------------------------------------------------
|
|
4
4
|
import { INTERVAL, UNISONUS } from "./data/constants.js";
|
|
5
5
|
// The three-tier consonance taxonomy [biblio: schulter-harmony] (the same table
|
|
6
|
-
// stated at docs/heavens.md and docs/tuning.md).
|
|
7
|
-
// deliberately NOT perfect here: in medieval counterpoint the P4 above the bass
|
|
6
|
+
// stated at ../../../docs/api/heavens.md and ../../../docs/api/tuning.md).
|
|
7
|
+
// Note that the perfect fourth is deliberately NOT perfect here: in medieval counterpoint the P4 above the bass
|
|
8
8
|
// is treated as a dissonance, unlike the melodic P4. So P1/P5/P8 are perfect,
|
|
9
9
|
// the thirds and sixths imperfect, and everything else — including P4 and the
|
|
10
10
|
// tritone — dissonant.
|
|
@@ -35,7 +35,8 @@ export function classifyInterval(a, b) {
|
|
|
35
35
|
semitones,
|
|
36
36
|
// Nominal equal-tempered cents (100 per semitone), NOT the tuned distance —
|
|
37
37
|
// the actual sounding interval depends on the temperament (see the ratio a
|
|
38
|
-
// temperamentum reports for a nota, and docs/tuning.md on
|
|
38
|
+
// temperamentum reports for a nota, and ../../../docs/api/tuning.md on
|
|
39
|
+
// nominal vs tuned).
|
|
39
40
|
cents: semitones * 100,
|
|
40
41
|
consonance: classifyConsonance(entry.class),
|
|
41
42
|
};
|
|
@@ -3,9 +3,18 @@ export interface ModalAffinity {
|
|
|
3
3
|
alias: string;
|
|
4
4
|
score: number;
|
|
5
5
|
}
|
|
6
|
+
export interface ModalAffinityOpts {
|
|
7
|
+
/** The chant's opening pitch class — applies the rank-weighted initials bonus. */
|
|
8
|
+
firstNotePc?: number;
|
|
9
|
+
/** The chant's closing pitch class — applies the final-note and tessitura bonuses. */
|
|
10
|
+
lastNotePc?: number;
|
|
11
|
+
/** Mean note MIDI minus the last note's MIDI — the melody's height above its close. */
|
|
12
|
+
tessitura?: number;
|
|
13
|
+
}
|
|
6
14
|
/**
|
|
7
15
|
* Rank a pitch-class distribution against the eight modes, best fit first.
|
|
8
|
-
*
|
|
16
|
+
* The optional signals sharpen the ranking: the opening note (initials bonus),
|
|
17
|
+
* the closing note (final-note bonus), and the tessitura (authentic/plagal).
|
|
9
18
|
*/
|
|
10
|
-
export declare function computeModalAffinity(pcDistribution: Record<number, number>,
|
|
19
|
+
export declare function computeModalAffinity(pcDistribution: Record<number, number>, opts?: ModalAffinityOpts): ModalAffinity[];
|
|
11
20
|
//# sourceMappingURL=modality.d.ts.map
|