@libraz/libcantus 0.9.4 → 1.0.0

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 (186) hide show
  1. package/README.md +61 -261
  2. package/README_ja.md +115 -0
  3. package/dist/analyze/index.cjs +54 -7
  4. package/dist/analyze/index.cjs.map +1 -1
  5. package/dist/analyze/index.d.cts +213 -456
  6. package/dist/analyze/index.d.ts +213 -456
  7. package/dist/analyze/index.js +56 -9
  8. package/dist/chunk-3JXCW6DX.cjs +3220 -0
  9. package/dist/chunk-3JXCW6DX.cjs.map +1 -0
  10. package/dist/chunk-4A2RMUCC.js +7647 -0
  11. package/dist/chunk-4A2RMUCC.js.map +1 -0
  12. package/dist/chunk-6XPTBE7Y.cjs +49 -0
  13. package/dist/chunk-6XPTBE7Y.cjs.map +1 -0
  14. package/dist/chunk-AFM2ULJH.cjs +5121 -0
  15. package/dist/chunk-AFM2ULJH.cjs.map +1 -0
  16. package/dist/chunk-E3F4AHAH.cjs +690 -0
  17. package/dist/chunk-E3F4AHAH.cjs.map +1 -0
  18. package/dist/chunk-ELCTZUJJ.js +379 -0
  19. package/dist/chunk-ELCTZUJJ.js.map +1 -0
  20. package/dist/chunk-FVSI36UV.js +690 -0
  21. package/dist/chunk-FVSI36UV.js.map +1 -0
  22. package/dist/chunk-GEDTJHAC.cjs +1 -0
  23. package/dist/chunk-GEDTJHAC.cjs.map +1 -0
  24. package/dist/chunk-GOZG57ER.js +3220 -0
  25. package/dist/chunk-GOZG57ER.js.map +1 -0
  26. package/dist/chunk-H3VORETI.js +2243 -0
  27. package/dist/chunk-H3VORETI.js.map +1 -0
  28. package/dist/chunk-H42UHPYW.js +1577 -0
  29. package/dist/chunk-H42UHPYW.js.map +1 -0
  30. package/dist/chunk-HFPS7JFD.js +1 -0
  31. package/dist/chunk-INHVXP6Z.js +3053 -0
  32. package/dist/chunk-INHVXP6Z.js.map +1 -0
  33. package/dist/chunk-K4ZJUGF4.cjs +379 -0
  34. package/dist/chunk-K4ZJUGF4.cjs.map +1 -0
  35. package/dist/chunk-KNJEKLKV.js +49 -0
  36. package/dist/chunk-KNJEKLKV.js.map +1 -0
  37. package/dist/chunk-M4CWTJWJ.cjs +7647 -0
  38. package/dist/chunk-M4CWTJWJ.cjs.map +1 -0
  39. package/dist/chunk-MXO72PLY.cjs +211 -0
  40. package/dist/chunk-MXO72PLY.cjs.map +1 -0
  41. package/dist/chunk-NLEU4QSE.cjs +360 -0
  42. package/dist/chunk-NLEU4QSE.cjs.map +1 -0
  43. package/dist/chunk-OE6WK2Y2.cjs +1577 -0
  44. package/dist/chunk-OE6WK2Y2.cjs.map +1 -0
  45. package/dist/chunk-Q2WGKJNN.cjs +1064 -0
  46. package/dist/chunk-Q2WGKJNN.cjs.map +1 -0
  47. package/dist/chunk-QYQQ5J6E.js +1 -0
  48. package/dist/chunk-QYQQ5J6E.js.map +1 -0
  49. package/dist/chunk-RHIJ3S4I.cjs +1 -0
  50. package/dist/chunk-RHIJ3S4I.cjs.map +1 -0
  51. package/dist/chunk-TTCLJX6D.js +211 -0
  52. package/dist/chunk-TTCLJX6D.js.map +1 -0
  53. package/dist/chunk-UADYAYAT.cjs +3053 -0
  54. package/dist/chunk-UADYAYAT.cjs.map +1 -0
  55. package/dist/chunk-VOHZB45S.js +5121 -0
  56. package/dist/chunk-VOHZB45S.js.map +1 -0
  57. package/dist/chunk-VTI6FOLB.js +1064 -0
  58. package/dist/chunk-VTI6FOLB.js.map +1 -0
  59. package/dist/chunk-WWURE4XT.cjs +2243 -0
  60. package/dist/chunk-WWURE4XT.cjs.map +1 -0
  61. package/dist/chunk-YH3BIKHH.js +360 -0
  62. package/dist/chunk-YH3BIKHH.js.map +1 -0
  63. package/dist/core/index.cjs +103 -6
  64. package/dist/core/index.cjs.map +1 -1
  65. package/dist/core/index.d.cts +96 -345
  66. package/dist/core/index.d.ts +96 -345
  67. package/dist/core/index.js +104 -7
  68. package/dist/generate/index.cjs +89 -10
  69. package/dist/generate/index.cjs.map +1 -1
  70. package/dist/generate/index.d.cts +437 -1095
  71. package/dist/generate/index.d.ts +437 -1095
  72. package/dist/generate/index.js +94 -15
  73. package/dist/index-9wcMhG73.d.ts +1095 -0
  74. package/dist/{index-D6b6na69.d.cts → index-B4o55NDr.d.cts} +13 -9
  75. package/dist/index-B9MNZboQ.d.cts +770 -0
  76. package/dist/index-B9MNZboQ.d.ts +770 -0
  77. package/dist/index-BGFW-8P2.d.cts +2307 -0
  78. package/dist/index-BO6xE9YU.d.cts +1804 -0
  79. package/dist/index-BkGmlyI3.d.cts +470 -0
  80. package/dist/index-CCNJRG-P.d.cts +1095 -0
  81. package/dist/index-CGpRv5sl.d.cts +643 -0
  82. package/dist/index-C_8o--VE.d.cts +1242 -0
  83. package/dist/index-CgHqxdps.d.ts +2307 -0
  84. package/dist/index-Chbst3Jx.d.ts +1343 -0
  85. package/dist/index-Cj49F892.d.ts +470 -0
  86. package/dist/{index-tSQh-xwY.d.ts → index-CunYKLiu.d.ts} +13 -9
  87. package/dist/index-D57QRbVn.d.ts +1804 -0
  88. package/dist/index-DInQqT8Q.d.cts +66 -0
  89. package/dist/index-DInQqT8Q.d.ts +66 -0
  90. package/dist/index-Dzr13q3d.d.cts +1343 -0
  91. package/dist/index-G1uwGGWQ.d.ts +643 -0
  92. package/dist/index-szBv3496.d.ts +1242 -0
  93. package/dist/index.cjs +358 -15
  94. package/dist/index.cjs.map +1 -1
  95. package/dist/index.d.cts +17 -15
  96. package/dist/index.d.ts +17 -15
  97. package/dist/index.js +407 -64
  98. package/dist/masks-DYbE485J.d.cts +445 -0
  99. package/dist/masks-DYbE485J.d.ts +445 -0
  100. package/dist/model/index.cjs +39 -7
  101. package/dist/model/index.cjs.map +1 -1
  102. package/dist/model/index.d.cts +4685 -243
  103. package/dist/model/index.d.ts +4685 -243
  104. package/dist/model/index.js +40 -8
  105. package/dist/positional-Cptlj-xs.d.ts +287 -0
  106. package/dist/positional-DvH_sd8u.d.cts +287 -0
  107. package/dist/theory/index.cjs +101 -7
  108. package/dist/theory/index.cjs.map +1 -1
  109. package/dist/theory/index.d.cts +513 -173
  110. package/dist/theory/index.d.ts +513 -173
  111. package/dist/theory/index.js +118 -24
  112. package/package.json +16 -11
  113. package/dist/chunk-36N73KZ3.cjs +0 -246
  114. package/dist/chunk-36N73KZ3.cjs.map +0 -1
  115. package/dist/chunk-4HLLZPXT.cjs +0 -3077
  116. package/dist/chunk-4HLLZPXT.cjs.map +0 -1
  117. package/dist/chunk-7MGZ7D62.cjs +0 -498
  118. package/dist/chunk-7MGZ7D62.cjs.map +0 -1
  119. package/dist/chunk-7NKDPF74.js +0 -3077
  120. package/dist/chunk-7NKDPF74.js.map +0 -1
  121. package/dist/chunk-CAN76NCU.cjs +0 -679
  122. package/dist/chunk-CAN76NCU.cjs.map +0 -1
  123. package/dist/chunk-D2FAJBYL.js +0 -479
  124. package/dist/chunk-D2FAJBYL.js.map +0 -1
  125. package/dist/chunk-EHVEBWM5.js +0 -1449
  126. package/dist/chunk-EHVEBWM5.js.map +0 -1
  127. package/dist/chunk-FCANXBAT.cjs +0 -30
  128. package/dist/chunk-FCANXBAT.cjs.map +0 -1
  129. package/dist/chunk-FNDWXRRI.cjs +0 -479
  130. package/dist/chunk-FNDWXRRI.cjs.map +0 -1
  131. package/dist/chunk-GQSGS2N4.js +0 -173
  132. package/dist/chunk-GQSGS2N4.js.map +0 -1
  133. package/dist/chunk-HKGYCQ34.js +0 -53
  134. package/dist/chunk-HKGYCQ34.js.map +0 -1
  135. package/dist/chunk-I24MV65U.js +0 -106
  136. package/dist/chunk-I24MV65U.js.map +0 -1
  137. package/dist/chunk-KMHOICIY.js +0 -679
  138. package/dist/chunk-KMHOICIY.js.map +0 -1
  139. package/dist/chunk-LW53AHQU.cjs +0 -1117
  140. package/dist/chunk-LW53AHQU.cjs.map +0 -1
  141. package/dist/chunk-N6IHQUOX.js +0 -1
  142. package/dist/chunk-NKIBQ6PT.js +0 -30
  143. package/dist/chunk-NKIBQ6PT.js.map +0 -1
  144. package/dist/chunk-NP4NA2FB.cjs +0 -106
  145. package/dist/chunk-NP4NA2FB.cjs.map +0 -1
  146. package/dist/chunk-NVE34YGZ.cjs +0 -1449
  147. package/dist/chunk-NVE34YGZ.cjs.map +0 -1
  148. package/dist/chunk-NYYWMV2C.js +0 -327
  149. package/dist/chunk-NYYWMV2C.js.map +0 -1
  150. package/dist/chunk-Q2XUP227.js +0 -845
  151. package/dist/chunk-Q2XUP227.js.map +0 -1
  152. package/dist/chunk-Q6OP4U43.cjs +0 -327
  153. package/dist/chunk-Q6OP4U43.cjs.map +0 -1
  154. package/dist/chunk-RC6GBMCD.cjs +0 -845
  155. package/dist/chunk-RC6GBMCD.cjs.map +0 -1
  156. package/dist/chunk-RK35RIN7.js +0 -1117
  157. package/dist/chunk-RK35RIN7.js.map +0 -1
  158. package/dist/chunk-RUMU3UA6.cjs +0 -53
  159. package/dist/chunk-RUMU3UA6.cjs.map +0 -1
  160. package/dist/chunk-T64C7ZD7.cjs +0 -1
  161. package/dist/chunk-T64C7ZD7.cjs.map +0 -1
  162. package/dist/chunk-XEBG3G53.js +0 -498
  163. package/dist/chunk-XEBG3G53.js.map +0 -1
  164. package/dist/chunk-YLEMGEFH.js +0 -246
  165. package/dist/chunk-YLEMGEFH.js.map +0 -1
  166. package/dist/chunk-ZEJR4TKL.cjs +0 -173
  167. package/dist/chunk-ZEJR4TKL.cjs.map +0 -1
  168. package/dist/function-BtiZb9i9.d.ts +0 -208
  169. package/dist/function-DNugAOrt.d.cts +0 -208
  170. package/dist/index-6VR8-kit.d.ts +0 -143
  171. package/dist/index-70p168VM.d.ts +0 -192
  172. package/dist/index-Be74mcon.d.cts +0 -152
  173. package/dist/index-BtXT6UQ0.d.cts +0 -192
  174. package/dist/index-C5UBbQ4G.d.ts +0 -152
  175. package/dist/index-C83mZI1P.d.ts +0 -242
  176. package/dist/index-DXd9JikN.d.cts +0 -246
  177. package/dist/index-DXd9JikN.d.ts +0 -246
  178. package/dist/index-OG65ocOr.d.cts +0 -272
  179. package/dist/index-OG65ocOr.d.ts +0 -272
  180. package/dist/index-tJhkmQgE.d.cts +0 -143
  181. package/dist/index-y_ak9_Hi.d.cts +0 -242
  182. package/dist/styled-DIRkk9Uz.d.ts +0 -506
  183. package/dist/styled-DJbkJQfj.d.cts +0 -506
  184. package/dist/types-BN6_Q9Kl.d.cts +0 -39
  185. package/dist/types-BN6_Q9Kl.d.ts +0 -39
  186. /package/dist/{chunk-N6IHQUOX.js.map → chunk-HFPS7JFD.js.map} +0 -0
