@libraz/libcantus 0.9.1 → 0.9.2

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 (154) hide show
  1. package/README.md +15 -0
  2. package/dist/analyze/index.cjs +1697 -0
  3. package/dist/analyze/index.cjs.map +1 -0
  4. package/dist/analyze/index.d.cts +429 -0
  5. package/dist/analyze/index.d.ts +429 -0
  6. package/dist/analyze/index.js +51 -0
  7. package/dist/analyze/index.js.map +1 -0
  8. package/dist/chunk-5HTNMQW4.js +653 -0
  9. package/dist/chunk-5HTNMQW4.js.map +1 -0
  10. package/dist/chunk-7Y7WF64D.js +410 -0
  11. package/dist/chunk-7Y7WF64D.js.map +1 -0
  12. package/dist/chunk-A4532ZFR.js +259 -0
  13. package/dist/chunk-A4532ZFR.js.map +1 -0
  14. package/dist/chunk-BHUT2S5G.js +22 -0
  15. package/dist/chunk-BHUT2S5G.js.map +1 -0
  16. package/dist/chunk-CCBLVAPK.js +42 -0
  17. package/dist/chunk-CCBLVAPK.js.map +1 -0
  18. package/dist/chunk-COKTX2G5.js +907 -0
  19. package/dist/chunk-COKTX2G5.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-GQKN5KDT.js +159 -0
  23. package/dist/chunk-GQKN5KDT.js.map +1 -0
  24. package/dist/chunk-LN7ZZMD2.js +530 -0
  25. package/dist/chunk-LN7ZZMD2.js.map +1 -0
  26. package/dist/chunk-MSWFZF7J.js +118 -0
  27. package/dist/chunk-MSWFZF7J.js.map +1 -0
  28. package/dist/chunk-R3S2LUBH.js +2722 -0
  29. package/dist/chunk-R3S2LUBH.js.map +1 -0
  30. package/dist/chunk-SDM3VJJ5.js +41 -0
  31. package/dist/chunk-SDM3VJJ5.js.map +1 -0
  32. package/dist/chunk-XTC3X6MS.js +816 -0
  33. package/dist/chunk-XTC3X6MS.js.map +1 -0
  34. package/dist/chunk-ZXW5YIWK.js +55 -0
  35. package/dist/chunk-ZXW5YIWK.js.map +1 -0
  36. package/dist/core/index.cjs +432 -0
  37. package/dist/core/index.cjs.map +1 -0
  38. package/dist/core/index.d.cts +210 -0
  39. package/dist/core/index.d.ts +210 -0
  40. package/dist/core/index.js +73 -0
  41. package/dist/core/index.js.map +1 -0
  42. package/dist/function-2bJW1Zp_.d.ts +187 -0
  43. package/dist/function-r2sh-jOO.d.cts +187 -0
  44. package/dist/generate/index.cjs +3787 -0
  45. package/dist/generate/index.cjs.map +1 -0
  46. package/dist/generate/index.d.cts +980 -0
  47. package/dist/generate/index.d.ts +980 -0
  48. package/dist/generate/index.js +52 -0
  49. package/dist/generate/index.js.map +1 -0
  50. package/dist/index-BMPxwjwV.d.ts +151 -0
  51. package/dist/index-BOBXBNwP.d.cts +157 -0
  52. package/dist/index-BdeQ1r_p.d.cts +56 -0
  53. package/dist/index-BreoJRw0.d.ts +127 -0
  54. package/dist/index-CGMZNCx6.d.cts +140 -0
  55. package/dist/index-CGMZNCx6.d.ts +140 -0
  56. package/dist/index-CM5W4rcm.d.ts +157 -0
  57. package/dist/index-CP3wAfEH.d.ts +56 -0
  58. package/dist/index-Cl7mhOFM.d.cts +164 -0
  59. package/dist/index-Cl7mhOFM.d.ts +164 -0
  60. package/dist/index-DZ1M21do.d.cts +127 -0
  61. package/dist/index-D_ZTUNKp.d.cts +151 -0
  62. package/dist/index.cjs +3016 -2984
  63. package/dist/index.cjs.map +1 -1
  64. package/dist/index.d.cts +14 -3982
  65. package/dist/index.d.ts +14 -3982
  66. package/dist/index.js +170 -6504
  67. package/dist/index.js.map +1 -1
  68. package/dist/model/index.cjs +2435 -0
  69. package/dist/model/index.cjs.map +1 -0
  70. package/dist/model/index.d.cts +630 -0
  71. package/dist/model/index.d.ts +630 -0
  72. package/dist/model/index.js +21 -0
  73. package/dist/model/index.js.map +1 -0
  74. package/dist/styled-BjXtOFNk.d.cts +282 -0
  75. package/dist/styled-D7BAx3S3.d.ts +282 -0
  76. package/dist/theory/index.cjs +1677 -0
  77. package/dist/theory/index.cjs.map +1 -0
  78. package/dist/theory/index.d.cts +535 -0
  79. package/dist/theory/index.d.ts +535 -0
  80. package/dist/theory/index.js +149 -0
  81. package/dist/theory/index.js.map +1 -0
  82. package/dist/types-MGP29r3t.d.cts +30 -0
  83. package/dist/types-MGP29r3t.d.ts +30 -0
  84. package/package.json +27 -2
  85. package/src/analyze/arrange/index.ts +21 -0
  86. package/src/analyze/arrange/internal.ts +125 -0
  87. package/src/analyze/arrange/tension.ts +187 -0
  88. package/src/analyze/arrange/tracks.ts +279 -0
  89. package/src/{detect → analyze/detect}/index.ts +4 -4
  90. package/src/analyze/functional/borrowed.ts +84 -0
  91. package/src/analyze/functional/cadence.ts +66 -0
  92. package/src/analyze/functional/function.ts +184 -0
  93. package/src/analyze/functional/index.ts +23 -0
  94. package/src/analyze/functional/internal.ts +29 -0
  95. package/src/{functional/index.ts → analyze/functional/roman.ts} +9 -327
  96. package/src/analyze/index.ts +49 -0
  97. package/src/{timeline → analyze/timeline}/index.ts +7 -11
  98. package/src/{analysis → analyze/voice}/index.ts +8 -5
  99. package/src/core/index.ts +47 -0
  100. package/src/{bass → generate/bass}/index.ts +5 -5
  101. package/src/{countermelody → generate/countermelody}/index.ts +11 -11
  102. package/src/{drums → generate/drums}/index.ts +2 -2
  103. package/src/{drums → generate/drums}/rng.ts +2 -2
  104. package/src/{groove → generate/groove}/index.ts +3 -3
  105. package/src/{harmonize → generate/harmonize}/index.ts +13 -13
  106. package/src/generate/index.ts +59 -0
  107. package/src/{motif → generate/motif}/index.ts +7 -7
  108. package/src/{progression → generate/progression}/index.ts +11 -25
  109. package/src/{reharmony → generate/reharmony}/index.ts +11 -11
  110. package/src/{rhythm → generate/rhythm}/index.ts +2 -2
  111. package/src/index.ts +130 -117
  112. package/src/model/chord.ts +423 -0
  113. package/src/model/index.ts +5 -1096
  114. package/src/model/interval.ts +82 -0
  115. package/src/model/key.ts +266 -0
  116. package/src/model/note.ts +176 -0
  117. package/src/model/progression.ts +161 -0
  118. package/src/model/shared.ts +12 -0
  119. package/src/{chord → theory/chord}/index.ts +19 -1
  120. package/src/{counterpoint → theory/counterpoint}/index.ts +2 -2
  121. package/src/theory/index.ts +107 -0
  122. package/src/{safety → theory/safety}/index.ts +1 -1
  123. package/src/theory/scale/degrees.ts +123 -0
  124. package/src/theory/scale/index.ts +28 -0
  125. package/src/theory/scale/key.ts +56 -0
  126. package/src/theory/scale/masks.ts +153 -0
  127. package/src/{spelling → theory/spelling}/index.ts +3 -3
  128. package/src/{symbol → theory/symbol}/index.ts +8 -1
  129. package/src/theory/voicing/index.ts +5 -0
  130. package/src/theory/voicing/internal.ts +168 -0
  131. package/src/theory/voicing/leading.ts +112 -0
  132. package/src/theory/voicing/satb.ts +195 -0
  133. package/src/theory/voicing/styled.ts +176 -0
  134. package/src/arrange/index.ts +0 -571
  135. package/src/scale/index.ts +0 -331
  136. package/src/voicing/index.ts +0 -623
  137. /package/src/{interval → core/interval}/index.ts +0 -0
  138. /package/src/{meter → core/meter}/index.ts +0 -0
  139. /package/src/{pitch → core/pitch}/index.ts +0 -0
  140. /package/src/{random → core/random}/index.ts +0 -0
  141. /package/src/{tuning → core/tuning}/index.ts +0 -0
  142. /package/src/{types.ts → core/types.ts} +0 -0
  143. /package/src/{drums → generate/drums}/beat.ts +0 -0
  144. /package/src/{drums → generate/drums}/euclid.ts +0 -0
  145. /package/src/{drums → generate/drums}/fills.ts +0 -0
  146. /package/src/{drums → generate/drums}/ghost.ts +0 -0
  147. /package/src/{drums → generate/drums}/hihat.ts +0 -0
  148. /package/src/{drums → generate/drums}/hit.ts +0 -0
  149. /package/src/{drums → generate/drums}/internal.ts +0 -0
  150. /package/src/{drums → generate/drums}/kick.ts +0 -0
  151. /package/src/{drums → generate/drums}/percussion.ts +0 -0
  152. /package/src/{drums → generate/drums}/swing.ts +0 -0
  153. /package/src/{chordscale → theory/chordscale}/index.ts +0 -0
  154. /package/src/{harmony → theory/harmony}/index.ts +0 -0
