@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.
- package/README.md +15 -0
- package/dist/analyze/index.cjs +1697 -0
- package/dist/analyze/index.cjs.map +1 -0
- package/dist/analyze/index.d.cts +429 -0
- package/dist/analyze/index.d.ts +429 -0
- package/dist/analyze/index.js +51 -0
- package/dist/analyze/index.js.map +1 -0
- package/dist/chunk-5HTNMQW4.js +653 -0
- package/dist/chunk-5HTNMQW4.js.map +1 -0
- package/dist/chunk-7Y7WF64D.js +410 -0
- package/dist/chunk-7Y7WF64D.js.map +1 -0
- package/dist/chunk-A4532ZFR.js +259 -0
- package/dist/chunk-A4532ZFR.js.map +1 -0
- package/dist/chunk-BHUT2S5G.js +22 -0
- package/dist/chunk-BHUT2S5G.js.map +1 -0
- package/dist/chunk-CCBLVAPK.js +42 -0
- package/dist/chunk-CCBLVAPK.js.map +1 -0
- package/dist/chunk-COKTX2G5.js +907 -0
- package/dist/chunk-COKTX2G5.js.map +1 -0
- package/dist/chunk-DYWFNC4A.js +152 -0
- package/dist/chunk-DYWFNC4A.js.map +1 -0
- package/dist/chunk-GQKN5KDT.js +159 -0
- package/dist/chunk-GQKN5KDT.js.map +1 -0
- package/dist/chunk-LN7ZZMD2.js +530 -0
- package/dist/chunk-LN7ZZMD2.js.map +1 -0
- package/dist/chunk-MSWFZF7J.js +118 -0
- package/dist/chunk-MSWFZF7J.js.map +1 -0
- package/dist/chunk-R3S2LUBH.js +2722 -0
- package/dist/chunk-R3S2LUBH.js.map +1 -0
- package/dist/chunk-SDM3VJJ5.js +41 -0
- package/dist/chunk-SDM3VJJ5.js.map +1 -0
- package/dist/chunk-XTC3X6MS.js +816 -0
- package/dist/chunk-XTC3X6MS.js.map +1 -0
- package/dist/chunk-ZXW5YIWK.js +55 -0
- package/dist/chunk-ZXW5YIWK.js.map +1 -0
- package/dist/core/index.cjs +432 -0
- package/dist/core/index.cjs.map +1 -0
- package/dist/core/index.d.cts +210 -0
- package/dist/core/index.d.ts +210 -0
- package/dist/core/index.js +73 -0
- package/dist/core/index.js.map +1 -0
- package/dist/function-2bJW1Zp_.d.ts +187 -0
- package/dist/function-r2sh-jOO.d.cts +187 -0
- package/dist/generate/index.cjs +3787 -0
- package/dist/generate/index.cjs.map +1 -0
- package/dist/generate/index.d.cts +980 -0
- package/dist/generate/index.d.ts +980 -0
- package/dist/generate/index.js +52 -0
- package/dist/generate/index.js.map +1 -0
- package/dist/index-BMPxwjwV.d.ts +151 -0
- package/dist/index-BOBXBNwP.d.cts +157 -0
- package/dist/index-BdeQ1r_p.d.cts +56 -0
- package/dist/index-BreoJRw0.d.ts +127 -0
- package/dist/index-CGMZNCx6.d.cts +140 -0
- package/dist/index-CGMZNCx6.d.ts +140 -0
- package/dist/index-CM5W4rcm.d.ts +157 -0
- package/dist/index-CP3wAfEH.d.ts +56 -0
- package/dist/index-Cl7mhOFM.d.cts +164 -0
- package/dist/index-Cl7mhOFM.d.ts +164 -0
- package/dist/index-DZ1M21do.d.cts +127 -0
- package/dist/index-D_ZTUNKp.d.cts +151 -0
- package/dist/index.cjs +3016 -2984
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +14 -3982
- package/dist/index.d.ts +14 -3982
- package/dist/index.js +170 -6504
- package/dist/index.js.map +1 -1
- package/dist/model/index.cjs +2435 -0
- package/dist/model/index.cjs.map +1 -0
- package/dist/model/index.d.cts +630 -0
- package/dist/model/index.d.ts +630 -0
- package/dist/model/index.js +21 -0
- package/dist/model/index.js.map +1 -0
- package/dist/styled-BjXtOFNk.d.cts +282 -0
- package/dist/styled-D7BAx3S3.d.ts +282 -0
- package/dist/theory/index.cjs +1677 -0
- package/dist/theory/index.cjs.map +1 -0
- package/dist/theory/index.d.cts +535 -0
- package/dist/theory/index.d.ts +535 -0
- package/dist/theory/index.js +149 -0
- package/dist/theory/index.js.map +1 -0
- package/dist/types-MGP29r3t.d.cts +30 -0
- package/dist/types-MGP29r3t.d.ts +30 -0
- package/package.json +27 -2
- package/src/analyze/arrange/index.ts +21 -0
- package/src/analyze/arrange/internal.ts +125 -0
- package/src/analyze/arrange/tension.ts +187 -0
- package/src/analyze/arrange/tracks.ts +279 -0
- package/src/{detect → analyze/detect}/index.ts +4 -4
- package/src/analyze/functional/borrowed.ts +84 -0
- package/src/analyze/functional/cadence.ts +66 -0
- package/src/analyze/functional/function.ts +184 -0
- package/src/analyze/functional/index.ts +23 -0
- package/src/analyze/functional/internal.ts +29 -0
- package/src/{functional/index.ts → analyze/functional/roman.ts} +9 -327
- package/src/analyze/index.ts +49 -0
- package/src/{timeline → analyze/timeline}/index.ts +7 -11
- package/src/{analysis → analyze/voice}/index.ts +8 -5
- package/src/core/index.ts +47 -0
- package/src/{bass → generate/bass}/index.ts +5 -5
- package/src/{countermelody → generate/countermelody}/index.ts +11 -11
- package/src/{drums → generate/drums}/index.ts +2 -2
- package/src/{drums → generate/drums}/rng.ts +2 -2
- package/src/{groove → generate/groove}/index.ts +3 -3
- package/src/{harmonize → generate/harmonize}/index.ts +13 -13
- package/src/generate/index.ts +59 -0
- package/src/{motif → generate/motif}/index.ts +7 -7
- package/src/{progression → generate/progression}/index.ts +11 -25
- package/src/{reharmony → generate/reharmony}/index.ts +11 -11
- package/src/{rhythm → generate/rhythm}/index.ts +2 -2
- package/src/index.ts +130 -117
- package/src/model/chord.ts +423 -0
- package/src/model/index.ts +5 -1096
- package/src/model/interval.ts +82 -0
- package/src/model/key.ts +266 -0
- package/src/model/note.ts +176 -0
- package/src/model/progression.ts +161 -0
- package/src/model/shared.ts +12 -0
- package/src/{chord → theory/chord}/index.ts +19 -1
- package/src/{counterpoint → theory/counterpoint}/index.ts +2 -2
- package/src/theory/index.ts +107 -0
- package/src/{safety → theory/safety}/index.ts +1 -1
- package/src/theory/scale/degrees.ts +123 -0
- package/src/theory/scale/index.ts +28 -0
- package/src/theory/scale/key.ts +56 -0
- package/src/theory/scale/masks.ts +153 -0
- package/src/{spelling → theory/spelling}/index.ts +3 -3
- package/src/{symbol → theory/symbol}/index.ts +8 -1
- package/src/theory/voicing/index.ts +5 -0
- package/src/theory/voicing/internal.ts +168 -0
- package/src/theory/voicing/leading.ts +112 -0
- package/src/theory/voicing/satb.ts +195 -0
- package/src/theory/voicing/styled.ts +176 -0
- package/src/arrange/index.ts +0 -571
- package/src/scale/index.ts +0 -331
- package/src/voicing/index.ts +0 -623
- /package/src/{interval → core/interval}/index.ts +0 -0
- /package/src/{meter → core/meter}/index.ts +0 -0
- /package/src/{pitch → core/pitch}/index.ts +0 -0
- /package/src/{random → core/random}/index.ts +0 -0
- /package/src/{tuning → core/tuning}/index.ts +0 -0
- /package/src/{types.ts → core/types.ts} +0 -0
- /package/src/{drums → generate/drums}/beat.ts +0 -0
- /package/src/{drums → generate/drums}/euclid.ts +0 -0
- /package/src/{drums → generate/drums}/fills.ts +0 -0
- /package/src/{drums → generate/drums}/ghost.ts +0 -0
- /package/src/{drums → generate/drums}/hihat.ts +0 -0
- /package/src/{drums → generate/drums}/hit.ts +0 -0
- /package/src/{drums → generate/drums}/internal.ts +0 -0
- /package/src/{drums → generate/drums}/kick.ts +0 -0
- /package/src/{drums → generate/drums}/percussion.ts +0 -0
- /package/src/{drums → generate/drums}/swing.ts +0 -0
- /package/src/{chordscale → theory/chordscale}/index.ts +0 -0
- /package/src/{harmony → theory/harmony}/index.ts +0 -0
|
@@ -0,0 +1,980 @@
|
|
|
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, a as ChordQuality, b as ChordSpan } from '../index-CM5W4rcm.js';
|
|
4
|
+
import { H as HarmonyRole } from '../index-CP3wAfEH.js';
|
|
5
|
+
import { b as ChordTimeline } from '../index-BMPxwjwV.js';
|
|
6
|
+
import { B as BorrowedSource, H as HarmonicFunction } from '../function-2bJW1Zp_.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Deterministic bass-line generation over a chord placement.
|
|
10
|
+
*
|
|
11
|
+
* Given a sequence of chord segments and a key, this builds a monophonic bass
|
|
12
|
+
* line in a chosen low register. Several idiomatic styles are supported, from a
|
|
13
|
+
* single held root per segment to a quarter-note walking line that leads by
|
|
14
|
+
* step into each chord change. All pitches are kept in a narrow bass band and
|
|
15
|
+
* consecutive notes move by small intervals; given a seed the output is fully
|
|
16
|
+
* reproducible.
|
|
17
|
+
*
|
|
18
|
+
* Positions and durations are measured in quarter-note beats, matching the
|
|
19
|
+
* library-wide convention.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* A chord sounding over a half-open beat span `[startBeat, endBeat)`.
|
|
24
|
+
*
|
|
25
|
+
* @category Composition
|
|
26
|
+
*/
|
|
27
|
+
type BassSegment = {
|
|
28
|
+
startBeat: number;
|
|
29
|
+
endBeat: number;
|
|
30
|
+
chord: Chord;
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* The bass-line idiom to generate.
|
|
34
|
+
*
|
|
35
|
+
* @category Composition
|
|
36
|
+
*/
|
|
37
|
+
type BassStyle = 'root' | 'rootFifth' | 'pop' | 'walking' | 'arpeggio';
|
|
38
|
+
/**
|
|
39
|
+
* Options controlling {@link generateBassLine}.
|
|
40
|
+
*
|
|
41
|
+
* @category Composition
|
|
42
|
+
*/
|
|
43
|
+
type BassLineOptions = {
|
|
44
|
+
/** Chord placement to follow; need not be pre-sorted. */
|
|
45
|
+
segments: BassSegment[];
|
|
46
|
+
/** Key/scale context, used for diatonic approach tones in `walking`. */
|
|
47
|
+
key: KeyScale;
|
|
48
|
+
/**
|
|
49
|
+
* Time signature; used for metric accents.
|
|
50
|
+
*
|
|
51
|
+
* @defaultValue `{ numerator: 4, denominator: 4 }`
|
|
52
|
+
*/
|
|
53
|
+
ts?: TimeSignature;
|
|
54
|
+
/**
|
|
55
|
+
* Bass-line idiom.
|
|
56
|
+
*
|
|
57
|
+
* @defaultValue `'root'`
|
|
58
|
+
*/
|
|
59
|
+
style?: BassStyle;
|
|
60
|
+
/**
|
|
61
|
+
* Target register as a base MIDI octave; roots land around `octave*12+12`.
|
|
62
|
+
*
|
|
63
|
+
* @defaultValue 2
|
|
64
|
+
*/
|
|
65
|
+
octave?: number;
|
|
66
|
+
/**
|
|
67
|
+
* Seed for the deterministic PRNG.
|
|
68
|
+
*
|
|
69
|
+
* @defaultValue 0
|
|
70
|
+
*/
|
|
71
|
+
seed?: number;
|
|
72
|
+
};
|
|
73
|
+
/**
|
|
74
|
+
* Generate a bass line following a chord placement.
|
|
75
|
+
*
|
|
76
|
+
* Each segment contributes notes in the chosen bass register per the selected
|
|
77
|
+
* style; consecutive notes are kept within roughly a fifth. Every note's
|
|
78
|
+
* duration extends to the next onset (the final note extends to the last
|
|
79
|
+
* segment's end). Given a seed the output is fully reproducible.
|
|
80
|
+
*
|
|
81
|
+
* @param opts Segments, key, and generation options.
|
|
82
|
+
* @returns Bass notes sorted by onset, non-overlapping.
|
|
83
|
+
*
|
|
84
|
+
* @example
|
|
85
|
+
* ```ts
|
|
86
|
+
* import { generateBassLine, makeChord, majorKey } from '@libraz/libcantus';
|
|
87
|
+
* const segments = [
|
|
88
|
+
* { startBeat: 0, endBeat: 4, chord: makeChord(0, 'maj') },
|
|
89
|
+
* { startBeat: 4, endBeat: 8, chord: makeChord(7, 'maj') },
|
|
90
|
+
* ];
|
|
91
|
+
* const notes = generateBassLine({ segments, key: majorKey(0), style: 'walking' });
|
|
92
|
+
* ```
|
|
93
|
+
*
|
|
94
|
+
* @category Composition
|
|
95
|
+
*/
|
|
96
|
+
declare function generateBassLine(opts: BassLineOptions): NoteEvent[];
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Options controlling {@link generateCounterMelody}.
|
|
100
|
+
*
|
|
101
|
+
* @category Voicing & Counterpoint
|
|
102
|
+
*/
|
|
103
|
+
type CounterMelodyOptions = {
|
|
104
|
+
/** The lead line to write against, in ascending onset order. */
|
|
105
|
+
melody: NoteEvent[];
|
|
106
|
+
/** Chord sounding at an absolute beat position, or null when none applies. */
|
|
107
|
+
chordAt: (beat: number) => Chord | null;
|
|
108
|
+
/** Key/scale context for scale-tone decisions. */
|
|
109
|
+
key: KeyScale;
|
|
110
|
+
/**
|
|
111
|
+
* Meter used for strong-beat decisions.
|
|
112
|
+
*
|
|
113
|
+
* @defaultValue 4/4
|
|
114
|
+
*/
|
|
115
|
+
ts?: TimeSignature;
|
|
116
|
+
/**
|
|
117
|
+
* Which side of the melody the counter line occupies.
|
|
118
|
+
*
|
|
119
|
+
* @defaultValue 'below'
|
|
120
|
+
*/
|
|
121
|
+
register?: 'above' | 'below';
|
|
122
|
+
/**
|
|
123
|
+
* Onset strategy: `'complement'` moves where the melody holds or rests and
|
|
124
|
+
* reinforces some strong beats; `'follow'` mirrors melody onsets.
|
|
125
|
+
*
|
|
126
|
+
* @defaultValue 'complement'
|
|
127
|
+
*/
|
|
128
|
+
rhythm?: 'complement' | 'follow';
|
|
129
|
+
/**
|
|
130
|
+
* Safety profile applied to candidate pitches.
|
|
131
|
+
*
|
|
132
|
+
* @defaultValue 'pop'
|
|
133
|
+
*/
|
|
134
|
+
profile?: 'strict' | 'pop';
|
|
135
|
+
/** Lowest MIDI pitch the counter line may use (default derived from `register`). */
|
|
136
|
+
pitchLow?: number;
|
|
137
|
+
/** Highest MIDI pitch the counter line may use (default derived from `register`). */
|
|
138
|
+
pitchHigh?: number;
|
|
139
|
+
/**
|
|
140
|
+
* PRNG seed; the same seed always yields the same line.
|
|
141
|
+
*
|
|
142
|
+
* @defaultValue 0
|
|
143
|
+
*/
|
|
144
|
+
seed?: number;
|
|
145
|
+
};
|
|
146
|
+
/**
|
|
147
|
+
* Generate a counter melody against a lead line.
|
|
148
|
+
*
|
|
149
|
+
* Onsets are chosen per `rhythm`: `'follow'` places one counter note on each
|
|
150
|
+
* melody onset; `'complement'` fills melody rests and sustains and reinforces
|
|
151
|
+
* an occasional strong beat, spaced at least a beat apart. Each onset's pitch
|
|
152
|
+
* is picked from the safe pitches in the counter register (chord tones and
|
|
153
|
+
* consonant tensions against the sounding melody note), rejecting candidates
|
|
154
|
+
* that would form a parallel perfect interval with the melody or sit on the
|
|
155
|
+
* wrong side of it, then scored to favour contrary or oblique motion, imperfect
|
|
156
|
+
* consonance, and stepwise movement. Ties break toward the lower pitch, so a
|
|
157
|
+
* given seed always yields the same line. Notes extend to the next counter
|
|
158
|
+
* onset (the last to the melody's end) at a velocity slightly under the melody.
|
|
159
|
+
*
|
|
160
|
+
* @param opts The lead line, chord context callback, key, and generation knobs.
|
|
161
|
+
* @returns The counter line as note events sorted by onset; `[]` for an empty melody.
|
|
162
|
+
* @example
|
|
163
|
+
* ```ts
|
|
164
|
+
* import { generateCounterMelody, majorKey, parseChordSymbol } from '@libraz/libcantus';
|
|
165
|
+
* const melody = [{ pitch: 72, startBeat: 0, durationBeat: 2 }];
|
|
166
|
+
* const counter = generateCounterMelody({
|
|
167
|
+
* melody,
|
|
168
|
+
* chordAt: () => parseChordSymbol('C'),
|
|
169
|
+
* key: majorKey(0),
|
|
170
|
+
* }); // note events below the melody, in onset order
|
|
171
|
+
* ```
|
|
172
|
+
* @category Voicing & Counterpoint
|
|
173
|
+
*/
|
|
174
|
+
declare function generateCounterMelody(opts: CounterMelodyOptions): NoteEvent[];
|
|
175
|
+
|
|
176
|
+
/** A single percussion onset. */
|
|
177
|
+
type DrumHit = {
|
|
178
|
+
pitch: number;
|
|
179
|
+
startBeat: number;
|
|
180
|
+
durationBeat: number;
|
|
181
|
+
velocity: number;
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
/** Groove feel controlling off-beat swing. */
|
|
185
|
+
type Feel = 'straight' | 'swing' | 'shuffle';
|
|
186
|
+
/** Public groove style identifiers. */
|
|
187
|
+
type GrooveStyle = 'standard' | 'funk' | 'shuffle' | 'bossa' | 'trap' | 'halftime' | 'breakbeat' | 'house' | 'synthpop';
|
|
188
|
+
/** Public role identifiers gating which voices are present. */
|
|
189
|
+
type DrumRole = 'full' | 'ambient' | 'minimal' | 'fxOnly';
|
|
190
|
+
/** Public section identifiers. */
|
|
191
|
+
type PublicSection = 'intro' | 'verse' | 'prechorus' | 'chorus' | 'bridge' | 'outro';
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Groove feel (the swing/straight rhythmic character) for {@link generateDrums}.
|
|
195
|
+
*
|
|
196
|
+
* @category Composition
|
|
197
|
+
*/
|
|
198
|
+
type GrooveFeel = Feel;
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Public section identifiers for {@link generateDrums}.
|
|
202
|
+
*
|
|
203
|
+
* @category Composition
|
|
204
|
+
*/
|
|
205
|
+
type Section = PublicSection;
|
|
206
|
+
/**
|
|
207
|
+
* A Euclidean (Bjorklund) kick pattern for {@link generateDrums}.
|
|
208
|
+
*
|
|
209
|
+
* @category Composition
|
|
210
|
+
*/
|
|
211
|
+
type EuclideanKick = {
|
|
212
|
+
/** Number of kick onsets, clamped to `[0, steps]`. */
|
|
213
|
+
pulses: number;
|
|
214
|
+
/**
|
|
215
|
+
* Total steps in the bar (1..16).
|
|
216
|
+
*
|
|
217
|
+
* @defaultValue 16
|
|
218
|
+
*/
|
|
219
|
+
steps?: number;
|
|
220
|
+
/**
|
|
221
|
+
* Steps to rotate the onsets toward later positions.
|
|
222
|
+
*
|
|
223
|
+
* @defaultValue 0
|
|
224
|
+
*/
|
|
225
|
+
rotation?: number;
|
|
226
|
+
};
|
|
227
|
+
/**
|
|
228
|
+
* Options controlling {@link generateDrums}.
|
|
229
|
+
*
|
|
230
|
+
* @category Composition
|
|
231
|
+
*/
|
|
232
|
+
type DrumsOptions = {
|
|
233
|
+
bars: number;
|
|
234
|
+
bpm: number;
|
|
235
|
+
style: GrooveStyle;
|
|
236
|
+
section: Section;
|
|
237
|
+
density: number;
|
|
238
|
+
/**
|
|
239
|
+
* Replace the final bar with a fill.
|
|
240
|
+
*
|
|
241
|
+
* @defaultValue false
|
|
242
|
+
*/
|
|
243
|
+
fills?: boolean;
|
|
244
|
+
feel?: GrooveFeel;
|
|
245
|
+
/**
|
|
246
|
+
* Voicing role; `'fxOnly'` suppresses the main kick/snare/ghost/fill voices.
|
|
247
|
+
*
|
|
248
|
+
* @defaultValue `'full'`
|
|
249
|
+
*/
|
|
250
|
+
role?: DrumRole;
|
|
251
|
+
/**
|
|
252
|
+
* Seed for the deterministic PRNG.
|
|
253
|
+
*
|
|
254
|
+
* @defaultValue 0
|
|
255
|
+
*/
|
|
256
|
+
seed?: number;
|
|
257
|
+
/**
|
|
258
|
+
* Section the final-bar fill leads into. Shapes which fill archetype is
|
|
259
|
+
* chosen (into-chorus and out-of-intro fills differ from generic ones).
|
|
260
|
+
* Defaults to `section`, i.e. a within-section fill.
|
|
261
|
+
*/
|
|
262
|
+
nextSection?: Section;
|
|
263
|
+
/**
|
|
264
|
+
* When set, the kick follows this Euclidean rhythm instead of the
|
|
265
|
+
* style/section pattern, giving direct access to evenly spread onsets.
|
|
266
|
+
*/
|
|
267
|
+
euclideanKick?: EuclideanKick;
|
|
268
|
+
};
|
|
269
|
+
/**
|
|
270
|
+
* Generate a drum performance as a flat list of onsets.
|
|
271
|
+
*
|
|
272
|
+
* Every voice (kick, snare, ghost snares, closed/open/foot hi-hats, ride, toms,
|
|
273
|
+
* crash, and auxiliary percussion) is emitted as a {@link DrumHit} distinguished
|
|
274
|
+
* by its General MIDI pitch. Groove style selects an internal style and feel;
|
|
275
|
+
* `density` sets the backing-density level; `section` shapes kick, hi-hat, ghost,
|
|
276
|
+
* and percussion density. 16th-note hi-hats drop to 8ths at or above 150 BPM.
|
|
277
|
+
* When `fills` is true the final bar is replaced with a fill whose archetype is
|
|
278
|
+
* shaped by `nextSection`; a fill that would emit nothing on its beat falls back
|
|
279
|
+
* to the normal groove so the phrase end is never silent. `euclideanKick`
|
|
280
|
+
* overrides the kick with an evenly-spread Euclidean pattern. Output is fully
|
|
281
|
+
* determined by the options plus `seed`.
|
|
282
|
+
*
|
|
283
|
+
* @param opts Generation options.
|
|
284
|
+
* @returns Percussion onsets in bar order.
|
|
285
|
+
*
|
|
286
|
+
* @example
|
|
287
|
+
* ```ts
|
|
288
|
+
* import { generateDrums } from '@libraz/libcantus';
|
|
289
|
+
* const hits = generateDrums({ bars: 4, bpm: 120, style: 'standard', section: 'chorus', density: 0.6, fills: true });
|
|
290
|
+
* // Fully determined by the options plus seed (defaults to 0).
|
|
291
|
+
* ```
|
|
292
|
+
*
|
|
293
|
+
* @category Composition
|
|
294
|
+
*/
|
|
295
|
+
declare function generateDrums(opts: DrumsOptions): DrumHit[];
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Groove and humanization: nudging quantized note events toward a more human
|
|
299
|
+
* feel, and capturing/replaying the timing-and-velocity "feel" of a
|
|
300
|
+
* performance as a reusable groove template.
|
|
301
|
+
*
|
|
302
|
+
* Positions and durations are measured in quarter-note beats, matching the
|
|
303
|
+
* library-wide convention. Randomness, where used, always goes through the
|
|
304
|
+
* seeded PRNG from the `random` module so results are fully reproducible.
|
|
305
|
+
*/
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Options controlling {@link humanize}.
|
|
309
|
+
*
|
|
310
|
+
* @category Rhythm & Meter
|
|
311
|
+
*/
|
|
312
|
+
type HumanizeOptions = {
|
|
313
|
+
/**
|
|
314
|
+
* Time signature used to derive metric accents.
|
|
315
|
+
*
|
|
316
|
+
* @defaultValue 4/4
|
|
317
|
+
*/
|
|
318
|
+
ts?: TimeSignature;
|
|
319
|
+
/**
|
|
320
|
+
* Maximum timing jitter in quarter-note beats, applied as ±this value.
|
|
321
|
+
*
|
|
322
|
+
* @defaultValue 0.02
|
|
323
|
+
*/
|
|
324
|
+
timing?: number;
|
|
325
|
+
/**
|
|
326
|
+
* Maximum velocity jitter (MIDI units), applied as ±this value.
|
|
327
|
+
*
|
|
328
|
+
* @defaultValue 8
|
|
329
|
+
*/
|
|
330
|
+
velocity?: number;
|
|
331
|
+
/**
|
|
332
|
+
* How much louder strong beats get relative to weak ones, in MIDI velocity units.
|
|
333
|
+
*
|
|
334
|
+
* @defaultValue 12
|
|
335
|
+
*/
|
|
336
|
+
accent?: number;
|
|
337
|
+
/**
|
|
338
|
+
* Velocity assumed for events with no `velocity` of their own.
|
|
339
|
+
*
|
|
340
|
+
* @defaultValue 80
|
|
341
|
+
*/
|
|
342
|
+
baseVelocity?: number;
|
|
343
|
+
/**
|
|
344
|
+
* Seed for the deterministic PRNG.
|
|
345
|
+
*
|
|
346
|
+
* @defaultValue 0
|
|
347
|
+
*/
|
|
348
|
+
seed?: number;
|
|
349
|
+
};
|
|
350
|
+
/**
|
|
351
|
+
* Humanize a sequence of note events by nudging their timing and velocity
|
|
352
|
+
* with small, deterministic (seeded) randomness plus a metric-accent shape:
|
|
353
|
+
* events on strong beats (see {@link metricWeight}) come out louder on
|
|
354
|
+
* average than events on weak ones.
|
|
355
|
+
*
|
|
356
|
+
* Each returned event is a copy; `startBeat` is jittered within
|
|
357
|
+
* `[-timing, +timing]` beats and clamped to be non-negative, and `velocity`
|
|
358
|
+
* is the event's own velocity (or `baseVelocity` if it has none) plus an
|
|
359
|
+
* accent term scaled by the event's metric weight plus a jitter within
|
|
360
|
+
* `[-velocity, +velocity]`, clamped to `[1, 127]` and rounded. The metric
|
|
361
|
+
* weight is computed from each event's original (pre-jitter) `startBeat`.
|
|
362
|
+
* `pitch` and `durationBeat` pass through unchanged, and the output keeps the
|
|
363
|
+
* input order.
|
|
364
|
+
*
|
|
365
|
+
* @param events The events to humanize.
|
|
366
|
+
* @param opts Humanization options.
|
|
367
|
+
* @returns Humanized copies of `events`, in input order.
|
|
368
|
+
* @example
|
|
369
|
+
* ```ts
|
|
370
|
+
* import { humanize } from '@libraz/libcantus';
|
|
371
|
+
* const events = [{ pitch: 60, startBeat: 0, durationBeat: 1, velocity: 80 }];
|
|
372
|
+
* humanize(events, { seed: 1, timing: 0.03 }); // copies with jittered timing and accented velocity
|
|
373
|
+
* ```
|
|
374
|
+
* @category Rhythm & Meter
|
|
375
|
+
*/
|
|
376
|
+
declare function humanize(events: NoteEvent[], opts?: HumanizeOptions): NoteEvent[];
|
|
377
|
+
/**
|
|
378
|
+
* The recorded feel at a single grid slot: an average timing deviation and velocity.
|
|
379
|
+
*
|
|
380
|
+
* @category Rhythm & Meter
|
|
381
|
+
*/
|
|
382
|
+
type GrooveSlot = {
|
|
383
|
+
/** Average `actualStartBeat - quantizedBeat` (in quarter-note beats) for events landing on this slot. */
|
|
384
|
+
timingOffset: number;
|
|
385
|
+
/**
|
|
386
|
+
* Average velocity of events landing on this slot, or `null` if no event
|
|
387
|
+
* with a velocity did. `null` (not 0) is the "unrecorded" sentinel so that a
|
|
388
|
+
* genuine velocity of 0 is preserved and applied like any other value.
|
|
389
|
+
*/
|
|
390
|
+
velocity: number | null;
|
|
391
|
+
};
|
|
392
|
+
/**
|
|
393
|
+
* A per-bar grid of timing and velocity deviations captured from a performance.
|
|
394
|
+
*
|
|
395
|
+
* @category Rhythm & Meter
|
|
396
|
+
*/
|
|
397
|
+
type GrooveTemplate = {
|
|
398
|
+
/** Grid resolution: equal grid steps per quarter-note beat. */
|
|
399
|
+
subdivision: number;
|
|
400
|
+
/** Number of grid slots per bar (`beatsPerBar(ts) * subdivision`). */
|
|
401
|
+
slotsPerBar: number;
|
|
402
|
+
/** One entry per grid slot, in slot-index order (slot 0 is the downbeat). */
|
|
403
|
+
slots: GrooveSlot[];
|
|
404
|
+
/**
|
|
405
|
+
* The time signature the template was extracted under. {@link applyGrooveTemplate}
|
|
406
|
+
* requires the apply-time meter to match this so the per-bar grid lines up
|
|
407
|
+
* instead of silently drifting. Optional for backward compatibility with
|
|
408
|
+
* templates constructed without it (in which case no check is performed).
|
|
409
|
+
*/
|
|
410
|
+
ts?: TimeSignature;
|
|
411
|
+
};
|
|
412
|
+
/**
|
|
413
|
+
* Extract a groove template from a set of note events: for each grid slot,
|
|
414
|
+
* the average timing deviation from the quantized grid and the average
|
|
415
|
+
* velocity of events landing on it. Slots with no velocity-carrying event
|
|
416
|
+
* record `velocity: null` (the "unrecorded" sentinel); empty slots default to
|
|
417
|
+
* `{ timingOffset: 0, velocity: null }`. The time signature is stored on the
|
|
418
|
+
* template so {@link applyGrooveTemplate} can verify the meter matches.
|
|
419
|
+
*
|
|
420
|
+
* @param events The (typically "groovy", human-played) events to analyze.
|
|
421
|
+
* @param ts The time signature, used to compute the bar length.
|
|
422
|
+
* @param subdivision Grid resolution: equal grid steps per quarter-note beat.
|
|
423
|
+
* @returns The extracted groove template.
|
|
424
|
+
* @category Rhythm & Meter
|
|
425
|
+
*/
|
|
426
|
+
declare function extractGrooveTemplate(events: NoteEvent[], ts: TimeSignature, subdivision: number): GrooveTemplate;
|
|
427
|
+
/**
|
|
428
|
+
* Apply a groove template to a set of (typically quantized) note events: each
|
|
429
|
+
* event is snapped to its grid slot and then offset by that slot's recorded
|
|
430
|
+
* timing deviation, pushing stiff timing toward the template's feel.
|
|
431
|
+
* Velocity is set to the slot's recorded average when it recorded one (a
|
|
432
|
+
* non-`null` velocity, including a genuine 0); otherwise the event's own
|
|
433
|
+
* velocity is left untouched.
|
|
434
|
+
*
|
|
435
|
+
* If the template carries a time signature (see {@link GrooveTemplate.ts}) it
|
|
436
|
+
* must match `ts`, since a differing bar length would misalign the per-bar
|
|
437
|
+
* grid and drift the feel; a mismatch throws rather than corrupting timing
|
|
438
|
+
* silently.
|
|
439
|
+
*
|
|
440
|
+
* @param events The events to reshape.
|
|
441
|
+
* @param template The groove template, from {@link extractGrooveTemplate}.
|
|
442
|
+
* @param ts The time signature, used to compute the bar length.
|
|
443
|
+
* @returns Reshaped copies of `events`, in input order.
|
|
444
|
+
* @throws If `template.ts` is set and does not match `ts`.
|
|
445
|
+
* @example
|
|
446
|
+
* ```ts
|
|
447
|
+
* import { extractGrooveTemplate, applyGrooveTemplate, parseTimeSignature } from '@libraz/libcantus';
|
|
448
|
+
* const ts = parseTimeSignature('4/4');
|
|
449
|
+
* // Learn the feel from a loosely played performance...
|
|
450
|
+
* const groovy = [
|
|
451
|
+
* { pitch: 36, startBeat: 0.02, durationBeat: 1, velocity: 100 },
|
|
452
|
+
* { pitch: 38, startBeat: 1.06, durationBeat: 1, velocity: 70 },
|
|
453
|
+
* ];
|
|
454
|
+
* const template = extractGrooveTemplate(groovy, ts, 4);
|
|
455
|
+
* // ...then impose it on stiff, quantized events.
|
|
456
|
+
* const quantized = [{ pitch: 36, startBeat: 0, durationBeat: 1, velocity: 80 }];
|
|
457
|
+
* applyGrooveTemplate(quantized, template, ts); // events nudged toward the captured feel
|
|
458
|
+
* ```
|
|
459
|
+
* @category Rhythm & Meter
|
|
460
|
+
*/
|
|
461
|
+
declare function applyGrooveTemplate(events: NoteEvent[], template: GrooveTemplate, ts: TimeSignature): NoteEvent[];
|
|
462
|
+
|
|
463
|
+
/**
|
|
464
|
+
* Broad production style a progression preset suits.
|
|
465
|
+
*
|
|
466
|
+
* @category Composition
|
|
467
|
+
*/
|
|
468
|
+
type ProgStyle = 'minimal' | 'dance' | 'idol' | 'rock';
|
|
469
|
+
/**
|
|
470
|
+
* Harmonic-function role of a progression as a whole.
|
|
471
|
+
*
|
|
472
|
+
* @category Composition
|
|
473
|
+
*/
|
|
474
|
+
type ProgFunction = 'loop' | 'tensionBuild' | 'cadenceStrong' | 'stable';
|
|
475
|
+
/**
|
|
476
|
+
* A named chord-progression preset expressed in scale degrees.
|
|
477
|
+
*
|
|
478
|
+
* @category Composition
|
|
479
|
+
*/
|
|
480
|
+
type ProgressionPreset = {
|
|
481
|
+
id: string;
|
|
482
|
+
name: string;
|
|
483
|
+
degrees: number[];
|
|
484
|
+
functional: ProgFunction;
|
|
485
|
+
styles: ProgStyle[];
|
|
486
|
+
};
|
|
487
|
+
/**
|
|
488
|
+
* Options controlling {@link generateProgression}.
|
|
489
|
+
*
|
|
490
|
+
* @category Composition
|
|
491
|
+
*/
|
|
492
|
+
type ProgressionOptions = {
|
|
493
|
+
key: KeyScale;
|
|
494
|
+
style: ProgStyle;
|
|
495
|
+
bars: number;
|
|
496
|
+
presetId?: string;
|
|
497
|
+
ext?: ChordQuality | 'auto';
|
|
498
|
+
reharmonize?: boolean;
|
|
499
|
+
/**
|
|
500
|
+
* Seed for the deterministic preset choice and reharmonization.
|
|
501
|
+
*
|
|
502
|
+
* @defaultValue 0
|
|
503
|
+
*/
|
|
504
|
+
seed?: number;
|
|
505
|
+
};
|
|
506
|
+
/**
|
|
507
|
+
* All built-in progression presets.
|
|
508
|
+
*
|
|
509
|
+
* @category Composition
|
|
510
|
+
*/
|
|
511
|
+
declare function progressions(): ProgressionPreset[];
|
|
512
|
+
/**
|
|
513
|
+
* Presets whose style list includes the given style.
|
|
514
|
+
*
|
|
515
|
+
* @category Composition
|
|
516
|
+
*/
|
|
517
|
+
declare function progressionsByStyle(style: ProgStyle): ProgressionPreset[];
|
|
518
|
+
/**
|
|
519
|
+
* Generate a chord progression laid out one chord per bar.
|
|
520
|
+
*
|
|
521
|
+
* A preset is chosen by `presetId` when given, otherwise deterministically from
|
|
522
|
+
* the presets matching `style`, seeded by `seed`. An unknown `presetId` is a
|
|
523
|
+
* caller error and throws rather than silently falling back to a random preset.
|
|
524
|
+
* The preset's degrees cycle to
|
|
525
|
+
* fill `bars`; each bar is four beats, so `startBeat` is `barIndex * 4`. Chord
|
|
526
|
+
* roots come from the key's diatonic scale-degree mapping. When `ext` is
|
|
527
|
+
* omitted or `'auto'`, each chord takes its diatonic triad quality; otherwise
|
|
528
|
+
* `ext` is forced on every chord. When `reharmonize` is set, some chords are
|
|
529
|
+
* deterministically replaced with the secondary dominant (V7) of the following
|
|
530
|
+
* chord, flagged with `secondaryDominant`.
|
|
531
|
+
*
|
|
532
|
+
* @param opts Generation options.
|
|
533
|
+
* @returns One chord per bar in timeline order.
|
|
534
|
+
* @throws If `presetId` is given but does not match any built-in preset.
|
|
535
|
+
*
|
|
536
|
+
* @example
|
|
537
|
+
* ```ts
|
|
538
|
+
* import { generateProgression, majorKey } from '@libraz/libcantus';
|
|
539
|
+
* const chords = generateProgression({ key: majorKey(0), style: 'dance', bars: 4 });
|
|
540
|
+
* // Deterministic for a given seed (defaults to 0); one ChordSpan per bar.
|
|
541
|
+
* ```
|
|
542
|
+
*
|
|
543
|
+
* @category Composition
|
|
544
|
+
*/
|
|
545
|
+
declare function generateProgression(opts: ProgressionOptions): ChordSpan[];
|
|
546
|
+
|
|
547
|
+
/**
|
|
548
|
+
* A melody note supplied to the harmonizer: a {@link NoteEvent}.
|
|
549
|
+
*
|
|
550
|
+
* @category Reharmonization
|
|
551
|
+
*/
|
|
552
|
+
type MelodyNote = NoteEvent;
|
|
553
|
+
/**
|
|
554
|
+
* Options controlling {@link harmonizeMelody}.
|
|
555
|
+
*
|
|
556
|
+
* @category Reharmonization
|
|
557
|
+
*/
|
|
558
|
+
type HarmonizeOptions = {
|
|
559
|
+
melody: MelodyNote[];
|
|
560
|
+
key: KeyScale | 'infer';
|
|
561
|
+
harmonicRhythm: number;
|
|
562
|
+
reharmonize: 'diatonic' | 'secondaryDominant' | 'borrowed';
|
|
563
|
+
placement: {
|
|
564
|
+
transposeSearch: boolean;
|
|
565
|
+
octaveSearch: boolean;
|
|
566
|
+
};
|
|
567
|
+
/**
|
|
568
|
+
* Seed for the deterministic tie-break perturbation.
|
|
569
|
+
*
|
|
570
|
+
* @defaultValue 0
|
|
571
|
+
*/
|
|
572
|
+
seed?: number;
|
|
573
|
+
};
|
|
574
|
+
/**
|
|
575
|
+
* The chosen transpose, key, chord path, and per-note roles.
|
|
576
|
+
*
|
|
577
|
+
* @category Reharmonization
|
|
578
|
+
*/
|
|
579
|
+
type HarmonizeResult = {
|
|
580
|
+
transposeSemitones: number;
|
|
581
|
+
key: KeyScale;
|
|
582
|
+
chords: ChordSpan[];
|
|
583
|
+
melodyRoles: {
|
|
584
|
+
noteIndex: number;
|
|
585
|
+
role: HarmonyRole;
|
|
586
|
+
}[];
|
|
587
|
+
};
|
|
588
|
+
/**
|
|
589
|
+
* Harmonize a melody with a min-cost chord progression.
|
|
590
|
+
*
|
|
591
|
+
* The melody is segmented by `harmonicRhythm`; each segment is scored against
|
|
592
|
+
* diatonic (and, per `reharmonize`, secondary-dominant and borrowed) candidate
|
|
593
|
+
* chords by melody fit, and a Viterbi search picks the lowest-cost path using a
|
|
594
|
+
* functional-flow transition cost. When `placement.transposeSearch`/`octaveSearch`
|
|
595
|
+
* is set, the whole melody is transposed across a range and the best
|
|
596
|
+
* `(transpose, progression)` pair is returned; a small tessitura cost breaks
|
|
597
|
+
* ties toward placements that keep the melody in a comfortable register.
|
|
598
|
+
*
|
|
599
|
+
* The `seed` drives a deterministic tie-break only: it perturbs candidates by a
|
|
600
|
+
* magnitude far below any real cost difference (see {@link TIE_BREAK_JITTER}),
|
|
601
|
+
* so it can decide between chords or paths of otherwise-equal cost but never
|
|
602
|
+
* overrides melody fit or functional flow. For a well-determined melody the
|
|
603
|
+
* result is identical across seeds; the same seed always yields the same result.
|
|
604
|
+
*
|
|
605
|
+
* @param opts Melody, key (or `'infer'`), harmonic rhythm, reharmonization
|
|
606
|
+
* strength, height-search flags, and seed.
|
|
607
|
+
* @returns The chosen transpose, key, chord path, and per-note roles.
|
|
608
|
+
* @example
|
|
609
|
+
* ```ts
|
|
610
|
+
* import { harmonizeMelody, majorKey } from '@libraz/libcantus';
|
|
611
|
+
* const result = harmonizeMelody({
|
|
612
|
+
* melody: [
|
|
613
|
+
* { pitch: 60, startBeat: 0, durationBeat: 1 },
|
|
614
|
+
* { pitch: 64, startBeat: 1, durationBeat: 1 },
|
|
615
|
+
* ],
|
|
616
|
+
* key: majorKey(0),
|
|
617
|
+
* harmonicRhythm: 1,
|
|
618
|
+
* reharmonize: 'diatonic',
|
|
619
|
+
* placement: { transposeSearch: false, octaveSearch: false },
|
|
620
|
+
* });
|
|
621
|
+
* result.chords; // one ChordSpan per harmonic-rhythm segment
|
|
622
|
+
* ```
|
|
623
|
+
* @category Reharmonization
|
|
624
|
+
*/
|
|
625
|
+
declare function harmonizeMelody(opts: HarmonizeOptions): HarmonizeResult;
|
|
626
|
+
|
|
627
|
+
/**
|
|
628
|
+
* A transformation applicable to a motif cell.
|
|
629
|
+
*
|
|
630
|
+
* @category Composition
|
|
631
|
+
*/
|
|
632
|
+
type MotifTransform = 'transposeDiatonic' | 'transposeChromatic' | 'invert' | 'retrograde' | 'augment' | 'diminish' | 'sequence';
|
|
633
|
+
/**
|
|
634
|
+
* A single note within a motif cell.
|
|
635
|
+
*
|
|
636
|
+
* @category Composition
|
|
637
|
+
*/
|
|
638
|
+
type MotifNote = {
|
|
639
|
+
pitch: number;
|
|
640
|
+
startBeat: number;
|
|
641
|
+
durationBeat: number;
|
|
642
|
+
};
|
|
643
|
+
/**
|
|
644
|
+
* A short melodic cell.
|
|
645
|
+
*
|
|
646
|
+
* @category Composition
|
|
647
|
+
*/
|
|
648
|
+
type MotifCell = {
|
|
649
|
+
notes: MotifNote[];
|
|
650
|
+
};
|
|
651
|
+
/**
|
|
652
|
+
* Melodic contour shape for {@link generateMotif}.
|
|
653
|
+
*
|
|
654
|
+
* @category Composition
|
|
655
|
+
*/
|
|
656
|
+
type MotifContour = 'arch' | 'ascending' | 'descending' | 'wave';
|
|
657
|
+
/**
|
|
658
|
+
* Options controlling {@link generateMotif}.
|
|
659
|
+
*
|
|
660
|
+
* @category Composition
|
|
661
|
+
*/
|
|
662
|
+
type MotifOptions = {
|
|
663
|
+
key: KeyScale;
|
|
664
|
+
chord?: Chord | null;
|
|
665
|
+
bars: number;
|
|
666
|
+
/**
|
|
667
|
+
* Melodic contour shape the line follows.
|
|
668
|
+
*
|
|
669
|
+
* @defaultValue `'arch'`
|
|
670
|
+
*/
|
|
671
|
+
contour?: MotifContour;
|
|
672
|
+
/**
|
|
673
|
+
* Probability in [0, 1] that a note is nudged by a single diatonic step. The
|
|
674
|
+
* nudge direction is balanced (up or down with equal odds), so it adds
|
|
675
|
+
* variety without biasing the line off its contour. Default 0, which
|
|
676
|
+
* reproduces the requested contour exactly (no drift, tails return to tonic).
|
|
677
|
+
*
|
|
678
|
+
* @defaultValue 0
|
|
679
|
+
*/
|
|
680
|
+
jitter?: number;
|
|
681
|
+
/**
|
|
682
|
+
* Seed for the deterministic PRNG.
|
|
683
|
+
*
|
|
684
|
+
* @defaultValue 0
|
|
685
|
+
*/
|
|
686
|
+
seed?: number;
|
|
687
|
+
};
|
|
688
|
+
/**
|
|
689
|
+
* Generate a short seed motif over `bars`.
|
|
690
|
+
*
|
|
691
|
+
* The melody follows the requested contour in diatonic steps from the tonic,
|
|
692
|
+
* snapped to the key. When a chord is supplied, notes landing on bar downbeats
|
|
693
|
+
* are pulled to the nearest chord tone. An optional `jitter` adds balanced
|
|
694
|
+
* per-note variation without biasing the contour; it is off by default, so the
|
|
695
|
+
* contour is reproduced exactly. Output is deterministic for a given seed.
|
|
696
|
+
*
|
|
697
|
+
* @param opts Key, optional chord, length, contour, jitter, and seed.
|
|
698
|
+
* @returns The generated motif cell.
|
|
699
|
+
*
|
|
700
|
+
* @example
|
|
701
|
+
* ```ts
|
|
702
|
+
* import { generateMotif, majorKey } from '@libraz/libcantus';
|
|
703
|
+
* const cell = generateMotif({ key: majorKey(0), bars: 2, contour: 'arch' });
|
|
704
|
+
* // cell.notes is a deterministic MotifCell (seed defaults to 0)
|
|
705
|
+
* ```
|
|
706
|
+
*
|
|
707
|
+
* @category Composition
|
|
708
|
+
*/
|
|
709
|
+
declare function generateMotif(opts: MotifOptions): MotifCell;
|
|
710
|
+
/**
|
|
711
|
+
* Apply a transformation to a motif cell.
|
|
712
|
+
*
|
|
713
|
+
* `invert` reflects pitches about the first note (chromatic, self-inverse);
|
|
714
|
+
* `retrograde` mirrors onsets about the cell span, preserving rests (self-inverse);
|
|
715
|
+
* `augment`/`diminish` scale time by `amount ?? 2` and its reciprocal;
|
|
716
|
+
* `transposeChromatic` adds `amount` semitones; `transposeDiatonic` shifts by
|
|
717
|
+
* `amount` scale degrees when a `key` is given, or — with no `key` — falls back
|
|
718
|
+
* to a chromatic shift of `amount` semitones; `sequence` appends a shifted copy
|
|
719
|
+
* (by `amount` diatonic degrees with a `key`, or by `amount` semitones without
|
|
720
|
+
* one, since it delegates to `transposeDiatonic`).
|
|
721
|
+
*
|
|
722
|
+
* @param cell The cell to transform.
|
|
723
|
+
* @param t The transformation.
|
|
724
|
+
* @param amount Optional parameter (semitones, degrees, or time factor).
|
|
725
|
+
* @param key Key context for the diatonic transforms; without it,
|
|
726
|
+
* `transposeDiatonic` and `sequence` shift chromatically by semitones.
|
|
727
|
+
* @returns The transformed cell.
|
|
728
|
+
*
|
|
729
|
+
* @example
|
|
730
|
+
* ```ts
|
|
731
|
+
* import { generateMotif, transformMotif, majorKey } from '@libraz/libcantus';
|
|
732
|
+
* const cell = generateMotif({ key: majorKey(0), bars: 1 });
|
|
733
|
+
* const inverted = transformMotif(cell, 'invert');
|
|
734
|
+
* ```
|
|
735
|
+
*
|
|
736
|
+
* @category Composition
|
|
737
|
+
*/
|
|
738
|
+
declare function transformMotif(cell: MotifCell, t: MotifTransform, amount?: number, key?: KeyScale): MotifCell;
|
|
739
|
+
/**
|
|
740
|
+
* Lay a motif across `bars` and snap it to a chord timeline.
|
|
741
|
+
*
|
|
742
|
+
* The cell is tiled back-to-back to fill the requested span; each note is then
|
|
743
|
+
* pulled to the nearest chord tone of the segment sounding at its onset, so the
|
|
744
|
+
* developed line spells the underlying harmony.
|
|
745
|
+
*
|
|
746
|
+
* @param cell The source motif.
|
|
747
|
+
* @param timeline Chord segments to snap against.
|
|
748
|
+
* @param key Key context (used to keep snapped pitches sensible).
|
|
749
|
+
* @param bars Number of four-beat bars to fill.
|
|
750
|
+
* @returns The developed, harmony-aware cell.
|
|
751
|
+
*
|
|
752
|
+
* @example
|
|
753
|
+
* ```ts
|
|
754
|
+
* import { chordTimelineFromChords, developMotif, generateMotif, majorKey } from '@libraz/libcantus';
|
|
755
|
+
* const key = majorKey(0);
|
|
756
|
+
* const timeline = chordTimelineFromChords([{ rootPc: 0, quality: 'maj', startBeat: 0 }], 8);
|
|
757
|
+
* const developed = developMotif(generateMotif({ key, bars: 1 }), timeline, key, 2);
|
|
758
|
+
* ```
|
|
759
|
+
*
|
|
760
|
+
* @category Composition
|
|
761
|
+
*/
|
|
762
|
+
declare function developMotif(cell: MotifCell, timeline: ChordTimeline, key: KeyScale, bars: number): MotifCell;
|
|
763
|
+
|
|
764
|
+
/**
|
|
765
|
+
* Reharmonization: chord substitution, modal-interchange palettes, and negative
|
|
766
|
+
* harmony. These transforms take an existing chord (or a key) and propose
|
|
767
|
+
* alternative harmonies that preserve a chosen relationship — a shared function,
|
|
768
|
+
* a common tone, a tonicizing dominant, or a reflection across the key axis.
|
|
769
|
+
*
|
|
770
|
+
* Roots are pitch classes (0..11); no spelled key signature is required.
|
|
771
|
+
*/
|
|
772
|
+
|
|
773
|
+
/**
|
|
774
|
+
* The kind of substitution relationship a candidate realizes.
|
|
775
|
+
*
|
|
776
|
+
* @category Reharmonization
|
|
777
|
+
*/
|
|
778
|
+
type SubstitutionType = 'tritone' | 'relative' | 'borrowed' | 'chromatic-mediant' | 'secondary-dominant';
|
|
779
|
+
/**
|
|
780
|
+
* A proposed chord substitution with its relationship, numeral, and function.
|
|
781
|
+
*
|
|
782
|
+
* @category Reharmonization
|
|
783
|
+
*/
|
|
784
|
+
type Substitution = {
|
|
785
|
+
chord: Chord;
|
|
786
|
+
type: SubstitutionType;
|
|
787
|
+
roman: string;
|
|
788
|
+
function: HarmonicFunction;
|
|
789
|
+
};
|
|
790
|
+
/**
|
|
791
|
+
* Options controlling which substitutions {@link substituteChord} returns.
|
|
792
|
+
*
|
|
793
|
+
* @category Reharmonization
|
|
794
|
+
*/
|
|
795
|
+
type SubstituteOptions = {
|
|
796
|
+
/**
|
|
797
|
+
* Melody pitch classes that must remain chord tones. When given, only
|
|
798
|
+
* substitutions whose pitch classes contain every listed pitch class are
|
|
799
|
+
* kept, so the melody stays consonant against the new harmony.
|
|
800
|
+
*/
|
|
801
|
+
melodyPcs?: number[];
|
|
802
|
+
};
|
|
803
|
+
/**
|
|
804
|
+
* Propose substitutions for a chord within a key.
|
|
805
|
+
*
|
|
806
|
+
* Candidates are generated by several relationships and deduplicated against the
|
|
807
|
+
* original chord and each other:
|
|
808
|
+
*
|
|
809
|
+
* - `tritone`: for a dominant-type chord, the dominant seventh a tritone away.
|
|
810
|
+
* - `secondary-dominant`: for a stable target, the dominant seventh a fifth
|
|
811
|
+
* above its root (tonicizing it).
|
|
812
|
+
* - `relative`: a diatonic triad a third away that shares two common tones.
|
|
813
|
+
* - `borrowed`: a triad from the parallel mode with the same harmonic function.
|
|
814
|
+
* - `chromatic-mediant`: a major/minor triad a third away sharing one common
|
|
815
|
+
* tone.
|
|
816
|
+
*
|
|
817
|
+
* Each result carries its Roman numeral and harmonic function in `key`. When
|
|
818
|
+
* `opts.melodyPcs` is given, only substitutions whose pitch classes contain all
|
|
819
|
+
* of those pitch classes are returned.
|
|
820
|
+
*
|
|
821
|
+
* @param chord The chord to reharmonize.
|
|
822
|
+
* @param key The prevailing key.
|
|
823
|
+
* @param opts Optional melody-preservation constraint.
|
|
824
|
+
* @returns The deduplicated substitution candidates.
|
|
825
|
+
* @example
|
|
826
|
+
* ```ts
|
|
827
|
+
* import { substituteChord, makeChord, majorKey } from '@libraz/libcantus';
|
|
828
|
+
* const subs = substituteChord(makeChord(7, 'dom7'), majorKey(0));
|
|
829
|
+
* // includes the tritone sub Db7 (rootPc 1, quality 'dom7')
|
|
830
|
+
* ```
|
|
831
|
+
* @category Reharmonization
|
|
832
|
+
*/
|
|
833
|
+
declare function substituteChord(chord: Chord, key: KeyScale, opts?: SubstituteOptions): Substitution[];
|
|
834
|
+
/**
|
|
835
|
+
* A borrowed chord in the modal-interchange palette of a key.
|
|
836
|
+
*
|
|
837
|
+
* @category Reharmonization
|
|
838
|
+
*/
|
|
839
|
+
type BorrowedChord = {
|
|
840
|
+
chord: Chord;
|
|
841
|
+
roman: string;
|
|
842
|
+
source: BorrowedSource;
|
|
843
|
+
};
|
|
844
|
+
/**
|
|
845
|
+
* The modal-interchange palette of a key.
|
|
846
|
+
*
|
|
847
|
+
* Lists the diatonic triads of the {@link parallelKey} that are not diatonic to
|
|
848
|
+
* `key` (the classic borrowed chords such as `iv`, `bVI`, and `bVII` in a major
|
|
849
|
+
* key), plus the Neapolitan (a major triad on the flat second degree). Each
|
|
850
|
+
* carries its Roman numeral and {@link borrowedSource} relative to `key`.
|
|
851
|
+
*
|
|
852
|
+
* @param key The prevailing key.
|
|
853
|
+
* @returns The borrowed-chord palette.
|
|
854
|
+
* @category Reharmonization
|
|
855
|
+
*/
|
|
856
|
+
declare function modalInterchangePalette(key: KeyScale): BorrowedChord[];
|
|
857
|
+
/**
|
|
858
|
+
* Reflect a chord across the tonic–dominant axis of a key (negative harmony).
|
|
859
|
+
*
|
|
860
|
+
* Every pitch class `p` maps to `(2*T + 7 - p) mod 12`, where `T` is the key
|
|
861
|
+
* tonic — the reflection that swaps the tonic with the dominant. The mirrored
|
|
862
|
+
* pitch classes (including the bass, if set) are reassembled into a chord by
|
|
863
|
+
* {@link detectChordBest}; when no chord is recognized, the mirrored root and
|
|
864
|
+
* the original quality are used as a fallback.
|
|
865
|
+
*
|
|
866
|
+
* @param chord The chord to reflect.
|
|
867
|
+
* @param key The prevailing key.
|
|
868
|
+
* @returns The negative-harmony counterpart of the chord.
|
|
869
|
+
* @example
|
|
870
|
+
* ```ts
|
|
871
|
+
* import { negativeHarmonyMirror, makeChord, majorKey } from '@libraz/libcantus';
|
|
872
|
+
* negativeHarmonyMirror(makeChord(7, 'maj'), majorKey(0));
|
|
873
|
+
* // G major reflected across C's axis => F minor (rootPc 5, quality 'min')
|
|
874
|
+
* ```
|
|
875
|
+
* @category Reharmonization
|
|
876
|
+
*/
|
|
877
|
+
declare function negativeHarmonyMirror(chord: Chord, key: KeyScale): Chord;
|
|
878
|
+
|
|
879
|
+
/**
|
|
880
|
+
* Deterministic rhythm generation over a metric grid.
|
|
881
|
+
*
|
|
882
|
+
* Builds a fixed-resolution grid across one or more bars of a time signature
|
|
883
|
+
* and decides, per grid slot, whether it becomes a note onset. Onset
|
|
884
|
+
* probability follows the metric-accent hierarchy from the `meter` module, so
|
|
885
|
+
* strong positions (downbeats, secondary pulses) are favored over weak
|
|
886
|
+
* off-pulse subdivisions. Given a seed the output is fully reproducible.
|
|
887
|
+
*
|
|
888
|
+
* Positions and durations are measured in quarter-note beats, matching the
|
|
889
|
+
* library-wide convention.
|
|
890
|
+
*/
|
|
891
|
+
|
|
892
|
+
/**
|
|
893
|
+
* A single rhythmic note: an onset position and how long it sounds.
|
|
894
|
+
*
|
|
895
|
+
* @category Rhythm & Meter
|
|
896
|
+
*/
|
|
897
|
+
type RhythmEvent = {
|
|
898
|
+
/** Onset position in quarter-note beats, absolute from the start of the span. */
|
|
899
|
+
position: number;
|
|
900
|
+
/** Duration in quarter-note beats, extending to the next onset. */
|
|
901
|
+
duration: number;
|
|
902
|
+
};
|
|
903
|
+
/**
|
|
904
|
+
* Options controlling {@link generateRhythm}.
|
|
905
|
+
*
|
|
906
|
+
* @category Rhythm & Meter
|
|
907
|
+
*/
|
|
908
|
+
type RhythmOptions = {
|
|
909
|
+
/**
|
|
910
|
+
* Seed for the deterministic PRNG.
|
|
911
|
+
*
|
|
912
|
+
* @defaultValue 0
|
|
913
|
+
*/
|
|
914
|
+
seed?: number;
|
|
915
|
+
/**
|
|
916
|
+
* Number of bars to generate.
|
|
917
|
+
*
|
|
918
|
+
* @defaultValue 1
|
|
919
|
+
*/
|
|
920
|
+
bars?: number;
|
|
921
|
+
/**
|
|
922
|
+
* Grid resolution as the number of equal grid steps per quarter-note beat.
|
|
923
|
+
* The default of 2 yields an eighth-note grid; 4 yields a sixteenth-note
|
|
924
|
+
* grid, 3 an eighth-note triplet grid, and so on.
|
|
925
|
+
*
|
|
926
|
+
* @defaultValue 2
|
|
927
|
+
*/
|
|
928
|
+
subdivision?: number;
|
|
929
|
+
/**
|
|
930
|
+
* Overall onset density in [0, 1]. Scales the per-slot onset probability, so
|
|
931
|
+
* higher values fill more grid slots. Values outside [0, 1] are clamped into
|
|
932
|
+
* range.
|
|
933
|
+
*
|
|
934
|
+
* @defaultValue 0.5
|
|
935
|
+
*/
|
|
936
|
+
density?: number;
|
|
937
|
+
};
|
|
938
|
+
/**
|
|
939
|
+
* Map a metric weight (0–3 from {@link metricWeight}) to a base onset
|
|
940
|
+
* probability before density scaling. Strong positions are much more likely to
|
|
941
|
+
* carry an onset than weak ones; the curve is intentionally small and explicit
|
|
942
|
+
* so its behavior is easy to inspect and test.
|
|
943
|
+
*
|
|
944
|
+
* @param weight The metric weight, 0 (off-pulse) to 3 (downbeat).
|
|
945
|
+
* @returns A base onset probability in [0, 1].
|
|
946
|
+
* @category Rhythm & Meter
|
|
947
|
+
*/
|
|
948
|
+
declare function onsetWeightCurve(weight: number): number;
|
|
949
|
+
/**
|
|
950
|
+
* Generate a deterministic rhythmic pattern over `bars` bars of a time
|
|
951
|
+
* signature. A grid is built at the chosen subdivision; each slot becomes an
|
|
952
|
+
* onset with a probability derived from its metric weight scaled by `density`
|
|
953
|
+
* and sampled from the seeded PRNG. Every bar's downbeat is always an onset, so
|
|
954
|
+
* a pattern of any length has a reliable pulse independent of the seed. Each
|
|
955
|
+
* event's duration extends to the next onset, and the last event extends to the
|
|
956
|
+
* end of the span. `density` is clamped to [0, 1].
|
|
957
|
+
*
|
|
958
|
+
* @param ts The time signature.
|
|
959
|
+
* @param opts Generation options.
|
|
960
|
+
* @returns Onset events sorted by position, non-overlapping, covering the span.
|
|
961
|
+
* @example
|
|
962
|
+
* ```ts
|
|
963
|
+
* import { parseTimeSignature, generateRhythm } from '@libraz/libcantus';
|
|
964
|
+
* const ts = parseTimeSignature('4/4');
|
|
965
|
+
* generateRhythm(ts, { seed: 42, density: 0.6 }); // onset events over one bar
|
|
966
|
+
* ```
|
|
967
|
+
* @category Rhythm & Meter
|
|
968
|
+
*/
|
|
969
|
+
declare function generateRhythm(ts: TimeSignature, opts?: RhythmOptions): RhythmEvent[];
|
|
970
|
+
/**
|
|
971
|
+
* Onset density of a generated pattern: the mean number of onsets per bar.
|
|
972
|
+
*
|
|
973
|
+
* @param events Events from {@link generateRhythm}.
|
|
974
|
+
* @param ts The time signature.
|
|
975
|
+
* @returns The onset count divided by the number of bars the events span.
|
|
976
|
+
* @category Rhythm & Meter
|
|
977
|
+
*/
|
|
978
|
+
declare function rhythmDensity(events: RhythmEvent[], ts: TimeSignature): number;
|
|
979
|
+
|
|
980
|
+
export { type BassLineOptions, type BassSegment, type BassStyle, type BorrowedChord, ChordSpan, type CounterMelodyOptions, type DrumHit, type DrumRole, type DrumsOptions, type EuclideanKick, type GrooveFeel, type GrooveSlot, type GrooveStyle, type GrooveTemplate, type HarmonizeOptions, type HarmonizeResult, type HumanizeOptions, type MelodyNote, type MotifCell, type MotifContour, type MotifNote, type MotifOptions, type MotifTransform, type ProgFunction, type ProgStyle, type ProgressionOptions, type ProgressionPreset, type RhythmEvent, type RhythmOptions, type Section, type SubstituteOptions, type Substitution, type SubstitutionType, applyGrooveTemplate, developMotif, extractGrooveTemplate, generateBassLine, generateCounterMelody, generateDrums, generateMotif, generateProgression, generateRhythm, harmonizeMelody, humanize, modalInterchangePalette, negativeHarmonyMirror, onsetWeightCurve, progressions, progressionsByStyle, rhythmDensity, substituteChord, transformMotif };
|