@libraz/libcantus 0.9.1 → 0.9.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.
Files changed (160) hide show
  1. package/README.md +19 -3
  2. package/dist/analyze/index.cjs +1970 -0
  3. package/dist/analyze/index.cjs.map +1 -0
  4. package/dist/analyze/index.d.cts +341 -0
  5. package/dist/analyze/index.d.ts +341 -0
  6. package/dist/analyze/index.js +51 -0
  7. package/dist/analyze/index.js.map +1 -0
  8. package/dist/chunk-2ZWMSJH6.js +30 -0
  9. package/dist/chunk-2ZWMSJH6.js.map +1 -0
  10. package/dist/chunk-4MHA432G.js +2854 -0
  11. package/dist/chunk-4MHA432G.js.map +1 -0
  12. package/dist/chunk-62QZKCJQ.js +49 -0
  13. package/dist/chunk-62QZKCJQ.js.map +1 -0
  14. package/dist/chunk-AKLYPWIN.js +83 -0
  15. package/dist/chunk-AKLYPWIN.js.map +1 -0
  16. package/dist/chunk-C5XYKUVG.js +904 -0
  17. package/dist/chunk-C5XYKUVG.js.map +1 -0
  18. package/dist/chunk-CHMXELDS.js +549 -0
  19. package/dist/chunk-CHMXELDS.js.map +1 -0
  20. package/dist/chunk-DYWFNC4A.js +152 -0
  21. package/dist/chunk-DYWFNC4A.js.map +1 -0
  22. package/dist/chunk-EH6EM37U.js +315 -0
  23. package/dist/chunk-EH6EM37U.js.map +1 -0
  24. package/dist/chunk-FBQK3ZQN.js +131 -0
  25. package/dist/chunk-FBQK3ZQN.js.map +1 -0
  26. package/dist/chunk-GOAW4VYL.js +719 -0
  27. package/dist/chunk-GOAW4VYL.js.map +1 -0
  28. package/dist/chunk-I5R7ZBJS.js +202 -0
  29. package/dist/chunk-I5R7ZBJS.js.map +1 -0
  30. package/dist/chunk-IT2O4XFE.js +154 -0
  31. package/dist/chunk-IT2O4XFE.js.map +1 -0
  32. package/dist/chunk-MIWO425M.js +412 -0
  33. package/dist/chunk-MIWO425M.js.map +1 -0
  34. package/dist/chunk-N6IHQUOX.js +1 -0
  35. package/dist/chunk-N6IHQUOX.js.map +1 -0
  36. package/dist/chunk-SGU7SPLK.js +924 -0
  37. package/dist/chunk-SGU7SPLK.js.map +1 -0
  38. package/dist/core/index.cjs +648 -0
  39. package/dist/core/index.cjs.map +1 -0
  40. package/dist/core/index.d.cts +262 -0
  41. package/dist/core/index.d.ts +262 -0
  42. package/dist/core/index.js +93 -0
  43. package/dist/core/index.js.map +1 -0
  44. package/dist/function-DVH4Eusr.d.ts +187 -0
  45. package/dist/function-DYt4iKsn.d.cts +187 -0
  46. package/dist/generate/index.cjs +4123 -0
  47. package/dist/generate/index.cjs.map +1 -0
  48. package/dist/generate/index.d.cts +986 -0
  49. package/dist/generate/index.d.ts +986 -0
  50. package/dist/generate/index.js +52 -0
  51. package/dist/generate/index.js.map +1 -0
  52. package/dist/index-BNlGP86A.d.cts +157 -0
  53. package/dist/index-Bg5dYMmZ.d.ts +56 -0
  54. package/dist/index-Bt4tOUD9.d.ts +157 -0
  55. package/dist/index-CGMZNCx6.d.cts +140 -0
  56. package/dist/index-CGMZNCx6.d.ts +140 -0
  57. package/dist/index-Cl7mhOFM.d.cts +164 -0
  58. package/dist/index-Cl7mhOFM.d.ts +164 -0
  59. package/dist/index-CnZCo29u.d.cts +108 -0
  60. package/dist/index-CpOsP1y9.d.cts +151 -0
  61. package/dist/index-CxqBQtH1.d.cts +128 -0
  62. package/dist/index-Dikst44v.d.cts +56 -0
  63. package/dist/index-DxvARIP2.d.ts +151 -0
  64. package/dist/index-Jc0Ryi79.d.ts +128 -0
  65. package/dist/index-JnF4v9bx.d.ts +108 -0
  66. package/dist/index.cjs +3710 -3143
  67. package/dist/index.cjs.map +1 -1
  68. package/dist/index.d.cts +15 -3982
  69. package/dist/index.d.ts +15 -3982
  70. package/dist/index.js +191 -6504
  71. package/dist/index.js.map +1 -1
  72. package/dist/model/index.cjs +2574 -0
  73. package/dist/model/index.cjs.map +1 -0
  74. package/dist/model/index.d.cts +631 -0
  75. package/dist/model/index.d.ts +631 -0
  76. package/dist/model/index.js +21 -0
  77. package/dist/model/index.js.map +1 -0
  78. package/dist/styled-DZ7GjShb.d.ts +282 -0
  79. package/dist/styled-ZTbdaVFM.d.cts +282 -0
  80. package/dist/theory/index.cjs +1823 -0
  81. package/dist/theory/index.cjs.map +1 -0
  82. package/dist/theory/index.d.cts +535 -0
  83. package/dist/theory/index.d.ts +535 -0
  84. package/dist/theory/index.js +150 -0
  85. package/dist/theory/index.js.map +1 -0
  86. package/dist/types-MGP29r3t.d.cts +30 -0
  87. package/dist/types-MGP29r3t.d.ts +30 -0
  88. package/package.json +61 -5
  89. package/src/analyze/arrange/index.ts +21 -0
  90. package/src/analyze/arrange/internal.ts +127 -0
  91. package/src/analyze/arrange/tension.ts +199 -0
  92. package/src/analyze/arrange/tracks.ts +312 -0
  93. package/src/{detect → analyze/detect}/index.ts +50 -13
  94. package/src/analyze/functional/borrowed.ts +84 -0
  95. package/src/analyze/functional/cadence.ts +66 -0
  96. package/src/analyze/functional/function.ts +184 -0
  97. package/src/analyze/functional/index.ts +23 -0
  98. package/src/analyze/functional/internal.ts +29 -0
  99. package/src/{functional/index.ts → analyze/functional/roman.ts} +23 -332
  100. package/src/analyze/index.ts +49 -0
  101. package/src/{timeline → analyze/timeline}/index.ts +50 -18
  102. package/src/{analysis → analyze/voice}/index.ts +19 -7
  103. package/src/core/event-index/index.ts +104 -0
  104. package/src/core/index.ts +61 -0
  105. package/src/{meter → core/meter}/index.ts +24 -4
  106. package/src/{random → core/random}/index.ts +26 -3
  107. package/src/{tuning → core/tuning}/index.ts +36 -6
  108. package/src/core/validation/index.ts +119 -0
  109. package/src/{bass → generate/bass}/index.ts +28 -5
  110. package/src/{countermelody → generate/countermelody}/index.ts +176 -85
  111. package/src/{drums → generate/drums}/euclid.ts +10 -3
  112. package/src/{drums → generate/drums}/index.ts +58 -26
  113. package/src/{drums → generate/drums}/percussion.ts +1 -1
  114. package/src/{drums → generate/drums}/rng.ts +2 -2
  115. package/src/{groove → generate/groove}/index.ts +28 -3
  116. package/src/{harmonize → generate/harmonize}/index.ts +68 -57
  117. package/src/generate/index.ts +58 -0
  118. package/src/{motif → generate/motif}/index.ts +52 -28
  119. package/src/{progression → generate/progression}/index.ts +19 -41
  120. package/src/{reharmony → generate/reharmony}/index.ts +12 -19
  121. package/src/{rhythm → generate/rhythm}/index.ts +13 -3
  122. package/src/index.ts +13 -234
  123. package/src/model/chord.ts +442 -0
  124. package/src/model/index.ts +5 -1096
  125. package/src/model/interval.ts +82 -0
  126. package/src/model/key.ts +266 -0
  127. package/src/model/note.ts +176 -0
  128. package/src/model/progression.ts +161 -0
  129. package/src/model/shared.ts +12 -0
  130. package/src/{chord → theory/chord}/index.ts +23 -3
  131. package/src/{counterpoint → theory/counterpoint}/index.ts +2 -2
  132. package/src/{harmony → theory/harmony}/index.ts +6 -21
  133. package/src/theory/index.ts +112 -0
  134. package/src/{safety → theory/safety}/index.ts +72 -3
  135. package/src/theory/scale/degrees.ts +123 -0
  136. package/src/theory/scale/index.ts +28 -0
  137. package/src/theory/scale/key.ts +56 -0
  138. package/src/theory/scale/masks.ts +153 -0
  139. package/src/{spelling → theory/spelling}/index.ts +73 -6
  140. package/src/{symbol → theory/symbol}/index.ts +8 -1
  141. package/src/theory/voicing/index.ts +5 -0
  142. package/src/theory/voicing/internal.ts +168 -0
  143. package/src/theory/voicing/leading.ts +112 -0
  144. package/src/theory/voicing/satb.ts +216 -0
  145. package/src/theory/voicing/styled.ts +199 -0
  146. package/src/arrange/index.ts +0 -571
  147. package/src/scale/index.ts +0 -331
  148. package/src/voicing/index.ts +0 -623
  149. /package/src/{interval → core/interval}/index.ts +0 -0
  150. /package/src/{pitch → core/pitch}/index.ts +0 -0
  151. /package/src/{types.ts → core/types.ts} +0 -0
  152. /package/src/{drums → generate/drums}/beat.ts +0 -0
  153. /package/src/{drums → generate/drums}/fills.ts +0 -0
  154. /package/src/{drums → generate/drums}/ghost.ts +0 -0
  155. /package/src/{drums → generate/drums}/hihat.ts +0 -0
  156. /package/src/{drums → generate/drums}/hit.ts +0 -0
  157. /package/src/{drums → generate/drums}/internal.ts +0 -0
  158. /package/src/{drums → generate/drums}/kick.ts +0 -0
  159. /package/src/{drums → generate/drums}/swing.ts +0 -0
  160. /package/src/{chordscale → theory/chordscale}/index.ts +0 -0