@@ -1,1294 +1,636 @@
1
- import { B as BorrowedSource, H as HarmonicFunction } from '../function-BtiZb9i9.js';
2
- import { K as KeyScale, N as NoteEvent } from '../types-BN6_Q9Kl.js';
3
- import { b as ChordSegment, C as Chord, a as ChordQuality, c as ChordSpan } from '../index-C83mZI1P.js';
4
- export { d as ChordToneRole, P as PitchSpelling } from '../index-C83mZI1P.js';
5
- import { a as ChordTimeline } from '../index-C5UBbQ4G.js';
6
- import { T as TimeSignature } from '../index-DXd9JikN.js';
7
- import { H as HarmonyRole } from '../index-tSQh-xwY.js';
1
+ import { a as Articulation, g as NoteEvent, I as IntervalLike } from '../index-B9MNZboQ.js';
2
+ export { b as IntervalQualityLabel, d as KeyScale, S as SpelledInterval } from '../index-B9MNZboQ.js';
3
+ import { e as ChordSegment, c as ChordLike, K as KeyLike } from '../index-szBv3496.js';
4
+ export { b as ChordData, d as ChordQuality, g as ChordSpan, j as ChordToneRole, l as PitchSpelling, m as ProfileWeights } from '../index-szBv3496.js';
5
+ export { B as BorrowedSource, e as ChordTimeline, H as HarmonicFunction } from '../index-Chbst3Jx.js';
6
+ import { f as StringedProfile, P as PercussionProfile, c as Limb } from '../positional-Cptlj-xs.js';
7
+ export { I as InstrumentProfile, a as InstrumentProfileCommon, d as PositionalRng, S as SeedPath } from '../positional-Cptlj-xs.js';
8
+ import { T as TimeSignature } from '../index-Cj49F892.js';
9
+ export { H as HarmonyRole } from '../index-CunYKLiu.js';
10
+ import { a3 as Vocabulary, s as GridEvent, r as Genre, q as GenerationContextInput, m as DrumStyle, $ as Section, n as DrumVoice, t as GrooveFeel, k as DrumHit } from '../index-CgHqxdps.js';
11
+ export { B as BAR_STEPS, a as BASS_STYLES, b as BEAT_STEPS, c as BORROWED_DEGREES, d as BassLineOptions, e as BassSegment, f as BassStyle, g as BorrowedChord, C as Complexity, h as CounterMelodyOptions, D as DRUM_NOTES, i as DeformOptions, j as Draw, l as DrumRole, o as DrumsOptions, E as EuclideanKick, G as GENRES, p as GenerationContext, u as GrooveSlot, v as GrooveStyle, w as GrooveTemplate, H as HarmonizeOptions, x as HarmonizePlacement, y as HarmonizeResult, z as HumanizeOptions, M as MelodyNote, A as MotifCell, F as MotifContour, I as MotifNote, J as MotifOptions, K as MotifTransform, O as ORNAMENT_STYLES, L as OrnamentOptions, N as OrnamentStyle, P as PROVENANCE_BASES, Q as ProgFunction, R as ProgStyle, S as ProgressionDegree, T as ProgressionOptions, U as ProgressionPreset, V as Provenance, W as ProvenanceBasis, X as ResolvedContext, Y as RhythmEvent, Z as RhythmOptions, _ as STEP_BEATS, a0 as SubstituteOptions, a1 as Substitution, a2 as SubstitutionType, a4 as VocabularyQuery, a5 as applyGrooveTemplate, a6 as assertVocabulary, a7 as deform, a8 as developMotif, a9 as double, aa as doubleTime, ab as drumVoiceOf, ac as extractGrooveTemplate, ad as fitsQuery, ae as generateBassLine, af as generateCounterMelody, ag as generateDrums, ah as generateMotif, ai as generateProgression, aj as generateRhythm, ak as gridMetricWeight, al as halfTime, am as harmonizeMelody, an as humanize, ao as mergeVocabulary, ap as modalInterchangePalette, aq as motifToNoteEvents, ar as negativeHarmonyMirror, as as onsetWeightCurve, at as ornament, au as ornamentBy, av as pickProgressionPreset, aw as pickVocabulary, ax as progressions, ay as progressionsByStyle, az as resolveContext, aA as rhythmDensity, aB as rhythmToNoteEvents, aC as selectVocabulary, aD as substituteChord, aE as syncopate, aF as thin, aG as transformMotif, aH as vocabularyOfKind, aI as withinCeiling } from '../index-CgHqxdps.js';
8
12
 
9
13
  /**
10
- * Deterministic bass-line generation over a chord placement.
14
+ * The playing-difficulty ceiling, as the generators apply it.
11
15
  *
12
- * Given a sequence of chord segments and a key, this builds a monophonic bass
13
- * line in a chosen low register. Several idiomatic styles are supported, from a
14
- * single held root per segment to a quarter-note walking line that leads by
15
- * step into each chord change. All pitches are kept in a narrow bass band and
16
- * consecutive notes move by small intervals; given a seed the output is fully
17
- * reproducible.
18
- *
19
- * Positions and durations are measured in quarter-note beats, matching the
20
- * library-wide convention.
21
- */
22
-
23
- /**
24
- * A chord sounding over a half-open beat span `[startBeat, endBeat)`.
25
- *
26
- * The same record {@link ChordSegment} names, so a timeline segment can be fed
27
- * straight to the bass generator.
28
- *
29
- * @category Composition
30
- */
31
- type BassSegment = ChordSegment;
32
- /**
33
- * Every bass-line idiom, in declaration order.
34
- *
35
- * @category Composition
36
- */
37
- declare const BASS_STYLES: readonly ["root", "rootFifth", "pop", "walking", "arpeggio"];
38
- /**
39
- * The bass-line idiom to generate.
40
- *
41
- * @category Composition
16
+ * A ceiling is not an intensity: a passage can be harmonically involved and
17
+ * easy to play, or plain and hard. So difficulty never proposes material — it
18
+ * only rejects what the other complexity dials proposed, and only on the
19
+ * timing layer, where speed is what stands in the way. Whether the note exists
20
+ * on the instrument at all, and whether the hand can hold the shape, belong to
21
+ * the instrument profile and hold regardless of the ceiling.
42
22
  */
43
- type BassStyle = (typeof BASS_STYLES)[number];
23
+ /** The ceiling's lowest level: what a beginner sustains. */
24
+ declare const MIN_DIFFICULTY = 1;
25
+ /** The ceiling's highest level: what a rudimental player sustains. */
26
+ declare const MAX_DIFFICULTY = 5;
44
27
  /**
45
- * Options controlling {@link generateBassLine}.
28
+ * Whether a player at this ceiling keeps up a stream of strokes on one voice.
46
29
  *
47
- * @category Composition
30
+ * @param stepBeats Gap between consecutive strokes, in quarter-note beats.
31
+ * @param bpm Tempo, or undefined when the caller gave none.
32
+ * @param difficulty The ceiling, or undefined for no ceiling.
33
+ * @returns True when the stream is inside the ceiling, and whenever either the
34
+ * ceiling or the tempo is unknown — a limit nobody stated constrains nothing.
48
35
  */
49
- type BassLineOptions = {
50
- /** Chord placement to follow; need not be pre-sorted. */
51
- segments: readonly BassSegment[];
52
- /** Key/scale context, used for diatonic approach tones in `walking`. */
53
- key: KeyScale;
54
- /**
55
- * Time signature; used for metric accents.
56
- *
57
- * @defaultValue `{ numerator: 4, denominator: 4 }`
58
- */
59
- ts?: TimeSignature;
60
- /**
61
- * Bass-line idiom.
62
- *
63
- * @defaultValue `'root'`
64
- */
65
- style?: BassStyle;
66
- /**
67
- * Target register as a base MIDI octave; roots land around `octave*12+12`.
68
- * An octave pickup in the `pop` style may drop a note into the octave below
69
- * that band, which is where such a pickup belongs.
70
- *
71
- * @defaultValue 2
72
- */
73
- octave?: number;
74
- /**
75
- * Seed for the deterministic PRNG.
76
- *
77
- * @defaultValue 0
78
- */
79
- seed?: number;
80
- };
36
+ declare function sustainsStrokes(stepBeats: number, bpm: number | undefined, difficulty: number | undefined): boolean;
81
37
  /**
82
- * Generate a bass line following a chord placement.
83
- *
84
- * Each segment contributes notes in the chosen bass register per the selected
85
- * style; consecutive notes are kept within roughly a fifth. Every note's
86
- * duration extends to the next onset (the final note extends to the last
87
- * segment's end). Given a seed the output is fully reproducible.
88
- *
89
- * @param opts Segments, key, and generation options.
90
- * @returns Bass notes sorted by onset, non-overlapping.
38
+ * Whether the fretting hand covers a leap in the time available.
91
39
  *
92
- * @example
93
- * ```ts
94
- * import { generateBassLine, makeChord, majorKey } from '@libraz/libcantus';
95
- * const segments = [
96
- * { startBeat: 0, endBeat: 4, chord: makeChord(0, 'maj') },
97
- * { startBeat: 4, endBeat: 8, chord: makeChord(7, 'maj') },
98
- * ];
99
- * const notes = generateBassLine({ segments, key: majorKey(0), style: 'walking' });
100
- * ```
101
- *
102
- * @category Composition
40
+ * @param semitones Distance moved; on a fretted neck one fret is one semitone.
41
+ * @param stepBeats Time to move it in, in quarter-note beats.
42
+ * @param bpm Tempo, or undefined when the caller gave none.
43
+ * @param difficulty The ceiling, or undefined for no ceiling.
44
+ * @returns True when the shift is inside the ceiling, and whenever either the
45
+ * ceiling or the tempo is unknown.
103
46
  */
104
- declare function generateBassLine(opts: BassLineOptions): NoteEvent[];
47
+ declare function sustainsShift(semitones: number, stepBeats: number, bpm: number | undefined, difficulty: number | undefined): boolean;
105
48
 
106
49
  /**
107
- * Options controlling {@link generateCounterMelody}.
50
+ * The bass lick dictionary: the figures a bass player reaches for, held as
51
+ * chord-relative degrees and beat positions and deformed to fit the progression
52
+ * actually in front of them.
108
53
  *
109
- * @category Voicing & Counterpoint
54
+ * Provenance: no entry reproduces a bass line from a particular song. Each is a
55
+ * figure that belongs to nobody — the descending soul walk-down, the octave
56
+ * slap, the boogie shuffle — of the kind every player of that music already
57
+ * plays, and each records the ground it qualifies under.
110
58
  */
