@libraz/libcantus 0.9.3 → 0.9.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (198) hide show
  1. package/README.md +2 -2
  2. package/dist/analyze/index.cjs +39 -1947
  3. package/dist/analyze/index.cjs.map +1 -1
  4. package/dist/analyze/index.d.cts +197 -37
  5. package/dist/analyze/index.d.ts +197 -37
  6. package/dist/analyze/index.js +19 -8
  7. package/dist/chunk-36N73KZ3.cjs +246 -0
  8. package/dist/chunk-36N73KZ3.cjs.map +1 -0
  9. package/dist/chunk-4HLLZPXT.cjs +3077 -0
  10. package/dist/chunk-4HLLZPXT.cjs.map +1 -0
  11. package/dist/chunk-7MGZ7D62.cjs +498 -0
  12. package/dist/chunk-7MGZ7D62.cjs.map +1 -0
  13. package/dist/{chunk-4MHA432G.js → chunk-7NKDPF74.js} +403 -180
  14. package/dist/chunk-7NKDPF74.js.map +1 -0
  15. package/dist/chunk-CAN76NCU.cjs +679 -0
  16. package/dist/chunk-CAN76NCU.cjs.map +1 -0
  17. package/dist/{chunk-MIWO425M.js → chunk-D2FAJBYL.js} +101 -34
  18. package/dist/chunk-D2FAJBYL.js.map +1 -0
  19. package/dist/{chunk-SGU7SPLK.js → chunk-EHVEBWM5.js} +1056 -531
  20. package/dist/chunk-EHVEBWM5.js.map +1 -0
  21. package/dist/chunk-FCANXBAT.cjs +30 -0
  22. package/dist/chunk-FCANXBAT.cjs.map +1 -0
  23. package/dist/chunk-FNDWXRRI.cjs +479 -0
  24. package/dist/chunk-FNDWXRRI.cjs.map +1 -0
  25. package/dist/{chunk-IT2O4XFE.js → chunk-GQSGS2N4.js} +43 -24
  26. package/dist/chunk-GQSGS2N4.js.map +1 -0
  27. package/dist/{chunk-62QZKCJQ.js → chunk-HKGYCQ34.js} +10 -6
  28. package/dist/chunk-HKGYCQ34.js.map +1 -0
  29. package/dist/{chunk-AKLYPWIN.js → chunk-I24MV65U.js} +29 -6
  30. package/dist/chunk-I24MV65U.js.map +1 -0
  31. package/dist/{chunk-CHMXELDS.js → chunk-KMHOICIY.js} +311 -181
  32. package/dist/chunk-KMHOICIY.js.map +1 -0
  33. package/dist/chunk-LW53AHQU.cjs +1117 -0
  34. package/dist/chunk-LW53AHQU.cjs.map +1 -0
  35. package/dist/{chunk-2ZWMSJH6.js → chunk-NKIBQ6PT.js} +2 -2
  36. package/dist/chunk-NKIBQ6PT.js.map +1 -0
  37. package/dist/chunk-NP4NA2FB.cjs +106 -0
  38. package/dist/chunk-NP4NA2FB.cjs.map +1 -0
  39. package/dist/chunk-NVE34YGZ.cjs +1449 -0
  40. package/dist/chunk-NVE34YGZ.cjs.map +1 -0
  41. package/dist/{chunk-EH6EM37U.js → chunk-NYYWMV2C.js} +61 -49
  42. package/dist/chunk-NYYWMV2C.js.map +1 -0
  43. package/dist/{chunk-GOAW4VYL.js → chunk-Q2XUP227.js} +243 -117
  44. package/dist/chunk-Q2XUP227.js.map +1 -0
  45. package/dist/chunk-Q6OP4U43.cjs +327 -0
  46. package/dist/chunk-Q6OP4U43.cjs.map +1 -0
  47. package/dist/chunk-RC6GBMCD.cjs +845 -0
  48. package/dist/chunk-RC6GBMCD.cjs.map +1 -0
  49. package/dist/{chunk-C5XYKUVG.js → chunk-RK35RIN7.js} +370 -157
  50. package/dist/chunk-RK35RIN7.js.map +1 -0
  51. package/dist/chunk-RUMU3UA6.cjs +53 -0
  52. package/dist/chunk-RUMU3UA6.cjs.map +1 -0
  53. package/dist/chunk-T64C7ZD7.cjs +1 -0
  54. package/dist/chunk-T64C7ZD7.cjs.map +1 -0
  55. package/dist/chunk-XEBG3G53.js +498 -0
  56. package/dist/chunk-XEBG3G53.js.map +1 -0
  57. package/dist/{chunk-I5R7ZBJS.js → chunk-YLEMGEFH.js} +103 -59
  58. package/dist/chunk-YLEMGEFH.js.map +1 -0
  59. package/dist/chunk-ZEJR4TKL.cjs +173 -0
  60. package/dist/chunk-ZEJR4TKL.cjs.map +1 -0
  61. package/dist/core/index.cjs +130 -647
  62. package/dist/core/index.cjs.map +1 -1
  63. package/dist/core/index.d.cts +301 -39
  64. package/dist/core/index.d.ts +301 -39
  65. package/dist/core/index.js +54 -16
  66. package/dist/{function-DYt4iKsn.d.cts → function-BtiZb9i9.d.ts} +28 -7
  67. package/dist/{function-DVH4Eusr.d.ts → function-DNugAOrt.d.cts} +28 -7
  68. package/dist/generate/index.cjs +28 -4085
  69. package/dist/generate/index.cjs.map +1 -1
  70. package/dist/generate/index.d.cts +370 -62
  71. package/dist/generate/index.d.ts +370 -62
  72. package/dist/generate/index.js +23 -9
  73. package/dist/{index-CxqBQtH1.d.cts → index-6VR8-kit.d.ts} +19 -4
  74. package/dist/index-70p168VM.d.ts +192 -0
  75. package/dist/{index-CpOsP1y9.d.cts → index-Be74mcon.d.cts} +18 -17
  76. package/dist/index-BtXT6UQ0.d.cts +192 -0
  77. package/dist/{index-DxvARIP2.d.ts → index-C5UBbQ4G.d.ts} +18 -17
  78. package/dist/{index-Bt4tOUD9.d.ts → index-C83mZI1P.d.ts} +93 -8
  79. package/dist/{index-Bg5dYMmZ.d.ts → index-D6b6na69.d.cts} +14 -11
  80. package/dist/{index-Cl7mhOFM.d.cts → index-DXd9JikN.d.cts} +92 -10
  81. package/dist/{index-Cl7mhOFM.d.ts → index-DXd9JikN.d.ts} +92 -10
  82. package/dist/index-OG65ocOr.d.cts +272 -0
  83. package/dist/index-OG65ocOr.d.ts +272 -0
  84. package/dist/{index-Jc0Ryi79.d.ts → index-tJhkmQgE.d.cts} +19 -4
  85. package/dist/{index-Dikst44v.d.cts → index-tSQh-xwY.d.ts} +14 -11
  86. package/dist/{index-BNlGP86A.d.cts → index-y_ak9_Hi.d.cts} +93 -8
  87. package/dist/index.cjs +419 -7383
  88. package/dist/index.cjs.map +1 -1
  89. package/dist/index.d.cts +14 -14
  90. package/dist/index.d.ts +14 -14
  91. package/dist/index.js +111 -27
  92. package/dist/model/index.cjs +8 -2562
  93. package/dist/model/index.cjs.map +1 -1
  94. package/dist/model/index.d.cts +433 -75
  95. package/dist/model/index.d.ts +433 -75
  96. package/dist/model/index.js +6 -7
  97. package/dist/styled-DIRkk9Uz.d.ts +506 -0
  98. package/dist/styled-DJbkJQfj.d.cts +506 -0
  99. package/dist/theory/index.cjs +172 -1822
  100. package/dist/theory/index.cjs.map +1 -1
  101. package/dist/theory/index.d.cts +151 -233
  102. package/dist/theory/index.d.ts +151 -233
  103. package/dist/theory/index.js +30 -7
  104. package/dist/{types-MGP29r3t.d.cts → types-BN6_Q9Kl.d.cts} +11 -2
  105. package/dist/{types-MGP29r3t.d.ts → types-BN6_Q9Kl.d.ts} +11 -2
  106. package/package.json +24 -6
  107. package/dist/chunk-2ZWMSJH6.js.map +0 -1
  108. package/dist/chunk-4MHA432G.js.map +0 -1
  109. package/dist/chunk-62QZKCJQ.js.map +0 -1
  110. package/dist/chunk-AKLYPWIN.js.map +0 -1
  111. package/dist/chunk-C5XYKUVG.js.map +0 -1
  112. package/dist/chunk-CHMXELDS.js.map +0 -1
  113. package/dist/chunk-DYWFNC4A.js +0 -152
  114. package/dist/chunk-DYWFNC4A.js.map +0 -1
  115. package/dist/chunk-EH6EM37U.js.map +0 -1
  116. package/dist/chunk-FBQK3ZQN.js +0 -131
  117. package/dist/chunk-FBQK3ZQN.js.map +0 -1
  118. package/dist/chunk-GOAW4VYL.js.map +0 -1
  119. package/dist/chunk-I5R7ZBJS.js.map +0 -1
  120. package/dist/chunk-IT2O4XFE.js.map +0 -1
  121. package/dist/chunk-MIWO425M.js.map +0 -1
  122. package/dist/chunk-SGU7SPLK.js.map +0 -1
  123. package/dist/index-CGMZNCx6.d.cts +0 -140
  124. package/dist/index-CGMZNCx6.d.ts +0 -140
  125. package/dist/index-CnZCo29u.d.cts +0 -108
  126. package/dist/index-JnF4v9bx.d.ts +0 -108
  127. package/dist/styled-DZ7GjShb.d.ts +0 -282
  128. package/dist/styled-ZTbdaVFM.d.cts +0 -282
  129. package/src/analyze/arrange/index.ts +0 -21
  130. package/src/analyze/arrange/internal.ts +0 -127
  131. package/src/analyze/arrange/tension.ts +0 -199
  132. package/src/analyze/arrange/tracks.ts +0 -312
  133. package/src/analyze/detect/index.ts +0 -272
  134. package/src/analyze/functional/borrowed.ts +0 -84
  135. package/src/analyze/functional/cadence.ts +0 -66
  136. package/src/analyze/functional/function.ts +0 -184
  137. package/src/analyze/functional/index.ts +0 -23
  138. package/src/analyze/functional/internal.ts +0 -29
  139. package/src/analyze/functional/roman.ts +0 -402
  140. package/src/analyze/index.ts +0 -49
  141. package/src/analyze/timeline/index.ts +0 -451
  142. package/src/analyze/voice/index.ts +0 -339
  143. package/src/core/event-index/index.ts +0 -104
  144. package/src/core/index.ts +0 -61
  145. package/src/core/interval/index.ts +0 -81
  146. package/src/core/meter/index.ts +0 -302
  147. package/src/core/pitch/index.ts +0 -311
  148. package/src/core/random/index.ts +0 -76
  149. package/src/core/tuning/index.ts +0 -186
  150. package/src/core/types.ts +0 -29
  151. package/src/core/validation/index.ts +0 -119
  152. package/src/generate/bass/index.ts +0 -373
  153. package/src/generate/countermelody/index.ts +0 -470
  154. package/src/generate/drums/beat.ts +0 -266
  155. package/src/generate/drums/euclid.ts +0 -65
  156. package/src/generate/drums/fills.ts +0 -356
  157. package/src/generate/drums/ghost.ts +0 -119
  158. package/src/generate/drums/hihat.ts +0 -285
  159. package/src/generate/drums/hit.ts +0 -31
  160. package/src/generate/drums/index.ts +0 -361
  161. package/src/generate/drums/internal.ts +0 -257
  162. package/src/generate/drums/kick.ts +0 -160
  163. package/src/generate/drums/percussion.ts +0 -121
  164. package/src/generate/drums/rng.ts +0 -7
  165. package/src/generate/drums/swing.ts +0 -71
  166. package/src/generate/groove/index.ts +0 -351
  167. package/src/generate/harmonize/index.ts +0 -451
  168. package/src/generate/index.ts +0 -58
  169. package/src/generate/motif/index.ts +0 -422
  170. package/src/generate/progression/index.ts +0 -362
  171. package/src/generate/reharmony/index.ts +0 -323
  172. package/src/generate/rhythm/index.ts +0 -175
  173. package/src/index.ts +0 -13
  174. package/src/model/chord.ts +0 -442
  175. package/src/model/index.ts +0 -15
  176. package/src/model/interval.ts +0 -82
  177. package/src/model/key.ts +0 -266
  178. package/src/model/note.ts +0 -176
  179. package/src/model/progression.ts +0 -161
  180. package/src/model/shared.ts +0 -12
  181. package/src/theory/chord/index.ts +0 -375
  182. package/src/theory/chordscale/index.ts +0 -374
  183. package/src/theory/counterpoint/index.ts +0 -230
  184. package/src/theory/harmony/index.ts +0 -77
  185. package/src/theory/index.ts +0 -112
  186. package/src/theory/safety/index.ts +0 -488
  187. package/src/theory/scale/degrees.ts +0 -123
  188. package/src/theory/scale/index.ts +0 -28
  189. package/src/theory/scale/key.ts +0 -56
  190. package/src/theory/scale/masks.ts +0 -153
  191. package/src/theory/spelling/index.ts +0 -249
  192. package/src/theory/symbol/index.ts +0 -268
  193. package/src/theory/voicing/index.ts +0 -5
  194. package/src/theory/voicing/internal.ts +0 -168
  195. package/src/theory/voicing/leading.ts +0 -112
  196. package/src/theory/voicing/satb.ts +0 -216
  197. package/src/theory/voicing/styled.ts +0 -199
  198. package/tsconfig.json +0 -23