@@ -0,0 +1,152 @@
1
+ // src/core/pitch/index.ts
2
+ var LETTER_SEMITONES = [0, 2, 4, 5, 7, 9, 11];
3
+ var LETTER_NAMES = ["C", "D", "E", "F", "G", "A", "B"];
4
+ function mod12(n) {
5
+ return (n % 12 + 12) % 12;
6
+ }
7
+ function mod7(n) {
8
+ return (n % 7 + 7) % 7;
9
+ }
10
+ function parseNote(text) {
11
+ const match = /^([A-Ga-g])([#x]*|b*)(-?\d+)?$/.exec(text.trim());
12
+ if (!match) {
13
+ throw new Error(`Invalid note: ${text}`);
14
+ }
15
+ const letterGlyph = (match[1] ?? "").toUpperCase();
16
+ const letter = LETTER_NAMES.indexOf(letterGlyph);
17
+ let alter = 0;
18
+ for (const ch of match[2] ?? "") {
19
+ if (ch === "#") {
20
+ alter += 1;
21
+ } else if (ch === "x") {
22
+ alter += 2;
23
+ } else if (ch === "b") {
24
+ alter -= 1;
25
+ }
26
+ }
27
+ const note = { letter, alter };
28
+ if (match[3] !== void 0) {
29
+ note.octave = Number.parseInt(match[3], 10);
30
+ }
31
+ return note;
32
+ }
33
+ function formatAlter(alter) {
34
+ if (alter > 0) {
35
+ return "#".repeat(alter);
36
+ }
37
+ if (alter < 0) {
38
+ return "b".repeat(-alter);
39
+ }
40
+ return "";
41
+ }
42
+ function formatNote(note) {
43
+ const glyph = LETTER_NAMES[mod7(note.letter)] ?? "C";
44
+ const octave = note.octave === void 0 ? "" : String(note.octave);
45
+ return `${glyph}${formatAlter(note.alter)}${octave}`;
46
+ }
47
+ function noteToPitchClass(note) {
48
+ const natural = LETTER_SEMITONES[mod7(note.letter)] ?? 0;
49
+ return mod12(natural + note.alter);
50
+ }
51
+ function noteToMidi(note) {
52
+ if (note.octave === void 0) {
53
+ throw new Error("noteToMidi requires an octave");
54
+ }
55
+ const natural = LETTER_SEMITONES[mod7(note.letter)] ?? 0;
56
+ return (note.octave + 1) * 12 + natural + note.alter;
57
+ }
58
+ var SHARP_SPELLING = [
59
+ [0, 0],
60
+ [0, 1],
61
+ [1, 0],
62
+ [1, 1],
63
+ [2, 0],
64
+ [3, 0],
65
+ [3, 1],
66
+ [4, 0],
67
+ [4, 1],
68
+ [5, 0],
69
+ [5, 1],
70
+ [6, 0]
71
+ ];
72
+ var FLAT_SPELLING = [
73
+ [0, 0],
74
+ [1, -1],
75
+ [1, 0],
76
+ [2, -1],
77
+ [2, 0],
78
+ [3, 0],
79
+ [4, -1],
80
+ [4, 0],
81
+ [5, -1],
82
+ [5, 0],
83
+ [6, -1],
84
+ [6, 0]
85
+ ];
86
+ function midiToNote(midi, spelling = "sharp") {
87
+ const rounded = Math.round(midi);
88
+ const pc = mod12(rounded);
89
+ const octave = Math.floor(rounded / 12) - 1;
90
+ const table = spelling === "sharp" ? SHARP_SPELLING : FLAT_SPELLING;
91
+ const entry = table[pc] ?? [0, 0];
92
+ return { letter: entry[0], alter: entry[1], octave };
93
+ }
94
+ var SIMPLE_REFERENCE = [0, 0, 2, 4, 5, 7, 9, 11];
95
+ function isPerfectNumber(simpleNumber) {
96
+ return simpleNumber === 1 || simpleNumber === 4 || simpleNumber === 5;
97
+ }
98
+ function qualityFromSpan(numberValue, semitones) {
99
+ const octaves = Math.floor((numberValue - 1) / 7);
100
+ const simple = numberValue - 7 * octaves;
101
+ const reference = (SIMPLE_REFERENCE[simple] ?? 0) + 12 * octaves;
102
+ const delta = semitones - reference;
103
+ if (isPerfectNumber(simple)) {
104
+ if (delta === 0) {
105
+ return "P";
106
+ }
107
+ return delta > 0 ? "A".repeat(delta) : "d".repeat(-delta);
108
+ }
109
+ if (delta === 0) {
110
+ return "M";
111
+ }
112
+ if (delta === -1) {
113
+ return "m";
114
+ }
115
+ return delta > 0 ? "A".repeat(delta) : "d".repeat(-delta - 1);
116
+ }
117
+ function diatonicIndex(note) {
118
+ return mod7(note.letter) + 7 * (note.octave ?? 0);
119
+ }
120
+ function spelledInterval(a, b) {
121
+ const octaved = a.octave !== void 0 && b.octave !== void 0;
122
+ let letterSteps;
123
+ let semitones;
124
+ if (octaved) {
125
+ letterSteps = diatonicIndex(b) - diatonicIndex(a);
126
+ semitones = noteToMidi(b) - noteToMidi(a);
127
+ } else {
128
+ letterSteps = mod7(b.letter - a.letter);
129
+ let rawSemis = mod12(noteToPitchClass(b) - noteToPitchClass(a));
130
+ const reference = SIMPLE_REFERENCE[letterSteps + 1] ?? 0;
131
+ if (reference - rawSemis > 6) {
132
+ rawSemis += 12;
133
+ } else if (rawSemis - reference > 6) {
134
+ rawSemis -= 12;
135
+ }
136
+ semitones = rawSemis;
137
+ }
138
+ const absSteps = Math.abs(letterSteps);
139
+ const number = absSteps + 1;
140
+ const quality = qualityFromSpan(number, Math.abs(semitones));
141
+ return { number, quality, semitones };
142
+ }
143
+
144
+ export {
145
+ parseNote,
146
+ formatNote,
147
+ noteToPitchClass,
148
+ noteToMidi,
149
+ midiToNote,
150
+ spelledInterval
151
+ };
152
+ //# sourceMappingURL=chunk-DYWFNC4A.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/pitch/index.ts"],"sourcesContent":["/**\n * Pitch spelling: letter-name notes, enharmonic-aware conversion, and spelled\n * intervals.\n *\n * The rest of the library works in pitch classes (0..11), which cannot tell a\n * G# from an Ab or an augmented fourth from a diminished fifth. This module adds\n * an explicit spelling layer — a note is a diatonic letter plus a chromatic\n * alteration — so interval quality (P/M/m/A/d) and enharmonic identity are\n * preserved.\n */\n\n/** Semitone offset of each natural letter above C: C D E F G A B. */\nconst LETTER_SEMITONES = [0, 2, 4, 5, 7, 9, 11] as const;\n\n/** Letter glyphs indexed by letter number (0 = C .. 6 = B). */\nconst LETTER_NAMES = ['C', 'D', 'E', 'F', 'G', 'A', 'B'] as const;\n\n/**\n * A spelled note: a diatonic letter, a chromatic alteration, and an optional\n * octave.\n *\n * - `letter` is 0..6 for C..B.\n * - `alter` is the number of semitones of alteration: -2 double-flat, -1 flat,\n * 0 natural, +1 sharp, +2 double-sharp.\n * - `octave` follows scientific pitch notation (middle C = C4 = MIDI 60). When\n * absent, the note denotes a bare pitch class.\n *\n * @category Pitch & Intervals\n */\nexport type Note = {\n letter: number;\n alter: number;\n octave?: number;\n};\n\n/**\n * A spelled interval: a diatonic number, a quality label, and its semitone span.\n *\n * @category Pitch & Intervals\n */\nexport type SpelledInterval = {\n /** Diatonic size: 1 = unison, 2 = second, ... 8 = octave, and beyond. */\n number: number;\n /** Quality label: 'P', 'M', 'm', or repeated 'A'/'d' for (multiply) aug/dim. */\n quality: string;\n /** Signed semitone distance from the first note to the second. */\n semitones: number;\n};\n\n/** Reduce any integer to a pitch class in [0, 11]. */\nfunction mod12(n: number): number {\n return ((n % 12) + 12) % 12;\n}\n\n/** Reduce any integer to [0, 7). */\nfunction mod7(n: number): number {\n return ((n % 7) + 7) % 7;\n}\n\n/**\n * Parse scientific pitch notation into a {@link Note}.\n *\n * Accepts a letter (case-insensitive) followed by any number of same-direction\n * accidentals and an optional octave, e.g. `\"C\"`, `\"C#4\"`, `\"Bb\"`, `\"F##3\"`,\n * `\"Ebb2\"`. Both `#` and `x` (double-sharp) are accepted for sharps; `b` for\n * flats. Mixing sharps and flats (e.g. `\"C#b\"`) is rejected.\n *\n * @param text The note text.\n * @returns The parsed note.\n * @throws If the text is not a valid note.\n * @example\n * ```ts\n * import { parseNote, noteToMidi } from '@libraz/libcantus';\n * const n = parseNote('C#4');\n * noteToMidi(n); // 61\n * ```\n * @category Pitch & Intervals\n */\nexport function parseNote(text: string): Note {\n const match = /^([A-Ga-g])([#x]*|b*)(-?\\d+)?$/.exec(text.trim());\n if (!match) {\n throw new Error(`Invalid note: ${text}`);\n }\n const letterGlyph = (match[1] ?? '').toUpperCase();\n const letter = LETTER_NAMES.indexOf(letterGlyph as (typeof LETTER_NAMES)[number]);\n let alter = 0;\n for (const ch of match[2] ?? '') {\n if (ch === '#') {\n alter += 1;\n } else if (ch === 'x') {\n alter += 2;\n } else if (ch === 'b') {\n alter -= 1;\n }\n }\n const note: Note = { letter, alter };\n if (match[3] !== undefined) {\n note.octave = Number.parseInt(match[3], 10);\n }\n return note;\n}\n\n/** Render an alteration as accidental glyphs (`##`, `b`, empty for natural). */\nfunction formatAlter(alter: number): string {\n if (alter > 0) {\n return '#'.repeat(alter);\n }\n if (alter < 0) {\n return 'b'.repeat(-alter);\n }\n return '';\n}\n\n/**\n * Render a {@link Note} as scientific pitch notation.\n *\n * @param note The note to format.\n * @returns The note text, including the octave when present.\n * @example\n * ```ts\n * import { formatNote, parseNote } from '@libraz/libcantus';\n * formatNote(parseNote('C#4')); // 'C#4'\n * ```\n * @category Pitch & Intervals\n */\nexport function formatNote(note: Note): string {\n const glyph = LETTER_NAMES[mod7(note.letter)] ?? 'C';\n const octave = note.octave === undefined ? '' : String(note.octave);\n return `${glyph}${formatAlter(note.alter)}${octave}`;\n}\n\n/**\n * Pitch class (0..11) of a note, ignoring octave.\n *\n * @param note The note.\n * @returns The pitch class.\n * @example\n * ```ts\n * import { noteToPitchClass, parseNote } from '@libraz/libcantus';\n * noteToPitchClass(parseNote('Db')); // 1\n * ```\n * @category Pitch & Intervals\n */\nexport function noteToPitchClass(note: Note): number {\n const natural = LETTER_SEMITONES[mod7(note.letter)] ?? 0;\n return mod12(natural + note.alter);\n}\n\n/**\n * MIDI number of a note (middle C = C4 = 60).\n *\n * @param note The note; must carry an octave.\n * @returns The MIDI number.\n * @throws If the note has no octave.\n * @example\n * ```ts\n * import { noteToMidi, parseNote } from '@libraz/libcantus';\n * noteToMidi(parseNote('A4')); // 69\n * ```\n * @category Pitch & Intervals\n */\nexport function noteToMidi(note: Note): number {\n if (note.octave === undefined) {\n throw new Error('noteToMidi requires an octave');\n }\n const natural = LETTER_SEMITONES[mod7(note.letter)] ?? 0;\n return (note.octave + 1) * 12 + natural + note.alter;\n}\n\n/** Preferred spelling when naming a black key from a bare MIDI number. */\nconst SHARP_SPELLING: readonly [number, number][] = [\n [0, 0],\n [0, 1],\n [1, 0],\n [1, 1],\n [2, 0],\n [3, 0],\n [3, 1],\n [4, 0],\n [4, 1],\n [5, 0],\n [5, 1],\n [6, 0],\n];\n\nconst FLAT_SPELLING: readonly [number, number][] = [\n [0, 0],\n [1, -1],\n [1, 0],\n [2, -1],\n [2, 0],\n [3, 0],\n [4, -1],\n [4, 0],\n [5, -1],\n [5, 0],\n [6, -1],\n [6, 0],\n];\n\n/**\n * Name a MIDI number as a {@link Note}, choosing sharp or flat spelling.\n *\n * The input is rounded to the nearest integer but is not clamped to the MIDI\n * range 0..127: out-of-range values extrapolate linearly (e.g. `-1` -> `B-2`,\n * `128` -> `G#9`) and remain an exact inverse of {@link noteToMidi}.\n *\n * @param midi The MIDI number.\n * @param spelling Whether to prefer sharps or flats for black keys.\n * @returns The spelled note, with octave.\n * @example\n * ```ts\n * import { midiToNote, formatNote } from '@libraz/libcantus';\n * formatNote(midiToNote(61)); // 'C#4'\n * formatNote(midiToNote(61, 'flat')); // 'Db4'\n * ```\n * @category Pitch & Intervals\n */\nexport function midiToNote(midi: number, spelling: 'sharp' | 'flat' = 'sharp'): Note {\n const rounded = Math.round(midi);\n const pc = mod12(rounded);\n const octave = Math.floor(rounded / 12) - 1;\n const table = spelling === 'sharp' ? SHARP_SPELLING : FLAT_SPELLING;\n const entry = table[pc] ?? [0, 0];\n return { letter: entry[0], alter: entry[1], octave };\n}\n\n/** Reference semitone span of a perfect/major simple interval by diatonic number. */\nconst SIMPLE_REFERENCE = [0, 0, 2, 4, 5, 7, 9, 11] as const;\n\n/** Whether a simple diatonic number (1..7 within an octave) is a perfect kind. */\nfunction isPerfectNumber(simpleNumber: number): boolean {\n return simpleNumber === 1 || simpleNumber === 4 || simpleNumber === 5;\n}\n\n/** Quality label from a diatonic number and its actual semitone span. */\nfunction qualityFromSpan(numberValue: number, semitones: number): string {\n const octaves = Math.floor((numberValue - 1) / 7);\n const simple = numberValue - 7 * octaves;\n const reference = (SIMPLE_REFERENCE[simple] ?? 0) + 12 * octaves;\n const delta = semitones - reference;\n if (isPerfectNumber(simple)) {\n if (delta === 0) {\n return 'P';\n }\n return delta > 0 ? 'A'.repeat(delta) : 'd'.repeat(-delta);\n }\n if (delta === 0) {\n return 'M';\n }\n if (delta === -1) {\n return 'm';\n }\n return delta > 0 ? 'A'.repeat(delta) : 'd'.repeat(-delta - 1);\n}\n\n/** Diatonic ladder index of a note (letter + 7 * octave when octave-bearing). */\nfunction diatonicIndex(note: Note): number {\n return mod7(note.letter) + 7 * (note.octave ?? 0);\n}\n\n/**\n * The spelled interval from one note to another.\n *\n * When both notes carry octaves, the interval reflects their full signed\n * distance (so `C4 -> C5` is a perfect octave). When either lacks an octave the\n * interval is measured within a single ascending octave from `a` to `b`.\n *\n * @param a The lower/first note.\n * @param b The upper/second note.\n * @returns The diatonic number, quality, and semitone span.\n * @example\n * ```ts\n * import { spelledInterval, parseNote } from '@libraz/libcantus';\n * spelledInterval(parseNote('C4'), parseNote('G4'));\n * // { number: 5, quality: 'P', semitones: 7 }\n * ```\n * @category Pitch & Intervals\n */\nexport function spelledInterval(a: Note, b: Note): SpelledInterval {\n const octaved = a.octave !== undefined && b.octave !== undefined;\n let letterSteps: number;\n let semitones: number;\n if (octaved) {\n letterSteps = diatonicIndex(b) - diatonicIndex(a);\n // Signed pitch distance from a to b, so a note below a yields a negative span.\n semitones = noteToMidi(b) - noteToMidi(a);\n } else {\n letterSteps = mod7(b.letter - a.letter);\n let rawSemis = mod12(noteToPitchClass(b) - noteToPitchClass(a));\n // Lift the chromatic span into the octave nearest the diatonic reference so\n // wraparound intervals stay consistent with the diatonic number instead of\n // collapsing modulo 12. The correction is bidirectional: an upward wrap\n // (e.g. Ab -> G# = augmented seventh) adds an octave, while a same-letter or\n // downward chromatic step (e.g. E -> Eb, F# -> F, C -> Cb = descending\n // diminished/augmented unison) subtracts one so the sign follows pitch\n // direction instead of returning a garbage stack of augmentations.\n const reference = SIMPLE_REFERENCE[letterSteps + 1] ?? 0;\n if (reference - rawSemis > 6) {\n rawSemis += 12;\n } else if (rawSemis - reference > 6) {\n rawSemis -= 12;\n }\n semitones = rawSemis;\n }\n const absSteps = Math.abs(letterSteps);\n const number = absSteps + 1;\n // Quality comes from the absolute chromatic span; semitones keeps its sign.\n const quality = qualityFromSpan(number, Math.abs(semitones));\n return { number, quality, semitones };\n}\n"],"mappings":";AAYA,IAAM,mBAAmB,CAAC,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,EAAE;AAG9C,IAAM,eAAe,CAAC,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,GAAG;AAmCvD,SAAS,MAAM,GAAmB;AAChC,UAAS,IAAI,KAAM,MAAM;AAC3B;AAGA,SAAS,KAAK,GAAmB;AAC/B,UAAS,IAAI,IAAK,KAAK;AACzB;AAqBO,SAAS,UAAU,MAAoB;AAC5C,QAAM,QAAQ,iCAAiC,KAAK,KAAK,KAAK,CAAC;AAC/D,MAAI,CAAC,OAAO;AACV,UAAM,IAAI,MAAM,iBAAiB,IAAI,EAAE;AAAA,EACzC;AACA,QAAM,eAAe,MAAM,CAAC,KAAK,IAAI,YAAY;AACjD,QAAM,SAAS,aAAa,QAAQ,WAA4C;AAChF,MAAI,QAAQ;AACZ,aAAW,MAAM,MAAM,CAAC,KAAK,IAAI;AAC/B,QAAI,OAAO,KAAK;AACd,eAAS;AAAA,IACX,WAAW,OAAO,KAAK;AACrB,eAAS;AAAA,IACX,WAAW,OAAO,KAAK;AACrB,eAAS;AAAA,IACX;AAAA,EACF;AACA,QAAM,OAAa,EAAE,QAAQ,MAAM;AACnC,MAAI,MAAM,CAAC,MAAM,QAAW;AAC1B,SAAK,SAAS,OAAO,SAAS,MAAM,CAAC,GAAG,EAAE;AAAA,EAC5C;AACA,SAAO;AACT;AAGA,SAAS,YAAY,OAAuB;AAC1C,MAAI,QAAQ,GAAG;AACb,WAAO,IAAI,OAAO,KAAK;AAAA,EACzB;AACA,MAAI,QAAQ,GAAG;AACb,WAAO,IAAI,OAAO,CAAC,KAAK;AAAA,EAC1B;AACA,SAAO;AACT;AAcO,SAAS,WAAW,MAAoB;AAC7C,QAAM,QAAQ,aAAa,KAAK,KAAK,MAAM,CAAC,KAAK;AACjD,QAAM,SAAS,KAAK,WAAW,SAAY,KAAK,OAAO,KAAK,MAAM;AAClE,SAAO,GAAG,KAAK,GAAG,YAAY,KAAK,KAAK,CAAC,GAAG,MAAM;AACpD;AAcO,SAAS,iBAAiB,MAAoB;AACnD,QAAM,UAAU,iBAAiB,KAAK,KAAK,MAAM,CAAC,KAAK;AACvD,SAAO,MAAM,UAAU,KAAK,KAAK;AACnC;AAeO,SAAS,WAAW,MAAoB;AAC7C,MAAI,KAAK,WAAW,QAAW;AAC7B,UAAM,IAAI,MAAM,+BAA+B;AAAA,EACjD;AACA,QAAM,UAAU,iBAAiB,KAAK,KAAK,MAAM,CAAC,KAAK;AACvD,UAAQ,KAAK,SAAS,KAAK,KAAK,UAAU,KAAK;AACjD;AAGA,IAAM,iBAA8C;AAAA,EAClD,CAAC,GAAG,CAAC;AAAA,EACL,CAAC,GAAG,CAAC;AAAA,EACL,CAAC,GAAG,CAAC;AAAA,EACL,CAAC,GAAG,CAAC;AAAA,EACL,CAAC,GAAG,CAAC;AAAA,EACL,CAAC,GAAG,CAAC;AAAA,EACL,CAAC,GAAG,CAAC;AAAA,EACL,CAAC,GAAG,CAAC;AAAA,EACL,CAAC,GAAG,CAAC;AAAA,EACL,CAAC,GAAG,CAAC;AAAA,EACL,CAAC,GAAG,CAAC;AAAA,EACL,CAAC,GAAG,CAAC;AACP;AAEA,IAAM,gBAA6C;AAAA,EACjD,CAAC,GAAG,CAAC;AAAA,EACL,CAAC,GAAG,EAAE;AAAA,EACN,CAAC,GAAG,CAAC;AAAA,EACL,CAAC,GAAG,EAAE;AAAA,EACN,CAAC,GAAG,CAAC;AAAA,EACL,CAAC,GAAG,CAAC;AAAA,EACL,CAAC,GAAG,EAAE;AAAA,EACN,CAAC,GAAG,CAAC;AAAA,EACL,CAAC,GAAG,EAAE;AAAA,EACN,CAAC,GAAG,CAAC;AAAA,EACL,CAAC,GAAG,EAAE;AAAA,EACN,CAAC,GAAG,CAAC;AACP;AAoBO,SAAS,WAAW,MAAc,WAA6B,SAAe;AACnF,QAAM,UAAU,KAAK,MAAM,IAAI;AAC/B,QAAM,KAAK,MAAM,OAAO;AACxB,QAAM,SAAS,KAAK,MAAM,UAAU,EAAE,IAAI;AAC1C,QAAM,QAAQ,aAAa,UAAU,iBAAiB;AACtD,QAAM,QAAQ,MAAM,EAAE,KAAK,CAAC,GAAG,CAAC;AAChC,SAAO,EAAE,QAAQ,MAAM,CAAC,GAAG,OAAO,MAAM,CAAC,GAAG,OAAO;AACrD;AAGA,IAAM,mBAAmB,CAAC,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,EAAE;AAGjD,SAAS,gBAAgB,cAA+B;AACtD,SAAO,iBAAiB,KAAK,iBAAiB,KAAK,iBAAiB;AACtE;AAGA,SAAS,gBAAgB,aAAqB,WAA2B;AACvE,QAAM,UAAU,KAAK,OAAO,cAAc,KAAK,CAAC;AAChD,QAAM,SAAS,cAAc,IAAI;AACjC,QAAM,aAAa,iBAAiB,MAAM,KAAK,KAAK,KAAK;AACzD,QAAM,QAAQ,YAAY;AAC1B,MAAI,gBAAgB,MAAM,GAAG;AAC3B,QAAI,UAAU,GAAG;AACf,aAAO;AAAA,IACT;AACA,WAAO,QAAQ,IAAI,IAAI,OAAO,KAAK,IAAI,IAAI,OAAO,CAAC,KAAK;AAAA,EAC1D;AACA,MAAI,UAAU,GAAG;AACf,WAAO;AAAA,EACT;AACA,MAAI,UAAU,IAAI;AAChB,WAAO;AAAA,EACT;AACA,SAAO,QAAQ,IAAI,IAAI,OAAO,KAAK,IAAI,IAAI,OAAO,CAAC,QAAQ,CAAC;AAC9D;AAGA,SAAS,cAAc,MAAoB;AACzC,SAAO,KAAK,KAAK,MAAM,IAAI,KAAK,KAAK,UAAU;AACjD;AAoBO,SAAS,gBAAgB,GAAS,GAA0B;AACjE,QAAM,UAAU,EAAE,WAAW,UAAa,EAAE,WAAW;AACvD,MAAI;AACJ,MAAI;AACJ,MAAI,SAAS;AACX,kBAAc,cAAc,CAAC,IAAI,cAAc,CAAC;AAEhD,gBAAY,WAAW,CAAC,IAAI,WAAW,CAAC;AAAA,EAC1C,OAAO;AACL,kBAAc,KAAK,EAAE,SAAS,EAAE,MAAM;AACtC,QAAI,WAAW,MAAM,iBAAiB,CAAC,IAAI,iBAAiB,CAAC,CAAC;AAQ9D,UAAM,YAAY,iBAAiB,cAAc,CAAC,KAAK;AACvD,QAAI,YAAY,WAAW,GAAG;AAC5B,kBAAY;AAAA,IACd,WAAW,WAAW,YAAY,GAAG;AACnC,kBAAY;AAAA,IACd;AACA,gBAAY;AAAA,EACd;AACA,QAAM,WAAW,KAAK,IAAI,WAAW;AACrC,QAAM,SAAS,WAAW;AAE1B,QAAM,UAAU,gBAAgB,QAAQ,KAAK,IAAI,SAAS,CAAC;AAC3D,SAAO,EAAE,QAAQ,SAAS,UAAU;AACtC;","names":[]}
@@ -0,0 +1,315 @@
1
+ import {
2
+ chordPitchClasses,
3
+ createsHiddenParallelPerfect,
4
+ createsParallelPerfect,
5
+ createsVerticalDissonance,
6
+ isForbiddenMelodicLeap,
7
+ isScaleTone
8
+ } from "./chunk-MIWO425M.js";
9
+ import {
10
+ assertFiniteNumber,
11
+ assertGenerationBudget,
12
+ assertInteger
13
+ } from "./chunk-FBQK3ZQN.js";
14
+
15
+ // src/theory/safety/index.ts
16
+ var NoteSafety = /* @__PURE__ */ ((NoteSafety2) => {
17
+ NoteSafety2[NoteSafety2["Safe"] = 0] = "Safe";
18
+ NoteSafety2[NoteSafety2["Warning"] = 1] = "Warning";
19
+ NoteSafety2[NoteSafety2["Dissonant"] = 2] = "Dissonant";
20
+ return NoteSafety2;
21
+ })(NoteSafety || {});
22
+ var ReasonFlag = /* @__PURE__ */ ((ReasonFlag2) => {
23
+ ReasonFlag2[ReasonFlag2["ChordTone"] = 1] = "ChordTone";
24
+ ReasonFlag2[ReasonFlag2["Tension"] = 2] = "Tension";
25
+ ReasonFlag2[ReasonFlag2["AvoidNote"] = 4] = "AvoidNote";
26
+ ReasonFlag2[ReasonFlag2["ScaleTone"] = 8] = "ScaleTone";
27
+ ReasonFlag2[ReasonFlag2["NonScale"] = 16] = "NonScale";
28
+ ReasonFlag2[ReasonFlag2["OutOfRange"] = 32] = "OutOfRange";
29
+ ReasonFlag2[ReasonFlag2["Tritone"] = 64] = "Tritone";
30
+ ReasonFlag2[ReasonFlag2["LargeLeap"] = 128] = "LargeLeap";
31
+ ReasonFlag2[ReasonFlag2["MinorSecond"] = 256] = "MinorSecond";
32
+ ReasonFlag2[ReasonFlag2["MajorSeventh"] = 512] = "MajorSeventh";
33
+ ReasonFlag2[ReasonFlag2["VerticalDissonance"] = 1024] = "VerticalDissonance";
34
+ ReasonFlag2[ReasonFlag2["ParallelPerfect"] = 2048] = "ParallelPerfect";
35
+ ReasonFlag2[ReasonFlag2["HiddenParallel"] = 4096] = "HiddenParallel";
36
+ ReasonFlag2[ReasonFlag2["VoiceCrossing"] = 8192] = "VoiceCrossing";
37
+ ReasonFlag2[ReasonFlag2["Suspension"] = 16384] = "Suspension";
38
+ ReasonFlag2[ReasonFlag2["NeedsResolution"] = 32768] = "NeedsResolution";
39
+ return ReasonFlag2;
40
+ })(ReasonFlag || {});
41
+ var SUGGESTION_WINDOW = 12;
42
+ var MAX_SUGGESTIONS = 3;
43
+ var REASON_SEVERITY = [
44
+ [2 /* Tension */, 1 /* Warning */, 1 /* Warning */],
45
+ [4 /* AvoidNote */, 1 /* Warning */, 2 /* Dissonant */],
46
+ [8 /* ScaleTone */, 1 /* Warning */, 1 /* Warning */],
47
+ [16 /* NonScale */, 2 /* Dissonant */, 2 /* Dissonant */],
48
+ [32 /* OutOfRange */, 1 /* Warning */, 1 /* Warning */],
49
+ [64 /* Tritone */, 2 /* Dissonant */, 2 /* Dissonant */],
50
+ [128 /* LargeLeap */, 1 /* Warning */, 2 /* Dissonant */],
51
+ [1024 /* VerticalDissonance */, 2 /* Dissonant */, 2 /* Dissonant */],
52
+ [2048 /* ParallelPerfect */, 1 /* Warning */, 2 /* Dissonant */],
53
+ [4096 /* HiddenParallel */, 1 /* Warning */, 2 /* Dissonant */],
54
+ [8192 /* VoiceCrossing */, 1 /* Warning */, 2 /* Dissonant */],
55
+ [16384 /* Suspension */, 2 /* Dissonant */, 2 /* Dissonant */],
56
+ [32768 /* NeedsResolution */, 1 /* Warning */, 2 /* Dissonant */]
57
+ ];
58
+ function minimumSafetyForReasons(reasons, profile) {
59
+ let minimum = 0 /* Safe */;
60
+ const severityIndex = profile === "strict" ? 2 : 1;
61
+ for (const entry of REASON_SEVERITY) {
62
+ if (reasons & entry[0]) {
63
+ minimum = Math.max(minimum, entry[severityIndex]);
64
+ }
65
+ }
66
+ return minimum;
67
+ }
68
+ function pitchClass(pitch) {
69
+ return (Math.trunc(pitch) % 12 + 12) % 12;
70
+ }
71
+ function intervalAboveRoot(pitch, chord) {
72
+ return ((pitchClass(pitch) - pitchClass(chord.rootPc)) % 12 + 12) % 12;
73
+ }
74
+ function isChordMember(pitch, chord) {
75
+ return chordPitchClasses(chord).includes(pitchClass(pitch));
76
+ }
77
+ function avoidsFourth(chord) {
78
+ return chord.intervals.includes(4) && !chord.intervals.includes(5);
79
+ }
80
+ function avoidsMajorSeventh(chord) {
81
+ return chord.intervals.includes(4) && chord.intervals.includes(10);
82
+ }
83
+ function stepResolution(pitch, chord) {
84
+ for (let delta = 1; delta <= 2; delta += 1) {
85
+ if (isChordMember(pitch - delta, chord)) {
86
+ return pitch - delta;
87
+ }
88
+ }
89
+ return void 0;
90
+ }
91
+ function evaluateSafety(q) {
92
+ assertSafetyContext(q);
93
+ assertFiniteNumber(q.candidatePitch, "candidatePitch");
94
+ return evaluateInternal(q, true);
95
+ }
96
+ function assertSafetyContext(q) {
97
+ if (q.prevPitch !== void 0) assertFiniteNumber(q.prevPitch, "prevPitch");
98
+ if (q.vocalLow !== void 0) assertFiniteNumber(q.vocalLow, "vocalLow");
99
+ if (q.vocalHigh !== void 0) assertFiniteNumber(q.vocalHigh, "vocalHigh");
100
+ if (q.vocalLow !== void 0 && q.vocalHigh !== void 0 && q.vocalLow > q.vocalHigh) {
101
+ throw new RangeError("vocalLow must not exceed vocalHigh");
102
+ }
103
+ assertGenerationBudget(q.otherVoices.length, "other voices");
104
+ for (let index = 0; index < q.otherVoices.length; index += 1) {
105
+ const voice = q.otherVoices[index];
106
+ if (!voice) continue;
107
+ assertFiniteNumber(voice.pitch, `otherVoices[${index}].pitch`);
108
+ if (voice.prevPitch !== void 0) {
109
+ assertFiniteNumber(voice.prevPitch, `otherVoices[${index}].prevPitch`);
110
+ }
111
+ }
112
+ }
113
+ function evaluateInternal(q, collectSuggestions) {
114
+ let reasons = 0;
115
+ let level = 0 /* Safe */;
116
+ let resolveTo;
117
+ const raise = (l) => {
118
+ if (l > level) {
119
+ level = l;
120
+ }
121
+ };
122
+ const avoidSeverity = q.profile === "strict" ? 2 /* Dissonant */ : 1 /* Warning */;
123
+ const parallelSeverity = avoidSeverity;
124
+ const pitch = q.candidatePitch;
125
+ if (q.vocalLow !== void 0 && pitch < q.vocalLow || q.vocalHigh !== void 0 && pitch > q.vocalHigh) {
126
+ reasons |= 32 /* OutOfRange */;
127
+ raise(1 /* Warning */);
128
+ }
129
+ const inScale = isScaleTone(pitch, q.key);
130
+ const chord = q.chord;
131
+ if (chord && isChordMember(pitch, chord)) {
132
+ reasons |= 1 /* ChordTone */;
133
+ } else if (chord) {
134
+ const ic = intervalAboveRoot(pitch, chord);
135
+ const isTension = ic === 2 || ic === 5 || ic === 9;
136
+ const avoid = ic === 5 && avoidsFourth(chord) || ic === 11 && avoidsMajorSeventh(chord);
137
+ if (avoid) {
138
+ reasons |= 4 /* AvoidNote */;
139
+ if (isTension) {
140
+ reasons |= 2 /* Tension */;
141
+ }
142
+ reasons |= 32768 /* NeedsResolution */;
143
+ resolveTo = stepResolution(pitch, chord);
144
+ raise(avoidSeverity);
145
+ } else if (isTension && inScale) {
146
+ reasons |= 2 /* Tension */;
147
+ raise(1 /* Warning */);
148
+ } else if (inScale) {
149
+ reasons |= 8 /* ScaleTone */;
150
+ raise(1 /* Warning */);
151
+ } else {
152
+ reasons |= 16 /* NonScale */;
153
+ raise(2 /* Dissonant */);
154
+ }
155
+ } else if (inScale) {
156
+ reasons |= 8 /* ScaleTone */;
157
+ raise(1 /* Warning */);
158
+ } else {
159
+ reasons |= 16 /* NonScale */;
160
+ raise(2 /* Dissonant */);
161
+ }
162
+ if (chord && !(chord.intervals.includes(4) && chord.intervals.includes(10))) {
163
+ const ic = intervalAboveRoot(pitch, chord);
164
+ const toneOffsets = chord.intervals.map((i) => (i % 12 + 12) % 12);
165
+ if (toneOffsets.some((t) => Math.abs(ic - t) === 6)) {
166
+ reasons |= 64 /* Tritone */;
167
+ }
168
+ }
169
+ if (q.prevPitch !== void 0) {
170
+ const pc = Math.abs(pitch - q.prevPitch) % 12;
171
+ if (pc === 6) {
172
+ reasons |= 64 /* Tritone */;
173
+ raise(2 /* Dissonant */);
174
+ }
175
+ if (isForbiddenMelodicLeap(q.prevPitch, pitch)) {
176
+ reasons |= 128 /* LargeLeap */;
177
+ raise(parallelSeverity);
178
+ }
179
+ if (pc === 1) {
180
+ reasons |= 256 /* MinorSecond */;
181
+ }
182
+ if (pc === 11) {
183
+ reasons |= 512 /* MajorSeventh */;
184
+ }
185
+ }
186
+ if (q.strongBeat) {
187
+ const twoVoice = q.otherVoices.length === 1;
188
+ const prev = q.prevPitch;
189
+ for (const ov of q.otherVoices) {
190
+ if (!createsVerticalDissonance(pitch, ov.pitch, twoVoice)) {
191
+ continue;
192
+ }
193
+ reasons |= 1024 /* VerticalDissonance */;
194
+ raise(2 /* Dissonant */);
195
+ if (prev !== void 0 && pitch === prev && ov.prevPitch !== void 0 && !createsVerticalDissonance(prev, ov.prevPitch, twoVoice)) {
196
+ reasons |= 16384 /* Suspension */;
197
+ }
198
+ }
199
+ }
200
+ if (q.prevPitch !== void 0) {
201
+ const prev = q.prevPitch;
202
+ for (const ov of q.otherVoices) {
203
+ if (ov.prevPitch === void 0) {
204
+ continue;
205
+ }
206
+ if (createsParallelPerfect(prev, pitch, ov.prevPitch, ov.pitch)) {
207
+ reasons |= 2048 /* ParallelPerfect */;
208
+ raise(parallelSeverity);
209
+ }
210
+ if (createsHiddenParallelPerfect(prev, pitch, ov.prevPitch, ov.pitch)) {
211
+ reasons |= 4096 /* HiddenParallel */;
212
+ raise(parallelSeverity);
213
+ }
214
+ const crossedNow = pitch - ov.pitch;
215
+ const crossedPrev = prev - ov.prevPitch;
216
+ if (crossedNow !== 0 && crossedPrev !== 0 && crossedNow > 0 !== crossedPrev > 0) {
217
+ reasons |= 8192 /* VoiceCrossing */;
218
+ raise(parallelSeverity);
219
+ }
220
+ }
221
+ }
222
+ raise(minimumSafetyForReasons(reasons, q.profile));
223
+ const result = { safety: level, reasons, rationale: describe(reasons, q) };
224
+ if (resolveTo !== void 0) {
225
+ result.resolveTo = resolveTo;
226
+ }
227
+ if (collectSuggestions && level !== 0 /* Safe */) {
228
+ const suggestions = findSafeNearby(q, pitch);
229
+ if (suggestions.length > 0) {
230
+ result.suggestions = suggestions;
231
+ }
232
+ }
233
+ return result;
234
+ }
235
+ function findSafeNearby(q, candidate) {
236
+ const out = [];
237
+ for (let d = 1; d <= SUGGESTION_WINDOW; d += 1) {
238
+ for (const p of [candidate - d, candidate + d]) {
239
+ if (evaluateInternal({ ...q, candidatePitch: p }, false).safety === 0 /* Safe */) {
240
+ out.push(p);
241
+ if (out.length >= MAX_SUGGESTIONS) {
242
+ return out;
243
+ }
244
+ }
245
+ }
246
+ }
247
+ return out;
248
+ }
249
+ function describe(reasons, q) {
250
+ if (reasons & 32 /* OutOfRange */) {
251
+ return "Outside the target vocal range";
252
+ }
253
+ if (reasons & 16384 /* Suspension */) {
254
+ return "Prepared suspension \u2014 a held consonance now dissonant, awaiting resolution";
255
+ }
256
+ if (reasons & 1024 /* VerticalDissonance */) {
257
+ return "Dissonant against a sounding voice on a strong beat";
258
+ }
259
+ if (reasons & 4 /* AvoidNote */) {
260
+ return "Avoid note \u2014 clashes with a chord tone a semitone away";
261
+ }
262
+ if (reasons & 2048 /* ParallelPerfect */) {
263
+ return "Parallel perfect interval with another voice";
264
+ }
265
+ if (reasons & 4096 /* HiddenParallel */) {
266
+ return "Hidden perfect interval by similar motion";
267
+ }
268
+ if (reasons & 8192 /* VoiceCrossing */) {
269
+ return "Crosses another voice";
270
+ }
271
+ if (reasons & 1 /* ChordTone */) {
272
+ return "Chord tone";
273
+ }
274
+ if (reasons & 2 /* Tension */) {
275
+ return "Chord tension";
276
+ }
277
+ if (reasons & 16 /* NonScale */) {
278
+ return "Outside the key";
279
+ }
280
+ if (reasons & 8 /* ScaleTone */) {
281
+ return "Scale tone, not in the chord";
282
+ }
283
+ return q.chord ? "Placeable" : "No chord context";
284
+ }
285
+ function enumerateSafePitches(q, pitchLow, pitchHigh) {
286
+ assertSafetyContext(q);
287
+ const chordTones = [];
288
+ const others = [];
289
+ assertInteger(pitchLow, "pitchLow");
290
+ assertInteger(pitchHigh, "pitchHigh");
291
+ if (pitchLow > pitchHigh) {
292
+ throw new RangeError(`pitchLow must not exceed pitchHigh; received ${pitchLow} > ${pitchHigh}`);
293
+ }
294
+ assertGenerationBudget(pitchHigh - pitchLow + 1, "safe pitch candidates");
295
+ for (let pitch = pitchHigh; pitch >= pitchLow; pitch -= 1) {
296
+ const result = evaluateInternal({ ...q, candidatePitch: pitch }, false);
297
+ if (result.safety === 2 /* Dissonant */) {
298
+ continue;
299
+ }
300
+ if (result.reasons & 1 /* ChordTone */) {
301
+ chordTones.push(pitch);
302
+ } else {
303
+ others.push(pitch);
304
+ }
305
+ }
306
+ return [...chordTones, ...others];
307
+ }
308
+
309
+ export {
310
+ NoteSafety,
311
+ ReasonFlag,
312
+ evaluateSafety,
313
+ enumerateSafePitches
314
+ };
315
+ //# sourceMappingURL=chunk-EH6EM37U.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/theory/safety/index.ts"],"sourcesContent":["import type { KeyScale } from '../../core/types.js';\nimport {\n assertFiniteNumber,\n assertGenerationBudget,\n assertInteger,\n} from '../../core/validation/index.js';\nimport type { Chord } from '../chord/index.js';\nimport { chordPitchClasses } from '../chord/index.js';\nimport {\n createsHiddenParallelPerfect,\n createsParallelPerfect,\n createsVerticalDissonance,\n isForbiddenMelodicLeap,\n} from '../counterpoint/index.js';\nimport { isScaleTone } from '../scale/index.js';\n\n/**\n * Severity policy: `strict` counterpoint vs. lenient `pop` voice-leading.\n *\n * @category Arrangement & Analysis\n */\nexport type SafetyProfile = 'strict' | 'pop';\n\n/**\n * Overall placeability verdict for a candidate pitch.\n *\n * @category Arrangement & Analysis\n */\nexport enum NoteSafety {\n Safe = 0,\n Warning = 1,\n Dissonant = 2,\n}\n\n/**\n * Bit flags describing why a pitch received its verdict.\n *\n * @category Arrangement & Analysis\n */\nexport enum ReasonFlag {\n ChordTone = 1 << 0,\n Tension = 1 << 1,\n AvoidNote = 1 << 2,\n ScaleTone = 1 << 3,\n NonScale = 1 << 4,\n OutOfRange = 1 << 5,\n Tritone = 1 << 6,\n LargeLeap = 1 << 7,\n MinorSecond = 1 << 8,\n MajorSeventh = 1 << 9,\n VerticalDissonance = 1 << 10,\n ParallelPerfect = 1 << 11,\n HiddenParallel = 1 << 12,\n VoiceCrossing = 1 << 13,\n Suspension = 1 << 14,\n NeedsResolution = 1 << 15,\n}\n\n/**\n * A neighbouring voice's current and previous pitch.\n *\n * @category Arrangement & Analysis\n */\nexport type VoiceSnapshot = {\n pitch: number;\n prevPitch?: number;\n};\n\n/**\n * Everything needed to evaluate one candidate pitch.\n *\n * @category Arrangement & Analysis\n */\nexport type SafetyQuery = {\n profile: SafetyProfile;\n candidatePitch: number;\n prevPitch?: number;\n chord: Chord | null;\n key: KeyScale;\n otherVoices: VoiceSnapshot[];\n strongBeat: boolean;\n vocalLow?: number;\n vocalHigh?: number;\n};\n\n/**\n * The verdict, reason flags, and optional resolution guidance.\n *\n * @category Arrangement & Analysis\n */\nexport type SafetyResult = {\n safety: NoteSafety;\n reasons: number;\n resolveTo?: number;\n suggestions?: number[];\n rationale?: string;\n};\n\n/** Semitone radius searched around a rejected candidate for safe alternatives. */\nconst SUGGESTION_WINDOW = 12;\n/** Maximum number of alternative pitches returned in `SafetyResult.suggestions`. */\nconst MAX_SUGGESTIONS = 3;\n\n/** Canonical minimum verdict contributed by each non-informational reason. */\nconst REASON_SEVERITY: ReadonlyArray<\n readonly [flag: ReasonFlag, pop: NoteSafety, strict: NoteSafety]\n> = [\n [ReasonFlag.Tension, NoteSafety.Warning, NoteSafety.Warning],\n [ReasonFlag.AvoidNote, NoteSafety.Warning, NoteSafety.Dissonant],\n [ReasonFlag.ScaleTone, NoteSafety.Warning, NoteSafety.Warning],\n [ReasonFlag.NonScale, NoteSafety.Dissonant, NoteSafety.Dissonant],\n [ReasonFlag.OutOfRange, NoteSafety.Warning, NoteSafety.Warning],\n [ReasonFlag.Tritone, NoteSafety.Dissonant, NoteSafety.Dissonant],\n [ReasonFlag.LargeLeap, NoteSafety.Warning, NoteSafety.Dissonant],\n [ReasonFlag.VerticalDissonance, NoteSafety.Dissonant, NoteSafety.Dissonant],\n [ReasonFlag.ParallelPerfect, NoteSafety.Warning, NoteSafety.Dissonant],\n [ReasonFlag.HiddenParallel, NoteSafety.Warning, NoteSafety.Dissonant],\n [ReasonFlag.VoiceCrossing, NoteSafety.Warning, NoteSafety.Dissonant],\n [ReasonFlag.Suspension, NoteSafety.Dissonant, NoteSafety.Dissonant],\n [ReasonFlag.NeedsResolution, NoteSafety.Warning, NoteSafety.Dissonant],\n];\n\nfunction minimumSafetyForReasons(reasons: number, profile: SafetyProfile): NoteSafety {\n let minimum = NoteSafety.Safe;\n const severityIndex = profile === 'strict' ? 2 : 1;\n for (const entry of REASON_SEVERITY) {\n if (reasons & entry[0]) {\n minimum = Math.max(minimum, entry[severityIndex]) as NoteSafety;\n }\n }\n return minimum;\n}\n\nfunction pitchClass(pitch: number): number {\n return ((Math.trunc(pitch) % 12) + 12) % 12;\n}\n\nfunction intervalAboveRoot(pitch: number, chord: Chord): number {\n return (((pitchClass(pitch) - pitchClass(chord.rootPc)) % 12) + 12) % 12;\n}\n\nfunction isChordMember(pitch: number, chord: Chord): boolean {\n return chordPitchClasses(chord).includes(pitchClass(pitch));\n}\n\n/** Whether the perfect fourth is an avoid note over this chord. */\nfunction avoidsFourth(chord: Chord): boolean {\n return chord.intervals.includes(4) && !chord.intervals.includes(5);\n}\n\n/** Whether the major seventh is an avoid note over this chord (dominant). */\nfunction avoidsMajorSeventh(chord: Chord): boolean {\n return chord.intervals.includes(4) && chord.intervals.includes(10);\n}\n\n/** Nearest chord tone at or below the candidate, within two semitones. */\nfunction stepResolution(pitch: number, chord: Chord): number | undefined {\n for (let delta = 1; delta <= 2; delta += 1) {\n if (isChordMember(pitch - delta, chord)) {\n return pitch - delta;\n }\n }\n return undefined;\n}\n\n/**\n * Evaluate the safety of placing a candidate pitch in a voice.\n *\n * Reasons are merged from harmonic context (chord tone / tension / avoid / scale)\n * and counterpoint (vertical dissonance, parallels, voice crossing). The `strict`\n * profile treats avoid notes and parallels as dissonant; `pop` treats them as\n * warnings. The final verdict is the worst severity among the reasons.\n *\n * @param q The candidate and its harmonic/voice-leading context.\n * @returns The verdict, reason bitmask, and optional resolution guidance.\n * @example\n * ```ts\n * import { evaluateSafety, makeChord, majorKey, NoteSafety } from '@libraz/libcantus';\n * const result = evaluateSafety({\n * profile: 'pop',\n * candidatePitch: 60, // C over a C major chord\n * chord: makeChord(0, 'maj'),\n * key: majorKey(0),\n * otherVoices: [],\n * strongBeat: true,\n * });\n * result.safety === NoteSafety.Safe; // true — C is a chord tone\n * ```\n * @category Arrangement & Analysis\n */\nexport function evaluateSafety(q: SafetyQuery): SafetyResult {\n assertSafetyContext(q);\n assertFiniteNumber(q.candidatePitch, 'candidatePitch');\n return evaluateInternal(q, true);\n}\n\n/** Validate all numeric context fields once at a public safety entry point. */\nfunction assertSafetyContext(q: Omit<SafetyQuery, 'candidatePitch'>): void {\n if (q.prevPitch !== undefined) assertFiniteNumber(q.prevPitch, 'prevPitch');\n if (q.vocalLow !== undefined) assertFiniteNumber(q.vocalLow, 'vocalLow');\n if (q.vocalHigh !== undefined) assertFiniteNumber(q.vocalHigh, 'vocalHigh');\n if (q.vocalLow !== undefined && q.vocalHigh !== undefined && q.vocalLow > q.vocalHigh) {\n throw new RangeError('vocalLow must not exceed vocalHigh');\n }\n assertGenerationBudget(q.otherVoices.length, 'other voices');\n for (let index = 0; index < q.otherVoices.length; index += 1) {\n const voice = q.otherVoices[index];\n if (!voice) continue;\n assertFiniteNumber(voice.pitch, `otherVoices[${index}].pitch`);\n if (voice.prevPitch !== undefined) {\n assertFiniteNumber(voice.prevPitch, `otherVoices[${index}].prevPitch`);\n }\n }\n}\n\n/**\n * Core safety evaluation.\n *\n * @param q The candidate and its harmonic/voice-leading context.\n * @param collectSuggestions Whether to search for safe alternatives on a\n * non-Safe verdict. Set false during that search itself to bound recursion.\n * @returns The verdict, reason bitmask, and optional guidance.\n */\nfunction evaluateInternal(q: SafetyQuery, collectSuggestions: boolean): SafetyResult {\n let reasons = 0;\n let level = NoteSafety.Safe;\n let resolveTo: number | undefined;\n const raise = (l: NoteSafety) => {\n if (l > level) {\n level = l;\n }\n };\n const avoidSeverity = q.profile === 'strict' ? NoteSafety.Dissonant : NoteSafety.Warning;\n const parallelSeverity = avoidSeverity;\n const pitch = q.candidatePitch;\n\n if (\n (q.vocalLow !== undefined && pitch < q.vocalLow) ||\n (q.vocalHigh !== undefined && pitch > q.vocalHigh)\n ) {\n reasons |= ReasonFlag.OutOfRange;\n raise(NoteSafety.Warning);\n }\n\n const inScale = isScaleTone(pitch, q.key);\n const chord = q.chord;\n\n if (chord && isChordMember(pitch, chord)) {\n reasons |= ReasonFlag.ChordTone;\n } else if (chord) {\n const ic = intervalAboveRoot(pitch, chord);\n const isTension = ic === 2 || ic === 5 || ic === 9;\n const avoid = (ic === 5 && avoidsFourth(chord)) || (ic === 11 && avoidsMajorSeventh(chord));\n if (avoid) {\n reasons |= ReasonFlag.AvoidNote;\n if (isTension) {\n reasons |= ReasonFlag.Tension;\n }\n reasons |= ReasonFlag.NeedsResolution;\n resolveTo = stepResolution(pitch, chord);\n raise(avoidSeverity);\n } else if (isTension && inScale) {\n reasons |= ReasonFlag.Tension;\n raise(NoteSafety.Warning);\n } else if (inScale) {\n reasons |= ReasonFlag.ScaleTone;\n raise(NoteSafety.Warning);\n } else {\n reasons |= ReasonFlag.NonScale;\n raise(NoteSafety.Dissonant);\n }\n } else if (inScale) {\n reasons |= ReasonFlag.ScaleTone;\n raise(NoteSafety.Warning);\n } else {\n reasons |= ReasonFlag.NonScale;\n raise(NoteSafety.Dissonant);\n }\n\n // Harmonic tritone against a chord tone (except in dominant chords, where the\n // tritone is a defining, stable colour).\n if (chord && !(chord.intervals.includes(4) && chord.intervals.includes(10))) {\n const ic = intervalAboveRoot(pitch, chord);\n const toneOffsets = chord.intervals.map((i) => ((i % 12) + 12) % 12);\n if (toneOffsets.some((t) => Math.abs(ic - t) === 6)) {\n reasons |= ReasonFlag.Tritone;\n }\n }\n\n if (q.prevPitch !== undefined) {\n // Quality flags reduce the melodic interval to its pitch-class interval so\n // compound intervals are treated uniformly with their simple form. The\n // forbidden-leap flag defers to the counterpoint rule, which preserves the\n // octave boundary (an octave leap is allowed, wider leaps are not).\n const pc = Math.abs(pitch - q.prevPitch) % 12;\n if (pc === 6) {\n reasons |= ReasonFlag.Tritone;\n raise(NoteSafety.Dissonant);\n }\n if (isForbiddenMelodicLeap(q.prevPitch, pitch)) {\n reasons |= ReasonFlag.LargeLeap;\n raise(parallelSeverity);\n }\n if (pc === 1) {\n reasons |= ReasonFlag.MinorSecond;\n }\n if (pc === 11) {\n reasons |= ReasonFlag.MajorSeventh;\n }\n }\n\n if (q.strongBeat) {\n const twoVoice = q.otherVoices.length === 1;\n const prev = q.prevPitch;\n for (const ov of q.otherVoices) {\n if (!createsVerticalDissonance(pitch, ov.pitch, twoVoice)) {\n continue;\n }\n reasons |= ReasonFlag.VerticalDissonance;\n raise(NoteSafety.Dissonant);\n // A held pitch that was consonant on the previous step and is now\n // dissonant against the same voice is a prepared suspension.\n if (\n prev !== undefined &&\n pitch === prev &&\n ov.prevPitch !== undefined &&\n !createsVerticalDissonance(prev, ov.prevPitch, twoVoice)\n ) {\n reasons |= ReasonFlag.Suspension;\n }\n }\n }\n\n if (q.prevPitch !== undefined) {\n const prev = q.prevPitch;\n for (const ov of q.otherVoices) {\n if (ov.prevPitch === undefined) {\n continue;\n }\n // createsParallelPerfect covers every perfect class (unison/octave and\n // fifth), so no separate octave check is needed.\n if (createsParallelPerfect(prev, pitch, ov.prevPitch, ov.pitch)) {\n reasons |= ReasonFlag.ParallelPerfect;\n raise(parallelSeverity);\n }\n if (createsHiddenParallelPerfect(prev, pitch, ov.prevPitch, ov.pitch)) {\n reasons |= ReasonFlag.HiddenParallel;\n raise(parallelSeverity);\n }\n const crossedNow = pitch - ov.pitch;\n const crossedPrev = prev - ov.prevPitch;\n if (crossedNow !== 0 && crossedPrev !== 0 && crossedNow > 0 !== crossedPrev > 0) {\n reasons |= ReasonFlag.VoiceCrossing;\n raise(parallelSeverity);\n }\n }\n }\n\n // Keep reason production and verdict policy as one invariant even when a new\n // call path skips an earlier local `raise`: informational flags (ChordTone,\n // MinorSecond, MajorSeventh) may coexist with Safe; every policy flag above\n // contributes at least its table severity.\n raise(minimumSafetyForReasons(reasons, q.profile));\n\n const result: SafetyResult = { safety: level, reasons, rationale: describe(reasons, q) };\n if (resolveTo !== undefined) {\n result.resolveTo = resolveTo;\n }\n if (collectSuggestions && level !== NoteSafety.Safe) {\n const suggestions = findSafeNearby(q, pitch);\n if (suggestions.length > 0) {\n result.suggestions = suggestions;\n }\n }\n return result;\n}\n\n/**\n * Find safe alternative pitches near a rejected candidate.\n *\n * Searches outward from the candidate within `SUGGESTION_WINDOW` semitones,\n * probing the pitch below before the one above at each distance so the result\n * is deterministic and ordered by nearness.\n *\n * @param q The safety context of the rejected candidate.\n * @param candidate The candidate pitch that was not Safe.\n * @returns Up to `MAX_SUGGESTIONS` safe pitches, nearest first.\n */\nfunction findSafeNearby(q: SafetyQuery, candidate: number): number[] {\n const out: number[] = [];\n for (let d = 1; d <= SUGGESTION_WINDOW; d += 1) {\n for (const p of [candidate - d, candidate + d]) {\n if (evaluateInternal({ ...q, candidatePitch: p }, false).safety === NoteSafety.Safe) {\n out.push(p);\n if (out.length >= MAX_SUGGESTIONS) {\n return out;\n }\n }\n }\n }\n return out;\n}\n\n/** Build a short human explanation from the reason flags. */\nfunction describe(reasons: number, q: SafetyQuery): string {\n if (reasons & ReasonFlag.OutOfRange) {\n return 'Outside the target vocal range';\n }\n if (reasons & ReasonFlag.Suspension) {\n return 'Prepared suspension — a held consonance now dissonant, awaiting resolution';\n }\n if (reasons & ReasonFlag.VerticalDissonance) {\n return 'Dissonant against a sounding voice on a strong beat';\n }\n if (reasons & ReasonFlag.AvoidNote) {\n return 'Avoid note — clashes with a chord tone a semitone away';\n }\n if (reasons & ReasonFlag.ParallelPerfect) {\n return 'Parallel perfect interval with another voice';\n }\n if (reasons & ReasonFlag.HiddenParallel) {\n return 'Hidden perfect interval by similar motion';\n }\n if (reasons & ReasonFlag.VoiceCrossing) {\n return 'Crosses another voice';\n }\n if (reasons & ReasonFlag.ChordTone) {\n return 'Chord tone';\n }\n if (reasons & ReasonFlag.Tension) {\n return 'Chord tension';\n }\n if (reasons & ReasonFlag.NonScale) {\n return 'Outside the key';\n }\n if (reasons & ReasonFlag.ScaleTone) {\n return 'Scale tone, not in the chord';\n }\n return q.chord ? 'Placeable' : 'No chord context';\n}\n\n/**\n * Enumerate placeable pitches in a range, chord tones first, descending.\n *\n * @param q The safety context minus the candidate pitch.\n * @param pitchLow Lowest MIDI pitch to consider (inclusive).\n * @param pitchHigh Highest MIDI pitch to consider (inclusive).\n * @returns Placeable pitches (non-dissonant), chord tones before others, each group descending.\n * @throws If either bound is non-finite/non-integral, reversed, or exceeds the generation budget.\n * @example\n * ```ts\n * import { enumerateSafePitches, makeChord, majorKey } from '@libraz/libcantus';\n * const pitches = enumerateSafePitches(\n * { profile: 'pop', chord: makeChord(0, 'maj'), key: majorKey(0), otherVoices: [], strongBeat: true },\n * 60,\n * 72,\n * );\n * pitches; // placeable pitches in [60, 72], chord tones first, each group descending\n * ```\n * @category Arrangement & Analysis\n */\nexport function enumerateSafePitches(\n q: Omit<SafetyQuery, 'candidatePitch'>,\n pitchLow: number,\n pitchHigh: number,\n): number[] {\n assertSafetyContext(q);\n const chordTones: number[] = [];\n const others: number[] = [];\n assertInteger(pitchLow, 'pitchLow');\n assertInteger(pitchHigh, 'pitchHigh');\n if (pitchLow > pitchHigh) {\n throw new RangeError(`pitchLow must not exceed pitchHigh; received ${pitchLow} > ${pitchHigh}`);\n }\n assertGenerationBudget(pitchHigh - pitchLow + 1, 'safe pitch candidates');\n for (let pitch = pitchHigh; pitch >= pitchLow; pitch -= 1) {\n const result = evaluateInternal({ ...q, candidatePitch: pitch }, false);\n if (result.safety === NoteSafety.Dissonant) {\n continue;\n }\n if (result.reasons & ReasonFlag.ChordTone) {\n chordTones.push(pitch);\n } else {\n others.push(pitch);\n }\n }\n return [...chordTones, ...others];\n}\n"],"mappings":"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","names":["NoteSafety","ReasonFlag"]}