@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
@@ -0,0 +1,52 @@
1
+ import {
2
+ applyGrooveTemplate,
3
+ developMotif,
4
+ extractGrooveTemplate,
5
+ generateBassLine,
6
+ generateCounterMelody,
7
+ generateDrums,
8
+ generateMotif,
9
+ generateProgression,
10
+ generateRhythm,
11
+ harmonizeMelody,
12
+ humanize,
13
+ onsetWeightCurve,
14
+ progressions,
15
+ progressionsByStyle,
16
+ rhythmDensity,
17
+ transformMotif
18
+ } from "../chunk-R3S2LUBH.js";
19
+ import "../chunk-BHUT2S5G.js";
20
+ import "../chunk-MSWFZF7J.js";
21
+ import {
22
+ modalInterchangePalette,
23
+ negativeHarmonyMirror,
24
+ substituteChord
25
+ } from "../chunk-GQKN5KDT.js";
26
+ import "../chunk-LN7ZZMD2.js";
27
+ import "../chunk-SDM3VJJ5.js";
28
+ import "../chunk-A4532ZFR.js";
29
+ import "../chunk-7Y7WF64D.js";
30
+ import "../chunk-CCBLVAPK.js";
31
+ export {
32
+ applyGrooveTemplate,
33
+ developMotif,
34
+ extractGrooveTemplate,
35
+ generateBassLine,
36
+ generateCounterMelody,
37
+ generateDrums,
38
+ generateMotif,
39
+ generateProgression,
40
+ generateRhythm,
41
+ harmonizeMelody,
42
+ humanize,
43
+ modalInterchangePalette,
44
+ negativeHarmonyMirror,
45
+ onsetWeightCurve,
46
+ progressions,
47
+ progressionsByStyle,
48
+ rhythmDensity,
49
+ substituteChord,
50
+ transformMotif
51
+ };
52
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,151 @@
1
+ import { T as TimeSignature } from './index-Cl7mhOFM.js';
2
+ import { K as KeyScale, N as NoteEvent } from './types-MGP29r3t.js';
3
+ import { C as Chord, b as ChordSpan } from './index-CM5W4rcm.js';
4
+ import { C as Cadence } from './function-2bJW1Zp_.js';
5
+
6
+ /**
7
+ * A chord occupying a half-open beat span.
8
+ *
9
+ * @category Arrangement & Analysis
10
+ */
11
+ type ChordSegment = {
12
+ startBeat: number;
13
+ endBeat: number;
14
+ chord: Chord;
15
+ };
16
+ /**
17
+ * A beat-indexed sequence of chord segments.
18
+ *
19
+ * @category Arrangement & Analysis
20
+ */
21
+ type ChordTimeline = {
22
+ /** The chord sounding at a beat, or null when no segment covers it. */
23
+ at: (beat: number) => Chord | null;
24
+ segments: ChordSegment[];
25
+ };
26
+ /**
27
+ * Build a chord timeline from placed chords.
28
+ *
29
+ * Each chord spans from its `startBeat` to the next chord's `startBeat`; the last
30
+ * chord runs to `totalBeats`. Segments with no positive length (from duplicate
31
+ * onsets or a last chord at or past `totalBeats`) are dropped. `at(beat)` returns
32
+ * the covering segment's chord, or null when the beat lies outside every segment.
33
+ *
34
+ * @param chords Placed chords in time order.
35
+ * @param totalBeats End of the timeline in beats.
36
+ * @returns A queryable chord timeline.
37
+ * @category Arrangement & Analysis
38
+ */
39
+ declare function chordTimelineFromChords(chords: ChordSpan[], totalBeats: number): ChordTimeline;
40
+ /**
41
+ * Options controlling {@link chordTimelineFromNotes}.
42
+ *
43
+ * @category Arrangement & Analysis
44
+ */
45
+ type ChordTimelineOptions = {
46
+ /** Key context; inferred from the notes with {@link detectKey} when omitted. */
47
+ key?: KeyScale;
48
+ /**
49
+ * Time signature used for metric accents; defaults to 4/4.
50
+ *
51
+ * @defaultValue `4/4`
52
+ */
53
+ ts?: TimeSignature;
54
+ /**
55
+ * Window length in beats per chord slot; defaults to one bar of `ts`.
56
+ *
57
+ * @defaultValue one bar of `ts`
58
+ */
59
+ harmonicRhythm?: number;
60
+ /**
61
+ * End of the analyzed span in beats; defaults to the end of the last note.
62
+ *
63
+ * @defaultValue the end of the last note
64
+ */
65
+ totalBeats?: number;
66
+ };
67
+ /**
68
+ * The result of {@link chordTimelineFromNotes}.
69
+ *
70
+ * @category Arrangement & Analysis
71
+ */
72
+ type ChordTimelineResult = {
73
+ /** The inferred timeline, with adjacent identical chords merged. */
74
+ timeline: ChordTimeline;
75
+ /** The key used for the analysis (given or inferred). */
76
+ key: KeyScale;
77
+ /** One confidence value in [0, 1] per segment, in segment order. */
78
+ segmentConfidence: number[];
79
+ };
80
+ /**
81
+ * Infer a chord timeline from raw multi-track notes.
82
+ *
83
+ * The span `[0, totalBeats)` is sliced into `harmonicRhythm`-beat windows; each
84
+ * window's chord is inferred from a pitch-class weight histogram of the notes
85
+ * overlapping it (weight = overlap duration x velocity x metric-accent bonus
86
+ * for onsets in the window). Adjacent windows carrying the identical chord are
87
+ * merged into one segment; windows with no notes produce no segment, so
88
+ * `at(beat)` returns null there. Each segment carries a confidence in [0, 1]:
89
+ * the fraction of the window weight explained by chord tones, reduced when the
90
+ * match is inexact, and duration-weighted across merged windows.
91
+ *
92
+ * Notes with a zero or negative duration never sound, so they are dropped at
93
+ * ingest: they contribute to neither the key inference, the span, nor any
94
+ * window's histogram.
95
+ *
96
+ * @param notes The notes to analyze (any number of tracks, flattened).
97
+ * @param opts Analysis options; see {@link ChordTimelineOptions}.
98
+ * @returns The inferred timeline, the key used, and per-segment confidences.
99
+ * @throws If `harmonicRhythm` is not positive.
100
+ * @example
101
+ * ```ts
102
+ * import { chordTimelineFromNotes } from '@libraz/libcantus';
103
+ * const notes = [
104
+ * { pitch: 60, startBeat: 0, durationBeat: 2 }, // C
105
+ * { pitch: 64, startBeat: 0, durationBeat: 2 }, // E
106
+ * { pitch: 67, startBeat: 0, durationBeat: 2 }, // G
107
+ * ];
108
+ * const { timeline, key } = chordTimelineFromNotes(notes);
109
+ * timeline.at(0); // the chord inferred over beat 0, or null
110
+ * ```
111
+ * @category Arrangement & Analysis
112
+ */
113
+ declare function chordTimelineFromNotes(notes: NoteEvent[], opts?: ChordTimelineOptions): ChordTimelineResult;
114
+ /**
115
+ * A cadence found between two consecutive timeline segments.
116
+ *
117
+ * @category Arrangement & Analysis
118
+ */
119
+ type CadenceHit = {
120
+ /** The beat where the cadence arrives (the second chord's onset). */
121
+ atBeat: number;
122
+ type: Exclude<Cadence, null>;
123
+ from: Chord;
124
+ to: Chord;
125
+ };
126
+ /**
127
+ * Find cadences between consecutive segments of a chord timeline.
128
+ *
129
+ * Each temporally adjacent segment pair is classified with
130
+ * {@link detectCadence}; pairs forming no cadence are skipped. Segments
131
+ * separated by a gap (a rest in the timeline) are not a chord-to-chord
132
+ * progression, so they are never paired.
133
+ *
134
+ * @param timeline The chord timeline to scan.
135
+ * @param key The prevailing key.
136
+ * @returns The cadences found, in time order.
137
+ * @example
138
+ * ```ts
139
+ * import { chordTimelineFromNotes, detectCadences } from '@libraz/libcantus';
140
+ * const notes = [
141
+ * { pitch: 67, startBeat: 0, durationBeat: 4 }, // G, a dominant
142
+ * { pitch: 60, startBeat: 4, durationBeat: 4 }, // C, the tonic
143
+ * ];
144
+ * const { timeline, key } = chordTimelineFromNotes(notes);
145
+ * const cadences = detectCadences(timeline, key);
146
+ * ```
147
+ * @category Arrangement & Analysis
148
+ */
149
+ declare function detectCadences(timeline: ChordTimeline, key: KeyScale): CadenceHit[];
150
+
151
+ export { type CadenceHit as C, type ChordSegment as a, type ChordTimeline as b, type ChordTimelineOptions as c, type ChordTimelineResult as d, chordTimelineFromChords as e, chordTimelineFromNotes as f, detectCadences as g };
@@ -0,0 +1,157 @@
1
+ import { K as KeyScale } from './types-MGP29r3t.cjs';
2
+
3
+ /**
4
+ * Chord quality identifiers understood by the chord builder.
5
+ *
6
+ * @category Chords
7
+ */
8
+ type ChordQuality = 'maj' | 'min' | 'dim' | 'aug' | 'maj7' | 'min7' | 'dom7' | 'dim7' | 'm7b5' | 'minMaj7' | 'aug7' | 'augMaj7' | 'majb5' | '6' | 'min6' | '6/9' | 'sus2' | 'sus4' | 'add9' | 'add11' | 'maj9' | 'min9' | 'dom9' | '7b9' | '7#9' | '7#11' | '7b13' | '11' | '13' | '5';
9
+ /**
10
+ * A bare spelled pitch used as an enharmonic hint: a diatonic letter
11
+ * (0..6 = C..B) plus a chromatic alteration (-2 double-flat .. +2 double-sharp).
12
+ * Mirrors the letter/alter half of the pitch module's `Note` without an octave.
13
+ *
14
+ * @category Chords
15
+ */
16
+ type PitchSpelling = {
17
+ letter: number;
18
+ alter: number;
19
+ };
20
+ /**
21
+ * A chord expressed as a root pitch class plus semitone offsets.
22
+ *
23
+ * `rootSpelling`/`bassSpelling` are optional enharmonic hints recorded by
24
+ * parsers (e.g. `parseChordSymbol('Bb7')`) so formatters can reproduce the
25
+ * original spelling instead of defaulting to sharps. Consumers may ignore
26
+ * them; a hint is only trusted when its pitch class still matches the
27
+ * corresponding `rootPc`/`bassPc`.
28
+ *
29
+ * @category Chords
30
+ */
31
+ type Chord = {
32
+ rootPc: number;
33
+ quality: ChordQuality;
34
+ intervals: number[];
35
+ bassPc?: number;
36
+ rootSpelling?: PitchSpelling;
37
+ bassSpelling?: PitchSpelling;
38
+ };
39
+ /**
40
+ * A chord positioned in time: a root pitch class and quality placed at a beat,
41
+ * with optional harmonic-analysis hints. Serves as the neutral exchange record
42
+ * between progression generators and timeline analysers.
43
+ *
44
+ * @category Composition
45
+ */
46
+ type ChordSpan = {
47
+ rootPc: number;
48
+ quality: ChordQuality;
49
+ startBeat: number;
50
+ bassPc?: number;
51
+ /** 0-based scale degree of the chord root, when known. */
52
+ degree?: number;
53
+ /** True when the chord is a secondary dominant tonicizing another degree. */
54
+ secondaryDominant?: boolean;
55
+ };
56
+ /**
57
+ * All chord qualities the builder understands, longest templates last.
58
+ *
59
+ * @category Chords
60
+ */
61
+ declare function chordQualities(): ChordQuality[];
62
+ /**
63
+ * Build a chord rooted on a diatonic scale degree.
64
+ *
65
+ * The root pitch class is the degree's diatonic pitch class in `key`; the
66
+ * quality's interval template is attached unchanged. Degrees beyond the scale
67
+ * length wrap around.
68
+ *
69
+ * @param degree 0-based scale degree of the chord root.
70
+ * @param ext Chord quality to apply.
71
+ * @param key Key context supplying the diatonic root.
72
+ * @returns The constructed chord.
73
+ *
74
+ * @category Chords
75
+ */
76
+ declare function chordFromDegree(degree: number, ext: ChordQuality, key: KeyScale): Chord;
77
+ /**
78
+ * Build a chord from an explicit root pitch class and quality.
79
+ *
80
+ * @param rootPc Root pitch class (0..11).
81
+ * @param quality Chord quality supplying the interval template.
82
+ * @param bassPc Optional slash-chord bass pitch class.
83
+ * @returns The constructed chord.
84
+ *
85
+ * @example
86
+ * ```ts
87
+ * import { makeChord } from '@libraz/libcantus';
88
+ * const cmaj7 = makeChord(0, 'maj7');
89
+ * // { rootPc: 0, quality: 'maj7', intervals: [0, 4, 7, 11] }
90
+ * ```
91
+ *
92
+ * @category Chords
93
+ */
94
+ declare function makeChord(rootPc: number, quality: ChordQuality, bassPc?: number): Chord;
95
+ /**
96
+ * Get the sorted, deduplicated pitch classes of a chord.
97
+ *
98
+ * @param chord The chord to enumerate.
99
+ * @returns The chord's pitch classes, sorted ascending in [0, 11].
100
+ *
101
+ * @example
102
+ * ```ts
103
+ * import { makeChord, chordPitchClasses } from '@libraz/libcantus';
104
+ * chordPitchClasses(makeChord(0, 'maj7')); // [0, 4, 7, 11]
105
+ * ```
106
+ *
107
+ * @category Chords
108
+ */
109
+ declare function chordPitchClasses(chord: Chord): number[];
110
+ /**
111
+ * Determine a pitch's harmonic role within a chord.
112
+ *
113
+ * The role is derived from the pitch's interval above the chord root, reduced
114
+ * modulo 12. A major sixth (9) reads as a `sixth` for sixth chords but as a
115
+ * `seventh` for a diminished-seventh chord; ninths and other tensions have no
116
+ * basic role and return null.
117
+ *
118
+ * @param pitch MIDI pitch or bare pitch class.
119
+ * @param chord The chord providing the root reference.
120
+ * @returns The chord-tone role, or null if the pitch has no basic role.
121
+ *
122
+ * @category Chords
123
+ */
124
+ declare function chordToneRole(pitch: number, chord: Chord): 'root' | 'third' | 'fifth' | 'sixth' | 'seventh' | null;
125
+ /**
126
+ * Build the diatonic triad rooted on a scale degree by stacking scale thirds.
127
+ *
128
+ * Unlike {@link chordFromDegree}, the chord quality is derived from the scale
129
+ * rather than supplied, so degrees yield their scale-correct triads (e.g. a
130
+ * diminished triad on the leading tone of a major key). Intended for heptatonic
131
+ * scales; other scales stack by scale step regardless.
132
+ *
133
+ * @param degree 0-based scale degree of the chord root.
134
+ * @param key Key/scale context.
135
+ * @returns The diatonic triad.
136
+ *
137
+ * @example
138
+ * ```ts
139
+ * import { majorKey, diatonicTriad } from '@libraz/libcantus';
140
+ * const tonic = diatonicTriad(0, majorKey(0)); // C major triad (degree 0 of C major)
141
+ * ```
142
+ *
143
+ * @category Chords
144
+ */
145
+ declare function diatonicTriad(degree: number, key: KeyScale): Chord;
146
+ /**
147
+ * Build the diatonic seventh chord rooted on a scale degree.
148
+ *
149
+ * @param degree 0-based scale degree of the chord root.
150
+ * @param key Key/scale context.
151
+ * @returns The diatonic seventh chord.
152
+ *
153
+ * @category Chords
154
+ */
155
+ declare function diatonicSeventh(degree: number, key: KeyScale): Chord;
156
+
157
+ export { type Chord as C, type ChordQuality as a, type ChordSpan as b, chordFromDegree as c, chordPitchClasses as d, chordQualities as e, chordToneRole as f, diatonicSeventh as g, diatonicTriad as h, makeChord as m };
@@ -0,0 +1,56 @@
1
+ import { C as Chord } from './index-BOBXBNwP.cjs';
2
+
3
+ /**
4
+ * Harmonic role a pitch plays within a chord.
5
+ *
6
+ * @category Functional Harmony
7
+ */
8
+ type HarmonyRole = 'root' | 'third' | 'fifth' | 'sixth' | 'seventh' | 'tension' | 'doubling';
9
+ /**
10
+ * How firmly a pitch is locked to the chord's identity.
11
+ *
12
+ * - `identity`: moving it produces a different chord (the root).
13
+ * - `quality`: moving it flips the chord quality (the third).
14
+ * - `voicing`: it can move freely without changing chord identity or quality.
15
+ *
16
+ * @category Functional Harmony
17
+ */
18
+ type LockLevel = 'identity' | 'quality' | 'voicing';
19
+ /**
20
+ * A pitch's role, lock level, and owning chord.
21
+ *
22
+ * @category Functional Harmony
23
+ */
24
+ type VoicedRole = {
25
+ role: HarmonyRole;
26
+ lock: LockLevel;
27
+ belongsToChordId: number;
28
+ };
29
+ /**
30
+ * Classify a pitch's harmonic role and lock level within a chord.
31
+ *
32
+ * The role comes from the pitch's interval class above the chord root: root (0),
33
+ * third (3/4), fifth (6/7/8), seventh (10/11), and 9/11/13 tensions (2/5/9, and
34
+ * the flat ninth 1). The root locks the chord identity, the third locks its
35
+ * quality, and everything else is free voicing. In a suspended chord (a chord
36
+ * whose intervals include a 4th (5) or 2nd (2) but no third), the suspended tone
37
+ * takes the third's place as the quality-defining tone and is locked to
38
+ * `quality`, since moving it changes the chord. Detecting an octave doubling
39
+ * requires the surrounding voicing, which this single-pitch query does not carry,
40
+ * so `doubling` is part of the type but not returned here.
41
+ *
42
+ * @param pitch MIDI pitch or bare pitch class.
43
+ * @param chord The chord providing the root reference.
44
+ * @param chordId Identifier stored on the result (defaults to 0).
45
+ * @returns The pitch's role, lock level, and owning chord id.
46
+ * @example
47
+ * ```ts
48
+ * import { roleOf, makeChord } from '@libraz/libcantus';
49
+ * roleOf(64, makeChord(0, 'maj')); // E over C major
50
+ * // { role: 'third', lock: 'quality', belongsToChordId: 0 }
51
+ * ```
52
+ * @category Functional Harmony
53
+ */
54
+ declare function roleOf(pitch: number, chord: Chord, chordId?: number): VoicedRole;
55
+
56
+ export { type HarmonyRole as H, type LockLevel as L, type VoicedRole as V, roleOf as r };
@@ -0,0 +1,127 @@
1
+ import { K as KeyScale } from './types-MGP29r3t.js';
2
+ import { C as Chord } from './index-CM5W4rcm.js';
3
+
4
+ /**
5
+ * Severity policy: `strict` counterpoint vs. lenient `pop` voice-leading.
6
+ *
7
+ * @category Arrangement & Analysis
8
+ */
9
+ type SafetyProfile = 'strict' | 'pop';
10
+ /**
11
+ * Overall placeability verdict for a candidate pitch.
12
+ *
13
+ * @category Arrangement & Analysis
14
+ */
15
+ declare enum NoteSafety {
16
+ Safe = 0,
17
+ Warning = 1,
18
+ Dissonant = 2
19
+ }
20
+ /**
21
+ * Bit flags describing why a pitch received its verdict.
22
+ *
23
+ * @category Arrangement & Analysis
24
+ */
25
+ declare enum ReasonFlag {
26
+ ChordTone = 1,
27
+ Tension = 2,
28
+ AvoidNote = 4,
29
+ ScaleTone = 8,
30
+ NonScale = 16,
31
+ OutOfRange = 32,
32
+ Tritone = 64,
33
+ LargeLeap = 128,
34
+ MinorSecond = 256,
35
+ MajorSeventh = 512,
36
+ VerticalDissonance = 1024,
37
+ ParallelPerfect = 2048,
38
+ HiddenParallel = 4096,
39
+ VoiceCrossing = 8192,
40
+ Suspension = 16384,
41
+ NeedsResolution = 32768
42
+ }
43
+ /**
44
+ * A neighbouring voice's current and previous pitch.
45
+ *
46
+ * @category Arrangement & Analysis
47
+ */
48
+ type VoiceSnapshot = {
49
+ pitch: number;
50
+ prevPitch?: number;
51
+ };
52
+ /**
53
+ * Everything needed to evaluate one candidate pitch.
54
+ *
55
+ * @category Arrangement & Analysis
56
+ */
57
+ type SafetyQuery = {
58
+ profile: SafetyProfile;
59
+ candidatePitch: number;
60
+ prevPitch?: number;
61
+ chord: Chord | null;
62
+ key: KeyScale;
63
+ otherVoices: VoiceSnapshot[];
64
+ strongBeat: boolean;
65
+ vocalLow?: number;
66
+ vocalHigh?: number;
67
+ };
68
+ /**
69
+ * The verdict, reason flags, and optional resolution guidance.
70
+ *
71
+ * @category Arrangement & Analysis
72
+ */
73
+ type SafetyResult = {
74
+ safety: NoteSafety;
75
+ reasons: number;
76
+ resolveTo?: number;
77
+ suggestions?: number[];
78
+ rationale?: string;
79
+ };
80
+ /**
81
+ * Evaluate the safety of placing a candidate pitch in a voice.
82
+ *
83
+ * Reasons are merged from harmonic context (chord tone / tension / avoid / scale)
84
+ * and counterpoint (vertical dissonance, parallels, voice crossing). The `strict`
85
+ * profile treats avoid notes and parallels as dissonant; `pop` treats them as
86
+ * warnings. The final verdict is the worst severity among the reasons.
87
+ *
88
+ * @param q The candidate and its harmonic/voice-leading context.
89
+ * @returns The verdict, reason bitmask, and optional resolution guidance.
90
+ * @example
91
+ * ```ts
92
+ * import { evaluateSafety, makeChord, majorKey, NoteSafety } from '@libraz/libcantus';
93
+ * const result = evaluateSafety({
94
+ * profile: 'pop',
95
+ * candidatePitch: 60, // C over a C major chord
96
+ * chord: makeChord(0, 'maj'),
97
+ * key: majorKey(0),
98
+ * otherVoices: [],
99
+ * strongBeat: true,
100
+ * });
101
+ * result.safety === NoteSafety.Safe; // true — C is a chord tone
102
+ * ```
103
+ * @category Arrangement & Analysis
104
+ */
105
+ declare function evaluateSafety(q: SafetyQuery): SafetyResult;
106
+ /**
107
+ * Enumerate placeable pitches in a range, chord tones first, descending.
108
+ *
109
+ * @param q The safety context minus the candidate pitch.
110
+ * @param pitchLow Lowest MIDI pitch to consider (inclusive).
111
+ * @param pitchHigh Highest MIDI pitch to consider (inclusive).
112
+ * @returns Placeable pitches (non-dissonant), chord tones before others, each group descending.
113
+ * @example
114
+ * ```ts
115
+ * import { enumerateSafePitches, makeChord, majorKey } from '@libraz/libcantus';
116
+ * const pitches = enumerateSafePitches(
117
+ * { profile: 'pop', chord: makeChord(0, 'maj'), key: majorKey(0), otherVoices: [], strongBeat: true },
118
+ * 60,
119
+ * 72,
120
+ * );
121
+ * pitches; // placeable pitches in [60, 72], chord tones first, each group descending
122
+ * ```
123
+ * @category Arrangement & Analysis
124
+ */
125
+ declare function enumerateSafePitches(q: Omit<SafetyQuery, 'candidatePitch'>, pitchLow: number, pitchHigh: number): number[];
126
+
127
+ export { NoteSafety as N, ReasonFlag as R, type SafetyProfile as S, type VoiceSnapshot as V, type SafetyQuery as a, type SafetyResult as b, evaluateSafety as c, enumerateSafePitches as e };
@@ -0,0 +1,140 @@
1
+ /**
2
+ * Pitch spelling: letter-name notes, enharmonic-aware conversion, and spelled
3
+ * intervals.
4
+ *
5
+ * The rest of the library works in pitch classes (0..11), which cannot tell a
6
+ * G# from an Ab or an augmented fourth from a diminished fifth. This module adds
7
+ * an explicit spelling layer — a note is a diatonic letter plus a chromatic
8
+ * alteration — so interval quality (P/M/m/A/d) and enharmonic identity are
9
+ * preserved.
10
+ */
11
+ /**
12
+ * A spelled note: a diatonic letter, a chromatic alteration, and an optional
13
+ * octave.
14
+ *
15
+ * - `letter` is 0..6 for C..B.
16
+ * - `alter` is the number of semitones of alteration: -2 double-flat, -1 flat,
17
+ * 0 natural, +1 sharp, +2 double-sharp.
18
+ * - `octave` follows scientific pitch notation (middle C = C4 = MIDI 60). When
19
+ * absent, the note denotes a bare pitch class.
20
+ *
21
+ * @category Pitch & Intervals
22
+ */
23
+ type Note = {
24
+ letter: number;
25
+ alter: number;
26
+ octave?: number;
27
+ };
28
+ /**
29
+ * A spelled interval: a diatonic number, a quality label, and its semitone span.
30
+ *
31
+ * @category Pitch & Intervals
32
+ */
33
+ type SpelledInterval = {
34
+ /** Diatonic size: 1 = unison, 2 = second, ... 8 = octave, and beyond. */
35
+ number: number;
36
+ /** Quality label: 'P', 'M', 'm', or repeated 'A'/'d' for (multiply) aug/dim. */
37
+ quality: string;
38
+ /** Signed semitone distance from the first note to the second. */
39
+ semitones: number;
40
+ };
41
+ /**
42
+ * Parse scientific pitch notation into a {@link Note}.
43
+ *
44
+ * Accepts a letter (case-insensitive) followed by any number of same-direction
45
+ * accidentals and an optional octave, e.g. `"C"`, `"C#4"`, `"Bb"`, `"F##3"`,
46
+ * `"Ebb2"`. Both `#` and `x` (double-sharp) are accepted for sharps; `b` for
47
+ * flats. Mixing sharps and flats (e.g. `"C#b"`) is rejected.
48
+ *
49
+ * @param text The note text.
50
+ * @returns The parsed note.
51
+ * @throws If the text is not a valid note.
52
+ * @example
53
+ * ```ts
54
+ * import { parseNote, noteToMidi } from '@libraz/libcantus';
55
+ * const n = parseNote('C#4');
56
+ * noteToMidi(n); // 61
57
+ * ```
58
+ * @category Pitch & Intervals
59
+ */
60
+ declare function parseNote(text: string): Note;
61
+ /**
62
+ * Render a {@link Note} as scientific pitch notation.
63
+ *
64
+ * @param note The note to format.
65
+ * @returns The note text, including the octave when present.
66
+ * @example
67
+ * ```ts
68
+ * import { formatNote, parseNote } from '@libraz/libcantus';
69
+ * formatNote(parseNote('C#4')); // 'C#4'
70
+ * ```
71
+ * @category Pitch & Intervals
72
+ */
73
+ declare function formatNote(note: Note): string;
74
+ /**
75
+ * Pitch class (0..11) of a note, ignoring octave.
76
+ *
77
+ * @param note The note.
78
+ * @returns The pitch class.
79
+ * @example
80
+ * ```ts
81
+ * import { noteToPitchClass, parseNote } from '@libraz/libcantus';
82
+ * noteToPitchClass(parseNote('Db')); // 1
83
+ * ```
84
+ * @category Pitch & Intervals
85
+ */
86
+ declare function noteToPitchClass(note: Note): number;
87
+ /**
88
+ * MIDI number of a note (middle C = C4 = 60).
89
+ *
90
+ * @param note The note; must carry an octave.
91
+ * @returns The MIDI number.
92
+ * @throws If the note has no octave.
93
+ * @example
94
+ * ```ts
95
+ * import { noteToMidi, parseNote } from '@libraz/libcantus';
96
+ * noteToMidi(parseNote('A4')); // 69
97
+ * ```
98
+ * @category Pitch & Intervals
99
+ */
100
+ declare function noteToMidi(note: Note): number;
101
+ /**
102
+ * Name a MIDI number as a {@link Note}, choosing sharp or flat spelling.
103
+ *
104
+ * The input is rounded to the nearest integer but is not clamped to the MIDI
105
+ * range 0..127: out-of-range values extrapolate linearly (e.g. `-1` -> `B-2`,
106
+ * `128` -> `G#9`) and remain an exact inverse of {@link noteToMidi}.
107
+ *
108
+ * @param midi The MIDI number.
109
+ * @param spelling Whether to prefer sharps or flats for black keys.
110
+ * @returns The spelled note, with octave.
111
+ * @example
112
+ * ```ts
113
+ * import { midiToNote, formatNote } from '@libraz/libcantus';
114
+ * formatNote(midiToNote(61)); // 'C#4'
115
+ * formatNote(midiToNote(61, 'flat')); // 'Db4'
116
+ * ```
117
+ * @category Pitch & Intervals
118
+ */
119
+ declare function midiToNote(midi: number, spelling?: 'sharp' | 'flat'): Note;
120
+ /**
121
+ * The spelled interval from one note to another.
122
+ *
123
+ * When both notes carry octaves, the interval reflects their full signed
124
+ * distance (so `C4 -> C5` is a perfect octave). When either lacks an octave the
125
+ * interval is measured within a single ascending octave from `a` to `b`.
126
+ *
127
+ * @param a The lower/first note.
128
+ * @param b The upper/second note.
129
+ * @returns The diatonic number, quality, and semitone span.
130
+ * @example
131
+ * ```ts
132
+ * import { spelledInterval, parseNote } from '@libraz/libcantus';
133
+ * spelledInterval(parseNote('C4'), parseNote('G4'));
134
+ * // { number: 5, quality: 'P', semitones: 7 }
135
+ * ```
136
+ * @category Pitch & Intervals
137
+ */
138
+ declare function spelledInterval(a: Note, b: Note): SpelledInterval;
139
+
140
+ export { type Note as N, type SpelledInterval as S, noteToPitchClass as a, formatNote as f, midiToNote as m, noteToMidi as n, parseNote as p, spelledInterval as s };