@@ -1,24 +1,24 @@
1
1
  import {
2
2
  createRng
3
- } from "./chunk-62QZKCJQ.js";
3
+ } from "./chunk-HKGYCQ34.js";
4
4
  import {
5
5
  beatsPerBar,
6
6
  createNoteEventIndex,
7
7
  formatTimeSignature,
8
8
  isStrongBeat,
9
9
  metricWeight
10
- } from "./chunk-I5R7ZBJS.js";
10
+ } from "./chunk-YLEMGEFH.js";
11
11
  import {
12
12
  isDiatonic,
13
13
  parallelKey
14
- } from "./chunk-CHMXELDS.js";
14
+ } from "./chunk-KMHOICIY.js";
15
15
  import {
16
16
  roleOf
17
- } from "./chunk-2ZWMSJH6.js";
17
+ } from "./chunk-NKIBQ6PT.js";
18
18
  import {
19
19
  enumerateSafePitches,
20
20
  evaluateSafety
21
- } from "./chunk-EH6EM37U.js";
21
+ } from "./chunk-NYYWMV2C.js";
22
22
  import {
23
23
  NATURAL_MINOR_MASK,
24
24
  chordPitchClasses,
@@ -32,18 +32,23 @@ import {
32
32
  minorKey,
33
33
  nearestScaleTone,
34
34
  scaleTonesInDegreeOrder
35
- } from "./chunk-MIWO425M.js";
35
+ } from "./chunk-D2FAJBYL.js";
36
36
  import {
37
+ InvalidInputError,
37
38
  assertFiniteNumber,
38
39
  assertGenerationBudget,
39
40
  assertInteger,
40
41
  assertNoteEvents,
42
+ assertOneOf,
41
43
  assertPositiveInt,
42
44
  assertRange,
43
- assertTimeSignature
44
- } from "./chunk-FBQK3ZQN.js";
45
+ assertTimeSignature,
46
+ dropSilentNotes,
47
+ pitchClassOf
48
+ } from "./chunk-XEBG3G53.js";
45
49
 
46
50
  // src/generate/bass/index.ts
51
+ var BASS_STYLES = ["root", "rootFifth", "pop", "walking", "arpeggio"];
47
52
  var DEFAULT_TS = { numerator: 4, denominator: 4 };
48
53
  var DEFAULT_STYLE = "root";
49
54
  var DEFAULT_OCTAVE = 2;
@@ -51,9 +56,6 @@ var STRONG_VELOCITY = 100;
51
56
  var WEAK_VELOCITY = 80;
52
57
  var PICKUP_PROB = 0.35;
53
58
  var EPS = 1e-9;
