@libraz/libcantus 0.9.3 → 0.9.4

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 (198) hide show
  1. package/README.md +2 -2
  2. package/dist/analyze/index.cjs +39 -1947
  3. package/dist/analyze/index.cjs.map +1 -1
  4. package/dist/analyze/index.d.cts +197 -37
  5. package/dist/analyze/index.d.ts +197 -37
  6. package/dist/analyze/index.js +19 -8
  7. package/dist/chunk-36N73KZ3.cjs +246 -0
  8. package/dist/chunk-36N73KZ3.cjs.map +1 -0
  9. package/dist/chunk-4HLLZPXT.cjs +3077 -0
  10. package/dist/chunk-4HLLZPXT.cjs.map +1 -0
  11. package/dist/chunk-7MGZ7D62.cjs +498 -0
  12. package/dist/chunk-7MGZ7D62.cjs.map +1 -0
  13. package/dist/{chunk-4MHA432G.js → chunk-7NKDPF74.js} +403 -180
  14. package/dist/chunk-7NKDPF74.js.map +1 -0
  15. package/dist/chunk-CAN76NCU.cjs +679 -0
  16. package/dist/chunk-CAN76NCU.cjs.map +1 -0
  17. package/dist/{chunk-MIWO425M.js → chunk-D2FAJBYL.js} +101 -34
  18. package/dist/chunk-D2FAJBYL.js.map +1 -0
  19. package/dist/{chunk-SGU7SPLK.js → chunk-EHVEBWM5.js} +1056 -531
  20. package/dist/chunk-EHVEBWM5.js.map +1 -0
  21. package/dist/chunk-FCANXBAT.cjs +30 -0
  22. package/dist/chunk-FCANXBAT.cjs.map +1 -0
  23. package/dist/chunk-FNDWXRRI.cjs +479 -0
  24. package/dist/chunk-FNDWXRRI.cjs.map +1 -0
  25. package/dist/{chunk-IT2O4XFE.js → chunk-GQSGS2N4.js} +43 -24
  26. package/dist/chunk-GQSGS2N4.js.map +1 -0
  27. package/dist/{chunk-62QZKCJQ.js → chunk-HKGYCQ34.js} +10 -6
  28. package/dist/chunk-HKGYCQ34.js.map +1 -0
  29. package/dist/{chunk-AKLYPWIN.js → chunk-I24MV65U.js} +29 -6
  30. package/dist/chunk-I24MV65U.js.map +1 -0
  31. package/dist/{chunk-CHMXELDS.js → chunk-KMHOICIY.js} +311 -181
  32. package/dist/chunk-KMHOICIY.js.map +1 -0
  33. package/dist/chunk-LW53AHQU.cjs +1117 -0
  34. package/dist/chunk-LW53AHQU.cjs.map +1 -0
  35. package/dist/{chunk-2ZWMSJH6.js → chunk-NKIBQ6PT.js} +2 -2
  36. package/dist/chunk-NKIBQ6PT.js.map +1 -0
  37. package/dist/chunk-NP4NA2FB.cjs +106 -0
  38. package/dist/chunk-NP4NA2FB.cjs.map +1 -0
  39. package/dist/chunk-NVE34YGZ.cjs +1449 -0
  40. package/dist/chunk-NVE34YGZ.cjs.map +1 -0
  41. package/dist/{chunk-EH6EM37U.js → chunk-NYYWMV2C.js} +61 -49
  42. package/dist/chunk-NYYWMV2C.js.map +1 -0
  43. package/dist/{chunk-GOAW4VYL.js → chunk-Q2XUP227.js} +243 -117
  44. package/dist/chunk-Q2XUP227.js.map +1 -0
  45. package/dist/chunk-Q6OP4U43.cjs +327 -0
  46. package/dist/chunk-Q6OP4U43.cjs.map +1 -0
  47. package/dist/chunk-RC6GBMCD.cjs +845 -0
  48. package/dist/chunk-RC6GBMCD.cjs.map +1 -0
  49. package/dist/{chunk-C5XYKUVG.js → chunk-RK35RIN7.js} +370 -157
  50. package/dist/chunk-RK35RIN7.js.map +1 -0
  51. package/dist/chunk-RUMU3UA6.cjs +53 -0
  52. package/dist/chunk-RUMU3UA6.cjs.map +1 -0
  53. package/dist/chunk-T64C7ZD7.cjs +1 -0
  54. package/dist/chunk-T64C7ZD7.cjs.map +1 -0
  55. package/dist/chunk-XEBG3G53.js +498 -0
  56. package/dist/chunk-XEBG3G53.js.map +1 -0
  57. package/dist/{chunk-I5R7ZBJS.js → chunk-YLEMGEFH.js} +103 -59
  58. package/dist/chunk-YLEMGEFH.js.map +1 -0
  59. package/dist/chunk-ZEJR4TKL.cjs +173 -0
  60. package/dist/chunk-ZEJR4TKL.cjs.map +1 -0
  61. package/dist/core/index.cjs +130 -647
  62. package/dist/core/index.cjs.map +1 -1
  63. package/dist/core/index.d.cts +301 -39
  64. package/dist/core/index.d.ts +301 -39
  65. package/dist/core/index.js +54 -16
  66. package/dist/{function-DYt4iKsn.d.cts → function-BtiZb9i9.d.ts} +28 -7
  67. package/dist/{function-DVH4Eusr.d.ts → function-DNugAOrt.d.cts} +28 -7
  68. package/dist/generate/index.cjs +28 -4085
  69. package/dist/generate/index.cjs.map +1 -1
  70. package/dist/generate/index.d.cts +370 -62
  71. package/dist/generate/index.d.ts +370 -62
  72. package/dist/generate/index.js +23 -9
  73. package/dist/{index-CxqBQtH1.d.cts → index-6VR8-kit.d.ts} +19 -4
  74. package/dist/index-70p168VM.d.ts +192 -0
  75. package/dist/{index-CpOsP1y9.d.cts → index-Be74mcon.d.cts} +18 -17
  76. package/dist/index-BtXT6UQ0.d.cts +192 -0
  77. package/dist/{index-DxvARIP2.d.ts → index-C5UBbQ4G.d.ts} +18 -17
  78. package/dist/{index-Bt4tOUD9.d.ts → index-C83mZI1P.d.ts} +93 -8
  79. package/dist/{index-Bg5dYMmZ.d.ts → index-D6b6na69.d.cts} +14 -11
  80. package/dist/{index-Cl7mhOFM.d.cts → index-DXd9JikN.d.cts} +92 -10
  81. package/dist/{index-Cl7mhOFM.d.ts → index-DXd9JikN.d.ts} +92 -10
  82. package/dist/index-OG65ocOr.d.cts +272 -0
  83. package/dist/index-OG65ocOr.d.ts +272 -0
  84. package/dist/{index-Jc0Ryi79.d.ts → index-tJhkmQgE.d.cts} +19 -4
  85. package/dist/{index-Dikst44v.d.cts → index-tSQh-xwY.d.ts} +14 -11
  86. package/dist/{index-BNlGP86A.d.cts → index-y_ak9_Hi.d.cts} +93 -8
  87. package/dist/index.cjs +419 -7383
  88. package/dist/index.cjs.map +1 -1
  89. package/dist/index.d.cts +14 -14
  90. package/dist/index.d.ts +14 -14
  91. package/dist/index.js +111 -27
  92. package/dist/model/index.cjs +8 -2562
  93. package/dist/model/index.cjs.map +1 -1
  94. package/dist/model/index.d.cts +433 -75
  95. package/dist/model/index.d.ts +433 -75
  96. package/dist/model/index.js +6 -7
  97. package/dist/styled-DIRkk9Uz.d.ts +506 -0
  98. package/dist/styled-DJbkJQfj.d.cts +506 -0
  99. package/dist/theory/index.cjs +172 -1822
  100. package/dist/theory/index.cjs.map +1 -1
  101. package/dist/theory/index.d.cts +151 -233
  102. package/dist/theory/index.d.ts +151 -233
  103. package/dist/theory/index.js +30 -7
  104. package/dist/{types-MGP29r3t.d.cts → types-BN6_Q9Kl.d.cts} +11 -2
  105. package/dist/{types-MGP29r3t.d.ts → types-BN6_Q9Kl.d.ts} +11 -2
  106. package/package.json +24 -6
  107. package/dist/chunk-2ZWMSJH6.js.map +0 -1
  108. package/dist/chunk-4MHA432G.js.map +0 -1
  109. package/dist/chunk-62QZKCJQ.js.map +0 -1
  110. package/dist/chunk-AKLYPWIN.js.map +0 -1
  111. package/dist/chunk-C5XYKUVG.js.map +0 -1
  112. package/dist/chunk-CHMXELDS.js.map +0 -1
  113. package/dist/chunk-DYWFNC4A.js +0 -152
  114. package/dist/chunk-DYWFNC4A.js.map +0 -1
  115. package/dist/chunk-EH6EM37U.js.map +0 -1
  116. package/dist/chunk-FBQK3ZQN.js +0 -131
  117. package/dist/chunk-FBQK3ZQN.js.map +0 -1
  118. package/dist/chunk-GOAW4VYL.js.map +0 -1
  119. package/dist/chunk-I5R7ZBJS.js.map +0 -1
  120. package/dist/chunk-IT2O4XFE.js.map +0 -1
  121. package/dist/chunk-MIWO425M.js.map +0 -1
  122. package/dist/chunk-SGU7SPLK.js.map +0 -1
  123. package/dist/index-CGMZNCx6.d.cts +0 -140
  124. package/dist/index-CGMZNCx6.d.ts +0 -140
  125. package/dist/index-CnZCo29u.d.cts +0 -108
  126. package/dist/index-JnF4v9bx.d.ts +0 -108
  127. package/dist/styled-DZ7GjShb.d.ts +0 -282
  128. package/dist/styled-ZTbdaVFM.d.cts +0 -282
  129. package/src/analyze/arrange/index.ts +0 -21
  130. package/src/analyze/arrange/internal.ts +0 -127
  131. package/src/analyze/arrange/tension.ts +0 -199
  132. package/src/analyze/arrange/tracks.ts +0 -312
  133. package/src/analyze/detect/index.ts +0 -272
  134. package/src/analyze/functional/borrowed.ts +0 -84
  135. package/src/analyze/functional/cadence.ts +0 -66
  136. package/src/analyze/functional/function.ts +0 -184
  137. package/src/analyze/functional/index.ts +0 -23
  138. package/src/analyze/functional/internal.ts +0 -29
  139. package/src/analyze/functional/roman.ts +0 -402
  140. package/src/analyze/index.ts +0 -49
  141. package/src/analyze/timeline/index.ts +0 -451
  142. package/src/analyze/voice/index.ts +0 -339
  143. package/src/core/event-index/index.ts +0 -104
  144. package/src/core/index.ts +0 -61
  145. package/src/core/interval/index.ts +0 -81
  146. package/src/core/meter/index.ts +0 -302
  147. package/src/core/pitch/index.ts +0 -311
  148. package/src/core/random/index.ts +0 -76
  149. package/src/core/tuning/index.ts +0 -186
  150. package/src/core/types.ts +0 -29
  151. package/src/core/validation/index.ts +0 -119
  152. package/src/generate/bass/index.ts +0 -373
  153. package/src/generate/countermelody/index.ts +0 -470
  154. package/src/generate/drums/beat.ts +0 -266
  155. package/src/generate/drums/euclid.ts +0 -65
  156. package/src/generate/drums/fills.ts +0 -356
  157. package/src/generate/drums/ghost.ts +0 -119
  158. package/src/generate/drums/hihat.ts +0 -285
  159. package/src/generate/drums/hit.ts +0 -31
  160. package/src/generate/drums/index.ts +0 -361
  161. package/src/generate/drums/internal.ts +0 -257
  162. package/src/generate/drums/kick.ts +0 -160
  163. package/src/generate/drums/percussion.ts +0 -121
  164. package/src/generate/drums/rng.ts +0 -7
  165. package/src/generate/drums/swing.ts +0 -71
  166. package/src/generate/groove/index.ts +0 -351
  167. package/src/generate/harmonize/index.ts +0 -451
  168. package/src/generate/index.ts +0 -58
  169. package/src/generate/motif/index.ts +0 -422
  170. package/src/generate/progression/index.ts +0 -362
  171. package/src/generate/reharmony/index.ts +0 -323
  172. package/src/generate/rhythm/index.ts +0 -175
  173. package/src/index.ts +0 -13
  174. package/src/model/chord.ts +0 -442
  175. package/src/model/index.ts +0 -15
  176. package/src/model/interval.ts +0 -82
  177. package/src/model/key.ts +0 -266
  178. package/src/model/note.ts +0 -176
  179. package/src/model/progression.ts +0 -161
  180. package/src/model/shared.ts +0 -12
  181. package/src/theory/chord/index.ts +0 -375
  182. package/src/theory/chordscale/index.ts +0 -374
  183. package/src/theory/counterpoint/index.ts +0 -230
  184. package/src/theory/harmony/index.ts +0 -77
  185. package/src/theory/index.ts +0 -112
  186. package/src/theory/safety/index.ts +0 -488
  187. package/src/theory/scale/degrees.ts +0 -123
  188. package/src/theory/scale/index.ts +0 -28
  189. package/src/theory/scale/key.ts +0 -56
  190. package/src/theory/scale/masks.ts +0 -153
  191. package/src/theory/spelling/index.ts +0 -249
  192. package/src/theory/symbol/index.ts +0 -268
  193. package/src/theory/voicing/index.ts +0 -5
  194. package/src/theory/voicing/internal.ts +0 -168
  195. package/src/theory/voicing/leading.ts +0 -112
  196. package/src/theory/voicing/satb.ts +0 -216
  197. package/src/theory/voicing/styled.ts +0 -199
  198. package/tsconfig.json +0 -23