111
- type CounterMelodyOptions = {
112
- /** The lead line to write against, in ascending onset order. */
113
- melody: readonly NoteEvent[];
114
- /**
115
- * The harmony to write against. A timeline carries its own segment
116
- * boundaries, so a chord change anywhere — including off the generator's
117
- * half-beat probe grid — is seen; prefer it over `chordAt`.
118
- *
119
- * Exactly one of `timeline` and `chordAt` is required; `timeline` wins when
120
- * both are given.
121
- */
122
- timeline?: ChordTimeline;
123
- /**
124
- * Chord sounding at an absolute beat position, or null when none applies.
125
- *
126
- * A callback is opaque: the generator cannot see where it changes, so it
127
- * probes a half-beat grid and otherwise relies on `chordChangeBeats`. A held
128
- * counter note that spans an unseen change is not checked against the new
129
- * chord.
130
- */
131
- chordAt?: (beat: number) => Chord | null;
132
- /**
133
- * Exact chord-change beats the `chordAt` callback does not expose. Ignored
134
- * when `timeline` is given, since a timeline lists its own boundaries.
135
- */
136
- chordChangeBeats?: number[];
137
- /** Key/scale context for scale-tone decisions. */
138
- key: KeyScale;
139
- /**
140
- * Meter used for strong-beat decisions.
141
- *
142
- * @defaultValue 4/4
143
- */
144
- ts?: TimeSignature;
145
- /**
146
- * Which side of the melody the counter line occupies.
147
- *
148
- * @defaultValue 'below'
149
- */
150
- register?: 'above' | 'below';
151
- /**
152
- * Onset strategy: `'complement'` moves where the melody holds or rests and
153
- * reinforces some strong beats; `'follow'` mirrors melody onsets.
154
- *
155
- * @defaultValue 'complement'
156
- */
157
- rhythm?: 'complement' | 'follow';
158
- /**
159
- * Safety profile applied to candidate pitches.
160
- *
161
- * @defaultValue 'pop'
162
- */
163
- profile?: 'strict' | 'pop';
164
- /** Lowest MIDI pitch the counter line may use (default derived from `register`). */
165
- pitchLow?: number;
166
- /** Highest MIDI pitch the counter line may use (default derived from `register`). */
167
- pitchHigh?: number;
168
- /**
169
- * PRNG seed. It places the `'complement'` onsets and breaks ties between
170
- * equally good candidate pitches; the same seed always yields the same line.
171
- * Where the constraints leave only one good answer — which is common with
172
- * `rhythm: 'follow'`, whose onsets are fixed by the melody — changing the seed
173
- * changes nothing.
174
- *
175
- * @defaultValue 0
176
- */
177
- seed?: number;
178
- };
179
- /**
180
- * Generate a counter melody against a lead line.
181
- *
182
- * Onsets are chosen per `rhythm`: `'follow'` places one counter note on each
183
- * melody onset; `'complement'` fills melody rests and sustains and reinforces
184
- * an occasional strong beat, spaced at least a beat apart. Each onset's pitch
185
- * is picked from the safe pitches in the counter register (chord tones and
186
- * consonant tensions against the sounding melody note), rejecting candidates
187
- * that would form a parallel perfect interval with the melody or sit on the
188
- * wrong side of it, then scored to favour contrary or oblique motion, imperfect
189
- * consonance, and stepwise movement. Ties are broken by a seeded perturbation far
190
- * below any real score difference (see `TIE_BREAK_JITTER`), so the same
191
- * seed always yields the same line while a different seed can only reshuffle
192
- * candidates that were already equally good. Notes extend to the next counter
193
- * onset (the last to the melody's end) at a velocity slightly under the melody.
194
- *
195
- * @param opts The lead line, chord context callback, key, and generation knobs.
196
- * @returns The counter line as note events sorted by onset; `[]` for an empty melody.
197
- * @example
198
- * ```ts
199
- * import { generateCounterMelody, majorKey, parseChordSymbol } from '@libraz/libcantus';
200
- * const melody = [{ pitch: 72, startBeat: 0, durationBeat: 2 }];
201
- * const counter = generateCounterMelody({
202
- * melody,
203
- * chordAt: () => parseChordSymbol('C'),
204
- * key: majorKey(0),
205
- * }); // note events below the melody, in onset order
206
- * ```
207
- * @category Voicing & Counterpoint
208
- */
209
- declare function generateCounterMelody(opts: CounterMelodyOptions): NoteEvent[];
210
59
 
211
60
  /**
212
- * The percussion voices the generator emits, as General MIDI note numbers.
61
+ * One note of a lick, relative to the chord it is played over.
213
62
  *
214
- * A {@link DrumHit} carries only its pitch, so splitting a kit across tracks —
215
- * kick to one, snare to another — means comparing against these numbers. They
216
- * are published so that comparison is a named constant covered by semver
217
- * rather than a hard-coded 36.
63
+ * A degree is 1-based and counts up from the chord's root: 1 is the root, 3 the
64
+ * third, 8 the octave. Degrees the chord itself names — the third, fifth and
65
+ * seventh — are taken from the chord, so the same figure comes out major over a
66
+ * major chord and minor over a minor one; the rest are taken from the key. That
67
+ * is what lets a figure be written once and fit a whole progression.
218
68
  *
219
- * @example
220
- * ```ts
221
- * import { DRUM_NOTES, generateDrums } from '@libraz/libcantus';
222
- * const hits = generateDrums({ bars: 1, bpm: 120, style: 'standard', section: 'verse', density: 0.5 });
223
- * const kick = hits.filter((hit) => hit.pitch === DRUM_NOTES.kick);
224
- * ```
225
69
  * @category Composition
226
70
  */