54
- function pitchClass(value) {
55
- return (Math.trunc(value) % 12 + 12) % 12;
56
- }
57
59
  function placePc(pc, anchor, low) {
58
60
  const high = low + 12;
59
61
  let midi = pc + 12 * Math.round((anchor - pc) / 12);
@@ -69,24 +71,24 @@ function chordTonePcs(chord) {
69
71
  const seen = /* @__PURE__ */ new Set();
70
72
  const pcs = [];
71
73
  for (const interval of chord.intervals) {
72
- const pc = pitchClass(chord.rootPc + interval);
74
+ const pc = pitchClassOf(chord.rootPc + interval);
73
75
  if (!seen.has(pc)) {
74
76
  seen.add(pc);
75
77
  pcs.push(pc);
76
78
  }
77
79
  }
78
80
  if (pcs.length === 0) {
79
- pcs.push(pitchClass(chord.rootPc));
81
+ pcs.push(pitchClassOf(chord.rootPc));
80
82
  }
81
83
  return pcs;
82
84
  }
83
85
  function fifthPcOf(chord) {
84
- const fifths = new Set(chord.intervals.map((i) => pitchClass(i)));
86
+ const fifths = new Set(chord.intervals.map((i) => pitchClassOf(i)));
85
87
  const chosen = fifths.has(7) ? 7 : fifths.has(6) ? 6 : fifths.has(8) ? 8 : void 0;
86
- return chosen === void 0 ? pitchClass(chord.rootPc) : pitchClass(chord.rootPc + chosen);
88
+ return chosen === void 0 ? pitchClassOf(chord.rootPc) : pitchClassOf(chord.rootPc + chosen);
87
89
  }
88
90
  function bassPcOf(chord) {
89
- return pitchClass(chord.bassPc ?? chord.rootPc);
91
+ return pitchClassOf(chord.bassPc ?? chord.rootPc);
90
92
  }
91
93
  function beatPositions(start, end) {
92
94
  const positions = [];
@@ -125,7 +127,8 @@ function buildPop(ctx, seg) {
125
127
  } else if (ctx.rng.prob(PICKUP_PROB)) {
126
128
  if (ctx.rng.prob(0.5)) {
127
129
  const base = placePc(rootPc, ctx.prevMidi, ctx.low);
128
- emit(ctx, pos, rootPc, Math.max(ctx.low, base - 12));
130
+ const dropped = base - 12;
131
+ emit(ctx, pos, rootPc, dropped >= 0 ? dropped : base);
129
132
  } else {
130
133
  ctx.prevMidi = emit(ctx, pos, fifthPcOf(seg.chord));
131
134
  }
@@ -145,12 +148,23 @@ function buildArpeggio(ctx, seg) {
145
148
  }
146
149
  function approachNote(ctx, target, from) {
147
150
  const dir = from <= target ? -1 : 1;
151
+ const chromatic = target + dir;
148
152
  if (ctx.rng.prob(0.5)) {
149
- return target + dir;
153
+ return foldIntoBand(chromatic, ctx.low);
150
154
  }
151
155
  const cand = nearestScaleTone(target + dir * 2, ctx.key);
152
156
  const step = Math.abs(cand - target);
153
- return step >= 1 && step <= 2 ? cand : target + dir;
157
+ return foldIntoBand(step >= 1 && step <= 2 ? cand : chromatic, ctx.low);
158
+ }
159
+ function foldIntoBand(midi, low) {
160
+ let result = midi;
161
+ while (result < low) {
162
+ result += 12;
163
+ }
164
+ while (result > low + 12) {
165
+ result -= 12;
166
+ }
167
+ return result;
154
168
  }
155
169
  function buildWalking(ctx, seg, next) {
156
170
  const tones = chordTonePcs(seg.chord);
@@ -164,7 +178,7 @@ function buildWalking(ctx, seg, next) {
164
178
  if (next && count > 1 && i === count - 1) {
165
179
  const nextBass = placePc(bassPcOf(next.chord), ctx.prevMidi, ctx.low);
166
180
  const midi = approachNote(ctx, nextBass, ctx.prevMidi);
167
- ctx.prevMidi = emit(ctx, pos, pitchClass(midi), midi);
181
+ ctx.prevMidi = emit(ctx, pos, pitchClassOf(midi), midi);
168
182
  return;
169
183
  }
170
184
  const pc = tones[i % tones.length] ?? bassPcOf(seg.chord);
@@ -179,7 +193,7 @@ function generateBassLine(opts) {
179
193
  assertRange(segment.startBeat, 0, Number.MAX_SAFE_INTEGER, `segments[${index}].startBeat`);
180
194
  assertRange(segment.endBeat, 0, Number.MAX_SAFE_INTEGER, `segments[${index}].endBeat`);
181
195
  if (segment.endBeat <= segment.startBeat) {
182
- throw new RangeError(`segments[${index}] must have a positive duration`);
196
+ throw new InvalidInputError(`segments[${index}] must have a positive duration`);
183
197
  }
184
198
  }
185
199
  const segments = [...opts.segments].sort((a, b) => a.startBeat - b.startBeat);
@@ -188,7 +202,7 @@ function generateBassLine(opts) {
188
202
  }
189
203
  const ts = opts.ts ?? DEFAULT_TS;
190
204
  assertTimeSignature(ts);
191
- const style = opts.style ?? DEFAULT_STYLE;
205
+ const style = assertOneOf(opts.style ?? DEFAULT_STYLE, BASS_STYLES, "bass style");
192
206
  const octave = opts.octave ?? DEFAULT_OCTAVE;
193
207
  assertInteger(octave, "bass octave", -1, 9);
194
208
  const estimatedNotes = segments.reduce(
@@ -263,6 +277,7 @@ var HOLD_OFF_BEAT_PROB = 0.2;
263
277
  var REINFORCE_PROB = 0.35;
264
278
  var DEFAULT_MELODY_VELOCITY = 96;
265
279
  var VELOCITY_DROP = 16;
280
+ var TIE_BREAK_JITTER = 1e-6;
266
281
  function isWholeBeat(beat) {
267
282
  return Math.abs(beat - Math.round(beat)) < EPS2;
268
283
  }
@@ -365,26 +380,40 @@ function heldNoteBoundaries(melody, startBeat, endBeat, chordChangeBeats) {
365
380
  }
366
381
  return [...boundaries].sort((a, b) => a - b);
367
382
  }
368
- function heldPitchSafety(pitch, startBeat, endBeat, prevPitch, melody, opts, profile, register, low, high, ts) {
369
- let worst = 0 /* Safe */;
370
- const boundaries = heldNoteBoundaries(melody, startBeat, endBeat, opts.chordChangeBeats ?? []);
371
- for (const boundary of boundaries) {
383
+ function boundaryContexts(melody, harmony, startBeat, endBeat, ts) {
384
+ const boundaries = heldNoteBoundaries(melody, startBeat, endBeat, harmony.chordChangeBeats);
385
+ return boundaries.map((boundary) => {
372
386
  const melodyVoice = melodySnapshotAt(melody, boundary);
387
+ return {
388
+ melodyVoice,
389
+ otherVoices: melodyVoice === void 0 ? [] : [melodyVoice],
390
+ chord: harmony.chordAt(boundary),
391
+ strongBeat: isStrongBeat(boundary, ts),
392
+ atCounterOnset: Math.abs(boundary - startBeat) < EPS2
393
+ };
394
+ });
395
+ }
396
+ function heldPitchSafety(pitch, contexts, prevPitch, key, profile, register, low, high) {
397
+ let worst = 0 /* Safe */;
398
+ for (const context of contexts) {
399
+ const melodyVoice = context.melodyVoice;
373
400
  if (melodyVoice !== void 0 && (register === "below" && pitch >= melodyVoice.pitch || register === "above" && pitch <= melodyVoice.pitch)) {
374
401
  return null;
375
402
  }
376
- const atCounterOnset = Math.abs(boundary - startBeat) < EPS2;
377
- const result = evaluateSafety({
378
- profile,
379
- candidatePitch: pitch,
380
- prevPitch: atCounterOnset ? prevPitch : pitch,
381
- chord: opts.chordAt(boundary),
382
- key: opts.key,
383
- otherVoices: melodyVoice ? [melodyVoice] : [],
384
- strongBeat: isStrongBeat(boundary, ts),
385
- vocalLow: low,
386
- vocalHigh: high
387
- });
403
+ const result = evaluateSafety(
404
+ {
405
+ profile,
406
+ candidatePitch: pitch,
407
+ prevPitch: context.atCounterOnset ? prevPitch : pitch,
408
+ chord: context.chord,
409
+ key,
410
+ otherVoices: context.otherVoices,
411
+ strongBeat: context.strongBeat,
412
+ vocalLow: low,
413
+ vocalHigh: high
414
+ },
415
+ { suggestions: false }
416
+ );
388
417
  worst = Math.max(worst, result.safety);
389
418
  if (worst === 2 /* Dissonant */) {
390
419
  return worst;
@@ -393,16 +422,31 @@ function heldPitchSafety(pitch, startBeat, endBeat, prevPitch, melody, opts, pro
393
422
  return worst;
394
423
  }
395
424
  function generateCounterMelody(opts) {
396
- const melody = createNoteEventIndex(opts.melody);
397
- if (melody.notes.length === 0) {
398
- return [];
425
+ const timeline = opts.timeline;
426
+ const chordAt = timeline === void 0 ? opts.chordAt : (beat) => timeline.at(beat);
427
+ if (chordAt === void 0) {
428
+ throw new TypeError("countermelody needs a timeline or a chordAt callback");
399
429
  }
430
+ const chordChangeBeats = timeline === void 0 ? opts.chordChangeBeats ?? [] : timeline.segments.flatMap((segment) => [segment.startBeat, segment.endBeat]);
431
+ const register = opts.register ?? "below";
432
+ const melody = createNoteEventIndex(opts.melody, {
433
+ allowNonPositiveDuration: true,
434
+ tieBreak: register === "below" ? "highest" : "lowest"
435
+ });
400
436
  const ts = opts.ts ?? DEFAULT_TS2;
401
437
  assertTimeSignature(ts);
402
- const register = opts.register ?? "below";
403
- const rhythm = opts.rhythm ?? "complement";
404
- const profile = opts.profile ?? "pop";
438
+ const rhythm = assertOneOf(opts.rhythm ?? "complement", ["complement", "follow"], "rhythm");
439
+ const profile = assertOneOf(opts.profile ?? "pop", ["strict", "pop"], "safety profile");
405
440
  const rng = createRng(opts.seed ?? 0);
441
+ if (opts.pitchLow !== void 0) {
442
+ assertInteger(opts.pitchLow, "countermelody pitchLow");
443
+ }
444
+ if (opts.pitchHigh !== void 0) {
445
+ assertInteger(opts.pitchHigh, "countermelody pitchHigh");
446
+ }
447
+ if (melody.notes.every((indexed) => indexed.note.durationBeat <= 0)) {
448
+ return [];
449
+ }
406
450
  const spanStart = melody.notes[0]?.note.startBeat ?? 0;
407
451
  const spanEnd = melody.notes.reduce((end, n) => Math.max(end, n.endBeat), spanStart);
408
452
  assertGenerationBudget(Math.ceil((spanEnd - spanStart) / GRID_STEP), "countermelody grid");
@@ -413,15 +457,15 @@ function generateCounterMelody(opts) {
413
457
  assertInteger(low, "countermelody pitchLow");
414
458
  assertInteger(high, "countermelody pitchHigh");
415
459
  if (low > high) {
416
- throw new RangeError(
460
+ throw new InvalidInputError(
417
461
  `countermelody pitchLow must not exceed pitchHigh; received ${low} > ${high}`
418
462
  );
419
463
  }
420
464
  assertGenerationBudget(high - low + 1, "countermelody pitch candidates");
421
- assertGenerationBudget(opts.chordChangeBeats?.length ?? 0, "chord change beats");
422
- for (let index = 0; index < (opts.chordChangeBeats?.length ?? 0); index += 1) {
465
+ assertGenerationBudget(chordChangeBeats.length, "chord change beats");
466
+ for (let index = 0; index < chordChangeBeats.length; index += 1) {
423
467
  assertRange(
424
- opts.chordChangeBeats?.[index] ?? Number.NaN,
468
+ chordChangeBeats[index] ?? Number.NaN,
425
469
  0,
426
470
  Number.MAX_SAFE_INTEGER,
427
471
  `chordChangeBeats[${index}]`
@@ -429,7 +473,9 @@ function generateCounterMelody(opts) {
429
473
  }
430
474
  const center = (low + high) / 2;
431
475
  const onsets = rhythm === "follow" ? followOnsets(melody) : complementOnsets(melody, ts, spanStart, spanEnd, rng);
432
- assertGenerationBudget(onsets.length * (high - low + 1), "countermelody search");
476
+ const boundaryEstimate = onsets.length + Math.ceil((spanEnd - spanStart) / GRID_STEP) + melody.notes.length + chordChangeBeats.length;
477
+ assertGenerationBudget(boundaryEstimate * (high - low + 1), "countermelody search");
478
+ const jitter = Array.from({ length: high - low + 1 }, () => rng.next() * TIE_BREAK_JITTER);
433
479
  const out = [];
434
480
  let prevPitch;
435
481
  for (let onsetIndex = 0; onsetIndex < onsets.length; onsetIndex += 1) {
@@ -443,7 +489,8 @@ function generateCounterMelody(opts) {
443
489
  const melPitch = melNote?.pitch;
444
490
  const melodyVoice = melodySnapshotAt(melody, beat);
445
491
  const melPrev = melodyVoice?.prevPitch;
446
- const chord = opts.chordAt(beat);
492
+ const contexts = boundaryContexts(melody, { chordAt, chordChangeBeats }, beat, endBeat, ts);
493
+ const chord = contexts[0]?.chord ?? chordAt(beat);
447
494
  const otherVoices = melodyVoice !== void 0 ? [melodyVoice] : [];
448
495
  const candidates = enumerateSafePitches(
449
496
  {
@@ -465,16 +512,13 @@ function generateCounterMelody(opts) {
465
512
  for (const candidate of candidates) {
466
513
  const worstSafety = heldPitchSafety(
467
514
  candidate,
468
- beat,
469
- endBeat,
515
+ contexts,
470
516
  prevPitch,
471
- melody,
472
- opts,
517
+ opts.key,
473
518
  profile,
474
519
  register,
475
520
  low,
476
- high,
477
- ts
521
+ high
478
522
  );
479
523
  if (worstSafety === null || worstSafety === 2 /* Dissonant */) {
480
524
  continue;
@@ -486,7 +530,7 @@ function generateCounterMelody(opts) {
486
530
  continue;
487
531
  }
488
532
  }
489
- const score = scoreCandidate(candidate, melPitch, melPrev, prevPitch, chord, center);
533
+ const score = scoreCandidate(candidate, melPitch, melPrev, prevPitch, chord, center) + (jitter[candidate - low] ?? 0);
490
534
  const tie = Math.abs(score - bestScore) <= EPS2 && candidate < (bestPitch ?? Number.POSITIVE_INFINITY);
491
535
  if (worstSafety < bestWorstSafety || worstSafety === bestWorstSafety && (score > bestScore + EPS2 || tie)) {
492
536
  bestWorstSafety = worstSafety;
@@ -528,6 +572,27 @@ var GM = {
528
572
  };
529
573
  var EIGHTH = 0.5;
530
574
  var SIXTEENTH = 0.25;
575
+ var DRUM_FEELS = ["straight", "swing", "shuffle"];
576
+ var GROOVE_STYLES = [
577
+ "standard",
578
+ "funk",
579
+ "shuffle",
580
+ "bossa",
581
+ "trap",
582
+ "halftime",
583
+ "breakbeat",
584
+ "house",
585
+ "synthpop"
586
+ ];
587
+ var DRUM_ROLES = ["full", "ambient", "minimal", "fxOnly"];
588
+ var PUBLIC_SECTIONS = [
589
+ "intro",
590
+ "verse",
591
+ "prechorus",
592
+ "chorus",
593
+ "bridge",
594
+ "outro"
595
+ ];
531
596
  var STYLE_MAP = {
532
597
  // Standard pop pulse: kick on beats 1 and 3, backbeat snare on 2 and 4.
533
598
  standard: { style: "standard", feel: "straight", ghostBoost: false, snareBeat3: false },
@@ -727,7 +792,7 @@ function getGhostDensity(mood, section, backingDensity, bpm) {
727
792
  }
728
793
  return levelToProbability(level);
729
794
  }
730
- function getGhostVelocity(section, beatPosition, isAfterSnare) {
795
+ function getGhostVelocity(section, beatPosition) {
731
796
  let base = 0.4;
732
797
  const even = beatPosition % 2 === 0 ? 0.05 : 0;
733
798
  switch (section) {
@@ -748,14 +813,11 @@ function getGhostVelocity(section, beatPosition, isAfterSnare) {
748
813
  base = 0.38;
749
814
  break;
750
815
  }
751
- if (isAfterSnare) {
752
- base += 0.1;
753
- }
754
816
  return Math.max(0.25, Math.min(0.65, base));
755
817
  }
756
818
  function getGhostProbabilityAtPosition(beat, sixteenthInBeat) {
757
- const nearSnare = beat === 0 && sixteenthInBeat === 3 || beat === 1 && sixteenthInBeat === 1 || beat === 2 && sixteenthInBeat === 3 || beat === 3 && sixteenthInBeat === 1;
758
- return nearSnare ? 0.6 : 0.25;
819
+ const leadsIntoBackbeat = (beat === 0 || beat === 2) && sixteenthInBeat === 3;
820
+ return leadsIntoBackbeat ? 0.6 : 0.25;
759
821
  }
760
822
  function selectGhostPositions(mood, rng) {
761
823
  if (mood === 2 /* Energetic */) {
@@ -1038,10 +1100,7 @@ function quantizeSwing(startBeat, swing, resolution) {
1038
1100
  }
1039
1101
 
1040
1102
  // src/generate/drums/beat.ts
1041
- function swing16(tick, feel, swingAmount) {
1042
- return quantizeSwing(tick, effectiveSwing(feel, swingAmount), "sixteenth");
1043
- }
1044
- function hatSwingFactor(style) {
1103
+ function styleSwingFactor(style) {
1045
1104
  if (style === "trap") {
1046
1105
  return 0;
1047
1106
  }
@@ -1050,6 +1109,12 @@ function hatSwingFactor(style) {
1050
1109
  }
1051
1110
  return 0.5;
1052
1111
  }
1112
+ function sectionSwing(sec, swingAmount) {
1113
+ return effectiveSwing(sec.feel, swingAmount) * styleSwingFactor(sec.style);
1114
+ }
1115
+ function swing16(tick, sec, swingAmount) {
1116
+ return quantizeSwing(tick, sectionSwing(sec, swingAmount), "sixteenth");
1117
+ }
1053
1118
  function generateKickForBeat(ctx, sec, kick) {
1054
1119
  if (ctx.inPrechorusLift) {
1055
1120
  return;
@@ -1060,7 +1125,7 @@ function generateKickForBeat(ctx, sec, kick) {
1060
1125
  ctx.track.add(GM.BD, ctx.beatTick, EIGHTH, ctx.velocity);
1061
1126
  }
1062
1127
  if (and) {
1063
- const andTick = swing16(ctx.beatTick + EIGHTH, sec.feel, ctx.swingAmount);
1128
+ const andTick = swing16(ctx.beatTick + EIGHTH, sec, ctx.swingAmount);
1064
1129
  ctx.track.add(GM.BD, andTick, EIGHTH, ctx.velocity * 0.85);
1065
1130
  }
1066
1131
  }
@@ -1077,12 +1142,17 @@ function generateSnareForBeat(ctx, sec, isIntroFirst) {
1077
1142
  }
1078
1143
  const backbeatVel = Math.min(127, ctx.velocity + 16);
1079
1144
  const promoteSparseChorus = sec.style === "sparse" && ctx.section === "chorus" && sec.role === "full";
1145
+ if (sec.role === "fxOnly") {
1146
+ return;
1147
+ }
1148
+ if (sec.role === "minimal") {
1149
+ ctx.track.add(GM.SIDESTICK, ctx.beatTick, EIGHTH, ctx.velocity * 0.65);
1150
+ return;
1151
+ }
1080
1152
  if (promoteSparseChorus) {
1081
1153
  ctx.track.add(GM.SD, ctx.beatTick, EIGHTH, backbeatVel);
1082
1154
  } else if (sec.style === "sparse" || sec.role === "ambient") {
1083
- if (sec.role !== "fxOnly" && sec.role !== "minimal") {
1084
- ctx.track.add(GM.SIDESTICK, ctx.beatTick, EIGHTH, ctx.velocity * 0.8);
1085
- }
1155
+ ctx.track.add(GM.SIDESTICK, ctx.beatTick, EIGHTH, ctx.velocity * 0.8);
1086
1156
  } else {
1087
1157
  ctx.track.add(GM.SD, ctx.beatTick, EIGHTH, backbeatVel);
1088
1158
  }
@@ -1103,13 +1173,13 @@ function generateGhostNotesForBeat(ctx, sec) {
1103
1173
  continue;
1104
1174
  }
1105
1175
  const variation = ctx.rng.float(0.85, 1.15);
1106
- const ghostBase = getGhostVelocity(ctx.section, ctx.beat / 2, false);
1176
+ const ghostBase = getGhostVelocity(ctx.section, ctx.beat / 2);
1107
1177
  let ghostVel = ctx.velocity * ghostBase * variation;
1108
1178
  if (pos === "a") {
1109
1179
  ghostVel *= 0.9;
1110
1180
  }
1111
1181
  const offset = pos === "e" ? SIXTEENTH : 3 * SIXTEENTH;
1112
- const tick = swing16(ctx.beatTick + offset, sec.feel, ctx.swingAmount);
1182
+ const tick = swing16(ctx.beatTick + offset, sec, ctx.swingAmount);
1113
1183
  ctx.track.add(GM.SD, tick, SIXTEENTH, ghostVel);
1114
1184
  }
1115
1185
  }
@@ -1171,7 +1241,7 @@ function generateHiHatForBeat(ctx, sec) {
1171
1241
  for (let eighth = 0; eighth < 2; eighth += 1) {
1172
1242
  let hhTick = ctx.beatTick + eighth * EIGHTH;
1173
1243
  if (eighth === 1) {
1174
- hhTick = swing16(hhTick, sec.feel, ctx.swingAmount);
1244
+ hhTick = swing16(hhTick, sec, ctx.swingAmount);
1175
1245
  }
1176
1246
  if (ctx.section === "intro" && eighth === 1) {
1177
1247
  if (sec.useFootHh && ctx.beat % 2 === 0) {
@@ -1201,9 +1271,8 @@ function generateHiHatForBeat(ctx, sec) {
1201
1271
  }
1202
1272
  for (let sixteenth = 0; sixteenth < 4; sixteenth += 1) {
1203
1273
  let hhTick = ctx.beatTick + sixteenth * SIXTEENTH;
1204
- if (sixteenth === 1 || sixteenth === 3) {
1205
- const swung = effectiveSwing(sec.feel, ctx.swingAmount) * hatSwingFactor(sec.style);
1206
- hhTick = quantizeSwing(hhTick, swung, "sixteenth");
1274
+ if (sixteenth !== 0) {
1275
+ hhTick = swing16(hhTick, sec, ctx.swingAmount);
1207
1276
  }
1208
1277
  const metricVel = hiHatVelocityMultiplier(sixteenth, ctx.rng);
1209
1278
  const hhVel = Math.max(20, ctx.velocity * dm * typeMult * metricVel);
@@ -1531,24 +1600,62 @@ function generateFill(track, beatTick, beat, fillType, velocity) {
1531
1600
  }
1532
1601
 
1533
1602
  // src/generate/drums/hit.ts
1603
+ var DRUM_NOTES = {
1604
+ kick: GM.BD,
1605
+ snare: GM.SD,
1606
+ sideStick: GM.SIDESTICK,
1607
+ handClap: GM.HANDCLAP,
1608
+ closedHiHat: GM.CHH,
1609
+ pedalHiHat: GM.FHH,
1610
+ openHiHat: GM.OHH,
1611
+ crash: GM.CRASH,
1612
+ ride: GM.RIDE,
1613
+ tambourine: GM.TAMBOURINE,
1614
+ highTom: GM.TOM_H,
1615
+ midTom: GM.TOM_M,
1616
+ lowTom: GM.TOM_L,
1617
+ shaker: GM.SHAKER
1618
+ };
1619
+ var VOICE_BY_NOTE = new Map(
1620
+ Object.entries(DRUM_NOTES).map(([voice, note]) => [note, voice])
1621
+ );
1622
+ function drumVoiceOf(pitch) {
1623
+ return VOICE_BY_NOTE.get(pitch);
1624
+ }
1625
+ var ONSET_KEY_SCALE = 128;
1534
1626
  var HitList = class {
1535
1627
  hits = [];
1628
+ /** Onset keys already occupied, as `pitch:quantizedBeat`. */
1629
+ #onsets = /* @__PURE__ */ new Set();
1536
1630
  /** Append a hit, clamping velocity and dropping non-positive positions. */
1537
1631
  add(pitch, startBeat, durationBeat, velocity) {
1632
+ const beat = Math.max(0, startBeat);
1538
1633
  this.hits.push({
1539
1634
  pitch,
1540
- startBeat: Math.max(0, startBeat),
1635
+ startBeat: beat,
1541
1636
  durationBeat,
1542
1637
  velocity: clampVel(velocity)
1543
1638
  });
1639
+ this.#onsets.add(onsetKey(pitch, beat));
1640
+ }
1641
+ /** Whether a hit of this pitch already starts exactly at `startBeat`. */
1642
+ hasOnset(pitch, startBeat) {
1643
+ return this.#onsets.has(onsetKey(pitch, startBeat));
1544
1644
  }
1545
1645
  /** True when a crash already sits within a 16th of `startBeat`. */
1546
1646
  hasCrashNear(startBeat) {
1547
- return this.hits.some(
1548
- (h) => h.pitch === GM.CRASH && h.startBeat >= startBeat && h.startBeat < startBeat + 0.25
1549
- );
1647
+ const steps = Math.round(0.25 * ONSET_KEY_SCALE);
1648
+ for (let step = 0; step < steps; step += 1) {
1649
+ if (this.#onsets.has(onsetKey(GM.CRASH, startBeat + step / ONSET_KEY_SCALE))) {
1650
+ return true;
1651
+ }
1652
+ }
1653
+ return false;
1550
1654
  }
1551
1655
  };
1656
+ function onsetKey(pitch, startBeat) {
1657
+ return `${pitch}:${Math.round(startBeat * ONSET_KEY_SCALE)}`;
1658
+ }
1552
1659
 
1553
1660
  // src/generate/drums/kick.ts
1554
1661
  var EMPTY = {
@@ -1736,25 +1843,31 @@ function generateAuxPercussionForBar(track, barStart, config, role, densityMult,
1736
1843
  }
1737
1844
 
1738
1845
  // src/generate/drums/index.ts
1846
+ var MAX_HITS_PER_BAR = 128;
1739
1847
  function generateDrums(opts) {
1740
1848
  assertPositiveInt(opts.bars, "drum bars");
1741
1849
  assertRange(opts.bpm, Number.MIN_VALUE, 1e3, "drum bpm");
1742
1850
  assertRange(opts.density, 0, 1, "drum density");
1743
- assertGenerationBudget(opts.bars * 128, "drum hits");
1851
+ assertGenerationBudget(opts.bars * MAX_HITS_PER_BAR, "drum hits");
1744
1852
  if (opts.euclideanKick) {
1745
1853
  const steps = opts.euclideanKick.steps ?? 16;
1746
1854
  assertInteger(steps, "euclidean steps", 1, 16);
1747
1855
  assertInteger(opts.euclideanKick.pulses, "euclidean pulses");
1748
1856
  assertInteger(opts.euclideanKick.rotation ?? 0, "euclidean rotation");
1749
1857
  }
1858
+ const publicStyle = assertOneOf(opts.style, GROOVE_STYLES, "drum style");
1859
+ const publicSection = assertOneOf(opts.section, PUBLIC_SECTIONS, "drum section");
1860
+ if (opts.nextSection !== void 0) {
1861
+ assertOneOf(opts.nextSection, PUBLIC_SECTIONS, "drum nextSection");
1862
+ }
1750
1863
  const track = new HitList();
1751
1864
  const rng = createRng(opts.seed ?? 0);
1752
- const mapping = mapStyle(opts.style);
1865
+ const mapping = mapStyle(publicStyle);
1753
1866
  const style = mapping.style;
1754
- const feel = opts.feel ?? mapping.feel;
1755
- const role = opts.role ?? "full";
1867
+ const feel = opts.feel === void 0 ? mapping.feel : assertOneOf(opts.feel, DRUM_FEELS, "drum feel");
1868
+ const role = opts.role === void 0 ? "full" : assertOneOf(opts.role, DRUM_ROLES, "drum role");
1756
1869
  const playMainVoices = role !== "fxOnly";
1757
- const section = mapSection(opts.section);
1870
+ const section = mapSection(publicSection);
1758
1871
  const backingDensity = mapDensity(opts.density);
1759
1872
  const swingAmount = feelSwingAmount(feel);
1760
1873
  let densityMult = sectionDensityMultiplier(section);
@@ -1806,7 +1919,7 @@ function generateDrums(opts) {
1806
1919
  } else if (!euclidKick) {
1807
1920
  kick = getKickPattern(section, style, bar, rng);
1808
1921
  }
1809
- const inLift = isInPreChorusLift(section, bar, opts.bars, section === "b");
1922
+ const inLift = isInPreChorusLift(section, bar, opts.bars, nextSection === "chorus");
1810
1923
  let currentFill = "snareRoll";
1811
1924
  for (let beat = 0; beat < 4; beat += 1) {
1812
1925
  const beatTick = barStart + beat;
@@ -1871,8 +1984,7 @@ function generateDrums(opts) {
1871
1984
  if (sec.useFootHh && shouldPlayHiHat(role)) {
1872
1985
  for (let fhhBeat = 0; fhhBeat < 4; fhhBeat += 2) {
1873
1986
  const fhhTick = barStart + fhhBeat;
1874
- const occupied = track.hits.some((h) => h.pitch === GM.FHH && h.startBeat === fhhTick);
1875
- if (!occupied) {
1987
+ if (!track.hasOnset(GM.FHH, fhhTick)) {
1876
1988
  track.add(GM.FHH, fhhTick, 0.5, footHiHatVelocity(rng));
1877
1989
  }
1878
1990
  }
@@ -1896,11 +2008,12 @@ function generateDrums(opts) {
1896
2008
  opts.bpm
1897
2009
  );
1898
2010
  }
1899
- return track.hits;
2011
+ return [...track.hits].sort((a, b) => a.startBeat - b.startBeat || a.pitch - b.pitch);
1900
2012
  }
1901
2013
 
1902
2014
  // src/generate/groove/index.ts
1903
2015
  var DEFAULT_TS3 = { numerator: 4, denominator: 4 };
2016
+ var DEFAULT_GROOVE_SUBDIVISION = 4;
1904
2017
  var DEFAULT_TIMING = 0.02;
1905
2018
  var DEFAULT_VELOCITY_JITTER = 8;
1906
2019
  var DEFAULT_ACCENT = 12;
@@ -1911,7 +2024,8 @@ var MAX_VELOCITY = 127;
1911
2024
  function humanize(events, opts = {}) {
1912
2025
  const ts = opts.ts ?? DEFAULT_TS3;
1913
2026
  assertTimeSignature(ts);
1914
- assertNoteEvents(events, "humanize events");
2027
+ assertNoteEvents(events, "humanize events", { allowNonPositiveDuration: true });
2028
+ const sounding = dropSilentNotes(events);
1915
2029
  const timing = opts.timing ?? DEFAULT_TIMING;
1916
2030
  const velocityJitter = opts.velocity ?? DEFAULT_VELOCITY_JITTER;
1917
2031
  const accent = opts.accent ?? DEFAULT_ACCENT;
@@ -1921,7 +2035,7 @@ function humanize(events, opts = {}) {
1921
2035
  assertRange(accent, 0, 127, "humanize accent");
1922
2036
  assertRange(baseVelocity, 0, 127, "humanize base velocity");
1923
2037
  const rng = createRng(opts.seed ?? 0);
1924
- return events.map((event) => {
2038
+ return sounding.map((event) => {
1925
2039
  const timingOffset = rng.float(-timing, timing);
1926
2040
  const startBeat = Math.max(0, event.startBeat + timingOffset);
1927
2041
  const weight = metricWeight(event.startBeat, ts);
@@ -1951,9 +2065,9 @@ function quantizeToGrid(beat, barBeats, subdivision, slotsPerBar) {
1951
2065
  slotIndex
1952
2066
  };
1953
2067
  }
1954
- function extractGrooveTemplate(events, ts, subdivision) {
2068
+ function extractGrooveTemplate(events, ts, subdivision = DEFAULT_GROOVE_SUBDIVISION) {
1955
2069
  assertTimeSignature(ts);
1956
- assertNoteEvents(events, "groove source events");
2070
+ assertNoteEvents(events, "groove source events", { allowNonPositiveDuration: true });
1957
2071
  assertPositiveInt(subdivision, "groove subdivision");
1958
2072
  const barBeats = beatsPerBar(ts);
1959
2073
  const slotsPerBar = Math.round(barBeats * subdivision);
@@ -1962,7 +2076,7 @@ function extractGrooveTemplate(events, ts, subdivision) {
1962
2076
  const velocitySums = new Array(slotsPerBar).fill(0);
1963
2077
  const counts = new Array(slotsPerBar).fill(0);
1964
2078
  const velocityCounts = new Array(slotsPerBar).fill(0);
1965
- for (const event of events) {
2079
+ for (const event of dropSilentNotes(events)) {
1966
2080
  const { quantizedBeat, slotIndex } = quantizeToGrid(
1967
2081
  event.startBeat,
1968
2082
  barBeats,
@@ -1993,20 +2107,20 @@ function extractGrooveTemplate(events, ts, subdivision) {
1993
2107
  }
1994
2108
  function applyGrooveTemplate(events, template, ts) {
1995
2109
  assertTimeSignature(ts);
1996
- assertNoteEvents(events, "groove target events");
2110
+ assertNoteEvents(events, "groove target events", { allowNonPositiveDuration: true });
1997
2111
  assertPositiveInt(template.subdivision, "template subdivision");
1998
2112
  assertPositiveInt(template.slotsPerBar, "template slotsPerBar");
1999
2113
  assertGenerationBudget(template.slots.length, "template slots");
2000
2114
  if (template.slots.length !== template.slotsPerBar) {
2001
- throw new RangeError("template slots length must equal slotsPerBar");
2115
+ throw new InvalidInputError("template slots length must equal slotsPerBar");
2002
2116
  }
2003
2117
  if (template.ts && (template.ts.numerator !== ts.numerator || template.ts.denominator !== ts.denominator)) {
2004
- throw new Error(
2118
+ throw new InvalidInputError(
2005
2119
  `Groove template meter ${formatTimeSignature(template.ts)} does not match apply-time meter ${formatTimeSignature(ts)}`
2006
2120
  );
2007
2121
  }
2008
2122
  const barBeats = beatsPerBar(ts);
2009
- return events.map((event) => {
2123
+ return dropSilentNotes(events).map((event) => {
2010
2124
  const { quantizedBeat, slotIndex } = quantizeToGrid(
2011
2125
  event.startBeat,
2012
2126
  barBeats,
@@ -2026,15 +2140,20 @@ function applyGrooveTemplate(events, template, ts) {
2026
2140
  }
2027
2141
 
2028
2142
  // src/generate/harmonize/index.ts
2029
- var FALLBACK = { rootPc: 0, quality: "maj", secondaryDominant: false, base: 0 };
2143
+ var FALLBACK = {
2144
+ rootPc: 0,
2145
+ quality: "maj",
2146
+ secondaryDominant: false,
2147
+ base: 0,
2148
+ pcs: chordPitchClasses(makeChord(0, "maj"))
2149
+ };
2150
+ var DEFAULT_HARMONIC_RHYTHM = 2;
2151
+ var DEFAULT_PLACEMENT = { transposeSearch: false, octaveSearch: false };
2030
2152
  var DEFAULT_METER = { numerator: 4, denominator: 4 };
2031
2153
  var COMFORT_LOW = 55;
2032
2154
  var COMFORT_HIGH = 79;
2033
2155
  var TESSITURA_WEIGHT = 1e-3;
2034
- var TIE_BREAK_JITTER = 1e-6;
2035
- function pitchClass2(pitch) {
2036
- return (Math.trunc(pitch) % 12 + 12) % 12;
2037
- }
2156
+ var TIE_BREAK_JITTER2 = 1e-6;
2038
2157
  function inferKey(melody) {
2039
2158
  let best = majorKey(0);
2040
2159
  let bestScore = Number.NEGATIVE_INFINITY;
@@ -2043,13 +2162,13 @@ function inferKey(melody) {
2043
2162
  candidates.push(majorKey(tonic), minorKey(tonic));
2044
2163
  }
2045
2164
  for (const key of candidates) {
2046
- const tonic = pitchClass2(key.rootPc);
2165
+ const tonic = pitchClassOf(key.rootPc);
2047
2166
  const isMinor = key.modeMask12 === NATURAL_MINOR_MASK;
2048
2167
  const leadingTone = (tonic + 11) % 12;
2049
2168
  let score = 0;
2050
2169
  for (const n of melody) {
2051
2170
  const w = Math.max(0.25, n.durationBeat);
2052
- const pc = pitchClass2(n.pitch);
2171
+ const pc = pitchClassOf(n.pitch);
2053
2172
  if (isScaleTone(n.pitch, key) || isMinor && pc === leadingTone) {
2054
2173
  score += w;
2055
2174
  }
@@ -2066,22 +2185,28 @@ function inferKey(melody) {
2066
2185
  }
2067
2186
  function buildCandidates(key, reharmonize) {
2068
2187
  const tones = scaleTonesInDegreeOrder(key);
2069
- const candidates = tones.map((rootPc, degree) => ({
2070
- rootPc,
2071
- quality: diatonicTriad(degree, key).quality,
2072
- degree,
2073
- secondaryDominant: false,
2074
- base: 0
2075
- }));
2188
+ const candidates = tones.map((rootPc, degree) => {
2189
+ const quality = diatonicTriad(degree, key).quality;
2190
+ return {
2191
+ rootPc,
2192
+ quality,
2193
+ degree,
2194
+ secondaryDominant: false,
2195
+ base: 0,
2196
+ pcs: chordPitchClasses(makeChord(rootPc, quality))
2197
+ };
2198
+ });
2076
2199
  if (reharmonize !== "diatonic") {
2077
2200
  for (const target of [1, 3, 4, 5]) {
2078
2201
  const targetRoot = tones[target] ?? 0;
2202
+ const rootPc = (targetRoot + 7) % 12;
2079
2203
  candidates.push({
2080
- rootPc: (targetRoot + 7) % 12,
2204
+ rootPc,
2081
2205
  quality: "dom7",
2082
2206
  secondaryDominant: true,
2083
2207
  targetDegree: target,
2084
- base: 1.5
2208
+ base: 1.5,
2209
+ pcs: chordPitchClasses(makeChord(rootPc, "dom7"))
2085
2210
  });
2086
2211
  }
2087
2212
  }
@@ -2095,7 +2220,8 @@ function buildCandidates(key, reharmonize) {
2095
2220
  rootPc: chord.rootPc,
2096
2221
  quality: chord.quality,
2097
2222
  secondaryDominant: false,
2098
- base: 1.25
2223
+ base: 1.25,
2224
+ pcs: chordPitchClasses(chord)
2099
2225
  });
2100
2226
  }
2101
2227
  }
@@ -2110,8 +2236,8 @@ function buildCandidates(key, reharmonize) {
2110
2236
  return true;
2111
2237
  });
2112
2238
  }
2113
- function emissionCost(seg, cand, melody, key) {
2114
- const pcs = chordPitchClasses(makeChord(cand.rootPc, cand.quality));
2239
+ function emissionCost(seg, cand, melody, key, ts) {
2240
+ const pcs = cand.pcs;
2115
2241
  let cost = cand.base;
2116
2242
  for (const idx of seg.noteIndices) {
2117
2243
  const note = melody[idx];
@@ -2124,10 +2250,10 @@ function emissionCost(seg, cand, melody, key) {
2124
2250
  continue;
2125
2251
  }
2126
2252
  const w = Math.max(0.25, overlap);
2127
- if (pcs.includes(pitchClass2(note.pitch))) {
2253
+ if (pcs.includes(pitchClassOf(note.pitch))) {
2128
2254
  continue;
2129
2255
  }
2130
- const strong = isStrongBeat(overlapStart, DEFAULT_METER);
2256
+ const strong = isStrongBeat(overlapStart, ts);
2131
2257
  cost += (strong ? 10 : 2) * w;
2132
2258
  if (!isScaleTone(note.pitch, key)) {
2133
2259
  cost += w;
@@ -2161,13 +2287,13 @@ function transitionCost(prev, cur, tonicPc) {
2161
2287
  cost += dist * 0.3;
2162
2288
  return cost;
2163
2289
  }
2164
- function harmonizeOnce(melody, key, candidates, segments, jitter) {
2165
- const tonicPc = pitchClass2(key.rootPc);
2290
+ function harmonizeOnce(melody, key, candidates, segments, jitter, ts) {
2291
+ const tonicPc = pitchClassOf(key.rootPc);
2166
2292
  const n = candidates.length;
2167
2293
  const candAt = (i) => candidates[i] ?? FALLBACK;
2168
2294
  const seg0 = segments[0];
2169
2295
  let dp = candidates.map(
2170
- (c, ci) => (seg0 ? emissionCost(seg0, c, melody, key) : 0) + (jitter[ci] ?? 0)
2296
+ (c, ci) => (seg0 ? emissionCost(seg0, c, melody, key, ts) : 0) + (jitter[ci] ?? 0)
2171
2297
  );
2172
2298
  const back = [];
2173
2299
  for (let s = 1; s < segments.length; s += 1) {
@@ -2187,7 +2313,7 @@ function harmonizeOnce(melody, key, candidates, segments, jitter) {
2187
2313
  bestPrev = p;
2188
2314
  }
2189
2315
  }
2190
- next[c] = best + emissionCost(seg, candAt(c), melody, key) + (jitter[c] ?? 0);
2316
+ next[c] = best + emissionCost(seg, candAt(c), melody, key, ts) + (jitter[c] ?? 0);
2191
2317
  ptr[c] = bestPrev;
2192
2318
  }
2193
2319
  dp = next;
@@ -2212,20 +2338,26 @@ function harmonizeOnce(melody, key, candidates, segments, jitter) {
2212
2338
  return { cost: bestCost, path };
2213
2339
  }
2214
2340
  function harmonizeMelody(opts) {
2215
- const noteIndex = createNoteEventIndex(opts.melody);
2216
- const key = opts.key === "infer" ? inferKey(opts.melody) : opts.key;
2217
- const candidates = buildCandidates(key, opts.reharmonize);
2341
+ const noteIndex = createNoteEventIndex(opts.melody, { allowNonPositiveDuration: true });
2342
+ const ts = opts.ts ?? DEFAULT_METER;
2343
+ assertTimeSignature(ts);
2344
+ const requestedKey = opts.key ?? "infer";
2345
+ const key = requestedKey === "infer" ? inferKey(opts.melody) : requestedKey;
2346
+ const placement = opts.placement ?? DEFAULT_PLACEMENT;
2347
+ if (noteIndex.notes.every((indexed) => indexed.note.durationBeat <= 0)) {
2348
+ return { transposeSemitones: 0, key, chords: [], melodyRoles: [] };
2349
+ }
2350
+ const candidates = buildCandidates(key, opts.reharmonize ?? "diatonic");
2218
2351
  const candAt = (i) => candidates[i] ?? FALLBACK;
2219
2352
  const rng = createRng(opts.seed ?? 0);
2220
- const jitter = candidates.map(() => rng.next() * TIE_BREAK_JITTER);
2353
+ const jitter = candidates.map(() => rng.next() * TIE_BREAK_JITTER2);
2221
2354
  const melodyEnd = noteIndex.notes.reduce((m, n) => Math.max(m, n.endBeat), 0);
2222
- const requestedHr = assertRange(
2223
- opts.harmonicRhythm,
2355
+ const hr = assertRange(
2356
+ opts.harmonicRhythm ?? DEFAULT_HARMONIC_RHYTHM,
2224
2357
  Number.MIN_VALUE,
2225
2358
  Number.MAX_SAFE_INTEGER,
2226
2359
  "harmonic rhythm"
2227
2360
  );
2228
- const hr = Math.max(0.25, requestedHr);
2229
2361
  const segCount = Math.max(1, Math.ceil(melodyEnd / hr));
2230
2362
  assertGenerationBudget(segCount, "harmonic segments");
2231
2363
  assertGenerationBudget(segCount * candidates.length * candidates.length, "harmonization search");
@@ -2236,6 +2368,9 @@ function harmonizeMelody(opts) {
2236
2368
  }));
2237
2369
  let memberships = 0;
2238
2370
  for (const indexed of noteIndex.notes) {
2371
+ if (indexed.note.durationBeat <= 0) {
2372
+ continue;
2373
+ }
2239
2374
  const first = Math.max(0, Math.floor(indexed.note.startBeat / hr));
2240
2375
  const lastExclusive = Math.min(segCount, Math.ceil(indexed.endBeat / hr - Number.EPSILON));
2241
2376
  memberships += Math.max(0, lastExclusive - first);
@@ -2245,14 +2380,14 @@ function harmonizeMelody(opts) {
2245
2380
  }
2246
2381
  }
2247
2382
  const transposes = [0];
2248
- if (opts.placement.transposeSearch) {
2383
+ if (placement.transposeSearch) {
2249
2384
  for (let s = -6; s <= 6; s += 1) {
2250
2385
  if (s !== 0) {
2251
2386
  transposes.push(s);
2252
2387
  }
2253
2388
  }
2254
2389
  }
2255
- if (opts.placement.octaveSearch) {
2390
+ if (placement.octaveSearch) {
2256
2391
  for (const o of [-12, 12]) {
2257
2392
  if (!transposes.includes(o)) {
2258
2393
  transposes.push(o);
@@ -2263,13 +2398,13 @@ function harmonizeMelody(opts) {
2263
2398
  let bestTs = 0;
2264
2399
  let bestPath = [];
2265
2400
  let bestMelody = opts.melody;
2266
- for (const ts of transposes) {
2267
- const shifted = opts.melody.map((n) => ({ ...n, pitch: n.pitch + ts }));
2268
- const { cost, path } = harmonizeOnce(shifted, key, candidates, segments, jitter);
2401
+ for (const shift of transposes) {
2402
+ const shifted = opts.melody.map((n) => ({ ...n, pitch: n.pitch + shift }));
2403
+ const { cost, path } = harmonizeOnce(shifted, key, candidates, segments, jitter, ts);
2269
2404
  const total = cost + tessituraCost(shifted);
2270
2405
  if (total < bestCost) {
2271
2406
  bestCost = total;
2272
- bestTs = ts;
2407
+ bestTs = shift;
2273
2408
  bestPath = path;
2274
2409
  bestMelody = shifted;
2275
2410
  }
@@ -2299,10 +2434,13 @@ function harmonizeMelody(opts) {
2299
2434
  }
2300
2435
 
2301
2436
  // src/generate/motif/index.ts
2302
- function pitchClass3(pitch) {
2303
- return (Math.trunc(pitch) % 12 + 12) % 12;
2304
- }
2305
2437
  var MIN_TILE_SPAN = 1 / 256;
2438
+ var DEFAULT_TS4 = { numerator: 4, denominator: 4 };
2439
+ var EPS3 = 1e-9;
2440
+ function isDownbeat(startBeat, barBeats) {
2441
+ const bar = startBeat / barBeats;
2442
+ return Math.abs(bar - Math.round(bar)) < EPS3;
2443
+ }
2306
2444
  function upScaleTone(pitch, key) {
2307
2445
  for (let d = 1; d <= 12; d += 1) {
2308
2446
  if (isScaleTone(pitch + d, key)) {
@@ -2330,10 +2468,10 @@ function stepDiatonic(pitch, degrees, key) {
2330
2468
  function nearestChordTone(pitch, chord) {
2331
2469
  const pcs = chordPitchClasses(chord);
2332
2470
  for (let d = 0; d <= 12; d += 1) {
2333
- if (pcs.includes(pitchClass3(pitch - d))) {
2471
+ if (pcs.includes(pitchClassOf(pitch - d))) {
2334
2472
  return pitch - d;
2335
2473
  }
2336
- if (pcs.includes(pitchClass3(pitch + d))) {
2474
+ if (pcs.includes(pitchClassOf(pitch + d))) {
2337
2475
  return pitch + d;
2338
2476
  }
2339
2477
  }
@@ -2387,14 +2525,15 @@ function contourOffsets(contour, count) {
2387
2525
  function generateMotif(opts) {
2388
2526
  const contour = opts.contour ?? "arch";
2389
2527
  const bars = assertPositiveInt(opts.bars, "motif bars");
2390
- const totalBeats = bars * 4;
2528
+ const barBeats = beatsPerBar(opts.ts ?? DEFAULT_TS4);
2529
+ const totalBeats = bars * barBeats;
2391
2530
  const noteCount = Math.max(3, bars * 2);
2392
2531
  assertGenerationBudget(noteCount, "motif notes");
2393
2532
  const beatsPerNote = totalBeats / noteCount;
2394
2533
  const rng = createRng(opts.seed ?? 0);
2395
2534
  const requestedJitter = assertFiniteNumber(opts.jitter ?? 0, "motif jitter");
2396
2535
  const jitterProb = Math.min(1, Math.max(0, requestedJitter));
2397
- const tonic = pitchClass3(opts.key.rootPc) + 60;
2536
+ const tonic = pitchClassOf(opts.key.rootPc) + 60;
2398
2537
  const offsets = contourOffsets(contour, noteCount);
2399
2538
  const notes = [];
2400
2539
  for (let i = 0; i < noteCount; i += 1) {
@@ -2404,14 +2543,30 @@ function generateMotif(opts) {
2404
2543
  }
2405
2544
  let pitch = stepDiatonic(tonic, (offsets[i] ?? 0) + jitter, opts.key);
2406
2545
  const startBeat = i * beatsPerNote;
2407
- if (opts.chord && startBeat % 4 === 0) {
2546
+ if (opts.chord && isDownbeat(startBeat, barBeats)) {
2408
2547
  pitch = nearestChordTone(pitch, opts.chord);
2409
2548
  }
2410
2549
  notes.push({ pitch, startBeat, durationBeat: beatsPerNote });
2411
2550
  }
2412
2551
  return { notes };
2413
2552
  }
2553
+ function motifToNoteEvents(cell) {
2554
+ return cell.notes.map((note) => ({ ...note }));
2555
+ }
2414
2556
  function transformMotif(cell, t, amount, key) {
2557
+ return assertInRange(transformUnchecked(cell, t, amount, key));
2558
+ }
2559
+ function assertInRange(cell) {
2560
+ for (const note of cell.notes) {
2561
+ if (note.pitch < 0 || note.pitch > 127) {
2562
+ throw new InvalidInputError(
2563
+ `transformed motif pitch ${note.pitch} is outside the MIDI range 0..127`
2564
+ );
2565
+ }
2566
+ }
2567
+ return cell;
2568
+ }
2569
+ function transformUnchecked(cell, t, amount, key) {
2415
2570
  assertNoteEvents(cell.notes, "motif notes");
2416
2571
  if (amount !== void 0) {
2417
2572
  assertFiniteNumber(amount, "motif transform amount");
@@ -2420,7 +2575,7 @@ function transformMotif(cell, t, amount, key) {
2420
2575
  assertInteger(amount, "diatonic transform amount");
2421
2576
  }
2422
2577
  if ((t === "augment" || t === "diminish") && amount !== void 0 && amount <= 0) {
2423
- throw new RangeError("time transform amount must be positive");
2578
+ throw new InvalidInputError("time transform amount must be positive");
2424
2579
  }
2425
2580
  const notes = cell.notes;
2426
2581
  switch (t) {
@@ -2480,11 +2635,11 @@ function scaleTime(cell, factor) {
2480
2635
  }))
2481
2636
  };
2482
2637
  }
2483
- function developMotif(cell, timeline, key, bars) {
2638
+ function developMotif(cell, timeline, key, bars, ts = DEFAULT_TS4) {
2484
2639
  assertPositiveInt(bars, "development bars");
2485
2640
  assertNoteEvents(cell.notes, "motif notes");
2486
2641
  const span = cellSpan(cell);
2487
- const totalBeats = bars * 4;
2642
+ const totalBeats = bars * beatsPerBar(ts);
2488
2643
  const origin = cellOrigin(cell.notes);
2489
2644
  const out = [];
2490
2645
  if (span <= 0) {
@@ -2503,7 +2658,7 @@ function developMotif(cell, timeline, key, bars) {
2503
2658
  const chord = timeline.at(startBeat);
2504
2659
  let pitch = n.pitch;
2505
2660
  if (chord) {
2506
- const isMember = chordPitchClasses(chord).includes(pitchClass3(pitch));
2661
+ const isMember = chordPitchClasses(chord).includes(pitchClassOf(pitch));
2507
2662
  pitch = isMember ? pitch : nearestChordTone(pitch, chord);
2508
2663
  } else {
2509
2664
  pitch = nearestScaleTone(pitch, key);
@@ -2515,6 +2670,20 @@ function developMotif(cell, timeline, key, bars) {
2515
2670
  }
2516
2671
 
2517
2672
  // src/generate/progression/index.ts
2673
+ var BORROWED_DEGREES = {
2674
+ /** Flat submediant: bVI. */
2675
+ bVI: 8,
2676
+ /** Flat subtonic: bVII. */
2677
+ bVII: 10,
2678
+ /** Flat mediant: bIII. */
2679
+ bIII: 11,
2680
+ /** Minor subdominant borrowed from the parallel minor: iv. */
2681
+ iv: 12,
2682
+ /** Neapolitan: bII. */
2683
+ bII: 13,
2684
+ /** Sharp subdominant, the diminished #IV. */
2685
+ sharpIV: 14
2686
+ };
2518
2687
  var PRESETS = [
2519
2688
  {
2520
2689
  id: "fourChordPop",
@@ -2672,18 +2841,12 @@ var PRESETS = [
2672
2841
  }
2673
2842
  ];
2674
2843
  var BORROWED_OFFSET = {
2675
- 8: 8,
2676
- // bVI
2677
- 10: 10,
2678
- // bVII
2679
- 11: 3,
2680
- // bIII
2681
- 12: 5,
2682
- // iv
2683
- 13: 1,
2684
- // bII
2685
- 14: 6
2686
- // #IV
2844
+ [BORROWED_DEGREES.bVI]: 8,
2845
+ [BORROWED_DEGREES.bVII]: 10,
2846
+ [BORROWED_DEGREES.bIII]: 3,
2847
+ [BORROWED_DEGREES.iv]: 5,
2848
+ [BORROWED_DEGREES.bII]: 1,
2849
+ [BORROWED_DEGREES.sharpIV]: 6
2687
2850
  };
2688
2851
  function progressions() {
2689
2852
  return PRESETS.map((p) => ({ ...p, degrees: [...p.degrees], styles: [...p.styles] }));
@@ -2711,35 +2874,78 @@ function autoQuality(degree, key) {
2711
2874
  }
2712
2875
  return "maj";
2713
2876
  }
2877
+ function resolveCycle(degrees, key, ext) {
2878
+ const steps = [];
2879
+ for (const degree of degrees) {
2880
+ const step = {
2881
+ source: degree,
2882
+ rootPc: degreeToRootPc(degree, key),
2883
+ quality: ext !== void 0 && ext !== "auto" ? ext : autoQuality(degree, key)
2884
+ };
2885
+ if (degree >= 0 && degree <= 6) {
2886
+ step.degree = degree;
2887
+ }
2888
+ const previous = steps[steps.length - 1];
2889
+ if (previous !== void 0 && previous.source !== step.source && sameStep(previous, step)) {
2890
+ continue;
2891
+ }
2892
+ steps.push(step);
2893
+ }
2894
+ const first = steps[0];
2895
+ const last = steps[steps.length - 1];
2896
+ if (steps.length > 1 && first !== void 0 && last !== void 0 && first.source !== last.source && sameStep(first, last)) {
2897
+ steps.pop();
2898
+ }
2899
+ return steps.length > 0 ? steps : [{ source: 0, degree: 0, rootPc: degreeToRootPc(0, key), quality: "maj" }];
2900
+ }
2901
+ function sameStep(a, b) {
2902
+ return a.rootPc === b.rootPc && a.quality === b.quality;
2903
+ }
2904
+ function pickProgressionPreset(style, seed = 0) {
2905
+ const pool = PRESETS.filter((preset) => preset.styles.includes(style));
2906
+ if (pool.length === 0) {
2907
+ throw new InvalidInputError(`Unknown progression style: ${style}`);
2908
+ }
2909
+ const rng = createRng(seed);
2910
+ const index = Math.floor(rng.next() * pool.length) % pool.length;
2911
+ return pool[index] ?? PRESETS[0];
2912
+ }
2714
2913
  function generateProgression(opts) {
2715
2914
  assertPositiveInt(opts.bars, "progression bars");
2716
2915
  assertGenerationBudget(opts.bars, "progression chords");
2717
2916
  const seed = opts.seed ?? 0;
2718
2917
  let preset;
2719
- if (opts.presetId !== void 0) {
2918
+ if (opts.preset !== void 0) {
2919
+ if (opts.preset.degrees.length === 0) {
2920
+ throw new InvalidInputError("progression preset must name at least one degree");
2921
+ }
2922
+ preset = {
2923
+ id: opts.preset.id ?? "custom",
2924
+ name: opts.preset.name ?? "Custom",
2925
+ degrees: opts.preset.degrees,
2926
+ functional: opts.preset.functional ?? "loop",
2927
+ styles: opts.preset.styles ?? [opts.style]
2928
+ };
2929
+ }
2930
+ if (preset === void 0 && opts.presetId !== void 0) {
2720
2931
  preset = PRESETS.find((p) => p.id === opts.presetId);
2721
2932
  if (preset === void 0) {
2722
- throw new Error(`Unknown progression preset: ${opts.presetId}`);
2933
+ throw new InvalidInputError(`Unknown progression preset: ${opts.presetId}`);
2723
2934
  }
2724
2935
  }
2725
- if (preset === void 0) {
2726
- const pool = PRESETS.filter((p) => p.styles.includes(opts.style));
2727
- const candidates = pool.length > 0 ? pool : PRESETS;
2728
- const rng = createRng(seed);
2729
- const index = Math.floor(rng.next() * candidates.length) % candidates.length;
2730
- preset = candidates[index] ?? PRESETS[0];
2731
- }
2732
- const degrees = preset?.degrees ?? [0];
2936
+ preset ??= pickProgressionPreset(opts.style, seed);
2937
+ const cycle = resolveCycle(preset?.degrees ?? [0], opts.key, opts.ext);
2733
2938
  const chords = [];
2734
2939
  for (let bar = 0; bar < opts.bars; bar += 1) {
2735
- const degree = degrees[bar % degrees.length] ?? 0;
2736
- const quality = opts.ext !== void 0 && opts.ext !== "auto" ? opts.ext : autoQuality(degree, opts.key);
2940
+ const step = cycle[bar % cycle.length];
2737
2941
  const chord = {
2738
- rootPc: degreeToRootPc(degree, opts.key),
2739
- quality,
2740
- startBeat: bar * 4,
2741
- degree: degree >= 0 && degree <= 6 ? degree : void 0
2942
+ rootPc: step?.rootPc ?? 0,
2943
+ quality: step?.quality ?? "maj",
2944
+ startBeat: bar * 4
2742
2945
  };
2946
+ if (step?.degree !== void 0) {
2947
+ chord.degree = step.degree;
2948
+ }
2743
2949
  chords.push(chord);
2744
2950
  }
2745
2951
  if (opts.reharmonize) {
@@ -2772,6 +2978,17 @@ function generateProgression(opts) {
2772
2978
  }
2773
2979
 
2774
2980
  // src/generate/rhythm/index.ts
2981
+ var DEFAULT_RHYTHM_VELOCITY = 96;
2982
+ function rhythmToNoteEvents(events, pitch, velocity = DEFAULT_RHYTHM_VELOCITY) {
2983
+ assertFiniteNumber(pitch, "pitch");
2984
+ assertRange(velocity, 0, 127, "velocity");
2985
+ return events.map((event) => ({
2986
+ pitch,
2987
+ startBeat: event.position,
2988
+ durationBeat: event.duration,
2989
+ velocity
2990
+ }));
2991
+ }
2775
2992
  var DEFAULT_SUBDIVISION = 2;
2776
2993
  var DEFAULT_DENSITY = 0.5;
2777
2994
  var DEFAULT_BARS = 1;
@@ -2791,8 +3008,7 @@ function generateRhythm(ts, opts = {}) {
2791
3008
  const seed = opts.seed ?? 0;
2792
3009
  const bars = opts.bars ?? DEFAULT_BARS;
2793
3010
  const subdivision = opts.subdivision ?? DEFAULT_SUBDIVISION;
2794
- const requestedDensity = assertFiniteNumber(opts.density ?? DEFAULT_DENSITY, "rhythm density");
2795
- const density = Math.min(1, Math.max(0, requestedDensity));
3011
+ const density = assertRange(opts.density ?? DEFAULT_DENSITY, 0, 1, "rhythm density");
2796
3012
  assertPositiveInt(bars, "rhythm bars");
2797
3013
  assertPositiveInt(subdivision, "rhythm subdivision");
2798
3014
  const barBeats = beatsPerBar(ts);
@@ -2834,21 +3050,28 @@ function rhythmDensity(events, ts) {
2834
3050
  }
2835
3051
 
2836
3052
  export {
3053
+ BASS_STYLES,
2837
3054
  generateBassLine,
2838
3055
  generateCounterMelody,
3056
+ DRUM_NOTES,
3057
+ drumVoiceOf,
2839
3058
  generateDrums,
2840
3059
  humanize,
2841
3060
  extractGrooveTemplate,
2842
3061
  applyGrooveTemplate,
2843
3062
  harmonizeMelody,
2844
3063
  generateMotif,
3064
+ motifToNoteEvents,
2845
3065
  transformMotif,
2846
3066
  developMotif,
3067
+ BORROWED_DEGREES,
2847
3068
  progressions,
2848
3069
  progressionsByStyle,
3070
+ pickProgressionPreset,
2849
3071
  generateProgression,
3072
+ rhythmToNoteEvents,
2850
3073
  onsetWeightCurve,
2851
3074
  generateRhythm,
2852
3075
  rhythmDensity
2853
3076
  };
2854
- //# sourceMappingURL=chunk-4MHA432G.js.map
3077
+ //# sourceMappingURL=chunk-7NKDPF74.js.map