@@ -1,123 +0,0 @@
1
- import type { KeyScale } from '../../core/types.js';
2
-
3
- /** Reduce a pitch (or pitch class) to a pitch class in [0, 11]. */
4
- function pitchClass(pitch: number): number {
5
- return ((Math.trunc(pitch) % 12) + 12) % 12;
6
- }
7
-
8
- /** Scale offset of a pitch relative to the key root, in [0, 11]. */
9
- function scaleOffset(pitch: number, key: KeyScale): number {
10
- return (pitchClass(pitch) - pitchClass(key.rootPc) + 12) % 12;
11
- }
12
-
13
- /**
14
- * Test whether a pitch belongs to the scale.
15
- *
16
- * @param pitch MIDI pitch or bare pitch class.
17
- * @param key The key/scale to test against.
18
- * @returns True if the pitch class is a member of the scale.
19
- *
20
- * @example
21
- * ```ts
22
- * import { majorKey, isScaleTone } from '@libraz/libcantus';
23
- * const key = majorKey(0); // C major
24
- * isScaleTone(64, key); // true — E is in C major
25
- * isScaleTone(61, key); // false — C# is not
26
- * ```
27
- *
28
- * @category Scales
29
- */
30
- export function isScaleTone(pitch: number, key: KeyScale): boolean {
31
- return ((key.modeMask12 >> scaleOffset(pitch, key)) & 1) === 1;
32
- }
33
-
34
- /**
35
- * Find the nearest MIDI pitch whose pitch class is in the scale.
36
- *
37
- * The search expands symmetrically outward from `pitch`. On a tie (equal
38
- * distance above and below) the lower pitch is returned.
39
- *
40
- * @param pitch MIDI pitch to snap.
41
- * @param key The key/scale to snap to.
42
- * @returns The nearest in-scale MIDI pitch.
43
- *
44
- * @category Scales
45
- */
46
- export function nearestScaleTone(pitch: number, key: KeyScale): number {
47
- const base = Math.round(pitch);
48
- for (let distance = 0; distance < 12; distance += 1) {
49
- const lower = base - distance;
50
- if (isScaleTone(lower, key)) {
51
- return lower;
52
- }
53
- const higher = base + distance;
54
- if (isScaleTone(higher, key)) {
55
- return higher;
56
- }
57
- }
58
- return base;
59
- }
60
-
61
- /**
62
- * Get the 0-based scale degree of a pitch.
63
- *
64
- * @param pitch MIDI pitch or bare pitch class.
65
- * @param key The key/scale to measure against.
66
- * @returns The 0-based degree, or -1 if the pitch is not in the scale.
67
- *
68
- * @category Scales
69
- */
70
- export function pitchToScaleDegree(pitch: number, key: KeyScale): number {
71
- const offset = scaleOffset(pitch, key);
72
- if (((key.modeMask12 >> offset) & 1) === 0) {
73
- return -1;
74
- }
75
- let degree = 0;
76
- for (let n = 0; n < offset; n += 1) {
77
- if (((key.modeMask12 >> n) & 1) === 1) {
78
- degree += 1;
79
- }
80
- }
81
- return degree;
82
- }
83
-
84
- /**
85
- * List the pitch classes of a key's scale.
86
- *
87
- * @param key The key/scale to enumerate.
88
- * @returns The member pitch classes, sorted ascending in [0, 11].
89
- *
90
- * @category Scales
91
- */
92
- export function diatonicPitchClasses(key: KeyScale): number[] {
93
- const root = pitchClass(key.rootPc);
94
- const pcs: number[] = [];
95
- for (let n = 0; n < 12; n += 1) {
96
- if (((key.modeMask12 >> n) & 1) === 1) {
97
- pcs.push((root + n) % 12);
98
- }
99
- }
100
- return pcs.sort((a, b) => a - b);
101
- }
102
-
103
- /**
104
- * List the pitch classes of a key's scale in ascending scale-degree order.
105
- *
106
- * Degree 0 is the root; degrees follow the mask bits in offset order rather
107
- * than sorted pitch-class order.
108
- *
109
- * @param key The key/scale to enumerate.
110
- * @returns The member pitch classes ordered by scale degree.
111
- *
112
- * @category Scales
113
- */
114
- export function scaleTonesInDegreeOrder(key: KeyScale): number[] {
115
- const root = pitchClass(key.rootPc);
116
- const pcs: number[] = [];
117
- for (let n = 0; n < 12; n += 1) {
118
- if (((key.modeMask12 >> n) & 1) === 1) {
119
- pcs.push((root + n) % 12);
120
- }
121
- }
122
- return pcs;
123
- }
@@ -1,28 +0,0 @@
1
- export {
2
- diatonicPitchClasses,
3
- isScaleTone,
4
- nearestScaleTone,
5
- pitchToScaleDegree,
6
- scaleTonesInDegreeOrder,
7
- } from './degrees.js';
8
- export { majorKey, minorKey, scaleByName } from './key.js';
9
- export {
10
- BLUES_MASK,
11
- CHROMATIC_MASK,
12
- DORIAN_MASK,
13
- HARMONIC_MINOR_MASK,
14
- LOCRIAN_MASK,
15
- LYDIAN_MASK,
16
- MAJOR_MASK,
17
- MAJOR_PENTATONIC_MASK,
18
- MELODIC_MINOR_MASK,
19
- MINOR_PENTATONIC_MASK,
20
- MIXOLYDIAN_MASK,
21
- maskFromOffsets,
22
- NAMED_SCALES,
23
- NATURAL_MINOR_MASK,
24
- OCTATONIC_HALF_WHOLE_MASK,
25
- OCTATONIC_WHOLE_HALF_MASK,
26
- PHRYGIAN_MASK,
27
- WHOLE_TONE_MASK,
28
- } from './masks.js';
@@ -1,56 +0,0 @@
1
- import type { KeyScale } from '../../core/types.js';
2
- import { MAJOR_MASK, NAMED_SCALES, NATURAL_MINOR_MASK } from './masks.js';
3
-
4
- /**
5
- * Build a `KeyScale` for a major key on the given root pitch class.
6
- *
7
- * @example
8
- * ```ts
9
- * import { majorKey } from '@libraz/libcantus';
10
- * const cMajor = majorKey(0); // C major: { rootPc: 0, modeMask12: MAJOR_MASK }
11
- * ```
12
- *
13
- * @category Scales
14
- */
15
- export function majorKey(rootPc: number): KeyScale {
16
- return { rootPc: ((rootPc % 12) + 12) % 12, modeMask12: MAJOR_MASK };
17
- }
18
-
19
- /**
20
- * Build a `KeyScale` for a natural-minor key on the given root pitch class.
21
- *
22
- * @example
23
- * ```ts
24
- * import { minorKey } from '@libraz/libcantus';
25
- * const aMinor = minorKey(9); // A natural minor: { rootPc: 9, modeMask12: NATURAL_MINOR_MASK }
26
- * ```
27
- *
28
- * @category Scales
29
- */
30
- export function minorKey(rootPc: number): KeyScale {
31
- return { rootPc: ((rootPc % 12) + 12) % 12, modeMask12: NATURAL_MINOR_MASK };
32
- }
33
-
34
- /**
35
- * Build a `KeyScale` from a named scale (see {@link NAMED_SCALES}).
36
- *
37
- * @param name The scale name, e.g. `'dorian'` or `'harmonicMinor'`.
38
- * @param rootPc The root pitch class.
39
- * @returns The key/scale.
40
- * @throws If the name is not a known scale.
41
- *
42
- * @example
43
- * ```ts
44
- * import { scaleByName } from '@libraz/libcantus';
45
- * const dDorian = scaleByName('dorian', 2); // D Dorian, rootPc 2
46
- * ```
47
- *
48
- * @category Scales
49
- */
50
- export function scaleByName(name: string, rootPc: number): KeyScale {
51
- const mask = NAMED_SCALES[name];
52
- if (mask === undefined) {
53
- throw new Error(`Unknown scale: ${name}`);
54
- }
55
- return { rootPc: ((rootPc % 12) + 12) % 12, modeMask12: mask };
56
- }
@@ -1,153 +0,0 @@
1
- /**
2
- * Build a 12-bit mode mask from a list of semitone offsets above the root.
3
- *
4
- * Bit 0 (the root) is always set, enforcing the `KeyScale` invariant that the
5
- * root is a scale tone even when the offset list omits 0.
6
- *
7
- * @category Scales
8
- */
9
- export function maskFromOffsets(offsets: readonly number[]): number {
10
- let mask = 1;
11
- for (const offset of offsets) {
12
- mask |= 1 << (((offset % 12) + 12) % 12);
13
- }
14
- return mask;
15
- }
16
-
17
- /**
18
- * Mode mask for the major (Ionian) scale: offsets {0, 2, 4, 5, 7, 9, 11}.
19
- *
20
- * @category Scales
21
- */
22
- export const MAJOR_MASK = 0b101010110101;
23
-
24
- /**
25
- * Mode mask for the natural minor (Aeolian) scale: offsets {0, 2, 3, 5, 7, 8, 10}.
26
- *
27
- * @category Scales
28
- */
29
- export const NATURAL_MINOR_MASK = 0b010110101101;
30
-
31
- /**
32
- * Harmonic minor: natural minor with a raised seventh — offsets {0,2,3,5,7,8,11}.
33
- *
34
- * @category Scales
35
- */
36
- export const HARMONIC_MINOR_MASK = maskFromOffsets([0, 2, 3, 5, 7, 8, 11]);
37
-
38
- /**
39
- * Ascending melodic minor: offsets {0,2,3,5,7,9,11}.
40
- *
41
- * @category Scales
42
- */
43
- export const MELODIC_MINOR_MASK = maskFromOffsets([0, 2, 3, 5, 7, 9, 11]);
44
-
45
- /**
46
- * Dorian mode: offsets {0,2,3,5,7,9,10}.
47
- *
48
- * @category Scales
49
- */
50
- export const DORIAN_MASK = maskFromOffsets([0, 2, 3, 5, 7, 9, 10]);
51
-
52
- /**
53
- * Phrygian mode: offsets {0,1,3,5,7,8,10}.
54
- *
55
- * @category Scales
56
- */
57
- export const PHRYGIAN_MASK = maskFromOffsets([0, 1, 3, 5, 7, 8, 10]);
58
-
59
- /**
60
- * Lydian mode: offsets {0,2,4,6,7,9,11}.
61
- *
62
- * @category Scales
63
- */
64
- export const LYDIAN_MASK = maskFromOffsets([0, 2, 4, 6, 7, 9, 11]);
65
-
66
- /**
67
- * Mixolydian mode: offsets {0,2,4,5,7,9,10}.
68
- *
69
- * @category Scales
70
- */
71
- export const MIXOLYDIAN_MASK = maskFromOffsets([0, 2, 4, 5, 7, 9, 10]);
72
-
73
- /**
74
- * Locrian mode: offsets {0,1,3,5,6,8,10}.
75
- *
76
- * @category Scales
77
- */
78
- export const LOCRIAN_MASK = maskFromOffsets([0, 1, 3, 5, 6, 8, 10]);
79
-
80
- /**
81
- * Major pentatonic: offsets {0,2,4,7,9}.
82
- *
83
- * @category Scales
84
- */
85
- export const MAJOR_PENTATONIC_MASK = maskFromOffsets([0, 2, 4, 7, 9]);
86
-
87
- /**
88
- * Minor pentatonic: offsets {0,3,5,7,10}.
89
- *
90
- * @category Scales
91
- */
92
- export const MINOR_PENTATONIC_MASK = maskFromOffsets([0, 3, 5, 7, 10]);
93
-
94
- /**
95
- * Hexatonic blues scale: minor pentatonic plus the flat-fifth — {0,3,5,6,7,10}.
96
- *
97
- * @category Scales
98
- */
99
- export const BLUES_MASK = maskFromOffsets([0, 3, 5, 6, 7, 10]);
100
-
101
- /**
102
- * Whole-tone scale: offsets {0,2,4,6,8,10}.
103
- *
104
- * @category Scales
105
- */
106
- export const WHOLE_TONE_MASK = maskFromOffsets([0, 2, 4, 6, 8, 10]);
107
-
108
- /**
109
- * Octatonic (half-step first): offsets {0,1,3,4,6,7,9,10}.
110
- *
111
- * @category Scales
112
- */
113
- export const OCTATONIC_HALF_WHOLE_MASK = maskFromOffsets([0, 1, 3, 4, 6, 7, 9, 10]);
114
-
115
- /**
116
- * Octatonic (whole-step first): offsets {0,2,3,5,6,8,9,11}.
117
- *
118
- * @category Scales
119
- */
120
- export const OCTATONIC_WHOLE_HALF_MASK = maskFromOffsets([0, 2, 3, 5, 6, 8, 9, 11]);
121
-
122
- /**
123
- * Chromatic scale: all twelve pitch classes.
124
- *
125
- * @category Scales
126
- */
127
- export const CHROMATIC_MASK = 0b111111111111;
128
-
129
- /**
130
- * Named scale masks addressable by {@link scaleByName}.
131
- *
132
- * @category Scales
133
- */
134
- export const NAMED_SCALES: Record<string, number> = {
135
- major: MAJOR_MASK,
136
- ionian: MAJOR_MASK,
137
- naturalMinor: NATURAL_MINOR_MASK,
138
- aeolian: NATURAL_MINOR_MASK,
139
- harmonicMinor: HARMONIC_MINOR_MASK,
140
- melodicMinor: MELODIC_MINOR_MASK,
141
- dorian: DORIAN_MASK,
142
- phrygian: PHRYGIAN_MASK,
143
- lydian: LYDIAN_MASK,
144
- mixolydian: MIXOLYDIAN_MASK,
145
- locrian: LOCRIAN_MASK,
146
- majorPentatonic: MAJOR_PENTATONIC_MASK,
147
- minorPentatonic: MINOR_PENTATONIC_MASK,
148
- blues: BLUES_MASK,
149
- wholeTone: WHOLE_TONE_MASK,
150
- octatonicHalfWhole: OCTATONIC_HALF_WHOLE_MASK,
151
- octatonicWholeHalf: OCTATONIC_WHOLE_HALF_MASK,
152
- chromatic: CHROMATIC_MASK,
153
- };
@@ -1,249 +0,0 @@
1
- /**
2
- * Spelling: derive letter-name notes for scales and chords from a spelled tonic.
3
- *
4
- * The theory core works in pitch classes, which cannot choose between (say) a
5
- * G# and an Ab. Given a spelled tonic, this module assigns diatonic letters to
6
- * scale degrees and chord tones so a C major scale spells as C D E F G A B and
7
- * A harmonic minor spells its seventh as G#.
8
- */
9
-
10
- import type { Note } from '../../core/pitch/index.js';
11
- import { formatNote } from '../../core/pitch/index.js';
12
- import type { KeyScale } from '../../core/types.js';
13
- import type { Chord } from '../chord/index.js';
14
- import { scaleTonesInDegreeOrder } from '../scale/index.js';
15
-
16
- /** Semitone offset of each natural letter above C: C D E F G A B. */
17
- const LETTER_SEMITONES = [0, 2, 4, 5, 7, 9, 11] as const;
18
-
19
- /**
20
- * Conventional degree assignment for each chromatic offset above the tonic.
21
- *
22
- * The letter is derived from the tonic's letter plus `degreeOffset` (so the
23
- * spelling follows the key's diatonic letters), and the accidental is recomputed
24
- * for that letter. This covers the flat-side alterations (b2, b3, b6, b7), the
25
- * raised fourth (#4), and the raised degrees of a minor key: the raised sixth
26
- * (#6) and the raised leading tone (#7). The latter two keep sharp-side minor
27
- * keys spelling their raised degrees with the key-correct letter (e.g. F# minor
28
- * spells its leading tone E#, not F).
29
- *
30
- * The `alter` field is illustrative only; `spellPitchClass` recomputes the
31
- * accidental from the chosen letter.
32
- */
33
- const CHROMATIC_SPELLING: Record<number, { degreeOffset: number; alter: number }> = {
34
- 1: { degreeOffset: 1, alter: -1 }, // b2
35
- 3: { degreeOffset: 2, alter: -1 }, // b3
36
- 6: { degreeOffset: 3, alter: 1 }, // #4
37
- 8: { degreeOffset: 5, alter: -1 }, // b6
38
- 9: { degreeOffset: 5, alter: 1 }, // #6 (raised sixth of a minor key)
39
- 10: { degreeOffset: 6, alter: -1 }, // b7
40
- 11: { degreeOffset: 6, alter: 1 }, // #7 (raised leading tone of a minor key)
41
- };
42
-
43
- function mod12(n: number): number {
44
- return ((n % 12) + 12) % 12;
45
- }
46
-
47
- function mod7(n: number): number {
48
- return ((n % 7) + 7) % 7;
49
- }
50
-
51
- /** Natural pitch class of a letter (0 = C .. 6 = B). */
52
- function naturalPc(letter: number): number {
53
- return LETTER_SEMITONES[mod7(letter)] ?? 0;
54
- }
55
-
56
- /** Shortest signed alteration (in [-6, 6]) taking a letter's natural pc to `pc`. */
57
- function alterFor(letter: number, pc: number): number {
58
- let d = mod12(pc - naturalPc(letter));
59
- if (d > 6) {
60
- d -= 12;
61
- }
62
- return d;
63
- }
64
-
65
- /** Whether the key's scale is a seven-note (heptatonic) scale. */
66
- function isHeptatonic(key: KeyScale): boolean {
67
- return scaleTonesInDegreeOrder(key).length === 7;
68
- }
69
-
70
- /**
71
- * Spell a single pitch class relative to a spelled tonic and key.
72
- *
73
- * Diatonic pitch classes take the scale's letter for their degree; the common
74
- * chromatic tones (b2, b3, #4, b6, b7) and a minor key's raised degrees (#6, #7)
75
- * take their conventional, key-correct accidental spelling; anything else falls
76
- * back to a sharp spelling of the nearest natural.
77
- *
78
- * @param pc The pitch class to spell.
79
- * @param tonic The spelled tonic (its letter anchors the spelling).
80
- * @param key The key/scale.
81
- * @returns The spelled note (without octave).
82
- * @category Pitch & Intervals
83
- */
84
- export function spellPitchClass(pc: number, tonic: Note, key: KeyScale): Note {
85
- const tonicPc = mod12(naturalPc(tonic.letter) + tonic.alter);
86
- const offset = mod12(pc - tonicPc);
87
- const tones = scaleTonesInDegreeOrder(key);
88
-
89
- if (isHeptatonic(key)) {
90
- const degree = tones.indexOf(mod12(pc));
91
- if (degree >= 0) {
92
- const letter = mod7(tonic.letter + degree);
93
- return { letter, alter: alterFor(letter, pc) };
94
- }
95
- const chromatic = CHROMATIC_SPELLING[offset];
96
- if (chromatic) {
97
- const letter = mod7(tonic.letter + chromatic.degreeOffset);
98
- return { letter, alter: alterFor(letter, pc) };
99
- }
100
- }
101
-
102
- // Fallback: name the pitch class from the nearest natural, preferring a sharp.
103
- for (let letter = 0; letter < 7; letter += 1) {
104
- if (naturalPc(letter) === mod12(pc)) {
105
- return { letter, alter: 0 };
106
- }
107
- }
108
- const belowLetter = mod7([0, 0, 1, 1, 2, 3, 3, 4, 4, 5, 5, 6][mod12(pc)] ?? 0);
109
- return { letter: belowLetter, alter: alterFor(belowLetter, pc) };
110
- }
111
-
112
- /**
113
- * Spell every pitch class of a scale, in ascending scale-degree order.
114
- *
115
- * Correct for heptatonic scales (each degree gets the next letter). Non-standard
116
- * scales are spelled tone-by-tone with a sharp preference.
117
- *
118
- * @param tonic The spelled tonic.
119
- * @param key The key/scale.
120
- * @returns Spelled notes, one per scale degree.
121
- * @example
122
- * ```ts
123
- * import { spellScale, noteNames, parseNote, majorKey } from '@libraz/libcantus';
124
- * noteNames(spellScale(parseNote('C'), majorKey(0)));
125
- * // ['C', 'D', 'E', 'F', 'G', 'A', 'B']
126
- * ```
127
- * @category Pitch & Intervals
128
- */
129
- export function spellScale(tonic: Note, key: KeyScale): Note[] {
130
- return scaleTonesInDegreeOrder(key).map((pc) => spellPitchClass(pc, tonic, key));
131
- }
132
-
133
- /**
134
- * Spell an arbitrary list of pitch classes relative to a key.
135
- *
136
- * @param pcs The pitch classes.
137
- * @param tonic The spelled tonic.
138
- * @param key The key/scale.
139
- * @returns Spelled notes, in input order.
140
- * @category Pitch & Intervals
141
- */
142
- export function spellPitchClasses(pcs: number[], tonic: Note, key: KeyScale): Note[] {
143
- return pcs.map((pc) => spellPitchClass(pc, tonic, key));
144
- }
145
-
146
- /**
147
- * Diatonic letter distance implied by a chord interval.
148
- *
149
- * Chord intervals are not merely pitch-class distances: 6 is a diminished
150
- * fifth in the basic chord templates while 18 is an augmented eleventh, and 9
151
- * is a diminished seventh in `dim7` but a sixth elsewhere. Keeping that
152
- * distinction is what prevents functional chord tones from being respelled by
153
- * their nearest key pitch (for example G# becoming Ab in E7).
154
- */
155
- function chordLetterOffset(interval: number, chord: Chord): number {
156
- switch (interval) {
157
- case 0:
158
- case 12:
159
- return 0;
160
- case 1:
161
- case 2:
162
- case 13:
163
- case 14:
164
- return 1;
165
- case 3:
166
- case 4:
167
- case 15:
168
- case 16:
169
- return 2;
170
- case 5:
171
- case 17:
172
- case 18:
173
- return 3;
174
- case 6:
175
- case 7:
176
- case 8:
177
- case 19:
178
- return 4;
179
- case 9:
180
- return chord.quality === 'dim7' ? 6 : 5;
181
- case 20:
182
- case 21:
183
- return 5;
184
- case 10:
185
- case 11:
186
- case 22:
187
- case 23:
188
- return 6;
189
- default: {
190
- // Unknown/custom interval data gets the nearest conventional diatonic
191
- // distance. Public builders use the explicit cases above.
192
- const octaves = Math.floor(Math.max(0, interval) / 12);
193
- const simple = mod12(interval);
194
- const simpleOffset = [0, 1, 1, 2, 2, 3, 4, 4, 4, 5, 6, 6][simple] ?? 0;
195
- return octaves * 7 + simpleOffset;
196
- }
197
- }
198
- }
199
-
200
- /**
201
- * Spell chord tones from an already chosen root spelling.
202
- *
203
- * This is exported for the model layer's inversion support; the package
204
- * barrels intentionally expose the key-aware {@link spellChord} entry point.
205
- */
206
- export function spellChordFromRoot(chord: Chord, root: Note): Note[] {
207
- return chord.intervals.map((interval) => {
208
- const letter = mod7(root.letter + chordLetterOffset(interval, chord));
209
- const pc = mod12(chord.rootPc + interval);
210
- return { letter, alter: alterFor(letter, pc) };
211
- });
212
- }
213
-
214
- /**
215
- * Spell a chord's tones, in the chord's own (tertian) order, relative to a key.
216
- *
217
- * Diatonic chords spell exactly (e.g. G7 in C major -> G B D F). Chromatic chord
218
- * tones take their conventional spelling; enharmonically ambiguous altered
219
- * tensions may be spelled by the general convention rather than by chord
220
- * function.
221
- *
222
- * @param chord The chord.
223
- * @param tonic The spelled tonic of the key.
224
- * @param key The key/scale.
225
- * @returns Spelled chord tones, root first.
226
- * @example
227
- * ```ts
228
- * import { spellChord, noteNames, parseNote, majorKey, makeChord } from '@libraz/libcantus';
229
- * // G7 in C major
230
- * noteNames(spellChord(makeChord(7, 'dom7'), parseNote('C'), majorKey(0)));
231
- * // ['G', 'B', 'D', 'F']
232
- * ```
233
- * @category Pitch & Intervals
234
- */
235
- export function spellChord(chord: Chord, tonic: Note, key: KeyScale): Note[] {
236
- const root = spellPitchClass(chord.rootPc, tonic, key);
237
- return spellChordFromRoot(chord, root);
238
- }
239
-
240
- /**
241
- * Convenience: render spelled notes as letter-name strings.
242
- *
243
- * @param notes The notes.
244
- * @returns Their formatted names.
245
- * @category Pitch & Intervals
246
- */
247
- export function noteNames(notes: Note[]): string[] {
248
- return notes.map(formatNote);
249
- }