@@ -1,5 +1,5 @@
1
+ import type { KeyScale } from '../../core/types.js';
1
2
  import { scaleTonesInDegreeOrder } from '../scale/index.js';
2
- import type { KeyScale } from '../types.js';
3
3
 
4
4
  /**
5
5
  * Chord quality identifiers understood by the chord builder.
@@ -70,6 +70,24 @@ export type Chord = {
70
70
  bassSpelling?: PitchSpelling;
71
71
  };
72
72
 
73
+ /**
74
+ * A chord positioned in time: a root pitch class and quality placed at a beat,
75
+ * with optional harmonic-analysis hints. Serves as the neutral exchange record
76
+ * between progression generators and timeline analysers.
77
+ *
78
+ * @category Composition
79
+ */
80
+ export type ChordSpan = {
81
+ rootPc: number;
82
+ quality: ChordQuality;
83
+ startBeat: number;
84
+ bassPc?: number;
85
+ /** 0-based scale degree of the chord root, when known. */
86
+ degree?: number;
87
+ /** True when the chord is a secondary dominant tonicizing another degree. */
88
+ secondaryDominant?: boolean;
89
+ };
90
+
73
91
  /** Semitone offsets from the root for each supported chord quality. */
74
92
  const QUALITY_INTERVALS: Record<ChordQuality, number[]> = {
75
93
  maj: [0, 4, 7],
@@ -1,5 +1,5 @@
1
- import { isConsonantInterval } from '../interval/index.js';
2
- import type { KeyScale } from '../types.js';
1
+ import { isConsonantInterval } from '../../core/interval/index.js';
2
+ import type { KeyScale } from '../../core/types.js';
3
3
 
4
4
  /** Reduce a MIDI pitch (or pitch class) to a pitch class in [0, 11]. */
5
5
  function pitchClass(pitch: number): number {
@@ -0,0 +1,107 @@
1
+ /**
2
+ * Public API of the theory layer. Re-exports every theory-layer module;
3
+ * also available from the package root.
4
+ */
5
+ export type { Chord as ChordData, ChordQuality } from './chord/index.js';
6
+ export {
7
+ chordFromDegree,
8
+ chordPitchClasses,
9
+ chordQualities,
10
+ chordToneRole,
11
+ diatonicSeventh,
12
+ diatonicTriad,
13
+ makeChord,
14
+ } from './chord/index.js';
15
+ export type {
16
+ ChordScaleMatch,
17
+ ChordScaleReportEntry,
18
+ ScaleChoice,
19
+ } from './chordscale/index.js';
20
+ export {
21
+ availableTensions,
22
+ avoidNotes,
23
+ chordScaleReport,
24
+ chordScales,
25
+ scaleMatchesChord,
26
+ scalesForChanges,
27
+ } from './chordscale/index.js';
28
+ export {
29
+ createsHiddenParallelPerfect,
30
+ createsParallelOctave,
31
+ createsParallelPerfect,
32
+ createsParallelUnison,
33
+ createsVerticalDissonance,
34
+ createsVoiceCrossing,
35
+ createsVoiceOverlap,
36
+ exceedsSpacing,
37
+ isForbiddenMelodicLeap,
38
+ isLeadingToneResolution,
39
+ } from './counterpoint/index.js';
40
+ export type { HarmonyRole, LockLevel, VoicedRole } from './harmony/index.js';
41
+ export { roleOf } from './harmony/index.js';
42
+ export type {
43
+ SafetyProfile,
44
+ SafetyQuery,
45
+ SafetyResult,
46
+ VoiceSnapshot,
47
+ } from './safety/index.js';
48
+ export {
49
+ enumerateSafePitches,
50
+ evaluateSafety,
51
+ NoteSafety,
52
+ ReasonFlag,
53
+ } from './safety/index.js';
54
+ export {
55
+ BLUES_MASK,
56
+ CHROMATIC_MASK,
57
+ DORIAN_MASK,
58
+ diatonicPitchClasses,
59
+ HARMONIC_MINOR_MASK,
60
+ isScaleTone,
61
+ LOCRIAN_MASK,
62
+ LYDIAN_MASK,
63
+ MAJOR_MASK,
64
+ MAJOR_PENTATONIC_MASK,
65
+ MELODIC_MINOR_MASK,
66
+ MINOR_PENTATONIC_MASK,
67
+ MIXOLYDIAN_MASK,
68
+ majorKey,
69
+ maskFromOffsets,
70
+ minorKey,
71
+ NAMED_SCALES,
72
+ NATURAL_MINOR_MASK,
73
+ nearestScaleTone,
74
+ OCTATONIC_HALF_WHOLE_MASK,
75
+ OCTATONIC_WHOLE_HALF_MASK,
76
+ PHRYGIAN_MASK,
77
+ pitchToScaleDegree,
78
+ scaleByName,
79
+ scaleTonesInDegreeOrder,
80
+ WHOLE_TONE_MASK,
81
+ } from './scale/index.js';
82
+ export {
83
+ noteNames,
84
+ spellChord,
85
+ spellPitchClass,
86
+ spellPitchClasses,
87
+ spellScale,
88
+ } from './spelling/index.js';
89
+ export {
90
+ formatChordSymbol,
91
+ parseChordSymbol,
92
+ transposeChordSymbol,
93
+ } from './symbol/index.js';
94
+ export type {
95
+ StyledVoicingOptions,
96
+ VoiceRange,
97
+ VoicingOptions,
98
+ VoicingStyle,
99
+ } from './voicing/index.js';
100
+ export {
101
+ nextVoicing,
102
+ SATB_RANGES,
103
+ voiceChord,
104
+ voiceChordStyled,
105
+ voiceLeadingCost,
106
+ voiceProgression,
107
+ } from './voicing/index.js';
@@ -1,3 +1,4 @@
1
+ import type { KeyScale } from '../../core/types.js';
1
2
  import type { Chord } from '../chord/index.js';
2
3
  import { chordPitchClasses } from '../chord/index.js';
3
4
  import {
@@ -7,7 +8,6 @@ import {
7
8
  isForbiddenMelodicLeap,
8
9
  } from '../counterpoint/index.js';
9
10
  import { isScaleTone } from '../scale/index.js';
10
- import type { KeyScale } from '../types.js';
11
11
 
12
12
  /**
13
13
  * Severity policy: `strict` counterpoint vs. lenient `pop` voice-leading.
@@ -0,0 +1,123 @@
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
+ }
@@ -0,0 +1,28 @@
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';
@@ -0,0 +1,56 @@
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
+ }
@@ -0,0 +1,153 @@
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
+ };
@@ -7,11 +7,11 @@
7
7
  * A harmonic minor spells its seventh as G#.
8
8
  */
9
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';
10
13
  import type { Chord } from '../chord/index.js';
11
- import type { Note } from '../pitch/index.js';
12
- import { formatNote } from '../pitch/index.js';
13
14
  import { scaleTonesInDegreeOrder } from '../scale/index.js';
14
- import type { KeyScale } from '../types.js';
15
15
 
16
16
  /** Semitone offset of each natural letter above C: C D E F G A B. */
17
17
  const LETTER_SEMITONES = [0, 2, 4, 5, 7, 9, 11] as const;
@@ -5,8 +5,15 @@
5
5
  * (`Cmaj7`, `F#m7b5`, `Bb7`, `C/G`) and the library's structured {@link Chord}
6
6
  * type, so callers can accept/emit plain text at the edges of the API.
7
7
  */
8
+
9
+ import {
10
+ formatNote,
11
+ midiToNote,
12
+ type Note,
13
+ noteToPitchClass,
14
+ parseNote,
15
+ } from '../../core/pitch/index.js';
8
16
  import { type Chord, type ChordQuality, makeChord, type PitchSpelling } from '../chord/index.js';
9
- import { formatNote, midiToNote, type Note, noteToPitchClass, parseNote } from '../pitch/index.js';
10
17
 
11
18
  /**
12
19
  * Matches a chord root: a letter A-G (case-insensitive, like {@link parseNote})
@@ -0,0 +1,5 @@
1
+ export { nextVoicing, voiceLeadingCost } from './leading.js';
2
+ export type { VoiceRange, VoicingOptions } from './satb.js';
3
+ export { SATB_RANGES, voiceChord, voiceProgression } from './satb.js';
4
+ export type { StyledVoicingOptions, VoicingStyle } from './styled.js';
5
+ export { voiceChordStyled } from './styled.js';