227
- declare const DRUM_NOTES: {
228
- readonly kick: 36;
229
- readonly snare: 38;
230
- readonly sideStick: 37;
231
- readonly handClap: 39;
232
- readonly closedHiHat: 42;
233
- readonly pedalHiHat: 44;
234
- readonly openHiHat: 46;
235
- readonly crash: 49;
236
- readonly ride: 51;
237
- readonly tambourine: 54;
238
- readonly highTom: 50;
239
- readonly midTom: 47;
240
- readonly lowTom: 45;
241
- readonly shaker: 82;
71
+ type LickNote = GridEvent & {
72
+ /** Chord degree, 1-based; 8 is the octave above the root. */
73
+ degree: number;
74
+ /** Chromatic alteration in semitones, for a flattened or raised degree. */
75
+ alter?: number;
76
+ /** Sounding length in sixteenths; one step when absent. */
77
+ lengthSteps?: number;
78
+ /** How the note is played, when it is more than a plain note. */
79
+ articulation?: Articulation;
242
80
  };
243
81
  /**
244
- * A percussion voice name, one key of {@link DRUM_NOTES}.
245
- *
246
- * @category Composition
247
- */
248
- type DrumVoice = keyof typeof DRUM_NOTES;
249
- /**
250
- * The voice a percussion note number names.
82
+ * A lick: its notes and how long the whole figure lasts.
251
83
  *
252
- * @param pitch The General MIDI note number.
253
- * @returns The voice, or undefined for a note this generator never emits.
254
84
  * @category Composition
255
85
  */
256
- declare function drumVoiceOf(pitch: number): DrumVoice | undefined;
257
- /**
258
- * A single percussion onset.
259
- *
260
- * @category Composition
261
- */
262
- type DrumHit = {
263
- /** General MIDI note number; {@link drumVoiceOf} names it. */
264
- pitch: number;
265
- startBeat: number;
266
- durationBeat: number;
267
- velocity: number;
86
+ type LickMaterial = {
87
+ /** Length of the figure in sixteenths. */
88
+ lengthSteps: number;
89
+ notes: LickNote[];
268
90
  };
269
-
270
- /** Every groove feel, in declaration order. */
271
- declare const DRUM_FEELS: readonly ["straight", "swing", "shuffle"];
272
- /** Groove feel controlling off-beat swing. */
273
- type Feel = (typeof DRUM_FEELS)[number];
274
- /**
275
- * Every public groove style, in declaration order. The type is derived from
276
- * this list so the runtime guard and the type can never disagree.
277
- */
278
- declare const GROOVE_STYLES: readonly ["standard", "funk", "shuffle", "bossa", "trap", "halftime", "breakbeat", "house", "synthpop"];
279
91
  /**
280
- * Public groove style identifiers.
92
+ * A lick with the conditions it fits.
281
93
  *
282
94
  * @category Composition
283
95
  */
284
- type GrooveStyle = (typeof GROOVE_STYLES)[number];
285
- /** Every public role, in declaration order. */
286
- declare const DRUM_ROLES: readonly ["full", "ambient", "minimal", "fxOnly"];
96
+ type BassLick = Vocabulary<LickMaterial>;
287
97
  /**
288
- * Public role identifiers gating which voices are present.
98
+ * Whether a caller's material is a bass lick.
289
99
  *
290
100
  * @category Composition
291
101
  */
292
- type DrumRole = (typeof DRUM_ROLES)[number];
293
- /** Every public section, in declaration order. */
294
- declare const PUBLIC_SECTIONS: readonly ["intro", "verse", "prechorus", "chorus", "bridge", "outro"];
295
- /** Public section identifiers. */
296
- type PublicSection = (typeof PUBLIC_SECTIONS)[number];
297
-
102
+ declare function isLickMaterial(material: unknown): material is LickMaterial;
298
103
  /**
299
- * Groove feel (the swing/straight rhythmic character) for {@link generateDrums}.
104
+ * The built-in bass licks, in declaration order.
300
105
  *
301
106
  * @category Composition
302
107
  */
303
- type GrooveFeel = Feel;
304
-
108
+ declare const BASS_LICKS: readonly BassLick[];
305
109
  /**
306
- * Public section identifiers for {@link generateDrums}.
110
+ * Options controlling {@link placeLicks}.
307
111
  *
308
112
  * @category Composition
309
113
  */
310
- type Section = PublicSection;
311
- /**
312
- * A Euclidean (Bjorklund) kick pattern for {@link generateDrums}.
313
- *
314
- * @category Composition
315
- */
316
- type EuclideanKick = {
317
- /** Number of kick onsets, clamped to `[0, steps]`. */
318
- pulses: number;
319
- /**
320
- * Total steps in the bar (1..16).
321
- *
322
- * @defaultValue 16
323
- */
324
- steps?: number;
114
+ type PlaceLicksOptions = {
325
115
  /**
326
- * Steps to rotate the onsets toward later positions.
327
- *
328
- * @defaultValue 0
116
+ * The genre whose figures may be used. Genre is what selects the material:
117
+ * the dials then deform it, and the ceiling rejects it.
329
118
  */
330
- rotation?: number;
331
- };
332
- /**
333
- * Options controlling {@link generateDrums}.
334
- *
335
- * @category Composition
336
- */
337
- type DrumsOptions = {
119
+ genre: Genre;
338
120
  /**
339
- * Number of bars to generate. Bars are four quarter-note beats long: the
340
- * patterns are written for 4/4 and there is no meter option, so a `bars`
341
- * count shared with a meter-aware generator such as
342
- * {@link generateRhythm} only lines up when that generator is also in 4/4.
121
+ * The hardest figure that may be used, 1 to 5. Sugar for
122
+ * `ctx: { complexity: { difficulty } }`; the context wins over both.
343
123
  */
344
- bars: number;
345
- bpm: number;
346
- style: GrooveStyle;
347
- section: Section;
348
- density: number;
124
+ difficulty?: number;
125
+ /** Time signature; defaults to 4/4. */
126
+ ts?: TimeSignature;
349
127
  /**
350
- * Replace the final bar with a fill.
351
- *
352
- * A pre-chorus leading into a chorus builds instead: the two-bar lift takes
353
- * precedence over the fill, since the buildup already marks the phrase end.
354
- * Set `nextSection` to anything but `'chorus'` to get the fill.
128
+ * Target register as a base MIDI octave; roots land around `octave*12+12`.
355
129
  *
356
- * @defaultValue false
130
+ * @defaultValue 2
357
131
  */
358
- fills?: boolean;
359
- feel?: GrooveFeel;
132
+ octave?: number;
133
+ /** The instrument the line is written for; its range always applies. */
134
+ instrument?: StringedProfile;
360
135
  /**
361
- * Voicing role, from busiest to sparsest:
362
- *
363
- * - `'full'`: every voice, backbeat snare at full weight.
364
- * - `'ambient'`: side-stick backbeat, ride instead of hi-hat.
365
- * - `'minimal'`: side-stick backbeat quieter still, pedal hi-hat, no
366
- * auxiliary percussion.
367
- * - `'fxOnly'`: no kick, snare, ghost notes, timekeeping hi-hat, or fills —
368
- * only the fx and auxiliary voices.
136
+ * The generation context. Its `vocabulary` brings the caller's own figures,
137
+ * its `complexity.ornament` decides how much decoration survives, and its
138
+ * `complexity.difficulty` is the ceiling figures are rejected against.
369
139
  *
370
- * The ordering holds in every style and section: a busier role never emits
371
- * fewer or quieter voices than a sparser one.
140
+ * Its `complexity.rhythmic` is how busy the line is, in [0, 1]: it decides
141
+ * both how often a segment takes a figure at all and, above its middle
142
+ * setting, how much the figure is syncopated — one dial, because "less busy"
143
+ * means both. Its `bpm` is the tempo the ceiling is measured against, and its
144
+ * `seed` fixes which figures are chosen and where they land.
372
145
  *
373
- * @defaultValue `'full'`
146
+ * @defaultValue `{ seed: 0, bpm: 120, complexity: { rhythmic: 0.6 } }`
374
147
  */
375
- role?: DrumRole;
148
+ ctx?: GenerationContextInput;
376
149
  /**
377
- * Seed for the deterministic PRNG.
150
+ * Maximum number of segments {@link placeLicks} may lay figures over. One
151
+ * figure is placed per segment, so this is the guard against an unbounded
152
+ * caller rather than a limit on any search.
378
153
  *
379
- * @defaultValue 0
380
- */
381
- seed?: number;
382
- /**
383
- * Section the final-bar fill leads into. Shapes which fill archetype is
384
- * chosen (into-chorus and out-of-intro fills differ from generic ones).
385
- * Defaults to `section`, i.e. a within-section fill.
386
- */
387
- nextSection?: Section;
388
- /**
389
- * When set, the kick follows this Euclidean rhythm instead of the
390
- * style/section pattern, giving direct access to evenly spread onsets.
154
+ * @defaultValue 1000000
391
155
  */
392
- euclideanKick?: EuclideanKick;
156
+ budget?: number;
393
157
  };
394
158
  /**
395
- * Generate a drum performance as a flat list of onsets.
159
+ * Lay licks over a chord placement.
396
160
  *
397
- * Every voice (kick, snare, ghost snares, closed/open/foot hi-hats, ride, toms,
398
- * crash, and auxiliary percussion) is emitted as a {@link DrumHit} distinguished
399
- * by its General MIDI pitch. Groove style selects an internal style and feel;
400
- * `density` sets the backing-density level; `section` shapes kick, hi-hat, ghost,
401
- * and percussion density. 16th-note hi-hats drop to 8ths at or above 150 BPM.
402
- * When `fills` is true the final bar is replaced with a fill whose archetype is
403
- * shaped by `nextSection`; a fill that would emit nothing on its beat falls back
404
- * to the normal groove so the phrase end is never silent. `euclideanKick`
405
- * overrides the kick with an evenly-spread Euclidean pattern. Output is fully
406
- * determined by the options plus `seed`.
161
+ * Each segment is offered the figures whose conditions it meets — the genre,
162
+ * the chord quality, the tempo, and the difficulty ceiling — and one of them is
163
+ * chosen by a draw addressed to that segment, so changing a chord elsewhere
164
+ * leaves this segment's figure where it was. A segment that takes no figure
165
+ * gets its root, and the last note before a chord change leads into the next
166
+ * root by step, the way a walking line does.
407
167
  *
408
- * The patterns are 4/4 only — backbeats, hi-hat subdivisions, and fills are all
409
- * written against a four-beat bar — so beat positions are quarter notes and bar
410
- * `n` starts at beat `4n`.
411
- *
412
- * @param opts Generation options.
413
- * @returns Percussion onsets in onset order, ties broken by pitch.
168
+ * @param timeline Chord segments to play over; they need not be pre-sorted, and
169
+ * each segment's chord may be written as a chord symbol.
170
+ * @param key Key context, which answers for the degrees the chords do not; a
171
+ * key name such as `'C major'` is read as that key.
172
+ * @param opts Genre, density, ceiling and seed.
173
+ * @returns Bass notes sorted by onset, non-overlapping.
174
+ * @throws If a segment has a non-positive duration, or the genre is not one
175
+ * this library names.
414
176
  *
415
177
  * @example
416
178
  * ```ts
417
- * import { generateDrums } from '@libraz/libcantus';
418
- * const hits = generateDrums({ bars: 4, bpm: 120, style: 'standard', section: 'chorus', density: 0.6, fills: true });
419
- * // Fully determined by the options plus seed (defaults to 0).
179
+ * import { majorKey, makeChord, placeLicks } from '@libraz/libcantus';
180
+ * const timeline = [
181
+ * { startBeat: 0, endBeat: 4, chord: makeChord(0, 'maj7') },
182
+ * { startBeat: 4, endBeat: 8, chord: makeChord(5, 'maj7') },
183
+ * ];
184
+ * const notes = placeLicks(timeline, majorKey(0), { genre: 'motown', ctx: { seed: 7 } });
420
185
  * ```
421
186
  *
422
187
  * @category Composition
423
188
  */
424
- declare function generateDrums(opts: DrumsOptions): DrumHit[];
189
+ declare function placeLicks(timeline: readonly (Omit<ChordSegment, 'chord'> & {
190
+ chord: ChordLike;
191
+ })[], key: KeyLike, opts: PlaceLicksOptions): NoteEvent[];
425
192
 
426
193
  /**
427
- * Groove and humanization: nudging quantized note events toward a more human
428
- * feel, and capturing/replaying the timing-and-velocity "feel" of a
429
- * performance as a reusable groove template.
194
+ * Imitative entries: a second voice restating what the first has just sung.
430
195
  *
431
- * Positions and durations are measured in quarter-note beats, matching the
432
- * library-wide convention. Randomness, where used, always goes through the
433
- * seeded PRNG from the `random` module so results are fully reproducible.
196
+ * A motif transform has no time relationship to anything — it returns a cell,
197
+ * and where that cell goes is the caller's problem. An imitation is defined by
198
+ * that relationship: it enters at a stated beat, at a stated interval, against a
199
+ * voice that is still sounding. That is what a canon is, and what the answering
200
+ * phrase in the second half of a chorus is.
434
201
  */
435
202
 
203
+ /** Every way an imitation can answer, in declaration order. */
204
+ declare const IMITATION_ANSWERS: readonly ["real", "tonal"];
205
+ /**
206
+ * How an imitation answers the voice it follows.
207
+ *
208
+ * A `real` answer transposes literally, keeping every interval of the subject
209
+ * and leaving the key where the interval puts it. A `tonal` answer counts scale
210
+ * degrees instead, so the answer stays in the key and adjusts an interval or two
211
+ * to do it — the fifth of a subject answered by a fourth.
212
+ *
213
+ * @category Composition
214
+ */
215
+ type ImitationAnswer = (typeof IMITATION_ANSWERS)[number];
436
216
  /**
437
- * Options controlling {@link humanize}.
217
+ * Options controlling {@link imitate}.
438
218
  *
439
- * @category Rhythm & Meter
219
+ * @category Composition
440
220
  */
441
- type HumanizeOptions = {
221
+ type ImitationOptions = {
442
222
  /**
443
- * Time signature used to derive metric accents.
444
- *
445
- * @defaultValue 4/4
223
+ * Beat at which the imitation enters; the copied material starts here.
224
+ * Negative like any other onset, for an answer that enters in a pickup.
446
225
  */
447
- ts?: TimeSignature;
226
+ atBeat: number;
448
227
  /**
449
- * Maximum timing jitter in quarter-note beats, applied as ±this value.
450
- *
451
- * @defaultValue 0.02
228
+ * Interval to imitate at, spelled: `'P5'` for an answer a fifth above,
229
+ * `'-P4'` for one a fourth below.
452
230
  */
453
- timing?: number;
231
+ interval: IntervalLike;
454
232
  /**
455
- * Maximum velocity jitter (MIDI units), applied as ±this value.
456
- *
457
- * @defaultValue 8
233
+ * The key the answer is measured in; what makes a tonal answer tonal. A key
234
+ * name such as `'C major'` is read as that key.
458
235
  */
459
- velocity?: number;
236
+ key: KeyLike;
460
237
  /**
461
- * How much louder strong beats get relative to weak ones, in MIDI velocity units.
238
+ * Whether the answer transposes literally or by scale degree.
462
239
  *
463
- * @defaultValue 12
240
+ * @defaultValue 'real'
464
241
  */
465
- accent?: number;
242
+ answer?: ImitationAnswer;
466
243
  /**
467
- * Velocity assumed for events with no `velocity` of their own.
244
+ * Turn the subject upside down before transposing it, mirroring it about its
245
+ * own first note: by semitone for a real answer, by scale degree for a tonal
246
+ * one.
468
247
  *
469
- * @defaultValue 80
248
+ * @defaultValue false
470
249
  */
471
- baseVelocity?: number;
250
+ invert?: boolean;
251
+ /** First beat of the lead to imitate; defaults to the start of the line. */
252
+ from?: number;
253
+ /** Beat to imitate up to, exclusive; defaults to the end of the line. */
254
+ to?: number;
472
255
  /**
473
- * Seed for the deterministic PRNG.
256
+ * Factor applied to the copied velocities, for an answer that sits under the
257
+ * voice it follows.
474
258
  *
475
- * @defaultValue 0
259
+ * @defaultValue 1
476
260
  */
477
- seed?: number;
261
+ velocityScale?: number;
478
262
  };
479
263
  /**
480
- * Humanize a sequence of note events by nudging their timing and velocity
481
- * with small, deterministic (seeded) randomness plus a metric-accent shape:
482
- * events on strong beats (see {@link metricWeight}) come out louder on
483
- * average than events on weak ones.
484
- *
485
- * Each returned event is a copy; `startBeat` is jittered within
486
- * `[-timing, +timing]` beats and clamped to be non-negative, and `velocity`
487
- * is the event's own velocity (or `baseVelocity` if it has none) plus an
488
- * accent term scaled by the event's metric weight plus a jitter within
489
- * `[-velocity, +velocity]`, clamped to `[1, 127]` and rounded. The metric
490
- * weight is computed from each event's original (pre-jitter) `startBeat`.
491
- * `pitch` and `durationBeat` pass through unchanged, and the output keeps the
492
- * input order.
493
- *
494
- * @param events The events to humanize.
495
- * @param opts Humanization options.
496
- * @returns Humanized copies of `events`, in input order.
264
+ * Answer a leading voice with an imitation of it.
265
+ *
266
+ * The chosen span of the lead is copied so that its first note falls on
267
+ * `atBeat`, keeping its internal rhythm; only the answer is returned, so the
268
+ * caller decides what to do with the two voices together. Pitches move by
269
+ * `interval`: literally for a real answer, by scale degree for a tonal one, and
270
+ * upside down first when `invert` is set. Inversion mirrors about the first note
271
+ * of the copied span, so the answer starts where the transposition puts that
272
+ * note either way. In a tonal answer a pitch outside the key keeps its distance
273
+ * from the scale tone below it, and inversion mirrors that distance too — a
274
+ * chromatic passing note stays a chromatic passing note, on the other side.
275
+ * Where a mirrored chromatic note falls into a diatonic semitone the key leaves
276
+ * it no room, and it lands on the scale tone there, so a subject saturated with
277
+ * chromatic steps can answer with a pitch repeated.
278
+ *
279
+ * @param lead The voice being imitated, in ascending onset order.
280
+ * @param opts Where the answer enters, at what interval, and in what key.
281
+ * @returns The imitation as note events sorted by onset; `[]` when the chosen
282
+ * span holds no sounding notes. Notes with a zero or negative duration never
283
+ * sound and are not copied, so the answer can be shorter than the span.
284
+ * @throws If the span is reversed, the answer is not a known kind, or a
285
+ * transposed pitch leaves the MIDI range.
497
286
  * @example
498
287
  * ```ts
499
- * import { humanize } from '@libraz/libcantus';
500
- * const events = [{ pitch: 60, startBeat: 0, durationBeat: 1, velocity: 80 }];
501
- * humanize(events, { seed: 1, timing: 0.03 }); // copies with jittered timing and accented velocity
288
+ * import { imitate, majorKey } from '@libraz/libcantus';
289
+ * const lead = [0, 1, 2].map((beat) => ({ pitch: 60 + beat, startBeat: beat, durationBeat: 1 }));
290
+ * imitate(lead, { atBeat: 2, interval: 'P5', key: majorKey(0) });
291
+ * // the same three notes a fifth up, entering at beat 2
502
292
  * ```
503
- * Notes with a zero or negative duration never sound and are dropped, so the
504
- * result can be shorter than the input.
505
- *
506
- * @category Rhythm & Meter
293
+ * @category Composition
507
294
  */
508
- declare function humanize(events: readonly NoteEvent[], opts?: HumanizeOptions): NoteEvent[];
295
+ declare function imitate(lead: readonly NoteEvent[], opts: ImitationOptions): NoteEvent[];
296
+
509
297
  /**
510
- * The recorded feel at a single grid slot: an average timing deviation and velocity.
298
+ * The fill vocabulary, as data.
511
299
  *
512
- * @category Rhythm & Meter
300
+ * Every archetype is a list of strokes on the bar's grid and every selection is
301
+ * a table indexed by a draw, so a fill can be added, replaced, or supplied by a
302
+ * caller without touching a generator. The generator's part is to look one up.
513
303
  */
514
- type GrooveSlot = {
515
- /** Average `actualStartBeat - quantizedBeat` (in quarter-note beats) for events landing on this slot. */
516
- timingOffset: number;
517
- /**
518
- * Average velocity of events landing on this slot, or `null` if no event
519
- * with a velocity did. `null` (not 0) is the "unrecorded" sentinel so that a
520
- * genuine velocity of 0 is preserved and applied like any other value.
521
- */
522
- velocity: number | null;
304
+
305
+ /** The thirteen fill archetypes for section transitions, in declaration order. */
306
+ declare const FILL_TYPES: readonly ["snareRoll", "tomDescend", "tomAscend", "snareTomCombo", "simpleCrash", "linearFill", "ghostToAccent", "bdSnareAlternate", "hiHatChoke", "tomShuffle", "breakdownFill", "flamsAndDrags", "halfTimeFill"];
307
+ /** The thirteen fill archetypes for section transitions. */
308
+ type FillType = (typeof FILL_TYPES)[number];
309
+ /**
310
+ * How loud one stroke of a fill is.
311
+ *
312
+ * A fill's dynamics are written against the fill's own base velocity rather
313
+ * than in absolute terms, so the same archetype sounds right in a verse and a
314
+ * chorus: `fill` is the body of the fill, `accent` the stroke that lands. One
315
+ * base for the whole gesture, not one per beat — a crescendo that restarts its
316
+ * arithmetic at every beat line falls back in the middle of itself.
317
+ */
318
+ type FillVelocity = {
319
+ base: 'fill' | 'accent';
320
+ /** Factor on the base, for a crescendo or a ghosted stroke. */
321
+ scale?: number;
322
+ /** Fixed offset in MIDI velocity, for a stroke a little above or below. */
323
+ offset?: number;
324
+ };
325
+ /** One stroke of a fill, positioned within the beat it belongs to. */
326
+ type FillStroke = {
327
+ /** General MIDI note number of the voice struck. */
328
+ voice: number;
329
+ /** Offset from the start of the beat, in quarter-note beats. */
330
+ offset: number;
331
+ /** Sounding length, in quarter-note beats. */
332
+ duration: number;
333
+ velocity: FillVelocity;
334
+ /** How the stroke is played, when it is more than a plain hit. */
335
+ articulation?: Articulation;
523
336
  };
524
337
  /**
525
- * A per-bar grid of timing and velocity deviations captured from a performance.
338
+ * A fill archetype: what each beat of the fill bar plays.
526
339
  *
527
- * @category Rhythm & Meter
340
+ * Beats are indexed from the start of the bar. A beat with no entry is silent,
341
+ * which is how the sparse archetypes leave the earlier beats to the groove.
528
342
  */
529
- type GrooveTemplate = {
530
- /** Grid resolution: equal grid steps per quarter-note beat. */
531
- subdivision: number;
532
- /** Number of grid slots per bar (`beatsPerBar(ts) * subdivision`). */
533
- slotsPerBar: number;
534
- /** One entry per grid slot, in slot-index order (slot 0 is the downbeat). */
535
- slots: GrooveSlot[];
343
+ type FillArchetype = {
344
+ /** Strokes per beat of the bar, indexed 0..3. */
345
+ atBeat: readonly (readonly FillStroke[])[];
536
346
  /**
537
- * The time signature the template was extracted under. {@link applyGrooveTemplate}
538
- * requires the apply-time meter to match this so the per-bar grid lines up
539
- * instead of silently drifting. Optional for backward compatibility with
540
- * templates constructed without it (in which case no check is performed).
347
+ * Whether a bar longer than four beats keeps playing the last beat's strokes.
348
+ * The archetypes are written against a four-beat bar; most read naturally when
349
+ * extended, but the half-time figure is a bar-long gesture and stops there.
541
350
  */
542
- ts?: TimeSignature;
351
+ boundToBar?: boolean;
543
352
  };
544
353
  /**
545
- * Extract a groove template from a set of note events: for each grid slot,
546
- * the average timing deviation from the quantized grid and the average
547
- * velocity of events landing on it. Slots with no velocity-carrying event
548
- * record `velocity: null` (the "unrecorded" sentinel); empty slots default to
549
- * `{ timingOffset: 0, velocity: null }`. The time signature is stored on the
550
- * template so {@link applyGrooveTemplate} can verify the meter matches.
354
+ * The fill dictionary.
551
355
  *
552
- * @param events The (typically "groovy", human-played) events to analyze.
553
- * @param ts The time signature, used to compute the bar length.
554
- * @param subdivision Grid resolution: equal grid steps per quarter-note beat.
555
- * Defaults to a sixteenth-note grid, which is where the timing deviations a
556
- * template records — swing, push, drag — are audible.
557
- * @returns The extracted groove template.
558
- * @category Rhythm & Meter
356
+ * Provenance: every archetype here is a rudiment or a stock phrase-end gesture
357
+ * — a snare crescendo, a descending tom run, a crash on the last off-beat — of
358
+ * the kind every drummer plays and nobody owns. None reproduces a fill from a
359
+ * particular recording.
559
360
  */
560
- declare function extractGrooveTemplate(events: readonly NoteEvent[], ts: TimeSignature, subdivision?: number): GrooveTemplate;
361
+ declare const FILL_ARCHETYPES: Readonly<Record<FillType, FillArchetype>>;
561
362
  /**
562
- * Apply a groove template to a set of (typically quantized) note events: each
563
- * event is snapped to its grid slot and then offset by that slot's recorded
564
- * timing deviation, pushing stiff timing toward the template's feel.
565
- * Velocity is set to the slot's recorded average when it recorded one (a
566
- * non-`null` velocity, including a genuine 0); otherwise the event's own
567
- * velocity is left untouched.
568
- *
569
- * If the template carries a time signature (see {@link GrooveTemplate.ts}) it
570
- * must match `ts`, since a differing bar length would misalign the per-bar
571
- * grid and drift the feel; a mismatch throws rather than corrupting timing
572
- * silently.
363
+ * Whether a caller's material is a fill.
573
364
  *
574
- * @param events The events to reshape.
575
- * @param template The groove template, from {@link extractGrooveTemplate}.
576
- * @param ts The time signature, used to compute the bar length.
577
- * @returns Reshaped copies of `events`, in input order.
578
- * @throws If `template.ts` is set and does not match `ts`.
579
- * @example
580
- * ```ts
581
- * import { extractGrooveTemplate, applyGrooveTemplate, parseTimeSignature } from '@libraz/libcantus';
582
- * const ts = parseTimeSignature('4/4');
583
- * // Learn the feel from a loosely played performance...
584
- * const groovy = [
585
- * { pitch: 36, startBeat: 0.02, durationBeat: 1, velocity: 100 },
586
- * { pitch: 38, startBeat: 1.06, durationBeat: 1, velocity: 70 },
587
- * ];
588
- * const template = extractGrooveTemplate(groovy, ts, 4);
589
- * // ...then impose it on stiff, quantized events.
590
- * const quantized = [{ pitch: 36, startBeat: 0, durationBeat: 1, velocity: 80 }];
591
- * applyGrooveTemplate(quantized, template, ts); // events nudged toward the captured feel
592
- * ```
593
- * @category Rhythm & Meter
365
+ * A context carries one dictionary for the whole piece, so each generator has
366
+ * to recognise its own material; a bass lick reaching the fill selector would
367
+ * otherwise be rendered as silence.
594
368
  */
595
- declare function applyGrooveTemplate(events: readonly NoteEvent[], template: GrooveTemplate, ts: TimeSignature): NoteEvent[];
369
+ declare function isFillArchetype(material: unknown): material is FillArchetype;
596
370
 
597
371
  /**
598
- * Broad production style a progression preset suits.
599
- *
600
- * @category Composition
601
- */
602
- type ProgStyle = 'minimal' | 'dance' | 'idol' | 'rock';
603
- /**
604
- * Harmonic-function role of a progression as a whole.
372
+ * The kick vocabulary, as data on a sixteenth grid.
605
373
  *
606
- * @category Composition
374
+ * The grid is sixteenths rather than eighths because most of what belongs in a
375
+ * kick dictionary cannot be written on eighths at all: the funk and modern-pop
376
+ * basics live on the sixteenths between the beats. The eighth-note patterns map
377
+ * onto the even steps of the same grid, so they read exactly as before.
607
378
  */
608
- type ProgFunction = 'loop' | 'tensionBuild' | 'cadenceStrong' | 'stable';
379
+
380
+ /** Sixteenth steps in one bar of 4/4; the grid a kick pattern is written on. */
381
+ declare const KICK_STEPS = 16;
382
+ /**
383
+ * Kick presence flags across the sixteen 16th-note slots of a bar.
384
+ *
385
+ * Index `n` is the `n`-th sixteenth from the downbeat, so beat 3 is index 8 and
386
+ * the "and" of 2 is index 6.
387
+ */
388
+ type KickPattern = readonly boolean[];
389
+ /**
390
+ * One onset of a kick figure.
391
+ *
392
+ * A slot with no probability is part of the figure itself and always sounds.
393
+ * One with a probability is where the rhythmic dial reaches the kick: the draw
394
+ * is fixed by position and leaned on by the dial, so raising the dial fills
395
+ * slots in without emptying any that were already there.
396
+ */
397
+ type KickSlot = {
398
+ /** Sixteenth index within the bar, 0..15. */
399
+ step: number;
400
+ /** Chance the slot is taken before the dial leans on it. */
401
+ probability?: number;
402
+ /** Draw address for the slot; slots that may sound must name one. */
403
+ slot?: string;
404
+ /** Restrict the slot to bars of one parity, for a two-bar figure. */
405
+ barParity?: 0 | 1;
406
+ /** Sections the slot is added in; absent means every section. */
407
+ sections?: Section[];
408
+ };
409
+ /** A style's kick figure: the slots it always plays, plus the ones it may. */
410
+ type KickFigure = readonly KickSlot[];
609
411
  /**
610
- * A named chord-progression preset expressed in scale degrees.
412
+ * The kick dictionary, keyed by internal style.
611
413
  *
612
- * @category Composition
414
+ * Provenance: each figure is the plain pulse of its style — the four-on-the-
415
+ * floor dance kick, the backbeat-answering "and" of two, the clave-leaning
416
+ * latin figure — none of them traceable to a particular record.
613
417
  */
614
- type ProgressionPreset = {
615
- id: string;
616
- name: string;
617
- /**
618
- * The chord roots, in order, as {@link ProgressionDegree} codes: 0..6 for the
619
- * key's own scale degrees and {@link BORROWED_DEGREES} for the borrowed ones.
620
- */
621
- degrees: ProgressionDegree[];
622
- functional: ProgFunction;
623
- styles: ProgStyle[];
624
- };
418
+ declare const KICK_FIGURES: Readonly<Record<DrumStyle, KickFigure>>;
419
+
625
420
  /**
626
- * The borrowed (non-diatonic) chord roots a preset can name, as their semitone
627
- * offset above the tonic.
421
+ * A drum kit played with four limbs, covering every voice
422
+ * {@link generateDrums} emits.
628
423
  *
629
- * Scale degrees 0..6 address the key's own chords; these codes continue the
630
- * numbering for the chromatic chords a pop progression borrows, so a preset is
631
- * one flat list of degree codes.
424
+ * A kit is bounded by limbs rather than by range: two voices wanting the same
425
+ * hand at the same instant is what makes a pattern unplayable, and a voice no
426
+ * limb reaches is not on the kit at all.
632
427
  *
633
428
  * @example
634
429
  * ```ts
635
- * import { BORROWED_DEGREES, generateProgression, majorKey } from '@libraz/libcantus';
636
- * generateProgression({
637
- * key: majorKey(0),
638
- * style: 'rock',
639
- * bars: 4,
640
- * preset: { degrees: [0, BORROWED_DEGREES.bVII, 3, 0] },
430
+ * import { DRUM_KIT, generateDrums, playability } from '@libraz/libcantus';
431
+ * const hits = generateDrums({
432
+ * bars: 1,
433
+ * style: 'funk',
434
+ * section: 'chorus',
435
+ * ctx: { bpm: 120, complexity: { rhythmic: 0.8 } },
641
436
  * });
437
+ * const report = playability(hits, DRUM_KIT, 120);
642
438
  * ```
643
439
  * @category Composition
644
440
  */
645
- declare const BORROWED_DEGREES: {
646
- /** Flat submediant: bVI. */
647
- readonly bVI: 8;
648
- /** Flat subtonic: bVII. */
649
- readonly bVII: 10;
650
- /** Flat mediant: bIII. */
651
- readonly bIII: 11;
652
- /** Minor subdominant borrowed from the parallel minor: iv. */
653
- readonly iv: 12;
654
- /** Neapolitan: bII. */
655
- readonly bII: 13;
656
- /** Sharp subdominant, the diminished #IV. */
657
- readonly sharpIV: 14;
658
- };
441
+ declare const DRUM_KIT: PercussionProfile;
442
+
659
443
  /**
660
- * A chord root in a preset: a scale degree 0..6, or one of
661
- * {@link BORROWED_DEGREES}.
444
+ * The drum pattern dictionary: the bar-long figures that make a groove belong
445
+ * to a genre rather than merely being correct.
662
446
  *
663
- * @category Composition
447
+ * Provenance: every pattern here is the plain, unattributable pulse of its
448
+ * genre — the figure taught as "this is how that music goes" — written out on
449
+ * the sixteenth grid from that description. None reproduces the drum part of a
450
+ * particular recording, and each entry says which ground it qualifies under.
664
451
  */
665
- type ProgressionDegree = number;
452
+
666
453
  /**
667
- * Options controlling {@link generateProgression}.
454
+ * One stroke of a pattern, on the bar's sixteenth grid.
668
455
  *
669
- * @category Composition
456
+ * Velocity is a factor of the passage's base velocity rather than a MIDI
457
+ * number, so the same figure sits right in a verse and in a chorus.
670
458
  */
671
- type ProgressionOptions = {
672
- key: KeyScale;
673
- /**
674
- * Which pool of built-in presets to choose from. Ignored when `preset` names
675
- * the progression outright.
676
- */
677
- style: ProgStyle;
678
- bars: number;
679
- /** Pick a specific built-in preset by id instead of choosing one by style. */
680
- presetId?: string;
681
- /**
682
- * Use this progression rather than a built-in one. Only `degrees` is
683
- * required; see {@link BORROWED_DEGREES} for the non-diatonic codes.
684
- */
685
- preset?: Partial<ProgressionPreset> & {
686
- degrees: ProgressionDegree[];
687
- };
688
- ext?: ChordQuality | 'auto';
689
- reharmonize?: boolean;
690
- /**
691
- * Seed for the deterministic preset choice and reharmonization.
692
- *
693
- * @defaultValue 0
694
- */
695
- seed?: number;
459
+ type DrumStroke = GridEvent & {
460
+ voice: DrumVoice;
461
+ /** Sounding length in quarter-note beats; one sixteenth when absent. */
462
+ duration?: number;
463
+ articulation?: Articulation;
464
+ /** Which limb plays the stroke, where the figure depends on it. */
465
+ limb?: Limb;
696
466
  };
697
467
  /**
698
- * All built-in progression presets.
468
+ * A bar-long drum figure.
699
469
  *
700
470
  * @category Composition
701
471
  */
702
- declare function progressions(): ProgressionPreset[];
472
+ type DrumPattern = {
473
+ /** Sixteenth steps in the figure; a 4/4 bar is sixteen. */
474
+ steps: number;
475
+ strokes: DrumStroke[];
476
+ };
703
477
  /**
704
- * Presets whose style list includes the given style.
478
+ * A drum figure with the conditions it fits.
705
479
  *
706
480
  * @category Composition
707
481
  */
708
- declare function progressionsByStyle(style: ProgStyle): ProgressionPreset[];
482
+ type DrumVocabulary = Vocabulary<DrumPattern>;
709
483
  /**
710
- * The preset {@link generateProgression} would choose for a style and seed.
484
+ * Whether a caller's material is a drum pattern.
711
485
  *
712
- * The choice is otherwise invisible: the generator returns chords, not the
713
- * preset it drew them from, so a caller who wants to show or reproduce it has
714
- * no way to name it.
486
+ * A context carries one dictionary for the whole piece, so each generator
487
+ * recognises its own material: an entry meant for the bass is invisible here
488
+ * rather than mis-read.
715
489
  *
716
- * @param style The style pool to choose from.
717
- * @param seed The same seed the generator would be given.
718
- * @returns The preset that seed selects.
719
- * @throws If no preset claims the style.
720
- * @example
721
- * ```ts
722
- * import { pickProgressionPreset } from '@libraz/libcantus';
723
- * pickProgressionPreset('dance', 3).name;
724
- * ```
725
490
  * @category Composition
726
491
  */
727
- declare function pickProgressionPreset(style: ProgStyle, seed?: number): ProgressionPreset;
492
+ declare function isDrumPattern(material: unknown): material is DrumPattern;
728
493
  /**
729
- * Generate a chord progression laid out one chord per bar.
730
- *
731
- * A preset is chosen by `presetId` when given, otherwise deterministically from
732
- * the presets matching `style`, seeded by `seed`. An unknown `presetId`, or a
733
- * `style` no preset claims, is a caller error and throws rather than silently
734
- * falling back to a random preset. The preset's degrees cycle to
735
- * fill `bars`; each bar is four beats, so `startBeat` is `barIndex * 4`. Chord
736
- * roots come from the key's diatonic scale-degree mapping. When `ext` is
737
- * omitted or `'auto'`, each chord takes its diatonic triad quality; otherwise
738
- * `ext` is forced on every chord — except a chord replaced by `reharmonize`,
739
- * which is a secondary dominant and is therefore always a `dom7`; those carry
740
- * `secondaryDominant: true`. When `reharmonize` is set, some chords are
741
- * deterministically replaced with the secondary dominant (V7) of the following
742
- * chord, flagged with `secondaryDominant`.
743
- *
744
- * @param opts Generation options.
745
- * @returns One chord per bar in timeline order.
746
- * @throws If `presetId` matches no built-in preset, or `style` matches none.
747
- *
748
- * @example
749
- * ```ts
750
- * import { generateProgression, majorKey } from '@libraz/libcantus';
751
- * const chords = generateProgression({ key: majorKey(0), style: 'dance', bars: 4 });
752
- * // Deterministic for a given seed (defaults to 0); one ChordSpan per bar.
753
- * ```
494
+ * The built-in drum patterns, in declaration order.
754
495
  *
755
496
  * @category Composition
756
497
  */
757
- declare function generateProgression(opts: ProgressionOptions): ChordSpan[];
758
-
498
+ declare const DRUM_PATTERNS: readonly DrumVocabulary[];
759
499
  /**
760
- * A melody note supplied to the harmonizer: a {@link NoteEvent}.
500
+ * Options controlling {@link placeDrumPattern}.
761
501
  *
762
- * @category Reharmonization
763
- */
764
- type MelodyNote = NoteEvent;
765
- /**
766
- * Options controlling {@link harmonizeMelody}.
767
- *
768
- * @category Reharmonization
769
- */
770
- type HarmonizePlacement = {
771
- /**
772
- * Search a range of transpositions and keep the best-fitting one. The result
773
- * reports the shift it chose in `transposeSemitones`.
774
- *
775
- * @defaultValue false
776
- */
777
- transposeSearch: boolean;
778
- /**
779
- * Search octave placements as well, so a melody written too high or too low
780
- * is moved into a comfortable register.
781
- *
782
- * @defaultValue false
783
- */
784
- octaveSearch: boolean;
785
- };
786
- /**
787
- * Options controlling {@link harmonizeMelody}.
788
- *
789
- * Only `melody` is required; every other knob has the default a first call
790
- * wants.
791
- *
792
- * @category Reharmonization
502
+ * @category Composition
793
503
  */
794
- type HarmonizeOptions = {
795
- /** The melody to harmonize, in ascending onset order. */
796
- melody: readonly MelodyNote[];
797
- /**
798
- * The key to harmonize in, or `'infer'` to estimate it from the melody's
799
- * pitch-class weighting — which is what a caller who has only a melody
800
- * wants, and so the default.
801
- *
802
- * @defaultValue `'infer'`
803
- */
804
- key?: KeyScale | 'infer';
805
- /**
806
- * Length of each chord slot in beats. Any positive value is honoured, as in
807
- * {@link chordTimelineFromNotes}; a value fine enough to make the search
808
- * explode is rejected by the generation budget rather than rounded up.
809
- *
810
- * @defaultValue 2
811
- */
812
- harmonicRhythm?: number;
504
+ type DrumPatternOptions = {
505
+ /** Number of bars to generate. */
506
+ bars: number;
813
507
  /**
814
- * Time signature used to weight metric accents; defaults to 4/4. A waltz or
815
- * a jig harmonized against a 4/4 accent grid gets its chords placed on the
816
- * wrong beats, and the error accumulates bar by bar.
817
- *
818
- * @defaultValue `4/4`
508
+ * The genre whose figures may be used. Genre is what selects the material;
509
+ * the complexity dials then deform it and the difficulty ceiling rejects it.
819
510
  */
511
+ genre: Genre;
512
+ /** Section the pattern plays in, matched against each entry's own sections. */
513
+ section?: Section;
514
+ /** Time signature; defaults to 4/4. */
820
515
  ts?: TimeSignature;
821
516
  /**
822
- * How far beyond the key's own triads the chord vocabulary reaches:
823
- * `'diatonic'` uses only them, `'secondaryDominant'` adds the dominants that
824
- * tonicize each degree, and `'borrowed'` adds the parallel mode's chords too.
517
+ * The generation context. `bpm` is the tempo, `complexity.rhythmic` thins the
518
+ * figure below its middle setting and syncopates it above,
519
+ * `complexity.ornament` decides how many ghosts survive,
520
+ * `complexity.difficulty` rejects a figure the player could not keep up at
521
+ * this tempo, `vocabulary` brings figures of the caller's own, and `seed`
522
+ * fixes which figure is chosen for each bar.
825
523
  *
826
- * @defaultValue `'diatonic'`
524
+ * @defaultValue `{ seed: 0, bpm: 120 }`
827
525
  */
828
- reharmonize?: 'diatonic' | 'secondaryDominant' | 'borrowed';
526
+ ctx?: GenerationContextInput;
829
527
  /**
830
- * Whether to search transpositions and octave placements for the melody.
831
- * Both are off by default, so the melody is harmonized where it was written.
528
+ * Play the figure at half or double its written rate. This is a deformation
529
+ * like the dials, not a different figure: the dictionary entry is the same.
832
530
  *
833
- * @defaultValue both false
834
- */
835
- placement?: HarmonizePlacement;
836
- /**
837
- * Seed for the deterministic tie-break perturbation.
531
+ * It is named for the note values it changes because `feel` names the swing
532
+ * the figure is played with, here and in {@link generateDrums} alike.
838
533
  *
839
- * @defaultValue 0
534
+ * @defaultValue `'straight'`
840
535
  */
841
- seed?: number;
842
- };
843
- /**
844
- * The chosen transpose, key, chord path, and per-note roles.
845
- *
846
- * @category Reharmonization
847
- */
848
- type HarmonizeResult = {
849
- transposeSemitones: number;
850
- key: KeyScale;
851
- chords: ChordSpan[];
852
- melodyRoles: {
853
- noteIndex: number;
854
- role: HarmonyRole;
855
- }[];
856
- };
857
- /**
858
- * Harmonize a melody with a min-cost chord progression.
859
- *
860
- * The melody is segmented by `harmonicRhythm`; each segment is scored against
861
- * diatonic (and, per `reharmonize`, secondary-dominant and borrowed) candidate
862
- * chords by melody fit, and a Viterbi search picks the lowest-cost path using a
863
- * functional-flow transition cost. When `placement.transposeSearch`/`octaveSearch`
864
- * is set, the whole melody is transposed across a range and the best
865
- * `(transpose, progression)` pair is returned; a small tessitura cost breaks
866
- * ties toward placements that keep the melody in a comfortable register.
867
- *
868
- * The `seed` drives a deterministic tie-break only: it perturbs candidates by a
869
- * magnitude far below any real cost difference (see `TIE_BREAK_JITTER`),
870
- * so it can decide between chords or paths of otherwise-equal cost but never
871
- * overrides melody fit or functional flow. For a well-determined melody the
872
- * result is identical across seeds; the same seed always yields the same result.
873
- *
874
- * @param opts The melody and, optionally, the key, harmonic rhythm,
875
- * reharmonization strength, placement search, meter, and seed. Only `melody`
876
- * is required.
877
- * @returns The chosen transpose, key, chord path, and per-note roles.
878
- * @example
879
- * ```ts
880
- * import { harmonizeMelody } from '@libraz/libcantus';
881
- * const result = harmonizeMelody({
882
- * melody: [
883
- * { pitch: 60, startBeat: 0, durationBeat: 1 },
884
- * { pitch: 64, startBeat: 1, durationBeat: 1 },
885
- * ],
886
- * });
887
- * result.chords; // one ChordSpan per harmonic-rhythm segment
888
- * ```
889
- * @category Reharmonization
890
- */
891
- declare function harmonizeMelody(opts: HarmonizeOptions): HarmonizeResult;
892
-
893
- /**
894
- * A transformation applicable to a motif cell.
895
- *
896
- * @category Composition
897
- */
898
- type MotifTransform = 'transposeDiatonic' | 'transposeChromatic' | 'invert' | 'retrograde' | 'augment' | 'diminish' | 'sequence';
899
- /**
900
- * A single note within a motif cell.
901
- *
902
- * @category Composition
903
- */
904
- type MotifNote = {
905
- pitch: number;
906
- startBeat: number;
907
- durationBeat: number;
908
- };
909
- /**
910
- * A short melodic cell.
911
- *
912
- * @category Composition
913
- */
914
- type MotifCell = {
915
- notes: MotifNote[];
916
- };
917
- /**
918
- * Melodic contour shape for {@link generateMotif}.
919
- *
920
- * @category Composition
921
- */
922
- type MotifContour = 'arch' | 'ascending' | 'descending' | 'wave';
923
- /**
924
- * Options controlling {@link generateMotif}.
925
- *
926
- * @category Composition
927
- */
928
- type MotifOptions = {
929
- key: KeyScale;
930
- chord?: Chord | null;
931
- /** Length of the motif in bars of `ts`. */
932
- bars: number;
933
- /**
934
- * Meter the bars are counted in. It sets the bar length, so a motif shares a
935
- * bar grid with the other generators instead of assuming four beats.
936
- *
937
- * @defaultValue 4/4
938
- */
939
- ts?: TimeSignature;
536
+ rate?: 'straight' | 'half' | 'double';
940
537
  /**
941
- * Melodic contour shape the line follows.
538
+ * The swing the figure is played with. Some entries are written on the
539
+ * straight grid and are only themselves once a triplet feel is applied at
540
+ * render — the half-time shuffle is the plain case — so this is the surface
541
+ * those entries name when they say the feel comes from the performance.
942
542
  *
943
- * @defaultValue `'arch'`
543
+ * @defaultValue `'straight'`
944
544
  */
945
- contour?: MotifContour;
946
- /**
947
- * Probability in [0, 1] that a note is nudged by a single diatonic step. The
948
- * nudge direction is balanced (up or down with equal odds), so it adds
949
- * variety without biasing the line off its contour. Default 0, which
950
- * reproduces the requested contour exactly (no drift, tails return to tonic).
951
- *
952
- * @defaultValue 0
953
- */
954
- jitter?: number;
545
+ feel?: GrooveFeel;
546
+ /** Base velocity the figure's own factors are read against. */
547
+ velocity?: number;
955
548
  /**
956
- * Seed for the deterministic PRNG.
549
+ * Maximum number of onsets {@link placeDrumPattern} may write. One figure is
550
+ * placed per bar, so this is the guard against an unbounded caller rather
551
+ * than a limit on any search.
957
552
  *
958
- * @defaultValue 0
553
+ * @defaultValue 1000000
959
554
  */
960
- seed?: number;
555
+ budget?: number;
961
556
  };
962
557
  /**
963
- * Generate a short seed motif over `bars`.
964
- *
965
- * The melody follows the requested contour in diatonic steps from the tonic,
966
- * snapped to the key. When a chord is supplied, notes landing on bar downbeats
967
- * are pulled to the nearest chord tone. An optional `jitter` adds balanced
968
- * per-note variation without biasing the contour; it is off by default, so the
969
- * contour is reproduced exactly. Output is deterministic for a given seed.
970
- *
971
- * @param opts Key, optional chord, length, contour, jitter, and seed.
972
- * @returns The generated motif cell.
973
- *
974
- * @example
975
- * ```ts
976
- * import { generateMotif, majorKey } from '@libraz/libcantus';
977
- * const cell = generateMotif({ key: majorKey(0), bars: 2, contour: 'arch' });
978
- * // cell.notes is a deterministic MotifCell (seed defaults to 0)
979
- * ```
980
- *
981
- * @category Composition
982
- */
983
- declare function generateMotif(opts: MotifOptions): MotifCell;
984
- /**
985
- * Turn a motif cell into note events.
986
- *
987
- * A {@link MotifNote} already carries the {@link NoteEvent} fields, but a cell
988
- * wraps them in an object; this unwraps it, so a motif can be humanized,
989
- * grooved, or written out like any other line.
990
- *
991
- * @param cell The motif to convert.
992
- * @returns The cell's notes, copied, in the order they are stored.
993
- * @example
994
- * ```ts
995
- * import { generateMotif, humanize, majorKey, motifToNoteEvents } from '@libraz/libcantus';
996
- * humanize(motifToNoteEvents(generateMotif({ key: majorKey(0), bars: 2 })));
997
- * ```
998
- * @category Composition
999
- */
1000
- declare function motifToNoteEvents(cell: MotifCell): NoteEvent[];
1001
- /**
1002
- * Apply a transformation to a motif cell.
558
+ * Generate a genre-characteristic groove from the pattern dictionary.
1003
559
  *
1004
- * `invert` reflects pitches about the first note (chromatic, self-inverse);
1005
- * `retrograde` mirrors onsets about the cell span, preserving rests (self-inverse);
1006
- * `augment`/`diminish` scale time by `amount ?? 2` and its reciprocal;
1007
- * `transposeChromatic` adds `amount` semitones; `transposeDiatonic` shifts by
1008
- * `amount` scale degrees when a `key` is given, or — with no `key` — falls back
1009
- * to a chromatic shift of `amount` semitones; `sequence` appends a shifted copy
1010
- * (by `amount` diatonic degrees with a `key`, or by `amount` semitones without
1011
- * one, since it delegates to `transposeDiatonic`).
560
+ * The three dials never fight, because each owns a different step: the genre
561
+ * chooses which figures are candidates, the complexity dials deform the chosen
562
+ * figure, and the difficulty ceiling rejects a figure whose closest pair of
563
+ * strokes is faster than a player at that ceiling sustains. A rejected figure
564
+ * is dropped from the candidates rather than simplified, since a simplified
565
+ * figure is a different figure.
1012
566
  *
1013
- * @param cell The cell to transform.
1014
- * @param t The transformation.
1015
- * @param amount Optional parameter (semitones, degrees, or time factor).
1016
- * @param key Key context for the diatonic transforms; without it,
1017
- * `transposeDiatonic` and `sequence` shift chromatically by semitones.
1018
- * @returns The transformed cell.
1019
- * @throws If a transformed pitch would fall outside the MIDI range 0..127.
1020
- *
1021
- * @example
1022
- * ```ts
1023
- * import { generateMotif, transformMotif, majorKey } from '@libraz/libcantus';
1024
- * const cell = generateMotif({ key: majorKey(0), bars: 1 });
1025
- * const inverted = transformMotif(cell, 'invert');
1026
- * ```
1027
- *
1028
- * @category Composition
1029
- */
1030
- declare function transformMotif(cell: MotifCell, t: MotifTransform, amount?: number, key?: KeyScale): MotifCell;
1031
- /**
1032
- * Lay a motif across `bars` and snap it to a chord timeline.
1033
- *
1034
- * The cell is tiled back-to-back to fill the requested span; each note is then
1035
- * pulled to the nearest chord tone of the segment sounding at its onset, so the
1036
- * developed line spells the underlying harmony.
1037
- *
1038
- * @param cell The source motif.
1039
- * @param timeline Chord segments to snap against.
1040
- * @param key Key context (used to keep snapped pitches sensible).
1041
- * @param bars Number of bars to fill.
1042
- * @param ts Meter the bars are counted in; defaults to 4/4.
1043
- * @returns The developed, harmony-aware cell.
567
+ * @param opts Generation options.
568
+ * @returns Percussion onsets in onset order, ties broken by pitch.
569
+ * @throws If the bar count, tempo, or time signature is invalid, or the genre
570
+ * is not one this library names.
1044
571
  *
1045
572
  * @example
1046
573
  * ```ts
1047
- * import { chordTimelineFromChords, developMotif, generateMotif, majorKey } from '@libraz/libcantus';
1048
- * const key = majorKey(0);
1049
- * const timeline = chordTimelineFromChords([{ rootPc: 0, quality: 'maj', startBeat: 0 }], 8);
1050
- * const developed = developMotif(generateMotif({ key, bars: 1 }), timeline, key, 2);
574
+ * import { placeDrumPattern } from '@libraz/libcantus';
575
+ * const hits = placeDrumPattern({ bars: 2, genre: 'bossa', ctx: { seed: 7, bpm: 130 } });
1051
576
  * ```
1052
577
  *
1053
578
  * @category Composition
1054
579
  */
1055
- declare function developMotif(cell: MotifCell, timeline: ChordTimeline, key: KeyScale, bars: number, ts?: TimeSignature): MotifCell;
580
+ declare function placeDrumPattern(opts: DrumPatternOptions): DrumHit[];
1056
581
 
1057
582
  /**
1058
- * Reharmonization: chord substitution, modal-interchange palettes, and negative
1059
- * harmony. These transforms take an existing chord (or a key) and propose
1060
- * alternative harmonies that preserve a chosen relationship — a shared function,
1061
- * a common tone, a tonicizing dominant, or a reflection across the key axis.
1062
- *
1063
- * Roots are pitch classes (0..11); no spelled key signature is required.
1064
- */
1065
-
1066
- /**
1067
- * The kind of substitution relationship a candidate realizes.
1068
- *
1069
- * @category Reharmonization
1070
- */
1071
- type SubstitutionType = 'tritone' | 'relative' | 'borrowed' | 'chromaticMediant' | 'secondaryDominant';
1072
- /**
1073
- * A proposed chord substitution with its relationship, numeral, and function.
583
+ * What a melody note contributes to the harmony: either a structural tone the
584
+ * chord has to account for, or the ornament figure that explains it away.
1074
585
  *
1075
586
  * @category Reharmonization
1076
587
  */
1077
- type Substitution = {
1078
- chord: Chord;
1079
- type: SubstitutionType;
1080
- roman: string;
1081
- function: HarmonicFunction;
1082
- };
588
+ type MelodyToneRole = 'structural' | 'passing' | 'neighbor' | 'appoggiatura' | 'suspension' | 'anticipation' | 'escape';
1083
589
  /**
1084
- * Options controlling which substitutions {@link substituteChord} returns.
590
+ * One melody note's classification: its index in the classified melody, the
591
+ * figure it forms, and whether that figure makes it ornamental.
1085
592
  *
1086
593
  * @category Reharmonization
1087
594
  */
1088
- type SubstituteOptions = {
1089
- /**
1090
- * Melody pitch classes that must remain chord tones. When given, only
1091
- * substitutions whose pitch classes contain every listed pitch class are
1092
- * kept, so the melody stays consonant against the new harmony.
1093
- */
1094
- melodyPcs?: number[];
595
+ type ClassifiedMelodyTone = {
596
+ noteIndex: number;
597
+ role: MelodyToneRole;
598
+ /** True for every role but `'structural'`. */
599
+ ornamental: boolean;
1095
600
  };
1096
601
  /**
1097
- * Propose substitutions for a chord within a key.
1098
- *
1099
- * Candidates are generated by several relationships and deduplicated against the
1100
- * original chord and each other:
1101
- *
1102
- * - `tritone`: for a dominant-type chord, the dominant seventh a tritone away.
1103
- * - `secondaryDominant`: for a stable target, the dominant seventh a fifth
1104
- * above its root (tonicizing it).
1105
- * - `relative`: a diatonic triad a third away that shares two common tones.
1106
- * - `borrowed`: a triad from the parallel mode with the same harmonic function.
1107
- * - `chromaticMediant`: a major/minor triad a third away sharing one common
1108
- * tone.
1109
- *
1110
- * Each result carries its Roman numeral and harmonic function in `key`. When
1111
- * `opts.melodyPcs` is given, only substitutions whose pitch classes contain all
1112
- * of those pitch classes are returned.
1113
- *
1114
- * @param chord The chord to reharmonize.
1115
- * @param key The prevailing key.
1116
- * @param opts Optional melody-preservation constraint.
1117
- * @returns The deduplicated substitution candidates.
602
+ * Classify a melody's ornaments from the melody and the metre alone.
603
+ *
604
+ * This is deliberately chord-free: a harmonizer needs to know which notes the
605
+ * chords must explain *before* it can choose them, so nothing here may consult a
606
+ * harmony. What remains is the melodic and metric evidence — stepwise approach
607
+ * and departure, repeated pitches, leaps, and the relative metric weight and
608
+ * length of a note against its neighbours.
609
+ *
610
+ * The figures are recognized in the order suspension, passing, neighbor,
611
+ * anticipation, appoggiatura, escape. A note is judged ornamental only when it
612
+ * is metrically and rhythmically subordinate to the neighbours that frame it
613
+ * (or, for the accented figures, when it leans on a stronger beat than its
614
+ * approach and gives way to a resolution). A note without an adjacent
615
+ * predecessor and successor has no figure to belong to and stays structural, so
616
+ * the first and last notes of a phrase are always structural.
617
+ *
618
+ * @param melody The melody, in ascending onset order.
619
+ * @param ts Time signature used to weight metric accents.
620
+ * @returns One classification per input note, in input order.
1118
621
  * @example
1119
622
  * ```ts
1120
- * import { substituteChord, makeChord, majorKey } from '@libraz/libcantus';
1121
- * const subs = substituteChord(makeChord(7, 'dom7'), majorKey(0));
1122
- * // includes the tritone sub Db7 (rootPc 1, quality 'dom7')
623
+ * import { classifyMelodyTones } from '@libraz/libcantus';
624
+ * const melody = [60, 62, 64, 60].map((pitch, i) => ({
625
+ * pitch,
626
+ * startBeat: i,
627
+ * durationBeat: 1,
628
+ * }));
629
+ * classifyMelodyTones(melody, { numerator: 4, denominator: 4 })[1];
630
+ * // { noteIndex: 1, role: 'passing', ornamental: true }
1123
631
  * ```
1124
632
  * @category Reharmonization
1125
633
  */
1126
- declare function substituteChord(chord: Chord, key: KeyScale, opts?: SubstituteOptions): Substitution[];
1127
- /**
1128
- * A borrowed chord in the modal-interchange palette of a key.
1129
- *
1130
- * @category Reharmonization
1131
- */
1132
- type BorrowedChord = {
1133
- chord: Chord;
1134
- roman: string;
1135
- source: BorrowedSource;
1136
- };
1137
- /**
1138
- * The modal-interchange palette of a key.
1139
- *
1140
- * Lists the diatonic triads of the {@link parallelKey} that are not diatonic to
1141
- * `key` (the classic borrowed chords such as `iv`, `bVI`, and `bVII` in a major
1142
- * key), plus the Neapolitan (a major triad on the flat second degree). Each
1143
- * carries its Roman numeral and {@link borrowedSource} relative to `key`.
1144
- *
1145
- * @param key The prevailing key.
1146
- * @returns The borrowed-chord palette.
1147
- * @category Reharmonization
1148
- */
1149
- declare function modalInterchangePalette(key: KeyScale): BorrowedChord[];
1150
- /**
1151
- * Reflect a chord across the tonic–dominant axis of a key (negative harmony).
1152
- *
1153
- * Every pitch class `p` maps to `(2*T + 7 - p) mod 12`, where `T` is the key
1154
- * tonic — the reflection that swaps the tonic with the dominant. The mirrored
1155
- * pitch classes (including the bass, if set) are reassembled into a chord by
1156
- * {@link detectChordBest}; when no chord is recognized, the mirrored root and
1157
- * the original quality are used as a fallback.
1158
- *
1159
- * @param chord The chord to reflect.
1160
- * @param key The prevailing key.
1161
- * @returns The negative-harmony counterpart of the chord.
1162
- * @example
1163
- * ```ts
1164
- * import { negativeHarmonyMirror, makeChord, majorKey } from '@libraz/libcantus';
1165
- * negativeHarmonyMirror(makeChord(7, 'maj'), majorKey(0));
1166
- * // G major reflected across C's axis => F minor (rootPc 5, quality 'min')
1167
- * ```
1168
- * @category Reharmonization
1169
- */
1170
- declare function negativeHarmonyMirror(chord: Chord, key: KeyScale): Chord;
1171
-
1172
- /**
1173
- * Deterministic rhythm generation over a metric grid.
1174
- *
1175
- * Builds a fixed-resolution grid across one or more bars of a time signature
1176
- * and decides, per grid slot, whether it becomes a note onset. Onset
1177
- * probability follows the metric-accent hierarchy from the `meter` module, so
1178
- * strong positions (downbeats, secondary pulses) are favored over weak
1179
- * off-pulse subdivisions. Given a seed the output is fully reproducible.
1180
- *
1181
- * Positions and durations are measured in quarter-note beats, matching the
1182
- * library-wide convention.
1183
- */
1184
-
1185
- /**
1186
- * A single rhythmic note: an onset position and how long it sounds.
1187
- *
1188
- * @category Rhythm & Meter
1189
- */
1190
- type RhythmEvent = {
1191
- /** Onset position in quarter-note beats, absolute from the start of the span. */
1192
- position: number;
1193
- /** Duration in quarter-note beats, extending to the next onset. */
1194
- duration: number;
1195
- };
1196
- /**
1197
- * Turn rhythm events into note events at a fixed pitch.
1198
- *
1199
- * {@link RhythmEvent} names its fields `position`/`duration` because a rhythm
1200
- * has no pitch; every other generator in the library speaks {@link NoteEvent}.
1201
- * This is the bridge, so a generated rhythm can be humanized, grooved, or
1202
- * written out like any other line.
1203
- *
1204
- * @param events The rhythm events to convert.
1205
- * @param pitch The MIDI pitch to give every note.
1206
- * @param velocity Velocity for every note; defaults to a mezzo-forte 96.
1207
- * @returns The note events, in the order given.
1208
- * @example
1209
- * ```ts
1210
- * import { generateRhythm, humanize, rhythmToNoteEvents } from '@libraz/libcantus';
1211
- * const rhythm = generateRhythm({ numerator: 4, denominator: 4 }, { bars: 2 });
1212
- * humanize(rhythmToNoteEvents(rhythm, 38));
1213
- * ```
1214
- * @category Rhythm & Meter
1215
- */
1216
- declare function rhythmToNoteEvents(events: RhythmEvent[], pitch: number, velocity?: number): NoteEvent[];
1217
- /**
1218
- * Options controlling {@link generateRhythm}.
1219
- *
1220
- * @category Rhythm & Meter
1221
- */
1222
- type RhythmOptions = {
1223
- /**
1224
- * Seed for the deterministic PRNG.
1225
- *
1226
- * @defaultValue 0
1227
- */
1228
- seed?: number;
1229
- /**
1230
- * Number of bars to generate.
1231
- *
1232
- * @defaultValue 1
1233
- */
1234
- bars?: number;
1235
- /**
1236
- * Grid resolution as the number of equal grid steps per quarter-note beat.
1237
- * The default of 2 yields an eighth-note grid; 4 yields a sixteenth-note
1238
- * grid, 3 an eighth-note triplet grid, and so on.
1239
- *
1240
- * @defaultValue 2
1241
- */
1242
- subdivision?: number;
1243
- /**
1244
- * Overall onset density in [0, 1]. Scales the per-slot onset probability, so
1245
- * higher values fill more grid slots. Values outside [0, 1] are clamped into
1246
- * range.
1247
- *
1248
- * @defaultValue 0.5
1249
- */
1250
- density?: number;
1251
- };
1252
- /**
1253
- * Map a metric weight (0–3 from {@link metricWeight}) to a base onset
1254
- * probability before density scaling. Strong positions are much more likely to
1255
- * carry an onset than weak ones; the curve is intentionally small and explicit
1256
- * so its behavior is easy to inspect and test.
1257
- *
1258
- * @param weight The metric weight, 0 (off-pulse) to 3 (downbeat).
1259
- * @returns A base onset probability in [0, 1].
1260
- * @category Rhythm & Meter
1261
- */
1262
- declare function onsetWeightCurve(weight: number): number;
1263
- /**
1264
- * Generate a deterministic rhythmic pattern over `bars` bars of a time
1265
- * signature. A grid is built at the chosen subdivision; each slot becomes an
1266
- * onset with a probability derived from its metric weight scaled by `density`
1267
- * and sampled from the seeded PRNG. Every bar's downbeat is always an onset, so
1268
- * a pattern of any length has a reliable pulse independent of the seed. Each
1269
- * event's duration extends to the next onset, and the last event extends to the
1270
- * end of the span. `density` is clamped to [0, 1].
1271
- *
1272
- * @param ts The time signature.
1273
- * @param opts Generation options.
1274
- * @returns Onset events sorted by position, non-overlapping, covering the span.
1275
- * @example
1276
- * ```ts
1277
- * import { parseTimeSignature, generateRhythm } from '@libraz/libcantus';
1278
- * const ts = parseTimeSignature('4/4');
1279
- * generateRhythm(ts, { seed: 42, density: 0.6 }); // onset events over one bar
1280
- * ```
1281
- * @category Rhythm & Meter
1282
- */
1283
- declare function generateRhythm(ts: TimeSignature, opts?: RhythmOptions): RhythmEvent[];
1284
- /**
1285
- * Onset density of a generated pattern: the mean number of onsets per bar.
1286
- *
1287
- * @param events Events from {@link generateRhythm}.
1288
- * @param ts The time signature.
1289
- * @returns The onset count divided by the number of bars the events span.
1290
- * @category Rhythm & Meter
1291
- */
1292
- declare function rhythmDensity(events: RhythmEvent[], ts: TimeSignature): number;
634
+ declare function classifyMelodyTones(melody: readonly NoteEvent[], ts: TimeSignature): ClassifiedMelodyTone[];
1293
635
 
1294
- export { BASS_STYLES, BORROWED_DEGREES, type BassLineOptions, type BassSegment, type BassStyle, type BorrowedChord, BorrowedSource, Chord as ChordData, ChordQuality, ChordSegment, ChordSpan, ChordTimeline, type CounterMelodyOptions, DRUM_NOTES, type DrumHit, type DrumRole, type DrumVoice, type DrumsOptions, type EuclideanKick, type GrooveFeel, type GrooveSlot, type GrooveStyle, type GrooveTemplate, HarmonicFunction, type HarmonizeOptions, type HarmonizePlacement, type HarmonizeResult, HarmonyRole, type HumanizeOptions, KeyScale, type MelodyNote, type MotifCell, type MotifContour, type MotifNote, type MotifOptions, type MotifTransform, NoteEvent, type ProgFunction, type ProgStyle, type ProgressionDegree, type ProgressionOptions, type ProgressionPreset, type RhythmEvent, type RhythmOptions, type Section, type SubstituteOptions, type Substitution, type SubstitutionType, TimeSignature, applyGrooveTemplate, developMotif, drumVoiceOf, extractGrooveTemplate, generateBassLine, generateCounterMelody, generateDrums, generateMotif, generateProgression, generateRhythm, harmonizeMelody, humanize, modalInterchangePalette, motifToNoteEvents, negativeHarmonyMirror, onsetWeightCurve, pickProgressionPreset, progressions, progressionsByStyle, rhythmDensity, rhythmToNoteEvents, substituteChord, transformMotif };
636
+ export { Articulation, BASS_LICKS, type BassLick, ChordLike, ChordSegment, type ClassifiedMelodyTone, DRUM_KIT, DRUM_PATTERNS, DrumHit, type DrumPattern, type DrumPatternOptions, type DrumStroke, DrumStyle, type DrumVocabulary, DrumVoice, FILL_ARCHETYPES, FILL_TYPES, type FillArchetype, type FillStroke, type FillType, type FillVelocity, GenerationContextInput, Genre, GridEvent, GrooveFeel, type ImitationAnswer, type ImitationOptions, IntervalLike, KICK_FIGURES, KICK_STEPS, KeyLike, type KickFigure, type KickPattern, type KickSlot, type LickMaterial, type LickNote, Limb, MAX_DIFFICULTY, MIN_DIFFICULTY, type MelodyToneRole, NoteEvent, PercussionProfile, type PlaceLicksOptions, Section, StringedProfile, TimeSignature, Vocabulary, classifyMelodyTones, imitate, isDrumPattern, isFillArchetype, isLickMaterial, placeDrumPattern, placeLicks, sustainsShift, sustainsStrokes };