@libraz/libcantus 1.0.1 → 1.2.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.
- package/README.md +27 -65
- package/README_ja.md +28 -66
- package/dist/analyze/index.cjs +11 -7
- package/dist/analyze/index.cjs.map +1 -1
- package/dist/analyze/index.d.cts +79 -144
- package/dist/analyze/index.d.ts +79 -144
- package/dist/analyze/index.js +20 -16
- package/dist/chunk-35NBV2FT.cjs +145 -0
- package/dist/chunk-35NBV2FT.cjs.map +1 -0
- package/dist/{chunk-UADYAYAT.cjs → chunk-3R3QVKY5.cjs} +1353 -580
- package/dist/chunk-3R3QVKY5.cjs.map +1 -0
- package/dist/{chunk-WWURE4XT.cjs → chunk-76ZBTRG2.cjs} +624 -729
- package/dist/chunk-76ZBTRG2.cjs.map +1 -0
- package/dist/{chunk-FVSI36UV.js → chunk-7KG3RTYS.js} +197 -110
- package/dist/chunk-7KG3RTYS.js.map +1 -0
- package/dist/{chunk-VTI6FOLB.js → chunk-AGOVW7U6.js} +709 -133
- package/dist/chunk-AGOVW7U6.js.map +1 -0
- package/dist/{chunk-E3F4AHAH.cjs → chunk-CGBADIO5.cjs} +221 -134
- package/dist/chunk-CGBADIO5.cjs.map +1 -0
- package/dist/{chunk-6XH6QZYQ.cjs → chunk-DSMJRXCZ.cjs} +1455 -849
- package/dist/chunk-DSMJRXCZ.cjs.map +1 -0
- package/dist/{chunk-PKFNUHDY.js → chunk-FBH75DAY.js} +1218 -612
- package/dist/chunk-FBH75DAY.js.map +1 -0
- package/dist/chunk-H2KJHBW4.js +3329 -0
- package/dist/chunk-H2KJHBW4.js.map +1 -0
- package/dist/chunk-HPVSH7SF.cjs +2294 -0
- package/dist/chunk-HPVSH7SF.cjs.map +1 -0
- package/dist/{chunk-MXO72PLY.cjs → chunk-HWHGOVO6.cjs} +21 -19
- package/dist/chunk-HWHGOVO6.cjs.map +1 -0
- package/dist/chunk-HYJJ2V2I.js +2294 -0
- package/dist/chunk-HYJJ2V2I.js.map +1 -0
- package/dist/{chunk-AFM2ULJH.cjs → chunk-J7FHVZII.cjs} +1567 -1067
- package/dist/chunk-J7FHVZII.cjs.map +1 -0
- package/dist/chunk-K6OZXIR6.js +1 -0
- package/dist/{chunk-H3VORETI.js → chunk-M5RYMAFI.js} +487 -592
- package/dist/chunk-M5RYMAFI.js.map +1 -0
- package/dist/chunk-NB25P3EH.js +363 -0
- package/dist/chunk-NB25P3EH.js.map +1 -0
- package/dist/{chunk-TTCLJX6D.js → chunk-NFJ4P6ZZ.js} +10 -8
- package/dist/chunk-NFJ4P6ZZ.js.map +1 -0
- package/dist/chunk-NM7UAMC3.cjs +457 -0
- package/dist/chunk-NM7UAMC3.cjs.map +1 -0
- package/dist/chunk-PM4XJE2Y.cjs +363 -0
- package/dist/chunk-PM4XJE2Y.cjs.map +1 -0
- package/dist/chunk-PUKJP2Z5.js +145 -0
- package/dist/chunk-PUKJP2Z5.js.map +1 -0
- package/dist/{chunk-VOHZB45S.js → chunk-Q6LFSAKV.js} +1321 -821
- package/dist/chunk-Q6LFSAKV.js.map +1 -0
- package/dist/{chunk-INHVXP6Z.js → chunk-RFIBF2RR.js} +1203 -430
- package/dist/chunk-RFIBF2RR.js.map +1 -0
- package/dist/{chunk-Q2WGKJNN.cjs → chunk-S5BKZZGM.cjs} +699 -123
- package/dist/chunk-S5BKZZGM.cjs.map +1 -0
- package/dist/chunk-SGLEQVJH.cjs +3329 -0
- package/dist/chunk-SGLEQVJH.cjs.map +1 -0
- package/dist/chunk-UGLFDG7L.cjs +1 -0
- package/dist/chunk-UGLFDG7L.cjs.map +1 -0
- package/dist/{chunk-YH3BIKHH.js → chunk-UMWZ4ZET.js} +146 -49
- package/dist/chunk-UMWZ4ZET.js.map +1 -0
- package/dist/{index-szBv3496.d.ts → coerce-DP0vYu-x.d.ts} +286 -199
- package/dist/{index-C_8o--VE.d.cts → coerce-DRERzdve.d.cts} +286 -199
- package/dist/core/index.cjs +9 -5
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.d.cts +122 -108
- package/dist/core/index.d.ts +122 -108
- package/dist/core/index.js +32 -28
- package/dist/generate/index.cjs +8 -8
- package/dist/generate/index.d.cts +168 -84
- package/dist/generate/index.d.ts +168 -84
- package/dist/generate/index.js +7 -7
- package/dist/{index-CGpRv5sl.d.cts → index-9eoQs2C1.d.ts} +34 -19
- package/dist/{index-CCNJRG-P.d.cts → index-B9QLziDe.d.cts} +293 -56
- package/dist/{index-Dzr13q3d.d.cts → index-BBWNZS0f.d.cts} +135 -58
- package/dist/{index-9wcMhG73.d.ts → index-BHIUyjhZ.d.ts} +293 -56
- package/dist/{index-BGFW-8P2.d.cts → index-BJgP97N1.d.ts} +196 -114
- package/dist/{index-CunYKLiu.d.ts → index-BL5N2Oyz.d.cts} +1 -1
- package/dist/{index-Cj49F892.d.ts → index-BMweUlm5.d.cts} +63 -23
- package/dist/{index-CgHqxdps.d.ts → index-BNSM-DSk.d.cts} +196 -114
- package/dist/{index-D57QRbVn.d.ts → index-BRMbPkHE.d.ts} +480 -254
- package/dist/{index-Chbst3Jx.d.ts → index-CJBBiAmU.d.ts} +135 -58
- package/dist/{index-BkGmlyI3.d.cts → index-CL4b4gUQ.d.ts} +63 -23
- package/dist/{index-B9MNZboQ.d.ts → index-Cy15oTvh.d.cts} +52 -22
- package/dist/{index-B9MNZboQ.d.cts → index-Cy15oTvh.d.ts} +52 -22
- package/dist/{index-BO6xE9YU.d.cts → index-DGCNKAUr.d.cts} +480 -254
- package/dist/{index-B4o55NDr.d.cts → index-Dw2DP7vm.d.ts} +1 -1
- package/dist/{index-G1uwGGWQ.d.ts → index-cHvbYgJI.d.cts} +34 -19
- package/dist/index.cjs +42 -14
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +14 -14
- package/dist/index.d.ts +14 -14
- package/dist/index.js +82 -54
- package/dist/{masks-DYbE485J.d.cts → masks-Cc7-MzzY.d.ts} +49 -2
- package/dist/{masks-DYbE485J.d.ts → masks-pADfTOiD.d.cts} +49 -2
- package/dist/model/index.cjs +19 -12
- package/dist/model/index.cjs.map +1 -1
- package/dist/model/index.d.cts +504 -182
- package/dist/model/index.d.ts +504 -182
- package/dist/model/index.js +18 -11
- package/dist/{positional-Cptlj-xs.d.ts → positional-DJgs6ltD.d.ts} +85 -8
- package/dist/{positional-DvH_sd8u.d.cts → positional-DMZer-FM.d.cts} +85 -8
- package/dist/theory/index.cjs +35 -6
- package/dist/theory/index.cjs.map +1 -1
- package/dist/theory/index.d.cts +257 -62
- package/dist/theory/index.d.ts +257 -62
- package/dist/theory/index.js +41 -12
- package/package.json +7 -5
- package/dist/chunk-2LCPM3EG.js +0 -3224
- package/dist/chunk-2LCPM3EG.js.map +0 -1
- package/dist/chunk-6XH6QZYQ.cjs.map +0 -1
- package/dist/chunk-6XPTBE7Y.cjs +0 -49
- package/dist/chunk-6XPTBE7Y.cjs.map +0 -1
- package/dist/chunk-AFM2ULJH.cjs.map +0 -1
- package/dist/chunk-B6XNKWZV.cjs +0 -3224
- package/dist/chunk-B6XNKWZV.cjs.map +0 -1
- package/dist/chunk-E3F4AHAH.cjs.map +0 -1
- package/dist/chunk-ELCTZUJJ.js +0 -379
- package/dist/chunk-ELCTZUJJ.js.map +0 -1
- package/dist/chunk-FVSI36UV.js.map +0 -1
- package/dist/chunk-H3VORETI.js.map +0 -1
- package/dist/chunk-H42UHPYW.js +0 -1577
- package/dist/chunk-H42UHPYW.js.map +0 -1
- package/dist/chunk-HFPS7JFD.js +0 -1
- package/dist/chunk-INHVXP6Z.js.map +0 -1
- package/dist/chunk-K4ZJUGF4.cjs +0 -379
- package/dist/chunk-K4ZJUGF4.cjs.map +0 -1
- package/dist/chunk-KNJEKLKV.js +0 -49
- package/dist/chunk-KNJEKLKV.js.map +0 -1
- package/dist/chunk-MXO72PLY.cjs.map +0 -1
- package/dist/chunk-NLEU4QSE.cjs +0 -360
- package/dist/chunk-NLEU4QSE.cjs.map +0 -1
- package/dist/chunk-OE6WK2Y2.cjs +0 -1577
- package/dist/chunk-OE6WK2Y2.cjs.map +0 -1
- package/dist/chunk-PKFNUHDY.js.map +0 -1
- package/dist/chunk-Q2WGKJNN.cjs.map +0 -1
- package/dist/chunk-RHIJ3S4I.cjs +0 -1
- package/dist/chunk-RHIJ3S4I.cjs.map +0 -1
- package/dist/chunk-TTCLJX6D.js.map +0 -1
- package/dist/chunk-UADYAYAT.cjs.map +0 -1
- package/dist/chunk-VOHZB45S.js.map +0 -1
- package/dist/chunk-VTI6FOLB.js.map +0 -1
- package/dist/chunk-WWURE4XT.cjs.map +0 -1
- package/dist/chunk-YH3BIKHH.js.map +0 -1
- /package/dist/{chunk-HFPS7JFD.js.map → chunk-K6OZXIR6.js.map} +0 -0
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["/home/runner/work/libcantus/libcantus/dist/chunk-AFM2ULJH.cjs","../src/generate/drums/internal.ts","../src/generate/vocabulary/types.ts","../src/generate/context/difficulty.ts","../src/generate/context/draw.ts","../src/generate/context/index.ts","../src/generate/bass/internal.ts","../src/generate/vocabulary/transform.ts","../src/generate/bass/licks.ts","../src/generate/bass/index.ts","../src/generate/countermelody/imitation.ts","../src/generate/countermelody/index.ts","../src/generate/drums/ghost.ts","../src/generate/drums/hihat.ts","../src/generate/drums/swing.ts","../src/generate/drums/beat.ts","../src/generate/drums/euclid.ts","../src/generate/drums/fills.ts","../src/generate/drums/hit.ts","../src/generate/drums/kick.ts","../src/generate/drums/percussion.ts","../src/generate/drums/kit.ts","../src/generate/drums/vocabulary.ts","../src/generate/drums/index.ts","../src/generate/groove/index.ts","../src/generate/harmonize/nct.ts","../src/generate/harmonize/index.ts","../src/generate/motif/index.ts","../src/generate/ornament/index.ts","../src/generate/progression/index.ts","../src/generate/reharmony/index.ts","../src/generate/rhythm/index.ts"],"names":["at","ornament","EPS","DEFAULT_TS","DEFAULT_OCTAVE","bar","FOUR_FOUR","DEFAULT_BPM","hit","pulse","note"],"mappings":"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ACD,MAAA;AACR,IAAA;AACF,EAAA;AACA,EAAA;AACM,IAAA;AACG,IAAA;AACK,IAAA;AACW,IAAA;AACnBA,IAAAA;AACgB,IAAA;AACN,IAAA;AACI,MAAA;AACI,MAAA;AACH,MAAA;AACV,MAAA;AACA,MAAA;AACC,MAAA;AACA,MAAA;AACsB,MAAA;AAC/B,IAAA;AACW,IAAA;AACH,MAAA;AACD,MAAA;AACR,IAAA;AACF,EAAA;AACA,EAAA;AACM,IAAA;AACG,IAAA;AACK,IAAA;AACI,IAAA;AACZA,IAAAA;AACiB,IAAA;AACP,IAAA;AACM,MAAA;AACA,MAAA;AACA,MAAA;AACC,MAAA;AACI,MAAA;AACA,MAAA;AACA,MAAA;AACC,MAAA;AACA,MAAA;AACG,MAAA;AAC5B,IAAA;AACW,IAAA;AACH,MAAA;AACD,MAAA;AACR,IAAA;AACF,EAAA;AACA,EAAA;AACM,IAAA;AACG,IAAA;AACK,IAAA;AACI,IAAA;AACZA,IAAAA;AACgB,IAAA;AACN,IAAA;AACO,MAAA;AACD,MAAA;AACF,MAAA;AACE,MAAA;AACC,MAAA;AACA,MAAA;AACF,MAAA;AACE,MAAA;AACK,MAAA;AACC,MAAA;AACA,MAAA;AAC1B,IAAA;AACW,IAAA;AACH,MAAA;AACD,MAAA;AACR,IAAA;AACF,EAAA;AACA,EAAA;AACM,IAAA;AACG,IAAA;AACK,IAAA;AACW,IAAA;AACnBA,IAAAA;AACgB,IAAA;AACN,IAAA;AACI,MAAA;AACE,MAAA;AACE,MAAA;AACA,MAAA;AACH,MAAA;AACC,MAAA;AACX,MAAA;AACA,MAAA;AACC,MAAA;AACA,MAAA;AACsB,MAAA;AAC/B,IAAA;AACW,IAAA;AACH,MAAA;AACD,MAAA;AACR,IAAA;AACF,EAAA;AACA,EAAA;AACM,IAAA;AACG,IAAA;AACK,IAAA;AACW,IAAA;AACnBA,IAAAA;AACiB,IAAA;AACP,IAAA;AACI,MAAA;AACG,MAAA;AACF,MAAA;AACI,MAAA;AACH,MAAA;AACX,MAAA;AACC,MAAA;AACmB,MAAA;AAC5B,IAAA;AACW,IAAA;AACH,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,IAAA;AACvB,MAAA;AACF,IAAA;AACkB,IAAA;AACW,IAAA;AACP,MAAA;AACS,MAAA;AAC3B,QAAA;AACF,MAAA;AACF,IAAA;AACa,IAAA;AACM,MAAA;AACD,MAAA;AACc,MAAA;AACT,MAAA;AACtB,IAAA;AACH,EAAA;AACO,EAAA;AACT;AAwCwC;AAEV,EAAA;AACM,EAAA;AACjB,EAAA;AACa,EAAA;AACP,IAAA;AACI,IAAA;AACvB,MAAA;AACF,IAAA;AACkB,IAAA;AACL,IAAA;AACX,MAAA;AAC2B,MAAA;AACG,MAAA;AAC/B,IAAA;AACH,EAAA;AAE8B,EAAA;AACI,EAAA;AACd,EAAA;AACU,IAAA;AACF,MAAA;AAC1B,IAAA;AACF,EAAA;AAK6B,EAAA;AACI,EAAA;AACD,IAAA;AAChC,EAAA;AACO,EAAA;AACT;AA0BsC;AAEJ,EAAA;AACJ,EAAA;AACN,EAAA;AACQ,EAAA;AACA,EAAA;AACA,EAAA;AACG,EAAA;AAGN,EAAA;AACM,EAAA;AACnB,EAAA;AAIgB,IAAA;AACI,MAAA;AAChC,IAAA;AAC8B,IAAA;AAChC,EAAA;AACqC,EAAA;AACP,EAAA;AAChC;A9Bw5IoC;AACA;A+Bx0JJ;AAwB9B;AAGiC,EAAA;AACH,EAAA;AACA,EAAA;AAC5B,IAAA;AACiB,IAAA;AACG,IAAA;AACpB,IAAA;AACA,EAAA;AACJ;AAmC4B;AACJ;AACH;AAYoC;AACvC,EAAA;AACT,IAAA;AACI,MAAA;AACJ,IAAA;AACI,MAAA;AACJ,IAAA;AACI,MAAA;AACT,IAAA;AACS,MAAA;AACX,EAAA;AACF;AAyByE;AAC7C,EAAA;AACD,EAAA;AACO,EAAA;AACA,EAAA;AAER,EAAA;AACO,EAAA;AAEA,EAAA;AACF,EAAA;AACZ,EAAA;AACa,EAAA;AACE,EAAA;AACE,EAAA;AACJ,EAAA;AAED,EAAA;AACCL,EAAAA;AACL,IAAA;AACG,IAAA;AACA,IAAA;AACI,MAAA;AACH,MAAA;AACvB,QAAA;AACF,MAAA;AAEgB,MAAA;AACS,QAAA;AACvB,QAAA;AACF,MAAA;AAC4B,MAAA;AACR,MAAA;AACO,MAAA;AACF,QAAA;AACzB,MAAA;AACF,IAAA;AACF,EAAA;AAE+B,EAAA;AACD,EAAA;AACA,IAAA;AACG,IAAA;AACP,IAAA;AAC1B,EAAA;AACO,EAAA;AACT;AAUgF;AACrD,EAAA;AAChB,IAAA;AACT,EAAA;AAC2B,EAAA;AACF,EAAA;AACA,EAAA;AACS,EAAA;AACpC;A/B8tJoC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA","file":"/home/runner/work/libcantus/libcantus/dist/chunk-AFM2ULJH.cjs","sourcesContent":[null,"/** General MIDI percussion note numbers used across the drum engine. */\nexport const GM = {\n BD: 36,\n SD: 38,\n SIDESTICK: 37,\n HANDCLAP: 39,\n CHH: 42,\n FHH: 44,\n OHH: 46,\n CRASH: 49,\n RIDE: 51,\n TAMBOURINE: 54,\n TOM_H: 50,\n TOM_M: 47,\n TOM_L: 45,\n SHAKER: 82,\n} as const;\n\n/** One quarter, one eighth and one sixteenth expressed in quarter-note beats. */\nexport const QUARTER = 1;\nexport const EIGHTH = 0.5;\nexport const SIXTEENTH = 0.25;\n\n/** Internal drum style selected by the public groove style. */\nexport type DrumStyle =\n | 'sparse'\n | 'standard'\n | 'fourOnFloor'\n | 'upbeat'\n | 'rock'\n | 'synth'\n | 'trap'\n | 'latin';\n\n/** Every groove feel, in declaration order. */\nexport const DRUM_FEELS = ['straight', 'swing', 'shuffle'] as const;\n\n/** Groove feel controlling off-beat swing. */\nexport type Feel = (typeof DRUM_FEELS)[number];\n\n/**\n * Groove feel (the swing/straight rhythmic character), under the name the\n * public surface uses.\n *\n * @category Composition\n */\nexport type GrooveFeel = Feel;\n\n/** Reduced section set the engine reasons about. */\nexport type SectionType = 'intro' | 'a' | 'b' | 'chorus' | 'bridge' | 'outro';\n\n/** Rhythmic density assumed when the caller names none. */\nexport const DEFAULT_RHYTHMIC = 0.5;\n\n/** Section energy level, used to shape fills. */\nexport type SectionEnergy = 'low' | 'medium' | 'high' | 'peak';\n\n/** Ghost-note mood category for the density table. */\nexport enum MoodCategory {\n Calm = 0,\n Standard = 1,\n Energetic = 2,\n}\n\n/** Percussion mood category for the activation table. */\nexport enum PercMoodCategory {\n Calm = 0,\n Standard = 1,\n Energetic = 2,\n Idol = 3,\n RockDark = 4,\n}\n\n/**\n * Every public groove style, in declaration order. The type is derived from\n * this list so the runtime guard and the type can never disagree.\n */\nexport const GROOVE_STYLES = [\n 'standard',\n 'funk',\n 'shuffle',\n 'bossa',\n 'trap',\n 'halftime',\n 'breakbeat',\n 'house',\n 'synthpop',\n] as const;\n\n/**\n * Public groove style identifiers.\n *\n * @category Composition\n */\nexport type GrooveStyle = (typeof GROOVE_STYLES)[number];\n\n/** Every public role, in declaration order. */\nexport const DRUM_ROLES = ['full', 'ambient', 'minimal', 'fxOnly'] as const;\n\n/**\n * Public role identifiers gating which voices are present.\n *\n * @category Composition\n */\nexport type DrumRole = (typeof DRUM_ROLES)[number];\n\n/** Every public section, in declaration order. */\nexport const PUBLIC_SECTIONS = [\n 'intro',\n 'verse',\n 'prechorus',\n 'chorus',\n 'bridge',\n 'outro',\n] as const;\n\n/**\n * Public section identifiers for {@link generateDrums}.\n *\n * These are the names a caller writes. The generator maps them onto a\n * narrower internal set the pattern tables are keyed by, which answers to\n * `'a'` and `'b'` and refuses `'verse'` and `'prechorus'`. That set is not\n * published: a caller who reached for it by name would be held to a\n * vocabulary that is not this one.\n *\n * @category Composition\n */\nexport type Section = (typeof PUBLIC_SECTIONS)[number];\n\n/** Resolved internal parameters for a public groove style. */\nexport type StyleMapping = {\n style: DrumStyle;\n feel: Feel;\n ghostBoost: boolean;\n snareBeat3: boolean;\n};\n\nconst STYLE_MAP: Record<GrooveStyle, StyleMapping> = {\n // Standard pop pulse: kick on beats 1 and 3, backbeat snare on 2 and 4.\n standard: { style: 'standard', feel: 'straight', ghostBoost: false, snareBeat3: false },\n funk: { style: 'upbeat', feel: 'straight', ghostBoost: true, snareBeat3: false },\n shuffle: { style: 'standard', feel: 'shuffle', ghostBoost: false, snareBeat3: false },\n bossa: { style: 'latin', feel: 'straight', ghostBoost: false, snareBeat3: false },\n trap: { style: 'trap', feel: 'straight', ghostBoost: false, snareBeat3: true },\n halftime: { style: 'sparse', feel: 'straight', ghostBoost: false, snareBeat3: true },\n breakbeat: { style: 'rock', feel: 'straight', ghostBoost: false, snareBeat3: false },\n // Four-on-the-floor dance kick with idol-style auxiliary percussion.\n house: { style: 'fourOnFloor', feel: 'straight', ghostBoost: false, snareBeat3: false },\n // Synth-pop pulse with denser 16th hi-hats and busy kick syncopation.\n synthpop: { style: 'synth', feel: 'straight', ghostBoost: true, snareBeat3: false },\n};\n\n/** Resolve the internal style/feel parameters for a public groove style. */\nexport function mapStyle(style: GrooveStyle): StyleMapping {\n return STYLE_MAP[style];\n}\n\n/** Map a public section to the internal section type. */\nexport function mapSection(section: Section): SectionType {\n switch (section) {\n case 'intro':\n return 'intro';\n case 'verse':\n return 'a';\n case 'prechorus':\n return 'b';\n case 'chorus':\n return 'chorus';\n case 'bridge':\n return 'bridge';\n case 'outro':\n return 'outro';\n }\n}\n\n/**\n * How loudly the backing sits, as a factor on the section's density.\n *\n * The dial is read as the continuous quantity it is, which is what lets it be a\n * slider: bucketing it into three levels would make two thirds of the travel do\n * nothing and the remaining third jump by half.\n */\nexport function backingScale(rhythmic: number): number {\n return BACKING_SCALE_FLOOR + (BACKING_SCALE_CEILING - BACKING_SCALE_FLOOR) * rhythmic;\n}\n\n/** Backing weight at the bottom and the top of the rhythmic dial. */\nconst BACKING_SCALE_FLOOR = 0.75;\nconst BACKING_SCALE_CEILING = 1.15;\n\n/**\n * A probability leaned on by the rhythmic dial: unchanged at the middle\n * setting, halved at the bottom, half again as likely at the top.\n *\n * Comparing the result against a draw fixed by position is what makes the dial\n * monotone — raising it can only turn an onset on, never off.\n */\nexport function leanedBy(probability: number, rhythmic: number): number {\n return Math.max(0, Math.min(1, probability * (0.5 + rhythmic)));\n}\n\n/**\n * Swing amount (0..1) implied by a groove feel, as a share of the full triplet\n * displacement.\n *\n * A shuffle is the triplet itself — that is what the word names — so it is the\n * whole of it; a swing leans two thirds of the way, which is what separates the\n * two as rhythmic characters rather than as two settings of the same one.\n */\nexport function feelSwingAmount(feel: Feel): number {\n if (feel === 'swing') {\n return 2 / 3;\n }\n if (feel === 'shuffle') {\n return 1;\n }\n return 0;\n}\n\n/** Ghost-note mood category derived from the internal style. */\nexport function ghostMoodCategory(style: DrumStyle): MoodCategory {\n if (style === 'sparse') {\n return MoodCategory.Calm;\n }\n if (style === 'trap' || style === 'upbeat') {\n return MoodCategory.Energetic;\n }\n return MoodCategory.Standard;\n}\n\n/** Percussion mood category derived from the internal style. */\nexport function percMoodCategory(style: DrumStyle): PercMoodCategory {\n switch (style) {\n case 'sparse':\n return PercMoodCategory.Calm;\n case 'upbeat':\n case 'latin':\n return PercMoodCategory.Energetic;\n case 'fourOnFloor':\n return PercMoodCategory.Idol;\n case 'trap':\n case 'rock':\n return PercMoodCategory.RockDark;\n default:\n return PercMoodCategory.Standard;\n }\n}\n\n/** Zero-based section index shared by the ghost and percussion tables. */\nexport function sectionIndex(section: SectionType): number {\n switch (section) {\n case 'intro':\n return 0;\n case 'a':\n return 1;\n case 'b':\n return 2;\n case 'chorus':\n return 3;\n case 'bridge':\n return 4;\n case 'outro':\n return 6;\n }\n}\n\n/** Section energy used to size fills. */\nexport function sectionEnergy(section: SectionType): SectionEnergy {\n switch (section) {\n case 'intro':\n case 'outro':\n return 'low';\n case 'a':\n case 'bridge':\n return 'medium';\n case 'b':\n return 'high';\n case 'chorus':\n return 'peak';\n }\n}\n\n/** Section velocity multiplier applied to the base drum velocity. */\nexport function sectionVelocityMultiplier(section: SectionType): number {\n switch (section) {\n case 'intro':\n case 'outro':\n return 0.8;\n case 'a':\n return 0.9;\n case 'b':\n return 1.0;\n case 'chorus':\n return 1.1;\n case 'bridge':\n return 0.85;\n }\n}\n\n/** Section density multiplier before backing-density scaling. */\nexport function sectionDensityMultiplier(section: SectionType): number {\n switch (section) {\n case 'intro':\n return 0.5;\n case 'a':\n return 0.7;\n case 'b':\n return 0.85;\n case 'chorus':\n return 1.0;\n case 'bridge':\n return 0.6;\n case 'outro':\n return 0.6;\n }\n}\n\n/** Base drum velocity for a section beat (downbeats accented). */\nexport function calculateVelocity(section: SectionType, beat: number): number {\n const base = 80;\n const beatAdj = beat === 0 ? 10 : beat === 2 ? 5 : 0;\n const velocity = (base + beatAdj) * sectionVelocityMultiplier(section);\n return clampVel(velocity);\n}\n\n/**\n * How far a backbeat sits above the velocity of the beat it lands on.\n *\n * The fill reads it too: the stroke a fill lands on has to arrive above the\n * backbeats around it, or the phrase end is heard as a dip rather than a lift.\n */\nexport const BACKBEAT_LIFT = 16;\n\n/** How far the stroke a fill lands on sits above a backbeat. */\nexport const FILL_ACCENT_LIFT = 4;\n\n/** Round and clamp a velocity into the valid MIDI range. */\nexport function clampVel(velocity: number): number {\n return Math.max(1, Math.min(127, Math.round(velocity)));\n}\n","/**\n * The shared vocabulary model: a remembered figure, plus the conditions under\n * which a player would reach for it.\n *\n * A rule-based generator can build a correct line and still sound characterless,\n * because a player does not synthesise from scales — they assemble figures they\n * already know. Those figures are not derivable, so they are held as data here:\n * one record per figure, carrying its own applicability conditions, so choosing\n * one is a deterministic lookup rather than a branch in a generator.\n *\n * ## Provenance\n *\n * No entry may reproduce a phrase from a particular recorded song. Only figures\n * that belong to nobody are admitted — the common currency of a genre, the\n * things every player of that music already plays. Every entry records which\n * basis it qualifies under in {@link Vocabulary.provenance}, and the field is\n * required so the judgment cannot be skipped while curating.\n */\n\nimport { ARTICULATIONS, type Articulation } from '../../core/instrument/index.js';\nimport type { TimeSignature } from '../../core/meter/index.js';\nimport { assertOneOf, assertRange, assertTimeSignature } from '../../core/validation/index.js';\nimport { type ChordQuality, chordQualities } from '../../theory/chord/index.js';\nimport type { Draw } from '../context/draw.js';\nimport { PUBLIC_SECTIONS, type Section } from '../drums/internal.js';\n\n/**\n * Every genre the built-in dictionaries name, in declaration order.\n *\n * The list is open in spirit even though the type is closed: a caller with a\n * genre of its own supplies entries through the generation context rather than\n * waiting for this list to grow.\n *\n * @category Composition\n */\nexport const GENRES = Object.freeze([\n 'pop',\n 'rock',\n 'motown',\n 'funk',\n 'soul',\n 'gospel',\n 'jazz',\n 'blues',\n 'bossa',\n 'samba',\n 'reggae',\n 'country',\n 'hiphop',\n 'house',\n 'dnb',\n] as const);\n\n/**\n * The genre a figure belongs to. Genre is what selects material; it never\n * deforms or rejects it.\n *\n * @category Composition\n */\nexport type Genre = (typeof GENRES)[number];\n\n/**\n * Every ground on which a figure may enter a dictionary, in declaration order.\n *\n * - `'idiom'`: a genre-defining figure no one owns, played by every player of\n * that music — a backbeat, a walking approach, a two-and-four side stick.\n * - `'construction'`: derived here from the genre's rhythmic grid rather than\n * remembered from any performance.\n * - `'traditional'`: from a folk or traditional repertoire with no author.\n *\n * @category Composition\n */\nexport const PROVENANCE_BASES = Object.freeze(['idiom', 'construction', 'traditional'] as const);\n\n/**\n * The ground on which a figure qualifies for a dictionary.\n *\n * @category Composition\n */\nexport type ProvenanceBasis = (typeof PROVENANCE_BASES)[number];\n\n/**\n * Why a figure may be published as vocabulary.\n *\n * @category Composition\n */\nexport type Provenance = {\n /** Which ground the figure qualifies under. */\n basis: ProvenanceBasis;\n /**\n * What the figure is, in the terms a player would use — \"the genre's\n * two-and-four side stick\", not the name of a record it appears on.\n */\n note: string;\n};\n\n/**\n * One remembered figure, together with the conditions it fits.\n *\n * The optional conditions are read as \"no restriction\" when absent: an entry\n * with no `sections` fits every section, one with no `tempoRange` fits every\n * tempo. {@link Vocabulary.difficulty} is on the same 1..5 scale as the\n * playability report, and is compared against the difficulty ceiling: an entry\n * harder than the ceiling is rejected, never simplified in place.\n *\n * @typeParam T - The material this kind of figure is made of: chord-relative\n * degrees for a bass lick, voices on a grid for a drum pattern.\n *\n * @category Composition\n */\nexport type Vocabulary<T> = {\n /** Stable identifier; a caller entry with the same id replaces a built-in. */\n id: string;\n genre: Genre;\n /** The figure itself. */\n material: T;\n /**\n * How the figure is played, over and above the material's own strokes.\n *\n * These are requirements, not decoration: where a query names the techniques\n * the instrument can produce, an entry asking for one the instrument does not\n * have is not offered. A kit with no flam is never handed flam material.\n */\n articulations: Articulation[];\n /** Chord qualities the figure works over; absent means any chord. */\n fitsOver?: ChordQuality[];\n /** Sections the figure suits; absent means any section. */\n sections?: Section[];\n /** Inclusive tempo band in BPM; absent means any tempo. */\n tempoRange?: [number, number];\n /** Time signature the figure is written in; absent means any. */\n ts?: TimeSignature;\n /** How hard the figure is to play, from 1 to 5. */\n difficulty: number;\n /** Why this figure may be published; see the module documentation. */\n provenance: Provenance;\n};\n\n/**\n * What a generator knows about the moment it is choosing a figure for.\n *\n * Every field is optional and an absent field asks nothing: a query with no\n * genre matches every genre, exactly as an entry with no `sections` matches\n * every section.\n *\n * @category Composition\n */\nexport type VocabularyQuery = {\n genre?: Genre;\n section?: Section;\n /** Tempo in BPM, matched against each entry's band. */\n bpm?: number;\n ts?: TimeSignature;\n /** The difficulty ceiling: entries above it are rejected. */\n difficulty?: number;\n /** The chord in force, matched against each entry's `fitsOver`. */\n quality?: ChordQuality;\n /**\n * The techniques the instrument this part is written for can produce, from\n * its profile. An entry asking for one that is not here is not offered;\n * absent asks nothing, which is the programmed case.\n */\n articulations?: readonly Articulation[];\n};\n\n/** Whether two time signatures name the same bar. */\nfunction sameTs(a: TimeSignature, b: TimeSignature): boolean {\n return a.numerator === b.numerator && a.denominator === b.denominator;\n}\n\n/**\n * Whether one entry answers a query.\n *\n * @param entry The dictionary entry.\n * @param query What the generator knows about the moment.\n * @returns True when every condition the entry states is met.\n *\n * @category Composition\n */\nexport function fitsQuery<T>(entry: Vocabulary<T>, query: VocabularyQuery): boolean {\n if (query.genre !== undefined && entry.genre !== query.genre) {\n return false;\n }\n if (query.section !== undefined && entry.sections && !entry.sections.includes(query.section)) {\n return false;\n }\n if (query.bpm !== undefined && entry.tempoRange) {\n const [low, high] = entry.tempoRange;\n if (query.bpm < low || query.bpm > high) {\n return false;\n }\n }\n if (query.ts !== undefined && entry.ts && !sameTs(entry.ts, query.ts)) {\n return false;\n }\n if (query.quality !== undefined && entry.fitsOver && !entry.fitsOver.includes(query.quality)) {\n return false;\n }\n if (query.articulations !== undefined) {\n const available = query.articulations;\n if (entry.articulations.some((articulation) => !available.includes(articulation))) {\n return false;\n }\n }\n // The ceiling is the one condition the entry does not state itself: it is the\n // caller's limit, and it only ever takes candidates away.\n return query.difficulty === undefined || entry.difficulty <= query.difficulty;\n}\n\n/**\n * Every entry of a dictionary that answers a query, in dictionary order.\n *\n * Order is preserved so a selection made from the result is reproducible: the\n * same dictionary and the same query always offer the same candidates in the\n * same positions.\n *\n * @param dictionary The entries to search.\n * @param query What the generator knows about the moment.\n * @returns The matching entries, in the order they appear in the dictionary.\n *\n * @example\n * ```ts\n * import { BASS_LICKS, selectVocabulary } from '@libraz/libcantus';\n * const candidates = selectVocabulary(BASS_LICKS, { genre: 'motown', difficulty: 3 });\n * ```\n *\n * @category Composition\n */\nexport function selectVocabulary<T>(\n dictionary: readonly Vocabulary<T>[],\n query: VocabularyQuery,\n): Vocabulary<T>[] {\n return dictionary.filter((entry) => fitsQuery(entry, query));\n}\n\n/**\n * Choose one entry from a dictionary at a position.\n *\n * The choice is addressed by the path, not by how many choices came before it,\n * so the figure at bar 12 does not move because the figure at bar 3 changed.\n *\n * @param dictionary The entries to choose from.\n * @param query What the generator knows about the moment.\n * @param draw The position-addressed sampler.\n * @param path Where the choice is being made.\n * @returns The chosen entry, or undefined when nothing fits.\n *\n * @category Composition\n */\nexport function pickVocabulary<T>(\n dictionary: readonly Vocabulary<T>[],\n query: VocabularyQuery,\n draw: Draw,\n ...path: readonly (string | number)[]\n): Vocabulary<T> | undefined {\n const candidates = selectVocabulary(dictionary, query);\n if (candidates.length === 0) {\n return undefined;\n }\n return candidates[draw.range(0, candidates.length - 1, ...path)];\n}\n\n/**\n * Merge a caller's dictionary into a built-in one.\n *\n * Caller entries are appended, so every built-in figure stays reachable, except\n * where a caller entry carries the id of a built-in: that one is a replacement\n * and takes the built-in's place, which is how a caller corrects a figure it\n * disagrees with rather than having both offered.\n *\n * @param builtIn The library's own entries.\n * @param supplied The caller's entries, if any.\n * @returns The merged dictionary, built-ins first.\n *\n * @category Composition\n */\nexport function mergeVocabulary<T>(\n builtIn: readonly Vocabulary<T>[],\n supplied: readonly Vocabulary<T>[] | undefined,\n): Vocabulary<T>[] {\n if (!supplied || supplied.length === 0) {\n return [...builtIn];\n }\n const byId = new Map(supplied.map((entry) => [entry.id, entry]));\n const merged = builtIn.map((entry) => byId.get(entry.id) ?? entry);\n const replaced = new Set(builtIn.map((entry) => entry.id));\n for (const entry of supplied) {\n if (!replaced.has(entry.id)) {\n merged.push(entry);\n }\n }\n return merged;\n}\n\n/**\n * Narrow a mixed dictionary to the entries one part understands.\n *\n * A context carries a single list, because a caller thinks in terms of \"the\n * vocabulary of this piece\" rather than of one list per generator. Each\n * generator recognises its own material by shape, so an entry meant for the\n * bass is invisible to the drums instead of being mis-read by it.\n *\n * @param entries The mixed dictionary.\n * @param isMaterial Guard recognising one kind of material.\n * @returns The entries whose material the guard accepts.\n *\n * @category Composition\n */\nexport function vocabularyOfKind<T>(\n entries: readonly Vocabulary<unknown>[],\n isMaterial: (material: unknown) => material is T,\n): Vocabulary<T>[] {\n const out: Vocabulary<T>[] = [];\n for (const entry of entries) {\n if (isMaterial(entry.material)) {\n out.push(entry as Vocabulary<T>);\n }\n }\n return out;\n}\n\n/**\n * Reject a dictionary entry whose declared fields are outside their domains.\n *\n * Applied to caller-supplied entries at the point they enter a generator: an\n * entry with a difficulty of 40 would otherwise pass every ceiling and be\n * chosen for a beginner's part.\n *\n * @param entry The entry to check.\n * @param label Name used in the error message.\n * @returns The entry, unchanged.\n * @throws If the difficulty is outside 1..5 or the tempo band is inverted.\n *\n * @category Composition\n */\nexport function assertVocabulary<T>(\n entry: Vocabulary<T>,\n label = 'vocabulary entry',\n): Vocabulary<T> {\n // The names are checked against the tables that define them rather than\n // against a list written out again here: a genre or an articulation added to\n // the library is admitted by this check on the same commit, and a typo from a\n // JavaScript caller or a config file is a stated error instead of an entry\n // that silently matches nothing.\n assertOneOf(entry.genre, GENRES, `${label} genre`);\n assertOneOf(entry.provenance?.basis, PROVENANCE_BASES, `${label} provenance basis`);\n entry.articulations.forEach((articulation, index) => {\n assertOneOf(articulation, ARTICULATIONS, `${label} articulations[${index}]`);\n });\n entry.sections?.forEach((section, index) => {\n assertOneOf(section, PUBLIC_SECTIONS, `${label} sections[${index}]`);\n });\n entry.fitsOver?.forEach((quality, index) => {\n assertOneOf(quality, chordQualities(), `${label} fitsOver[${index}]`);\n });\n if (entry.ts !== undefined) {\n assertTimeSignature(entry.ts, `${label} ts`);\n }\n assertRange(entry.difficulty, 1, 5, `${label} difficulty`);\n if (entry.tempoRange) {\n const [low, high] = entry.tempoRange;\n assertRange(low, Number.MIN_VALUE, 1000, `${label} tempoRange low`);\n assertRange(high, low, 1000, `${label} tempoRange high`);\n }\n return entry;\n}\n","/**\n * The playing-difficulty ceiling, as the generators apply it.\n *\n * A ceiling is not an intensity: a passage can be harmonically involved and\n * easy to play, or plain and hard. So difficulty never proposes material — it\n * only rejects what the other complexity dials proposed, and only on the\n * timing layer, where speed is what stands in the way. Whether the note exists\n * on the instrument at all, and whether the hand can hold the shape, belong to\n * the instrument profile and hold regardless of the ceiling.\n */\n\nimport { assertRange } from '../../core/validation/index.js';\n\n/** The ceiling's lowest level: what a beginner sustains. */\nexport const MIN_DIFFICULTY = 1;\n\n/** The ceiling's highest level: what a rudimental player sustains. */\nexport const MAX_DIFFICULTY = 5;\n\n/**\n * Strokes per second one limb sustains at each level, from a beginner's plain\n * eighths to the wrist speed a rudimental player keeps up.\n */\nconst STROKES_PER_SECOND = [3, 5, 8, 12, 16] as const;\n\n/**\n * Frets per second the fretting hand travels at each level. A shift is a\n * displacement, so the figure is a rate rather than a count of notes.\n */\nconst FRETS_PER_SECOND = [6, 12, 20, 32, 50] as const;\n\n/** Seconds one quarter-note beat lasts at a tempo. */\nfunction secondsPerBeat(bpm: number): number {\n return 60 / bpm;\n}\n\n/** Read a per-level table at a possibly fractional level, interpolating. */\nfunction atLevel(table: readonly number[], difficulty: number): number {\n const level = Math.min(MAX_DIFFICULTY, Math.max(MIN_DIFFICULTY, difficulty));\n const lower = Math.floor(level);\n const upper = Math.ceil(level);\n const low = table[lower - 1] ?? table[0] ?? 0;\n const high = table[upper - 1] ?? low;\n return low + (high - low) * (level - lower);\n}\n\n/** Reject a ceiling outside the scale the profiles and reports share. */\nexport function assertDifficulty(difficulty: number, label = 'complexity difficulty'): number {\n return assertRange(difficulty, MIN_DIFFICULTY, MAX_DIFFICULTY, label);\n}\n\n/**\n * Whether a player at this ceiling keeps up a stream of strokes on one voice.\n *\n * @param stepBeats Gap between consecutive strokes, in quarter-note beats.\n * @param bpm Tempo, or undefined when the caller gave none.\n * @param difficulty The ceiling, or undefined for no ceiling.\n * @returns True when the stream is inside the ceiling, and whenever either the\n * ceiling or the tempo is unknown — a limit nobody stated constrains nothing.\n */\nexport function sustainsStrokes(\n stepBeats: number,\n bpm: number | undefined,\n difficulty: number | undefined,\n): boolean {\n if (difficulty === undefined || bpm === undefined || stepBeats <= 0) {\n return true;\n }\n const rate = 1 / (stepBeats * secondsPerBeat(bpm));\n return rate <= atLevel(STROKES_PER_SECOND, difficulty);\n}\n\n/**\n * Whether the fretting hand covers a leap in the time available.\n *\n * @param semitones Distance moved; on a fretted neck one fret is one semitone.\n * @param stepBeats Time to move it in, in quarter-note beats.\n * @param bpm Tempo, or undefined when the caller gave none.\n * @param difficulty The ceiling, or undefined for no ceiling.\n * @returns True when the shift is inside the ceiling, and whenever either the\n * ceiling or the tempo is unknown.\n */\nexport function sustainsShift(\n semitones: number,\n stepBeats: number,\n bpm: number | undefined,\n difficulty: number | undefined,\n): boolean {\n if (difficulty === undefined || bpm === undefined || stepBeats <= 0) {\n return true;\n }\n const seconds = stepBeats * secondsPerBeat(bpm);\n return Math.abs(semitones) / seconds <= atLevel(FRETS_PER_SECOND, difficulty);\n}\n","import { InvalidInputError } from '../../core/errors/index.js';\nimport { includeAt, type PositionalRng, type SeedPath } from '../../core/random/index.js';\nimport { assertFiniteNumber, assertInteger } from '../../core/validation/index.js';\n\n/**\n * The samplers a generator needs, each addressed by the position it is drawing\n * for rather than by how many draws came before it.\n *\n * Every method is a pure function of the source and the path: asking twice\n * gives the same answer, asking in a different order changes nothing, and a\n * position nobody asks about costs nothing. That is what lets a caller change\n * one parameter — or one bar — without redrawing the rest of the piece.\n *\n * @category Composition\n */\nexport type Draw = {\n /** The raw draw at a position: a float in [0, 1). */\n at: (...path: SeedPath) => number;\n /**\n * Whether an event of probability `p` happens at a position. Because the\n * underlying draw is fixed by the path, raising `p` only ever turns more\n * positions on.\n */\n prob: (p: number, ...path: SeedPath) => boolean;\n /** An integer in the inclusive range [lo, hi] at a position. */\n range: (lo: number, hi: number, ...path: SeedPath) => number;\n /** A float in [lo, hi) at a position. */\n float: (lo: number, hi: number, ...path: SeedPath) => number;\n};\n\n/**\n * One draw from a positional source, held to the range the source promises.\n *\n * A caller-supplied {@link PositionalRng} is part of the contract, so a source\n * returning 1, a negative value or NaN is rejected here rather than becoming an\n * index past the end of a vocabulary, a NaN pitch, or a bar silently dropped far\n * from the cause. This is the guard `includeAt` already applies to `prob`, on\n * the same predicate.\n */\nfunction drawFrom(rng: PositionalRng, path: SeedPath): number {\n const draw = rng.at(...path);\n if (!(draw >= 0 && draw < 1)) {\n throw new InvalidInputError(`positional draw must be in [0, 1); received ${draw}`);\n }\n return draw;\n}\n\n/**\n * Build the sampler set for one namespace of a positional source.\n *\n * The prefix separates parts that share a source: a caller-supplied\n * {@link PositionalRng} is used by every generator at once, and without a\n * prefix the drums' bar 3 and the bass's bar 3 would draw the same number.\n *\n * @param rng The positional source.\n * @param prefix Path segments prepended to every draw.\n * @returns The samplers, addressed under `prefix`.\n */\nexport function drawsFrom(rng: PositionalRng, ...prefix: SeedPath): Draw {\n const at = (...path: SeedPath) => drawFrom(rng, [...prefix, ...path]);\n return {\n at,\n prob: (p, ...path) => includeAt(rng, p, ...prefix, ...path),\n range: (lo, hi, ...path) => {\n assertInteger(lo, 'range lower bound');\n assertInteger(hi, 'range upper bound');\n if (lo > hi) {\n throw new InvalidInputError(\n `range lower bound must not exceed upper bound; received ${lo} > ${hi}`,\n );\n }\n return lo + Math.floor(at(...path) * (hi - lo + 1));\n },\n float: (lo, hi, ...path) => {\n assertFiniteNumber(lo, 'float lower bound');\n assertFiniteNumber(hi, 'float upper bound');\n if (lo > hi) {\n throw new InvalidInputError(\n `float lower bound must not exceed upper bound; received ${lo} > ${hi}`,\n );\n }\n return lo + at(...path) * (hi - lo);\n },\n };\n}\n","/**\n * The generation context: one seed, one set of dials, one tempo, shared by\n * every generator.\n *\n * Before it, each generator carried its own idea of \"how involved should this\n * be\" — a 0..1 density here, a style name there, a boolean somewhere else — and\n * only the drums knew the tempo. A context names those things once, so a piece\n * can be asked for at a given complexity in one place, and every part derives\n * its own randomness from the same project seed.\n */\n\nimport type { InstrumentProfile } from '../../core/instrument/index.js';\nimport {\n createPositionalRng,\n deriveSeed,\n type PositionalRng,\n resolveAlgorithmVersion,\n} from '../../core/random/index.js';\nimport { assertInteger, assertRange } from '../../core/validation/index.js';\nimport { assertVocabulary, type Vocabulary } from '../vocabulary/types.js';\nimport { assertDifficulty } from './difficulty.js';\nimport { type Draw, drawsFrom } from './draw.js';\n\nexport {\n assertDifficulty,\n MAX_DIFFICULTY,\n MIN_DIFFICULTY,\n sustainsShift,\n sustainsStrokes,\n} from './difficulty.js';\nexport type { Draw } from './draw.js';\nexport { drawsFrom } from './draw.js';\n\n/**\n * How involved the generated music should be, on axes that do not imply one\n * another.\n *\n * The first three ask for more of something and are continuous: moving one of\n * them adds material without disturbing what is already sounding.\n * {@link Complexity.difficulty} is not one of those — it is a ceiling, and it\n * only ever takes candidates away.\n *\n * @category Composition\n */\nexport type Complexity = {\n /** Subdivision and syncopation, in [0, 1]. */\n rhythmic?: number;\n /** Tension, substitution and passing chords, in [0, 1]. */\n harmonic?: number;\n /** Ghosts, flams and decoration, in [0, 1]. */\n ornament?: number;\n /**\n * The hardest the result may be to play, from 1 (a beginner's part) to 5 (at\n * the limit of the instrument). It is a ceiling rather than a strength:\n * \"involved but easy\" and \"plain but hard\" are both real requests, so this\n * filters what the other three proposed instead of scaling it. It governs the\n * timing layer alone — whether the note exists on the instrument, and whether\n * the shape can be held, follow from {@link GenerationContext.instruments} and\n * apply whatever the ceiling says.\n */\n difficulty?: number;\n};\n\n/**\n * Everything a generator needs that is not about the notes themselves.\n *\n * @category Composition\n */\nexport type GenerationContext = {\n /**\n * The project seed every part derives its own randomness from. Optional, and\n * zero when left out: a context naming only a tempo is a whole request, and\n * requiring the seed beside it would make the commonest context unwritable\n * now that it is the only place a seed can go.\n */\n seed?: number;\n /** The dials; see {@link Complexity}. */\n complexity?: Complexity;\n /**\n * Draw from this source instead of one derived from `seed`. Each part still\n * addresses its own namespace, so parts do not collide.\n */\n rng?: PositionalRng;\n /**\n * The instruments the parts are written for, keyed by part name — `'bass'`,\n * `'drums'`. Naming one is itself the request that the part be playable on\n * it, so its range and physical limits always apply.\n */\n instruments?: Record<string, InstrumentProfile>;\n /**\n * Tempo in quarter-note beats per minute. Without it no generator can judge\n * whether a passage is reachable in the time available, so a ceiling on\n * difficulty has nothing to measure against.\n */\n bpm?: number;\n /**\n * The algorithm version to generate under; defaults to the version this build\n * produces. See {@link resolveAlgorithmVersion} for what the number promises.\n */\n algorithmVersion?: number;\n /**\n * Figures this piece may draw on, over and above the library's own.\n *\n * Genres are endless, which is the reason the vocabulary is data at all: a\n * caller brings the dictionary for the music it is writing instead of waiting\n * for the library to grow one. Entries for every part travel in one list —\n * each generator recognises its own material by shape — and an entry carrying\n * the id of a built-in replaces it rather than being offered alongside.\n */\n vocabulary?: readonly Vocabulary<unknown>[];\n};\n\n/**\n * A context, or just the seed for one.\n *\n * A bare number is sugar for `{ seed }`: the common case is a caller who wants\n * reproducible output and nothing else.\n *\n * @category Composition\n */\nexport type GenerationContextInput = GenerationContext | number;\n\n/**\n * A context with everything resolved: dials validated, tempo and ceiling\n * settled, and a positional source ready for each part.\n */\nexport type ResolvedContext = {\n seed: number;\n /** Requested rhythmic density, or undefined when the caller named none. */\n rhythmic: number | undefined;\n /** Requested harmonic richness, or undefined when the caller named none. */\n harmonic: number | undefined;\n /** Requested ornament density, or undefined when the caller named none. */\n ornament: number | undefined;\n /** The difficulty ceiling, or undefined for no ceiling. */\n difficulty: number | undefined;\n /** Tempo in BPM, or undefined when the caller named none. */\n bpm: number | undefined;\n /** The resolved algorithm version. */\n algorithmVersion: number;\n /** The profile written for a part, if one was named for it. */\n instrument: (part: string) => InstrumentProfile | undefined;\n /** The figures the caller brought, validated; empty when it brought none. */\n vocabulary: readonly Vocabulary<unknown>[];\n /** The position-addressed samplers for a part. */\n part: (name: string) => Draw;\n};\n\n/** The seed a generator uses when the caller names none. */\nconst DEFAULT_SEED = 0;\n\n/** Path segment separating one algorithm version's draws from another's. */\nconst VERSION_SEGMENT = 'v';\n\n/**\n * Resolve a context (or a bare seed) into the form the generators use.\n *\n * The algorithm version takes part in every derivation, so two versions never\n * share a draw: pinning one is what makes a saved project reopen as the piece\n * it was, and a version this build does not produce is rejected rather than\n * quietly rendered as another.\n *\n * @param input The context, the seed alone, or nothing.\n * @returns The resolved context.\n * @throws If the seed is not a 32-bit integer, a dial is out of range, the\n * tempo is not positive, or the algorithm version is unknown.\n * @example\n * ```ts\n * import { resolveContext } from '@libraz/libcantus';\n * const ctx = resolveContext({ seed: 42, complexity: { rhythmic: 0.8 }, bpm: 96 });\n * ctx.part('drums').prob(0.5, 'ghost', 1, 2); // the same answer however often it is asked\n * ```\n */\nexport function resolveContext(input?: GenerationContextInput): ResolvedContext {\n const ctx: GenerationContext =\n typeof input === 'number' ? { seed: input } : (input ?? { seed: DEFAULT_SEED });\n const seed = assertInteger(ctx.seed ?? DEFAULT_SEED, 'seed', 0, 0xffffffff);\n const complexity = ctx.complexity ?? {};\n const rhythmic =\n complexity.rhythmic === undefined\n ? undefined\n : assertRange(complexity.rhythmic, 0, 1, 'complexity rhythmic');\n const harmonic =\n complexity.harmonic === undefined\n ? undefined\n : assertRange(complexity.harmonic, 0, 1, 'complexity harmonic');\n const ornament =\n complexity.ornament === undefined\n ? undefined\n : assertRange(complexity.ornament, 0, 1, 'complexity ornament');\n const difficulty =\n complexity.difficulty === undefined ? undefined : assertDifficulty(complexity.difficulty);\n const bpm =\n ctx.bpm === undefined ? undefined : assertRange(ctx.bpm, Number.MIN_VALUE, 1000, 'bpm');\n const algorithmVersion = resolveAlgorithmVersion(ctx.algorithmVersion);\n const supplied = ctx.rng;\n const instruments = ctx.instruments;\n // Validated on the way in: an entry naming a difficulty of 40 would otherwise\n // clear every ceiling and be chosen for a beginner's part.\n const vocabulary = (ctx.vocabulary ?? []).map((entry, index) =>\n assertVocabulary(entry, `vocabulary[${index}]`),\n );\n\n return {\n seed,\n rhythmic,\n harmonic,\n ornament,\n difficulty,\n bpm,\n algorithmVersion,\n instrument: (part) => instruments?.[part],\n vocabulary,\n part: (name) =>\n supplied\n ? drawsFrom(supplied, name, VERSION_SEGMENT, algorithmVersion)\n : drawsFrom(createPositionalRng(deriveSeed(seed, name, VERSION_SEGMENT, algorithmVersion))),\n };\n}\n\n/**\n * Merge a generator's own dial into a context.\n *\n * A generator that names one of the dials in its own vocabulary — the motif's\n * `jitter`, the ornament's `amount` — resolves it here, so the dial reaches the\n * generator by one path whichever surface named it. The context wins where both\n * are given, since it is the one thing that speaks for the whole piece.\n *\n * @param input The context or bare seed, if any.\n * @param sugar The generator's own dial.\n * @returns The resolved context, with the dial filled in where the context was\n * silent.\n */\nexport function resolveContextWith(\n input: GenerationContextInput | undefined,\n sugar: { ornament?: number },\n): ResolvedContext {\n const base = resolveContext(input);\n const ornament =\n base.ornament ??\n (sugar.ornament === undefined\n ? undefined\n : assertRange(sugar.ornament, 0, 1, 'complexity ornament'));\n return { ...base, ornament };\n}\n","/**\n * The placement rules a bass line is built from, shared by the phrase-shape\n * styles and the lick dictionary.\n *\n * They are the part of a bass part that is derivable — where in the register a\n * pitch class lands, which beats a segment offers, how one note leads into the\n * next. What is not derivable is which figure to play, and that is the\n * dictionary's business.\n */\n\nimport { instrumentRange, type StringedProfile } from '../../core/instrument/index.js';\nimport { beatsPerBar, pulseBeats, type TimeSignature } from '../../core/meter/index.js';\nimport { pitchClassOf as pitchClass } from '../../core/pitch/index.js';\nimport type { KeyScale } from '../../core/types.js';\nimport type { Chord } from '../../theory/chord/index.js';\nimport { nearestScaleTone } from '../../theory/scale/index.js';\n\n/** Velocity for notes on metrically strong positions. */\nexport const STRONG_VELOCITY = 100;\n\n/** Velocity for notes on weak positions. */\nexport const WEAK_VELOCITY = 80;\n\n/** Tolerance for comparing beat positions. */\nexport const EPS = 1e-9;\n\n/**\n * Place a pitch class as a MIDI note near an anchor, clamped to the bass band.\n *\n * The octave nearest the anchor is chosen first (so motion from the previous\n * note is minimal), then the result is shifted by whole octaves into the band\n * `[low, low + 12]` if it falls outside.\n */\nexport function placePc(pc: number, anchor: number, low: number): number {\n const high = low + 12;\n let midi = pc + 12 * Math.round((anchor - pc) / 12);\n while (midi < low) {\n midi += 12;\n }\n while (midi > high) {\n midi -= 12;\n }\n return midi;\n}\n\n/** Chord-tone pitch classes in stacked-thirds order, deduplicated. */\nexport function chordTonePcs(chord: Chord): number[] {\n const seen = new Set<number>();\n const pcs: number[] = [];\n for (const interval of chord.intervals) {\n const pc = pitchClass(chord.rootPc + interval);\n if (!seen.has(pc)) {\n seen.add(pc);\n pcs.push(pc);\n }\n }\n if (pcs.length === 0) {\n pcs.push(pitchClass(chord.rootPc));\n }\n return pcs;\n}\n\n/**\n * The chord's fifth pitch class.\n *\n * Uses the perfect fifth when present, otherwise the chord's actual altered\n * fifth (diminished = 6, augmented = 8 semitones above the root), so dim/aug/\n * m7b5 chords sound their real fifth rather than a repeated root. Falls back to\n * the root only when the chord has no fifth degree at all.\n */\nexport function fifthPcOf(chord: Chord): number {\n const fifths = new Set(chord.intervals.map((i) => pitchClass(i)));\n const chosen = fifths.has(7) ? 7 : fifths.has(6) ? 6 : fifths.has(8) ? 8 : undefined;\n return chosen === undefined ? pitchClass(chord.rootPc) : pitchClass(chord.rootPc + chosen);\n}\n\n/** The sounding bass pitch class of a chord: its slash bass, else its root. */\nexport function bassPcOf(chord: Chord): number {\n return pitchClass(chord.bassPc ?? chord.rootPc);\n}\n\n/** Main-pulse positions within `[start, end)`, aligned to the global bar grid. */\nexport function beatPositions(start: number, end: number, ts: TimeSignature): number[] {\n const positions: number[] = [];\n const barBeats = beatsPerBar(ts);\n const pulse = pulseBeats(ts);\n const firstBar = Math.floor(start / barBeats);\n const lastBar = Math.floor((end - EPS) / barBeats);\n for (let bar = firstBar; bar <= lastBar; bar += 1) {\n const barStart = bar * barBeats;\n const firstPulse = Math.max(0, Math.ceil((start - barStart - EPS) / pulse));\n for (let index = firstPulse; ; index += 1) {\n const position = barStart + index * pulse;\n if (position >= barStart + barBeats - EPS || position >= end - EPS) {\n break;\n }\n if (position >= start - EPS) {\n positions.push(position);\n }\n }\n }\n // A segment shorter than a pulse still needs a bass note. Its boundary is\n // necessarily the harmonic placement rather than an invented local grid.\n if (positions.length === 0) {\n positions.push(start);\n }\n return positions;\n}\n\n/** Shift a pitch by whole octaves until it lies in the band `[low, low + 12]`. */\nexport function foldIntoBand(midi: number, low: number): number {\n let result = midi;\n while (result < low) {\n result += 12;\n }\n while (result > low + 12) {\n result -= 12;\n }\n return result;\n}\n\n/**\n * The register band's floor, moved by whole octaves until `[low, low + 12]`\n * lies inside the instrument.\n *\n * Moving the band rather than each note is what keeps the line intact: every\n * interval between consecutive notes survives, and only the register — the one\n * thing a player would have chosen differently — moves.\n */\nexport function bandFloor(low: number, instrument: StringedProfile | undefined): number {\n if (!instrument) {\n return low;\n }\n const range = instrumentRange(instrument);\n let floor = low;\n while (floor + 12 > range.high) {\n floor -= 12;\n }\n while (floor < range.low) {\n floor += 12;\n }\n return floor;\n}\n\n/**\n * A diatonic or chromatic neighbor of `target`, a step toward `from`.\n *\n * The result is folded back into the register band: an approach note may be\n * emitted with an explicit MIDI value, bypassing the placement clamp, and a\n * target on the band edge would otherwise put it a semitone outside — below\n * MIDI 0 at the lowest accepted octave.\n *\n * @param target The note being led into.\n * @param from Where the line is coming from, which decides the direction.\n * @param low Floor of the register band.\n * @param key Key context for the diatonic neighbour.\n * @param chromatic Whether to take the semitone rather than the scale step. The\n * choice is the caller's so that each caller addresses its own draw.\n */\nexport function approachNote(\n target: number,\n from: number,\n low: number,\n key: KeyScale,\n chromatic: boolean,\n): number {\n const dir = from <= target ? -1 : 1;\n const semitone = target + dir;\n if (chromatic) {\n return foldIntoBand(semitone, low);\n }\n const cand = nearestScaleTone(target + dir * 2, key);\n const step = Math.abs(cand - target);\n return foldIntoBand(step >= 1 && step <= 2 ? cand : semitone, low);\n}\n","/**\n * The transformation rules that deform a remembered figure into the one this\n * bar needs.\n *\n * The division of labour is the whole point of keeping these separate from the\n * dictionary: **genre selects the material, complexity deforms it, difficulty\n * rejects it.** A transform therefore never consults a genre and never consults\n * a ceiling — it takes the figure it is given and reshapes it, and whether the\n * result is playable is decided afterwards, by {@link withinCeiling}.\n *\n * Every figure is a list of onsets on a sixteenth grid, whatever it is made of,\n * so the rules are written once and used by both the bass and the drums.\n */\n\nimport { assertRange } from '../../core/validation/index.js';\nimport { sustainsStrokes } from '../context/difficulty.js';\nimport type { Draw } from '../context/draw.js';\n\n/** Sixteenth steps in one 4/4 bar; the grid every figure is written on. */\nexport const BAR_STEPS = 16;\n\n/** Sixteenth steps in one quarter-note beat. */\nexport const BEAT_STEPS = 4;\n\n/** One sixteenth expressed in quarter-note beats. */\nexport const STEP_BEATS = 1 / BEAT_STEPS;\n\n/**\n * An onset on the shared sixteenth grid.\n *\n * Both kinds of material are lists of these with their own extra fields, so a\n * transform is generic over the record and only ever reads the fields named\n * here.\n *\n * @category Composition\n */\nexport type GridEvent = {\n /** Sixteenth-note index from the start of the figure. */\n step: number;\n /** Loudness as a factor of the passage's base velocity. */\n velocity: number;\n /**\n * The voice this onset is played on, where the material has more than one.\n * A figure written for a single voice — a bass lick — leaves it out.\n */\n voice?: string;\n /**\n * The limb that plays it, where the material names one. It outranks\n * {@link GridEvent.voice} as the stream an onset belongs to, since what\n * crowds a player is one limb rather than one drum.\n */\n limb?: string;\n};\n\n/**\n * The stream an onset belongs to: its limb, else its voice, else the one\n * stream a single-voice figure has.\n *\n * Transforms and the ceiling both group by this. Two hands are two players, so\n * a ghost snare neither blocks a hi-hat from anticipating nor makes the pair\n * count as one limb repeating.\n */\nfunction streamOf(event: GridEvent): string {\n return event.limb ?? event.voice ?? '';\n}\n\n/**\n * How strongly a position on the sixteenth grid is felt, from 4 (the downbeat)\n * to 0 (an off sixteenth).\n *\n * This is the ranking the thinning rule works down: the notes a player drops\n * first when asked for something easier are the ones carrying the least of the\n * metre. It is named for the grid it reads because the analysis layer has a\n * metric weight of its own, on another scale and against a time signature; the\n * two answer different questions and are deliberately not the same function.\n *\n * @param step Sixteenth index within the bar.\n * @returns The weight, 0 to 4.\n *\n * @category Composition\n */\nexport function gridMetricWeight(step: number): number {\n const inBar = ((step % BAR_STEPS) + BAR_STEPS) % BAR_STEPS;\n if (inBar === 0) {\n return 4;\n }\n if (inBar % 8 === 0) {\n return 3;\n }\n if (inBar % 4 === 0) {\n return 2;\n }\n if (inBar % 2 === 0) {\n return 1;\n }\n return 0;\n}\n\n/**\n * Drop the notes carrying the least of the metre.\n *\n * The easing direction: at 0 nothing is dropped, and as the amount rises whole\n * ranks of positions go, off sixteenths first and the downbeat last. It is a\n * ranking rather than a sampling, so the same amount always removes the same\n * notes and a caller can explain which ones went.\n *\n * @param events The figure.\n * @param amount How much to thin, in [0, 1].\n * @returns The surviving events, in the order given.\n *\n * @category Composition\n */\nexport function thin<T extends GridEvent>(events: readonly T[], amount: number): T[] {\n assertRange(amount, 0, 1, 'thin amount');\n // Five ranks (0..4), so a full turn of the dial reaches the downbeat alone.\n const floor = Math.min(4, Math.floor(amount * 5));\n return events.filter((event) => gridMetricWeight(event.step) >= floor);\n}\n\n/**\n * Fill in the gaps of a figure, one note per gap.\n *\n * Doubling is what a player does when asked for more of the same rather than\n * for something different: the added note sits halfway between two the figure\n * already has, takes its predecessor's identity, and is played softer.\n *\n * @param events The figure, which need not be sorted.\n * @param spanSteps Length of the figure in sixteenths.\n * @returns The figure with the midpoints filled, sorted by position.\n *\n * @category Composition\n */\nexport function double<T extends GridEvent>(events: readonly T[], spanSteps = BAR_STEPS): T[] {\n const sorted = [...events].sort((a, b) => a.step - b.step);\n const out: T[] = [];\n for (let i = 0; i < sorted.length; i += 1) {\n const event = sorted[i];\n if (!event) {\n continue;\n }\n out.push(event);\n const next = sorted[i + 1]?.step ?? spanSteps;\n const mid = event.step + (next - event.step) / 2;\n // Only where the grid has a position for it: a gap of one sixteenth is\n // already as dense as the figure is written.\n if (Number.isInteger(mid) && mid > event.step && mid < spanSteps) {\n out.push({ ...event, step: mid, velocity: event.velocity * ADDED_NOTE_VELOCITY });\n }\n }\n return out;\n}\n\n/** How loud a note added by a transform is, relative to the one it follows. */\nconst ADDED_NOTE_VELOCITY = 0.75;\n\n/**\n * Stretch a figure to twice its length: everything falls half as often.\n *\n * @param events The figure.\n * @param spanSteps Length of the figure in sixteenths; notes stretched past it\n * are dropped, since the bar is the bar however the figure is felt.\n * @returns The stretched figure.\n *\n * @category Composition\n */\nexport function halfTime<T extends GridEvent>(events: readonly T[], spanSteps = BAR_STEPS): T[] {\n return events\n .map((event) => ({ ...event, step: event.step * 2 }))\n .filter((event) => event.step < spanSteps);\n}\n\n/**\n * Compress a figure to half its length and play it twice.\n *\n * The repeat is what makes this the mirror of {@link halfTime} rather than a\n * figure with a hole in it: the bar stays full.\n *\n * @param events The figure.\n * @param spanSteps Length of the figure in sixteenths.\n * @returns The compressed figure, repeated to fill the span.\n *\n * @category Composition\n */\nexport function doubleTime<T extends GridEvent>(events: readonly T[], spanSteps = BAR_STEPS): T[] {\n const out: T[] = [];\n for (const event of events) {\n // An odd step halves onto no grid position, so it is placed on the nearest\n // one. Dropping it instead took the \"a\" of the kick and three of the four\n // ghosts out of the figure that names the genre, and returned the result as\n // though it were the same figure played faster.\n const step = Math.round(event.step / 2);\n out.push({ ...event, step });\n const repeated = step + spanSteps / 2;\n if (repeated < spanSteps) {\n out.push({ ...event, step: repeated, velocity: event.velocity * ADDED_NOTE_VELOCITY });\n }\n }\n return out.sort((a, b) => a.step - b.step);\n}\n\n/**\n * Anticipate beats, one sixteenth early.\n *\n * Anticipation is the direction syncopation runs in every genre this library\n * names: the note arrives before the beat it belongs to. It is written as an\n * addition rather than a displacement — the anticipating stroke is softer, and\n * the note it anticipates stays where it was, the way a player ties into the\n * beat rather than abandoning it — because this is one of the dials that only\n * ever add material. Displacing instead would move onsets around under the\n * finger of anyone who puts the dial on a slider, and a saved project reopened\n * one notch along would not contain the piece it was. Which beats are\n * anticipated is fixed by position, so the bar next door is unaffected either\n * way.\n *\n * A position already taken blocks the anticipation only within the same stream\n * — the same limb, else the same voice. A ghost snare sitting where the hi-hat\n * wants to anticipate is another hand playing, not an obstacle, and whether two\n * limbs can actually strike together is the playability layer's question rather\n * than this one's.\n *\n * @param events The figure.\n * @param amount How much syncopation, in [0, 1].\n * @param draw The position-addressed sampler.\n * @param path Where the figure sits, so the same bar always syncopates alike.\n * @returns The figure with its anticipations, sorted by position.\n *\n * @category Composition\n */\nexport function syncopate<T extends GridEvent>(\n events: readonly T[],\n amount: number,\n draw: Draw,\n ...path: readonly (string | number)[]\n): T[] {\n assertRange(amount, 0, 1, 'syncopate amount');\n const occupied = new Set(events.map((event) => `${streamOf(event)}@${event.step}`));\n const out: T[] = [...events];\n for (const event of events) {\n const anticipated = event.step - 1;\n // The downbeat takes none: its anticipation would fall in the bar before\n // the one the figure belongs to.\n if (\n gridMetricWeight(event.step) < 2 ||\n anticipated < 1 ||\n occupied.has(`${streamOf(event)}@${anticipated}`)\n ) {\n continue;\n }\n if (!draw.prob(amount, ...path, 'syncopate', event.step)) {\n continue;\n }\n out.push({ ...event, step: anticipated, velocity: event.velocity * ADDED_NOTE_VELOCITY });\n }\n return out.sort((a, b) => a.step - b.step);\n}\n\n/**\n * How much decoration a figure keeps.\n *\n * A decorated note — a ghost, a flam, anything the figure marks as ornament —\n * survives on its own draw, so raising the dial only ever brings more of them\n * back and never disturbs the ones already sounding. Notes that are not\n * decoration are untouched.\n *\n * @param events The figure.\n * @param isOrnament Which events count as decoration.\n * @param amount The ornament dial, in [0, 1].\n * @param draw The position-addressed sampler.\n * @param path Where the figure sits.\n * @returns The figure with the surviving decoration.\n *\n * @category Composition\n */\nexport function ornamentBy<T extends GridEvent>(\n events: readonly T[],\n isOrnament: (event: T) => boolean,\n amount: number,\n draw: Draw,\n ...path: readonly (string | number)[]\n): T[] {\n assertRange(amount, 0, 1, 'ornament amount');\n return events.filter(\n (event) => !isOrnament(event) || draw.prob(amount, ...path, 'ornament', event.step),\n );\n}\n\n/**\n * Everything the complexity dials do to a figure, in one pass.\n *\n * @category Composition\n */\nexport type DeformOptions = {\n /**\n * Subdivision and syncopation, in [0, 1]. Below the neutral middle the figure\n * is thinned; above it, syncopated. The middle leaves it as the dictionary\n * wrote it, which is what makes the dictionary the thing a reader can check.\n */\n rhythmic?: number;\n /** Ghosts and decoration, in [0, 1]; absent keeps every ornament. */\n ornament?: number;\n /** Which events are decoration, for the ornament dial. */\n isOrnament?: (event: GridEvent) => boolean;\n /**\n * Rate: stretch or compress the figure before the dials are applied. It is\n * named for what it does to the note values, since \"feel\" elsewhere in the\n * drum surface names the swing a figure is played with — a different\n * question, asked of the same figure.\n */\n rate?: 'straight' | 'half' | 'double';\n /** Length of the figure in sixteenths. */\n spanSteps?: number;\n};\n\n/** The rhythmic setting at which a figure is left as written. */\nconst NEUTRAL_RHYTHMIC = 0.5;\n\n/**\n * Apply the complexity dials to a figure.\n *\n * This is the one place complexity touches vocabulary: genre has already chosen\n * the material, and the ceiling has not been consulted yet.\n *\n * @param events The figure.\n * @param opts What the dials say.\n * @param draw The position-addressed sampler.\n * @param path Where the figure sits.\n * @returns The deformed figure, sorted by position.\n *\n * @example\n * ```ts\n * import { deform, resolveContext } from '@libraz/libcantus';\n * const figure = [\n * { step: 0, velocity: 1, limb: 'kick' },\n * { step: 4, velocity: 0.9, limb: 'snare' },\n * { step: 8, velocity: 1, limb: 'kick' },\n * { step: 12, velocity: 0.9, limb: 'snare' },\n * ];\n * const draw = resolveContext(7).part('drums');\n * const busier = deform(figure, { rhythmic: 0.9 }, draw, 'bar', 0);\n * // Above the neutral middle the figure is syncopated: onsets are added off\n * // the beat, and the result comes back sorted by position.\n * busier.length; // 7\n * busier.map((event) => event.step); // [0, 3, 4, 7, 8, 11, 12]\n * ```\n *\n * @category Composition\n */\nexport function deform<T extends GridEvent>(\n events: readonly T[],\n opts: DeformOptions,\n draw: Draw,\n ...path: readonly (string | number)[]\n): T[] {\n const spanSteps = opts.spanSteps ?? BAR_STEPS;\n let out: T[] =\n opts.rate === 'half'\n ? halfTime(events, spanSteps)\n : opts.rate === 'double'\n ? doubleTime(events, spanSteps)\n : [...events];\n\n if (opts.rhythmic !== undefined) {\n const rhythmic = assertRange(opts.rhythmic, 0, 1, 'complexity rhythmic');\n if (rhythmic < NEUTRAL_RHYTHMIC) {\n // Below the middle the request is for less, and less means the notes that\n // carry the least of the metre.\n out = thin(out, (NEUTRAL_RHYTHMIC - rhythmic) / NEUTRAL_RHYTHMIC);\n } else if (rhythmic > NEUTRAL_RHYTHMIC) {\n out = syncopate(out, (rhythmic - NEUTRAL_RHYTHMIC) / NEUTRAL_RHYTHMIC, draw, ...path);\n }\n }\n\n if (opts.ornament !== undefined && opts.isOrnament) {\n const isOrnament = opts.isOrnament;\n out = ornamentBy(out, (event) => isOrnament(event), opts.ornament, draw, ...path);\n }\n\n return out.sort((a, b) => a.step - b.step);\n}\n\n/**\n * Whether a figure is inside the difficulty ceiling at this tempo.\n *\n * The ceiling is measured against the closest pair of onsets the figure asks\n * one player for, which is the thing that actually stops a hand — so the pairs\n * are counted within a stream (the limb, else the voice) rather than across the\n * whole kit, because a hi-hat and a kick a sixteenth apart are two limbs and not\n * one hand playing twice. A figure that fails is rejected — never quietly\n * simplified, because a simplified figure is a different figure and the caller\n * asked for this one.\n *\n * @param events The figure.\n * @param bpm Tempo, or undefined when the caller gave none.\n * @param difficulty The ceiling, or undefined for no ceiling.\n * @returns True when the figure is playable at the ceiling, and whenever either\n * the ceiling or the tempo is unknown.\n *\n * @category Composition\n */\nexport function withinCeiling(\n events: readonly GridEvent[],\n bpm: number | undefined,\n difficulty: number | undefined,\n): boolean {\n if (difficulty === undefined || bpm === undefined || events.length < 2) {\n return true;\n }\n const byStream = new Map<string, Set<number>>();\n for (const event of events) {\n const stream = streamOf(event);\n const steps = byStream.get(stream);\n if (steps) {\n steps.add(event.step);\n } else {\n byStream.set(stream, new Set([event.step]));\n }\n }\n let closest = Number.POSITIVE_INFINITY;\n for (const stream of byStream.values()) {\n const steps = [...stream].sort((a, b) => a - b);\n for (let i = 1; i < steps.length; i += 1) {\n const gap = (steps[i] ?? 0) - (steps[i - 1] ?? 0);\n if (gap > 0 && gap < closest) {\n closest = gap;\n }\n }\n }\n if (!Number.isFinite(closest)) {\n return true;\n }\n return sustainsStrokes(closest * STEP_BEATS, bpm, difficulty);\n}\n","/**\n * The bass lick dictionary: the figures a bass player reaches for, held as\n * chord-relative degrees and beat positions and deformed to fit the progression\n * actually in front of them.\n *\n * Provenance: no entry reproduces a bass line from a particular song. Each is a\n * figure that belongs to nobody — the descending soul walk-down, the octave\n * slap, the boogie shuffle — of the kind every player of that music already\n * plays, and each records the ground it qualifies under.\n */\n\nimport { InvalidInputError } from '../../core/errors/index.js';\nimport {\n type Articulation,\n foldIntoRange,\n type StringedProfile,\n} from '../../core/instrument/index.js';\nimport { beatsPerBar, isStrongBeat, type TimeSignature } from '../../core/meter/index.js';\nimport { pitchClassOf as pitchClass } from '../../core/pitch/index.js';\nimport type { KeyScale, NoteEvent } from '../../core/types.js';\nimport {\n assertGenerationBudget,\n assertInteger,\n assertOneOf,\n assertRange,\n assertTimeSignature,\n clampToMidi,\n} from '../../core/validation/index.js';\nimport type { Chord, ChordSegment } from '../../theory/chord/index.js';\nimport { type KeyLike, nearestScaleTone, toKeyScale } from '../../theory/scale/index.js';\nimport { type ChordLike, toChordData } from '../../theory/symbol/index.js';\nimport { assertDifficulty, type GenerationContextInput, resolveContext } from '../context/index.js';\nimport {\n BEAT_STEPS,\n deform,\n GENRES,\n type Genre,\n type GridEvent,\n mergeVocabulary,\n pickVocabulary,\n STEP_BEATS,\n type Vocabulary,\n vocabularyOfKind,\n withinCeiling,\n} from '../vocabulary/index.js';\nimport {\n approachNote,\n bandFloor,\n bassPcOf,\n placePc,\n STRONG_VELOCITY,\n WEAK_VELOCITY,\n} from './internal.js';\n\n/**\n * One note of a lick, relative to the chord it is played over.\n *\n * A degree is 1-based and counts up from the chord's root: 1 is the root, 3 the\n * third, 8 the octave. Degrees the chord itself names — the third, fifth and\n * seventh — are taken from the chord, so the same figure comes out major over a\n * major chord and minor over a minor one; the rest are taken from the key. That\n * is what lets a figure be written once and fit a whole progression.\n *\n * @category Composition\n */\nexport type LickNote = GridEvent & {\n /** Chord degree, 1-based; 8 is the octave above the root. */\n degree: number;\n /** Chromatic alteration in semitones, for a flattened or raised degree. */\n alter?: number;\n /** Sounding length in sixteenths; one step when absent. */\n lengthSteps?: number;\n /** How the note is played, when it is more than a plain note. */\n articulation?: Articulation;\n};\n\n/**\n * A lick: its notes and how long the whole figure lasts.\n *\n * @category Composition\n */\nexport type LickMaterial = {\n /** Length of the figure in sixteenths. */\n lengthSteps: number;\n notes: LickNote[];\n};\n\n/**\n * A lick with the conditions it fits.\n *\n * @category Composition\n */\nexport type BassLick = Vocabulary<LickMaterial>;\n\n/**\n * Whether a caller's material is a bass lick.\n *\n * @category Composition\n */\nexport function isLickMaterial(material: unknown): material is LickMaterial {\n const candidate = material as LickMaterial | null;\n return (\n typeof candidate === 'object' &&\n candidate !== null &&\n typeof candidate.lengthSteps === 'number' &&\n Array.isArray(candidate.notes)\n );\n}\n\n/** One note of a lick. */\nfunction note(\n degree: number,\n step: number,\n velocity: number,\n extra?: { alter?: number; lengthSteps?: number; articulation?: LickNote['articulation'] },\n): LickNote {\n return { degree, step, velocity, ...extra };\n}\n\n/** A one-bar figure on the sixteenth grid. */\nconst BAR: number = 4 * BEAT_STEPS;\n\n/**\n * The meter every built-in figure is written in.\n *\n * It is declared rather than left out because an absent condition means \"any\",\n * and these are not any: their accents fall where a four-beat bar puts them, so\n * a waltz or a jig would be handed a figure written against a bar it does not\n * have.\n */\nconst FOUR_FOUR: TimeSignature = { numerator: 4, denominator: 4 };\n\n/**\n * The built-in bass licks, in declaration order.\n *\n * @category Composition\n */\nexport const BASS_LICKS: readonly BassLick[] = Object.freeze([\n {\n id: 'motownWalkDown',\n genre: 'motown',\n difficulty: 3,\n articulations: [],\n ts: FOUR_FOUR,\n fitsOver: ['maj', 'maj7', '6', 'dom7'],\n tempoRange: [88, 136],\n material: {\n lengthSteps: BAR,\n notes: [\n note(1, 0, 1),\n note(8, 4, 0.85),\n note(7, 8, 0.85),\n note(6, 10, 0.8),\n note(5, 12, 0.9),\n note(3, 14, 0.8),\n ],\n },\n provenance: {\n basis: 'idiom',\n note: 'the stepwise walk down from the octave that every player of the style plays',\n },\n },\n {\n id: 'soulOctavePush',\n genre: 'soul',\n difficulty: 2,\n articulations: [],\n ts: FOUR_FOUR,\n tempoRange: [72, 120],\n material: {\n lengthSteps: BAR,\n notes: [note(1, 0, 1), note(8, 6, 0.8), note(1, 8, 0.9), note(5, 14, 0.8)],\n },\n provenance: {\n basis: 'idiom',\n note: 'root and octave answering each other across the bar, with a push into the next chord',\n },\n },\n {\n id: 'funkSixteenthPop',\n genre: 'funk',\n difficulty: 4,\n articulations: ['mute', 'slide'],\n ts: FOUR_FOUR,\n tempoRange: [84, 124],\n material: {\n lengthSteps: BAR,\n notes: [\n note(1, 0, 1),\n note(1, 2, 0.4, { articulation: 'mute' }),\n note(8, 3, 0.9),\n note(1, 6, 0.5, { articulation: 'mute' }),\n note(5, 8, 0.85),\n note(1, 11, 0.4, { articulation: 'mute' }),\n note(8, 12, 0.9),\n note(7, 15, 0.7, { alter: -1 }),\n ],\n },\n provenance: {\n basis: 'idiom',\n note: 'the octave-and-ghost sixteenth figure that defines the style, with a flat seventh on the way out',\n },\n },\n {\n id: 'bluesBoogie',\n genre: 'blues',\n difficulty: 2,\n articulations: [],\n ts: FOUR_FOUR,\n fitsOver: ['maj', 'dom7'],\n tempoRange: [76, 168],\n material: {\n lengthSteps: BAR,\n notes: [\n note(1, 0, 1),\n note(5, 2, 0.85),\n note(6, 4, 0.85),\n note(7, 6, 0.85, { alter: -1 }),\n note(6, 8, 0.85),\n note(5, 10, 0.85),\n note(1, 12, 0.9),\n note(5, 14, 0.8),\n ],\n },\n provenance: {\n basis: 'traditional',\n note: 'the boogie figure of the twelve-bar tradition: root, fifth, sixth, flat seventh and back',\n },\n },\n {\n id: 'jazzWalkingApproach',\n genre: 'jazz',\n difficulty: 3,\n articulations: [],\n ts: FOUR_FOUR,\n tempoRange: [100, 240],\n material: {\n lengthSteps: BAR,\n notes: [note(1, 0, 1), note(3, 4, 0.85), note(5, 8, 0.85), note(6, 12, 0.85)],\n },\n provenance: {\n basis: 'idiom',\n note: 'the quarter-note walk through the chord that ends a step from wherever it is going',\n },\n },\n {\n id: 'bossaTwoFeel',\n genre: 'bossa',\n difficulty: 2,\n articulations: [],\n ts: FOUR_FOUR,\n tempoRange: [104, 168],\n material: {\n lengthSteps: BAR,\n notes: [note(1, 0, 1), note(5, 6, 0.85), note(1, 8, 0.95), note(5, 14, 0.8)],\n },\n provenance: {\n basis: 'traditional',\n note: 'the two-beat root-and-fifth of the Brazilian dance form, anticipated across the bar line',\n },\n },\n {\n id: 'gospelPassing',\n genre: 'gospel',\n difficulty: 4,\n articulations: ['slide'],\n ts: FOUR_FOUR,\n tempoRange: [60, 108],\n material: {\n lengthSteps: BAR,\n notes: [\n note(1, 0, 1),\n note(2, 3, 0.7),\n note(3, 4, 0.9),\n note(5, 7, 0.8),\n note(6, 8, 0.85),\n note(5, 11, 0.75),\n note(3, 12, 0.85),\n note(2, 15, 0.7),\n ],\n },\n provenance: {\n basis: 'idiom',\n note: 'the busy stepwise motion between chord tones that the church style fills its bars with',\n },\n },\n {\n id: 'countryAlternating',\n genre: 'country',\n difficulty: 1,\n articulations: [],\n ts: FOUR_FOUR,\n tempoRange: [80, 180],\n material: {\n lengthSteps: BAR,\n notes: [note(1, 0, 1), note(5, 4, 0.85), note(1, 8, 0.95), note(5, 12, 0.85)],\n },\n provenance: {\n basis: 'traditional',\n note: 'the alternating root and fifth of the dance-band bass, older than any recording of it',\n },\n },\n {\n id: 'reggaeOffbeatDrop',\n genre: 'reggae',\n difficulty: 3,\n articulations: ['mute'],\n ts: FOUR_FOUR,\n tempoRange: [64, 104],\n material: {\n lengthSteps: BAR,\n notes: [\n note(1, 2, 0.9),\n note(3, 4, 0.85),\n note(5, 6, 0.85),\n note(1, 10, 0.95),\n note(7, 14, 0.8, { alter: -1 }),\n ],\n },\n provenance: {\n basis: 'idiom',\n note: 'the figure that leaves the downbeat empty and lands after it',\n },\n },\n]);\n\n/** Semitones above the root for each degree of a major scale, 1-based. */\nconst MAJOR_DEGREE_SEMITONES = [0, 2, 4, 5, 7, 9, 11] as const;\n\n/**\n * Semitones above the chord's root that a degree names.\n *\n * The chord answers for the degrees it actually contains, so a figure written\n * on the third comes out minor over a minor chord without the dictionary having\n * to hold two versions of it. Every other degree is taken from the key, which\n * is what keeps a passing tone inside the music rather than inside a template.\n *\n * The result is a signed offset, not a pitch class: degree 8 is the octave, and\n * reducing it modulo twelve would spell it as the root the figure just played,\n * turning every octave figure into a repeated note.\n */\nfunction degreeSemitone(degree: number, alter: number, chord: Chord, key: KeyScale): number {\n const octaves = Math.floor((degree - 1) / 7);\n const within = ((degree - 1) % 7) + 1;\n const fromChord = chordDegreeSemitone(within, chord);\n const template = MAJOR_DEGREE_SEMITONES[within - 1] ?? 0;\n const semitone =\n fromChord ??\n // A degree the chord does not name is a passing tone, so the key decides it.\n nearestScaleTone(chord.rootPc + template, key) - chord.rootPc;\n // A degree names a position inside one octave; the octaves it spans are what\n // `octaves` carries. An extended chord states its ninth as fourteen semitones\n // and the key's answer may land on the octave itself, so both are folded here.\n return pitchClass(semitone) + 12 * octaves + alter;\n}\n\n/** The semitone a chord gives one of its own degrees, if it has that degree. */\nfunction chordDegreeSemitone(degree: number, chord: Chord): number | undefined {\n for (const interval of chord.intervals) {\n if (degreeOfInterval(interval) === degree) {\n return interval;\n }\n }\n return undefined;\n}\n\n/** Which chord degree an interval from the root spells. */\nfunction degreeOfInterval(interval: number): number | undefined {\n const within = ((interval % 12) + 12) % 12;\n if (within === 0) {\n return 1;\n }\n if (within === 1 || within === 2) {\n return 2;\n }\n if (within === 3 || within === 4) {\n return 3;\n }\n if (within === 5) {\n return 4;\n }\n if (within === 6 || within === 7 || within === 8) {\n return 5;\n }\n if (within === 9) {\n return 6;\n }\n return 7;\n}\n\n/**\n * Options controlling {@link placeLicks}.\n *\n * @category Composition\n */\nexport type PlaceLicksOptions = {\n /**\n * The genre whose figures may be used. Genre is what selects the material:\n * the dials then deform it, and the ceiling rejects it.\n */\n genre: Genre;\n /**\n * The hardest figure that may be used, 1 to 5. Sugar for\n * `ctx: { complexity: { difficulty } }`; the context wins over both.\n */\n difficulty?: number;\n /** Time signature; defaults to 4/4. */\n ts?: TimeSignature;\n /**\n * Target register as a base MIDI octave; roots land around `octave*12+12`.\n *\n * @defaultValue 2\n */\n octave?: number;\n /** The instrument the line is written for; its range always applies. */\n instrument?: StringedProfile;\n /**\n * The generation context. Its `vocabulary` brings the caller's own figures,\n * its `complexity.ornament` decides how much decoration survives, and its\n * `complexity.difficulty` is the ceiling figures are rejected against.\n *\n * Its `complexity.rhythmic` is how busy the line is, in [0, 1]: it decides\n * both how often a segment takes a figure at all and, above its middle\n * setting, how much the figure is syncopated — one dial, because \"less busy\"\n * means both. Its `bpm` is the tempo the ceiling is measured against, and its\n * `seed` fixes which figures are chosen and where they land.\n *\n * @defaultValue `{ seed: 0, bpm: 120, complexity: { rhythmic: 0.6 } }`\n */\n ctx?: GenerationContextInput;\n /**\n * Maximum number of segments {@link placeLicks} may lay figures over. One\n * figure is placed per segment, so this is the guard against an unbounded\n * caller rather than a limit on any search.\n *\n * @defaultValue 1000000\n */\n budget?: number;\n};\n\nconst DEFAULT_TS: TimeSignature = FOUR_FOUR;\nconst DEFAULT_OCTAVE = 2;\nconst DEFAULT_BPM = 120;\n\n/** How much of the line is figures when the caller names nothing. */\nconst DEFAULT_LICK_DENSITY = 0.6;\n\nconst EPS = 1e-9;\n\n/**\n * Lay licks over a chord placement.\n *\n * Each segment is offered the figures whose conditions it meets — the genre,\n * the chord quality, the tempo, and the difficulty ceiling — and one of them is\n * chosen by a draw addressed to that segment, so changing a chord elsewhere\n * leaves this segment's figure where it was. A segment that takes no figure\n * gets its root, and the last note before a chord change leads into the next\n * root by step, the way a walking line does.\n *\n * @param timeline Chord segments to play over; they need not be pre-sorted, and\n * each segment's chord may be written as a chord symbol.\n * @param key Key context, which answers for the degrees the chords do not; a\n * key name such as `'C major'` is read as that key.\n * @param opts Genre, density, ceiling and seed.\n * @returns Bass notes sorted by onset, non-overlapping.\n * @throws If a segment has a non-positive duration, or the genre is not one\n * this library names.\n *\n * @example\n * ```ts\n * import { majorKey, makeChord, placeLicks } from '@libraz/libcantus';\n * const timeline = [\n * { startBeat: 0, endBeat: 4, chord: makeChord(0, 'maj7') },\n * { startBeat: 4, endBeat: 8, chord: makeChord(5, 'maj7') },\n * ];\n * const notes = placeLicks(timeline, majorKey(0), { genre: 'motown', ctx: { seed: 7 } });\n * ```\n *\n * @category Composition\n */\nexport function placeLicks(\n timeline: readonly (Omit<ChordSegment, 'chord'> & { chord: ChordLike })[],\n key: KeyLike,\n opts: PlaceLicksOptions,\n): NoteEvent[] {\n assertGenerationBudget(timeline.length, 'lick segments', opts.budget);\n const genre = assertOneOf(opts.genre, GENRES, 'lick genre');\n const ts = opts.ts ?? DEFAULT_TS;\n assertTimeSignature(ts, 'lick time signature');\n const octave = opts.octave ?? DEFAULT_OCTAVE;\n assertInteger(octave, 'lick octave', -1, 8);\n // Key and chords are read into their plain form once, here at the boundary;\n // everything below works on the plain forms alone.\n const scale = toKeyScale(key);\n const segments: ChordSegment[] = timeline\n .map((segment) => ({ ...segment, chord: toChordData(segment.chord) }))\n .sort((a, b) => a.startBeat - b.startBeat);\n for (const segment of segments) {\n assertRange(segment.startBeat, 0, Number.MAX_SAFE_INTEGER, 'lick segment startBeat');\n if (segment.endBeat <= segment.startBeat) {\n throw new InvalidInputError(\n `lick segment at beat ${segment.startBeat} must have a positive duration`,\n );\n }\n }\n if (segments.length === 0) {\n return [];\n }\n\n const resolved = resolveContext(opts.ctx);\n const draw = resolved.part('bass');\n const bpm = resolved.bpm ?? DEFAULT_BPM;\n const density = resolved.rhythmic ?? DEFAULT_LICK_DENSITY;\n const difficulty =\n resolved.difficulty ??\n (opts.difficulty === undefined\n ? undefined\n : assertDifficulty(opts.difficulty, 'lick difficulty'));\n const named = resolved.instrument('bass');\n const instrument =\n opts.instrument ?? (named !== undefined && named.kind === 'stringed' ? named : undefined);\n const low = bandFloor(octave * 12 + 12, instrument);\n\n const dictionary = mergeVocabulary(\n BASS_LICKS,\n vocabularyOfKind(resolved.vocabulary, isLickMaterial),\n );\n // Naming an instrument is the request that the line be playable on it, so a\n // figure calling for a technique it cannot produce — a slide on an instrument\n // with no slide — is not offered rather than written and misread.\n const playable = instrument === undefined ? {} : { articulations: instrument.articulations };\n const barBeats = beatsPerBar(ts);\n\n const raw: { startBeat: number; pitch: number; velocity: number; note?: LickNote }[] = [];\n /** The last pitch that sounded, which decides where a connecting tone leads. */\n let anchor = low;\n /** Where the last root sat, which is what keeps the line in its register. */\n let rootAnchor = low;\n\n for (let index = 0; index < segments.length; index += 1) {\n const segment = segments[index];\n if (!segment) {\n continue;\n }\n const rootPc = bassPcOf(segment.chord);\n const onsets: number[] = [];\n let lastTilePlayed = false;\n let firstOnset = segment.startBeat;\n\n // A figure is a bar long and a chord is not: a ballad or a modal vamp holds\n // one chord for four of them. The figure is laid across the whole segment,\n // one tile per bar of it, and each tile draws its own figure at its own\n // address — so the bar after the first is played rather than held, and a\n // chord elsewhere in the piece changing does not move any of them.\n for (let tile = 0; ; tile += 1) {\n const tileStart = segment.startBeat + tile * barBeats;\n const remaining = segment.endBeat - tileStart;\n if (remaining <= EPS) {\n break;\n }\n // The figure this tile would play is decided before the density dial is\n // consulted, so where its root sounds does not move as the dial travels:\n // a genre whose figures land after the downbeat keeps its root there\n // whether or not this turn of the dial takes the whole figure.\n const entry = pickVocabulary(\n dictionary,\n { genre, bpm, ts, difficulty, quality: segment.chord.quality, ...playable },\n draw,\n 'lickChoice',\n index,\n tile,\n );\n const takesLick = entry !== undefined && draw.prob(density, 'lick', index, tile);\n const notes = takesLick\n ? fitToSegment(\n entry.material,\n remaining,\n density,\n resolved,\n draw,\n [index, tile],\n bpm,\n difficulty,\n )\n : undefined;\n lastTilePlayed = notes !== undefined;\n\n const rootStep = entry === undefined ? 0 : anchorStepOf(entry.material, remaining);\n const rootAt = tileStart + rootStep * STEP_BEATS;\n if (tile === 0) {\n firstOnset = rootAt;\n }\n onsets.push(rootAt);\n // Roots are placed against the root before them rather than against the\n // last note that sounded: a figure that ends on its octave would\n // otherwise pull the next root up with it, and a four-bar vamp would\n // climb out of the register the caller asked for by its second bar.\n rootAnchor = placePc(rootPc, rootAnchor, low);\n anchor = rootAnchor;\n // Where the figure states its own note on this position, that note is the\n // one that sounds: the two share an onset, and the zero-length root is\n // dropped when durations are measured.\n raw.push({ startBeat: rootAt, pitch: rootAnchor, velocity: STRONG_VELOCITY });\n\n if (notes) {\n const figureRoot = placePc(pitchClass(segment.chord.rootPc), rootAnchor, low);\n for (const lickNote of notes) {\n const at = tileStart + lickNote.step * STEP_BEATS;\n // Placed against the figure's own root rather than folded one note at\n // a time into the register band, which is what an octave figure\n // needs: its octave has to stay an octave.\n const offset = degreeSemitone(lickNote.degree, lickNote.alter ?? 0, segment.chord, scale);\n anchor = figureRoot + offset;\n onsets.push(at);\n raw.push({\n startBeat: at,\n pitch: anchor,\n velocity: Math.round(\n (isStrongBeat(at, ts) ? STRONG_VELOCITY : WEAK_VELOCITY) * lickNote.velocity,\n ),\n note: lickNote,\n });\n }\n }\n }\n\n // The connecting tone between one figure and the next is the walking line's\n // approach note, reused: the figures are the vocabulary, and leading into\n // the next chord is grammar that belongs to neither of them. The beat before\n // a chord change therefore always sounds — the figure's own note where it\n // has one there, this one where it does not — so a busier setting never\n // empties a beat a quieter one filled.\n const next = segments[index + 1];\n if (next && lastTilePlayed) {\n const approachAt = next.startBeat - STEP_BEATS * BEAT_STEPS;\n const alreadySounds = onsets.some((onset) => Math.abs(onset - approachAt) < EPS);\n if (approachAt > firstOnset + EPS && !alreadySounds) {\n const nextRoot = placePc(bassPcOf(next.chord), rootAnchor, low);\n const midi = approachNote(\n nextRoot,\n anchor,\n low,\n scale,\n draw.prob(0.5, 'lickApproach', index),\n );\n anchor = midi;\n raw.push({ startBeat: approachAt, pitch: midi, velocity: WEAK_VELOCITY });\n }\n }\n }\n\n raw.sort((a, b) => a.startBeat - b.startBeat);\n const lastEnd = segments.reduce((m, seg) => Math.max(m, seg.endBeat), Number.NEGATIVE_INFINITY);\n const out: NoteEvent[] = [];\n for (let i = 0; i < raw.length; i += 1) {\n const item = raw[i];\n if (!item) {\n continue;\n }\n const nextStart = raw[i + 1]?.startBeat ?? lastEnd;\n const written = item.note?.lengthSteps;\n const durationBeat = Math.min(\n nextStart - item.startBeat,\n written === undefined ? Number.POSITIVE_INFINITY : written * STEP_BEATS,\n );\n if (durationBeat <= EPS) {\n continue;\n }\n const placed = instrument ? foldIntoRange(item.pitch, instrument) : item.pitch;\n const event: NoteEvent = {\n pitch: clampToMidi(placed, 'generated bass pitch'),\n startBeat: item.startBeat,\n durationBeat,\n velocity: Math.max(1, Math.min(127, item.velocity)),\n };\n if (item.note?.articulation) {\n event.articulation = item.note.articulation;\n }\n out.push(event);\n }\n return out;\n}\n\n/**\n * The step a figure begins on, which is where the segment sounds its root.\n *\n * Reading it from the material rather than from the deformed figure is what\n * keeps the position fixed as the density dial travels: thinning can take the\n * figure's first note away, and the root would otherwise jump to the downbeat.\n *\n * @param material The figure the segment would play.\n * @param spanBeats Length of the segment.\n * @returns The step, or 0 when the figure starts past the end of the segment.\n */\nfunction anchorStepOf(material: LickMaterial, spanBeats: number): number {\n let first: number | undefined;\n for (const note of material.notes) {\n if (first === undefined || note.step < first) {\n first = note.step;\n }\n }\n if (first === undefined || first < 0 || first * STEP_BEATS >= spanBeats - EPS) {\n return 0;\n }\n return first;\n}\n\n/**\n * Deform a figure to the segment it is being played over, and reject it if the\n * result is beyond the ceiling.\n *\n * The three never fight because each acts once and in one direction: the genre\n * has already chosen this figure, the dials reshape it here, and the ceiling\n * only says yes or no to what came out.\n */\nfunction fitToSegment(\n material: LickMaterial,\n spanBeats: number,\n density: number,\n resolved: ReturnType<typeof resolveContext>,\n draw: ReturnType<ReturnType<typeof resolveContext>['part']>,\n path: readonly (string | number)[],\n bpm: number,\n difficulty: number | undefined,\n): LickNote[] | undefined {\n const deformed = deform(\n material.notes,\n {\n // The one density the whole generator reads, defaults included: passing\n // the documented default explicitly and leaving it out have to be the\n // same request, and they were not while the figure was deformed by the\n // raw dial and chosen by the resolved one.\n rhythmic: density,\n ...(resolved.ornament === undefined ? {} : { ornament: resolved.ornament }),\n isOrnament: (event) => (event as LickNote).articulation === 'mute',\n spanSteps: material.lengthSteps,\n },\n draw,\n 'lickShape',\n ...path,\n );\n // A figure longer than the chord it is played over is cut at the chord\n // change: the harmony is the thing the figure exists to serve.\n const spanSteps = spanBeats / STEP_BEATS;\n const inside = deformed.filter((lickNote) => lickNote.step < spanSteps - EPS);\n if (inside.length === 0 || !withinCeiling(inside, bpm, difficulty)) {\n return undefined;\n }\n return inside;\n}\n","/**\n * Deterministic bass-line generation over a chord placement.\n *\n * Given a sequence of chord segments and a key, this builds a monophonic bass\n * line in a chosen low register. Several idiomatic styles are supported, from a\n * single held root per segment to a quarter-note walking line that leads by\n * step into each chord change. All pitches are kept in a narrow bass band and\n * consecutive notes move by small intervals; given a seed the output is fully\n * reproducible.\n *\n * Positions and durations are measured in quarter-note beats, matching the\n * library-wide convention.\n */\n\nimport { InvalidInputError } from '../../core/errors/index.js';\nimport { foldIntoRange, type StringedProfile } from '../../core/instrument/index.js';\nimport { isStrongBeat, type TimeSignature } from '../../core/meter/index.js';\nimport { pitchClassOf as pitchClass } from '../../core/pitch/index.js';\nimport type { KeyScale, NoteEvent } from '../../core/types.js';\nimport {\n assertGenerationBudget,\n assertInteger,\n assertOneOf,\n assertRange,\n assertTimeSignature,\n clampToMidi,\n} from '../../core/validation/index.js';\nimport type { ChordSegment } from '../../theory/chord/index.js';\nimport { type KeyLike, toKeyScale } from '../../theory/scale/index.js';\nimport { type ChordLike, toChordData } from '../../theory/symbol/index.js';\nimport {\n type Draw,\n type GenerationContextInput,\n resolveContext,\n sustainsShift,\n} from '../context/index.js';\nimport {\n bandFloor,\n bassPcOf,\n beatPositions,\n chordTonePcs,\n EPS,\n fifthPcOf,\n approachNote as neighborOf,\n placePc,\n STRONG_VELOCITY,\n WEAK_VELOCITY,\n} from './internal.js';\n\nexport type {\n BassLick,\n LickMaterial,\n LickNote,\n PlaceLicksOptions,\n} from './licks.js';\nexport { BASS_LICKS, isLickMaterial, placeLicks } from './licks.js';\n\n/**\n * A chord sounding over a half-open beat span `[startBeat, endBeat)`.\n *\n * The same record {@link ChordSegment} names, so a timeline segment can be fed\n * straight to the bass generator.\n *\n * @category Composition\n */\nexport type BassSegment = ChordSegment;\n\n/**\n * Every bass-line idiom, in declaration order.\n *\n * @category Composition\n */\nexport const BASS_STYLES = Object.freeze([\n 'root',\n 'rootFifth',\n 'pop',\n 'walking',\n 'arpeggio',\n] as const);\n\n/**\n * The bass-line idiom to generate.\n *\n * @category Composition\n */\nexport type BassStyle = (typeof BASS_STYLES)[number];\n\n/**\n * Options controlling {@link generateBassLine}.\n *\n * @category Composition\n */\nexport type BassLineOptions = {\n /**\n * Chord placement to follow; need not be pre-sorted. Each segment's chord may\n * be written as a chord symbol as well as given as chord data.\n */\n segments: readonly (Omit<BassSegment, 'chord'> & { chord: ChordLike })[];\n /**\n * Key/scale context, used for diatonic approach tones in `walking`. A key\n * name such as `'C major'` is read as that key.\n */\n key: KeyLike;\n /**\n * Time signature; used for metric accents.\n *\n * @defaultValue `{ numerator: 4, denominator: 4 }`\n */\n ts?: TimeSignature;\n /**\n * Bass-line idiom.\n *\n * @defaultValue `'root'`\n */\n style?: BassStyle;\n /**\n * Target register as a base MIDI octave; roots land around `octave*12+12`.\n * An octave pickup in the `pop` style may drop a note into the octave below\n * that band, which is where such a pickup belongs.\n *\n * @defaultValue 2\n */\n octave?: number;\n /**\n * The instrument the line is written for. Giving one is itself the request\n * that the line be playable on it: `octave` then says where in that\n * instrument to aim rather than which absolute band to use, and where the two\n * disagree the instrument wins — a note below the lowest string comes back an\n * octave up, the way a player would take it. Leave it out for a programmed\n * part, which is generated exactly as before.\n */\n instrument?: StringedProfile;\n /**\n * The generation context. Its `complexity.rhythmic` sets how often the `pop`\n * style takes a weak-beat pickup, its `instruments.bass` names the instrument\n * when `instrument` does not, its `bpm` together with\n * `complexity.difficulty` keeps leaps the hand could not make in time out of\n * the line, and its `seed` fixes every deterministic choice the line makes.\n *\n * @defaultValue `{ seed: 0 }`\n */\n ctx?: GenerationContextInput;\n /**\n * Maximum number of segments, and of notes, {@link generateBassLine} may\n * work through. The line is built segment by segment, so this is the guard\n * against an unbounded caller rather than a limit on any search.\n *\n * @defaultValue 1000000\n */\n budget?: number;\n};\n\nconst DEFAULT_TS: TimeSignature = { numerator: 4, denominator: 4 };\nconst DEFAULT_STYLE: BassStyle = 'root';\nconst DEFAULT_OCTAVE = 2;\n\n/** How often the `pop` style takes a weak-beat pickup when nothing else says. */\nconst DEFAULT_PICKUP_DENSITY = 0.35;\n\ntype RawNote = { startBeat: number; pitch: number; velocity: number };\n\n/** Working state threaded through the per-segment builders. */\ntype BuildContext = {\n ts: TimeSignature;\n low: number;\n key: KeyScale;\n /** Position-addressed draws: every decision is keyed by where it happens. */\n draw: Draw;\n notes: RawNote[];\n prevMidi: number;\n instrument: StringedProfile | undefined;\n /** How often a weak beat takes a pickup, in [0, 1]. */\n pickupDensity: number;\n bpm: number | undefined;\n difficulty: number | undefined;\n};\n\n/** Append a note for pitch class `pc` at `pos`, placed near the running anchor. */\nfunction emit(ctx: BuildContext, pos: number, pc: number, midiOverride?: number): number {\n const placed = clampToMidi(\n midiOverride ?? placePc(pc, ctx.prevMidi, ctx.low),\n 'generated bass pitch',\n );\n // The band already sits inside the instrument, so this catches the placements\n // that deliberately leave it — the pop octave pickup and walking approach\n // notes — rather than transposing the line wholesale.\n const midi = ctx.instrument ? foldIntoRange(placed, ctx.instrument) : placed;\n const velocity = isStrongBeat(pos, ctx.ts) ? STRONG_VELOCITY : WEAK_VELOCITY;\n ctx.notes.push({ startBeat: pos, pitch: midi, velocity });\n return midi;\n}\n\n/** One root note per segment. */\nfunction buildRoot(ctx: BuildContext, seg: BassSegment): void {\n ctx.prevMidi = emit(ctx, seg.startBeat, bassPcOf(seg.chord));\n}\n\n/** Root on the downbeat, fifth on the segment's midpoint. */\nfunction buildRootFifth(ctx: BuildContext, seg: BassSegment): void {\n ctx.prevMidi = emit(ctx, seg.startBeat, bassPcOf(seg.chord));\n const mid = (seg.startBeat + seg.endBeat) / 2;\n if (mid > seg.startBeat + EPS && mid < seg.endBeat - EPS) {\n ctx.prevMidi = emit(ctx, mid, fifthPcOf(seg.chord));\n }\n}\n\n/** Root on every strong beat, with occasional octave/fifth pickups on weak beats. */\nfunction buildPop(ctx: BuildContext, seg: BassSegment, index: number): void {\n const positions = beatPositions(seg.startBeat, seg.endBeat, ctx.ts);\n const rootPc = bassPcOf(seg.chord);\n let emitted = false;\n for (const pos of positions) {\n if (isStrongBeat(pos, ctx.ts)) {\n ctx.prevMidi = emit(ctx, pos, rootPc);\n emitted = true;\n } else if (ctx.draw.prob(ctx.pickupDensity, 'pickup', index, pos)) {\n // The octave pickup is a leap, so it is the candidate a difficulty\n // ceiling rejects first: at that tempo the hand does not get there, and\n // the player takes the fifth instead of dropping the pickup.\n if (ctx.draw.prob(0.5, 'pickupKind', index, pos) && reachableLeap(ctx, pos, 12)) {\n // Octave pickup: the root an octave below where it would normally sit.\n // `placePc` already lands inside `[low, low + 12]`, so clamping the drop\n // back into that band would return the band's floor — pitch class 0 —\n // whatever the chord root is. The pickup is allowed the octave below the\n // band instead, and falls back to the plain root when that leaves MIDI.\n const base = placePc(rootPc, ctx.prevMidi, ctx.low);\n const dropped = base - 12;\n emit(ctx, pos, rootPc, dropped >= 0 ? dropped : base);\n } else {\n ctx.prevMidi = emit(ctx, pos, fifthPcOf(seg.chord));\n }\n }\n }\n if (!emitted) {\n ctx.prevMidi = emit(ctx, seg.startBeat, rootPc);\n }\n}\n\n/**\n * Whether the hand covers a leap of `semitones` in the time since the last note.\n *\n * Answers true whenever no ceiling or no tempo was given: a limit nobody stated\n * constrains nothing, which is what keeps the programmed case unchanged.\n */\nfunction reachableLeap(ctx: BuildContext, pos: number, semitones: number): boolean {\n const previous = ctx.notes[ctx.notes.length - 1];\n const available = previous === undefined ? Number.POSITIVE_INFINITY : pos - previous.startBeat;\n return sustainsShift(semitones, available, ctx.bpm, ctx.difficulty);\n}\n\n/** Cycle root -> third -> fifth (-> seventh) across the segment's beats. */\nfunction buildArpeggio(ctx: BuildContext, seg: BassSegment): void {\n const tones = chordTonePcs(seg.chord);\n const positions = beatPositions(seg.startBeat, seg.endBeat, ctx.ts);\n positions.forEach((pos, i) => {\n const pc = tones[i % tones.length] ?? bassPcOf(seg.chord);\n ctx.prevMidi = emit(ctx, pos, pc);\n });\n}\n\n/**\n * The neighbour a walking line leads into the next chord with.\n *\n * Whether it is the semitone or the scale step is drawn per segment, so the\n * choice is fixed by where in the piece it happens.\n */\nfunction approachNote(ctx: BuildContext, target: number, from: number, index: number): number {\n return neighborOf(target, from, ctx.low, ctx.key, ctx.draw.prob(0.5, 'approach', index));\n}\n\n/** A quarter-note line of chord tones that leads by step into each chord change. */\nfunction buildWalking(\n ctx: BuildContext,\n seg: BassSegment,\n next: BassSegment | undefined,\n index: number,\n): void {\n const tones = chordTonePcs(seg.chord);\n const positions = beatPositions(seg.startBeat, seg.endBeat, ctx.ts);\n const count = positions.length;\n positions.forEach((pos, i) => {\n if (i === 0) {\n ctx.prevMidi = emit(ctx, pos, bassPcOf(seg.chord));\n return;\n }\n if (next && count > 1 && i === count - 1) {\n const nextBass = placePc(bassPcOf(next.chord), ctx.prevMidi, ctx.low);\n const midi = approachNote(ctx, nextBass, ctx.prevMidi, index);\n ctx.prevMidi = emit(ctx, pos, pitchClass(midi), midi);\n return;\n }\n const pc = tones[i % tones.length] ?? bassPcOf(seg.chord);\n ctx.prevMidi = emit(ctx, pos, pc);\n });\n}\n\n/**\n * Generate a bass line following a chord placement.\n *\n * Each segment contributes notes in the chosen bass register per the selected\n * style; consecutive notes are kept within roughly a fifth. Every note's\n * duration extends to the next onset (the final note extends to the last\n * segment's end). Given a seed the output is fully reproducible.\n *\n * The `pop` style's pickups are drawn per position, so raising\n * `complexity.rhythmic` adds pickups without moving the notes already written.\n *\n * With an `instrument` the line is written for that instrument: the register\n * band is moved into its range and any note still outside comes back an octave,\n * so every pitch has a fret. Nothing is dropped or replaced — the rhythm and\n * the note count are the same either way.\n *\n * @param opts Segments, key, and generation options.\n * @returns Bass notes sorted by onset, non-overlapping.\n *\n * @example\n * ```ts\n * import { generateBassLine, makeChord, majorKey } from '@libraz/libcantus';\n * const segments = [\n * { startBeat: 0, endBeat: 4, chord: makeChord(0, 'maj') },\n * { startBeat: 4, endBeat: 8, chord: makeChord(7, 'maj') },\n * ];\n * const notes = generateBassLine({ segments, key: majorKey(0), style: 'walking' });\n * ```\n *\n * @category Composition\n */\nexport function generateBassLine(opts: BassLineOptions): NoteEvent[] {\n assertGenerationBudget(opts.segments.length, 'bass segments', opts.budget);\n for (let index = 0; index < opts.segments.length; index += 1) {\n const segment = opts.segments[index];\n if (!segment) continue;\n assertRange(segment.startBeat, 0, Number.MAX_SAFE_INTEGER, `segments[${index}].startBeat`);\n assertRange(segment.endBeat, 0, Number.MAX_SAFE_INTEGER, `segments[${index}].endBeat`);\n if (segment.endBeat <= segment.startBeat) {\n throw new InvalidInputError(`segments[${index}] must have a positive duration`);\n }\n }\n // The chords are read into their plain form once, here at the boundary; the\n // builders below work on chord data alone.\n const segments: BassSegment[] = opts.segments\n .map((segment) => ({ ...segment, chord: toChordData(segment.chord) }))\n .sort((a, b) => a.startBeat - b.startBeat);\n for (let index = 1; index < segments.length; index += 1) {\n const previous = segments[index - 1];\n const current = segments[index];\n if (previous !== undefined && current !== undefined && current.startBeat < previous.endBeat) {\n throw new InvalidInputError(\n `bass segments must not overlap: segments ${index - 1} and ${index} overlap`,\n );\n }\n }\n if (segments.length === 0) {\n return [];\n }\n\n const ts = opts.ts ?? DEFAULT_TS;\n assertTimeSignature(ts);\n const style = assertOneOf(opts.style ?? DEFAULT_STYLE, BASS_STYLES, 'bass style');\n const octave = opts.octave ?? DEFAULT_OCTAVE;\n // The generated bass band spans `low..low + 12`, so octave 8 is the highest\n // base that can keep every emitted pitch in the MIDI domain.\n assertInteger(octave, 'bass octave', -1, 8);\n const estimatedNotes = segments.reduce(\n (count, segment) => count + Math.max(1, Math.ceil(segment.endBeat - segment.startBeat)),\n 0,\n );\n assertGenerationBudget(estimatedNotes, 'bass notes', opts.budget);\n const resolved = resolveContext(opts.ctx);\n const named = resolved.instrument('bass');\n const instrument =\n opts.instrument ?? (named !== undefined && named.kind === 'stringed' ? named : undefined);\n const low = bandFloor(octave * 12 + 12, instrument);\n\n const ctx: BuildContext = {\n ts,\n low,\n key: toKeyScale(opts.key),\n draw: resolved.part('bass'),\n notes: [],\n prevMidi: low,\n instrument,\n pickupDensity: resolved.rhythmic ?? DEFAULT_PICKUP_DENSITY,\n bpm: resolved.bpm,\n difficulty: resolved.difficulty,\n };\n\n for (let s = 0; s < segments.length; s += 1) {\n const seg = segments[s];\n if (!seg) {\n continue;\n }\n switch (style) {\n case 'rootFifth':\n buildRootFifth(ctx, seg);\n break;\n case 'pop':\n buildPop(ctx, seg, s);\n break;\n case 'walking':\n buildWalking(ctx, seg, segments[s + 1], s);\n break;\n case 'arpeggio':\n buildArpeggio(ctx, seg);\n break;\n default:\n buildRoot(ctx, seg);\n break;\n }\n }\n\n ctx.notes.sort((a, b) => a.startBeat - b.startBeat);\n const lastEnd = segments.reduce((m, seg) => Math.max(m, seg.endBeat), Number.NEGATIVE_INFINITY);\n\n const out: NoteEvent[] = [];\n for (let i = 0; i < ctx.notes.length; i += 1) {\n const note = ctx.notes[i];\n if (!note) {\n continue;\n }\n const nextStart = ctx.notes[i + 1]?.startBeat ?? lastEnd;\n const durationBeat = nextStart - note.startBeat;\n if (durationBeat <= EPS) {\n continue;\n }\n out.push({\n pitch: clampToMidi(note.pitch, 'generated bass pitch'),\n startBeat: note.startBeat,\n durationBeat,\n velocity: note.velocity,\n });\n }\n return out;\n}\n","/**\n * Imitative entries: a second voice restating what the first has just sung.\n *\n * A motif transform has no time relationship to anything — it returns a cell,\n * and where that cell goes is the caller's problem. An imitation is defined by\n * that relationship: it enters at a stated beat, at a stated interval, against a\n * voice that is still sounding. That is what a canon is, and what the answering\n * phrase in the second half of a chorus is.\n */\n\nimport { InvalidInputError } from '../../core/errors/index.js';\nimport type { IntervalLike, SpelledInterval } from '../../core/pitch/index.js';\nimport { toSpelledInterval } from '../../core/pitch/index.js';\nimport type { NoteEvent } from '../../core/types.js';\nimport {\n assertFiniteNumber,\n assertNoteEvents,\n assertOneOf,\n dropSilentNotes,\n} from '../../core/validation/index.js';\nimport {\n type KeyLike,\n scaleLadderPitch,\n scaleLadderPosition,\n shiftByScaleDegrees,\n toKeyScale,\n} from '../../theory/scale/index.js';\n\n/** Every way an imitation can answer, in declaration order. */\nconst IMITATION_ANSWERS = ['real', 'tonal'] as const;\n\n/**\n * How an imitation answers the voice it follows.\n *\n * A `real` answer transposes literally, keeping every interval of the subject\n * and leaving the key where the interval puts it. A `tonal` answer counts scale\n * degrees instead, so the answer stays in the key and adjusts an interval or two\n * to do it — the fifth of a subject answered by a fourth.\n *\n * @category Composition\n */\nexport type ImitationAnswer = (typeof IMITATION_ANSWERS)[number];\n\n/**\n * Options controlling {@link imitate}.\n *\n * @category Composition\n */\nexport type ImitationOptions = {\n /**\n * Beat at which the imitation enters; the copied material starts here.\n * Negative like any other onset, for an answer that enters in a pickup.\n */\n atBeat: number;\n /**\n * Interval to imitate at, spelled: `'P5'` for an answer a fifth above,\n * `'-P4'` for one a fourth below.\n */\n interval: IntervalLike;\n /**\n * The key the answer is measured in; what makes a tonal answer tonal. A key\n * name such as `'C major'` is read as that key.\n */\n key: KeyLike;\n /**\n * Whether the answer transposes literally or by scale degree.\n *\n * @defaultValue 'real'\n */\n answer?: ImitationAnswer;\n /**\n * Turn the subject upside down before transposing it, mirroring it about its\n * own first note: by semitone for a real answer, by scale degree for a tonal\n * one.\n *\n * @defaultValue false\n */\n invert?: boolean;\n /** First beat of the lead to imitate; defaults to the start of the line. */\n from?: number;\n /** Beat to imitate up to, exclusive; defaults to the end of the line. */\n to?: number;\n /**\n * Factor applied to the copied velocities, for an answer that sits under the\n * voice it follows.\n *\n * @defaultValue 1\n */\n velocityScale?: number;\n};\n\n/** How many scale degrees a spelled interval spans, signed by its direction. */\nfunction degreesOf(interval: SpelledInterval): number {\n const descending = interval.descending ?? interval.semitones < 0;\n return (interval.number - 1) * (descending ? -1 : 1);\n}\n\n/** Reject a pitch the transposition has pushed off the keyboard. */\nfunction assertPlayable(pitch: number): number {\n if (pitch < 0 || pitch > 127) {\n throw new InvalidInputError(`imitated pitch ${pitch} is outside the MIDI range 0..127`);\n }\n return pitch;\n}\n\n/**\n * Answer a leading voice with an imitation of it.\n *\n * The chosen span of the lead is copied so that its first note falls on\n * `atBeat`, keeping its internal rhythm; only the answer is returned, so the\n * caller decides what to do with the two voices together. Pitches move by\n * `interval`: literally for a real answer, by scale degree for a tonal one, and\n * upside down first when `invert` is set. Inversion mirrors about the first note\n * of the copied span, so the answer starts where the transposition puts that\n * note either way. In a tonal answer a pitch outside the key keeps its distance\n * from the scale tone below it, and inversion mirrors that distance too — a\n * chromatic passing note stays a chromatic passing note, on the other side.\n * Where a mirrored chromatic note falls into a diatonic semitone the key leaves\n * it no room, and it lands on the scale tone there, so a subject saturated with\n * chromatic steps can answer with a pitch repeated.\n *\n * @param lead The voice being imitated, in ascending onset order.\n * @param opts Where the answer enters, at what interval, and in what key.\n * @returns The imitation as note events sorted by onset; `[]` when the chosen\n * span holds no sounding notes. Notes with a zero or negative duration never\n * sound and are not copied, so the answer can be shorter than the span.\n * @throws If the span is reversed, the answer is not a known kind, or a\n * transposed pitch leaves the MIDI range.\n * @example\n * ```ts\n * import { imitate, majorKey } from '@libraz/libcantus';\n * const lead = [0, 1, 2].map((beat) => ({ pitch: 60 + beat, startBeat: beat, durationBeat: 1 }));\n * imitate(lead, { atBeat: 2, interval: 'P5', key: majorKey(0) });\n * // the same three notes a fifth up, entering at beat 2\n * ```\n * @category Composition\n */\nexport function imitate(lead: readonly NoteEvent[], opts: ImitationOptions): NoteEvent[] {\n assertNoteEvents(lead, 'imitate lead', { allowNonPositiveDuration: true });\n // Onsets are unbounded below library-wide, because a pickup sounds before the\n // downbeat and the downbeat is beat 0; an answer to a pickup enters there too.\n assertFiniteNumber(opts.atBeat, 'atBeat');\n const interval = toSpelledInterval(opts.interval);\n // The key is read into its plain form once, here at the boundary; the degree\n // arithmetic below is given the scale it resolved to.\n const key = toKeyScale(opts.key);\n const from = opts.from ?? Number.NEGATIVE_INFINITY;\n const to = opts.to ?? Number.POSITIVE_INFINITY;\n if (opts.from !== undefined) {\n assertFiniteNumber(opts.from, 'from');\n }\n if (opts.to !== undefined) {\n assertFiniteNumber(opts.to, 'to');\n }\n if (from > to) {\n throw new InvalidInputError(`imitate needs from <= to; received ${from} > ${to}`);\n }\n const velocityScale = opts.velocityScale ?? 1;\n assertFiniteNumber(velocityScale, 'velocityScale');\n // Checked before the empty-span exit, so a malformed option is rejected\n // whether or not the chosen span happens to hold a note.\n const tonal = assertOneOf(opts.answer ?? 'real', IMITATION_ANSWERS, 'imitate answer') === 'tonal';\n\n // Zero-length artefacts are routine in MIDI imports and never sound. Dropping\n // them before the copy keeps the answer passable to any entry point in the\n // library, which reject non-positive durations by default.\n const subject = dropSilentNotes(lead)\n .filter((note) => note.startBeat >= from && note.startBeat < to)\n .sort((a, b) => a.startBeat - b.startBeat);\n const origin = subject[0]?.startBeat;\n const pivot = subject[0]?.pitch;\n if (origin === undefined || pivot === undefined) {\n return [];\n }\n const shift = opts.atBeat - origin;\n const invert = opts.invert ?? false;\n const degrees = degreesOf(interval);\n\n return subject.map((note) => {\n let pitch = note.pitch;\n // A tonal answer counts along the key's ladder of scale tones, which is what\n // carries a pitch between two of them through the transposition as the note\n // it was: {@link shiftByScaleDegrees} keeps its distance above the scale\n // tone below it.\n let offset = 0;\n if (invert) {\n if (tonal) {\n // Mirroring reflects the rung, and the offset with it: a note a semitone\n // above its scale tone lands a semitone below the one it is mirrored\n // onto, which is what keeps two adjacent chromatic pitches from being\n // flattened onto the same answer. The mirrored pitch is a scale tone, so\n // the offset is added back after the degree arithmetic.\n const here = scaleLadderPosition(pitch, key);\n pitch = scaleLadderPitch(2 * scaleLadderPosition(pivot, key).rung - here.rung, key);\n offset = -here.offset;\n } else {\n pitch = 2 * pivot - pitch;\n }\n }\n pitch = tonal\n ? shiftByScaleDegrees(pitch, degrees, key) + offset\n : pitch + Math.round(interval.semitones);\n const answer: NoteEvent = {\n pitch: assertPlayable(pitch),\n startBeat: note.startBeat + shift,\n durationBeat: note.durationBeat,\n };\n if (note.velocity !== undefined) {\n answer.velocity = Math.max(1, Math.min(127, Math.round(note.velocity * velocityScale)));\n }\n return answer;\n });\n}\n","import type { ChordTimeline } from '../../analyze/timeline/index.js';\nimport { InvalidInputError } from '../../core/errors/index.js';\nimport type { NoteEventIndex } from '../../core/event-index/index.js';\nimport { createNoteEventIndex } from '../../core/event-index/index.js';\nimport type { TimeSignature } from '../../core/meter/index.js';\nimport { isStrongBeat } from '../../core/meter/index.js';\nimport type { KeyScale, NoteEvent } from '../../core/types.js';\nimport {\n assertGenerationBudget,\n assertNoteEvents,\n assertOneOf,\n assertRange,\n assertTimeSignature,\n clampToMidi,\n soundingNotesOnly,\n} from '../../core/validation/index.js';\nimport type { Chord } from '../../theory/chord/index.js';\nimport { chordToneRole } from '../../theory/chord/index.js';\nimport { createsParallelPerfect, isForbiddenMelodicLeap } from '../../theory/counterpoint/index.js';\nimport type { ProfileWeights, VoiceSnapshot } from '../../theory/safety/index.js';\nimport {\n enumerateSafePitches,\n evaluateSafety,\n NoteSafety,\n profileWeights,\n} from '../../theory/safety/index.js';\nimport { type KeyLike, toKeyScale } from '../../theory/scale/index.js';\nimport { type Draw, type GenerationContextInput, resolveContext } from '../context/index.js';\n\n/**\n * Options controlling {@link generateCounterMelody}.\n *\n * @category Voicing & Counterpoint\n */\nexport type CounterMelodyOptions = {\n /** The lead line to write against, in ascending onset order. */\n melody: readonly NoteEvent[];\n /**\n * The harmony to write against. A timeline carries its own segment\n * boundaries, so a chord change anywhere — including off the generator's\n * half-beat probe grid — is seen; prefer it over `chordAt`.\n *\n * Exactly one of `timeline` and `chordAt` is required; `timeline` wins when\n * both are given.\n */\n timeline?: ChordTimeline;\n /**\n * Chord sounding at an absolute beat position, or null when none applies.\n *\n * A callback is opaque: the generator cannot see where it changes, so it\n * probes a half-beat grid and otherwise relies on `chordChangeBeats`. A held\n * counter note that spans an unseen change is not checked against the new\n * chord.\n */\n chordAt?: (beat: number) => Chord | null;\n /**\n * Exact chord-change beats the `chordAt` callback does not expose. Ignored\n * when `timeline` is given, since a timeline lists its own boundaries.\n */\n chordChangeBeats?: number[];\n /**\n * Key/scale context for scale-tone decisions. A key name such as `'C major'`\n * is read as that key.\n */\n key: KeyLike;\n /**\n * Meter used for strong-beat decisions.\n *\n * @defaultValue 4/4\n */\n ts?: TimeSignature;\n /**\n * Which side of the melody the counter line occupies.\n *\n * @defaultValue 'below'\n */\n register?: 'above' | 'below';\n /**\n * Onset strategy: `'complement'` moves where the melody holds or rests and\n * reinforces some strong beats; `'follow'` mirrors melody onsets.\n *\n * @defaultValue 'complement'\n */\n rhythm?: 'complement' | 'follow';\n /**\n * Style profile applied to candidate pitches. It sets what is rejected\n * outright and, through {@link PROFILE_WEIGHTS}, what is preferred among the\n * candidates that remain: `'strict'` seeks contrary motion and avoids\n * similar, `'pop'` treats a run of parallel thirds or sixths as the harmony\n * line an arranger would write.\n *\n * @defaultValue 'pop'\n */\n profile?: 'strict' | 'pop';\n /**\n * Per-field overrides of the profile's ranking weights, for a preference the\n * two stock profiles do not express — a line that should leap more freely, or\n * one that should hold a pedal wherever it can.\n */\n weights?: Partial<ProfileWeights>;\n /** Lowest MIDI pitch the counter line may use (default derived from `register`). */\n pitchLow?: number;\n /** Highest MIDI pitch the counter line may use (default derived from `register`). */\n pitchHigh?: number;\n /**\n * The generation context, so the counter line is drawn from the same project\n * source as every other part and no two parts collide.\n *\n * Its `seed` places the `'complement'` onsets and breaks ties between equally\n * good candidate pitches; the same seed always yields the same line. Where the\n * constraints leave only one good answer — which is common with\n * `rhythm: 'follow'`, whose onsets are fixed by the melody — changing the seed\n * changes nothing.\n *\n * @defaultValue `{ seed: 0 }`\n */\n ctx?: GenerationContextInput;\n};\n\n/** Meter assumed when none is supplied. */\nconst DEFAULT_TS: TimeSignature = { numerator: 4, denominator: 4 };\n\n/** Tolerance for beat-position comparisons. */\nconst EPS = 1e-9;\n\n/** Grid resolution, in quarter-note beats, scanned for complement onsets. */\nconst GRID_STEP = 0.5;\n\n/** Minimum spacing between counter onsets, keeping the line uncluttered. */\nconst MIN_ONSET_GAP = 1;\n\n/** Semitone offset of the counter register's centre from the melody's mean pitch. */\nconst REGISTER_OFFSET = 12;\n\n/** Half-width, in semitones, of the default counter register. */\nconst DEFAULT_HALF_RANGE = 10;\n\n/** Probability of moving on a whole beat while the melody sustains. */\nconst HOLD_ON_BEAT_PROB = 0.55;\n\n/** Probability of moving on an off-beat subdivision while the melody sustains. */\nconst HOLD_OFF_BEAT_PROB = 0.2;\n\n/** Probability of reinforcing a strong beat that the melody also attacks. */\nconst REINFORCE_PROB = 0.35;\n\n/** Velocity assumed for melody notes that carry none. */\nconst DEFAULT_MELODY_VELOCITY = 96;\n\n/** How far below the concurrent melody velocity the counter line sits. */\nconst VELOCITY_DROP = 16;\n\n/**\n * Magnitude of the seed-driven per-candidate perturbation. Kept far below the\n * smallest real score difference (the register-centre term alone moves in steps\n * of 0.02), so the seed can only decide between candidates whose scores are\n * otherwise exactly equal, and never overrides consonance or motion.\n */\nconst TIE_BREAK_JITTER = 1e-6;\n\n/** Whether a beat position falls on a whole quarter-note beat. */\nfunction isWholeBeat(beat: number): boolean {\n return Math.abs(beat - Math.round(beat)) < EPS;\n}\n\n/** The melody note sounding at a beat (latest onset wins on overlaps). */\nfunction melodyNoteAt(melody: NoteEventIndex, beat: number): NoteEvent | undefined {\n return melody.at(beat)?.note;\n}\n\n/** Whether any melody note attacks exactly at a beat. */\nfunction melodyAttacksAt(melody: NoteEventIndex, beat: number): boolean {\n return melody.attacksAt(beat);\n}\n\n/** Melody voice at a transition, including the pitch immediately before it. */\nfunction melodySnapshotAt(melody: NoteEventIndex, beat: number): VoiceSnapshot | undefined {\n const current = melodyNoteAt(melody, beat);\n if (current === undefined) {\n return undefined;\n }\n const attacked = Math.abs(current.startBeat - beat) < EPS;\n const previous = attacked ? melodyNoteAt(melody, beat - EPS * 2) : current;\n const snapshot: VoiceSnapshot = { pitch: current.pitch };\n if (previous !== undefined) {\n snapshot.prevPitch = previous.pitch;\n }\n return snapshot;\n}\n\n/** Counter onsets mirroring the melody's own onsets, deduplicated and sorted. */\nfunction followOnsets(melody: NoteEventIndex): number[] {\n return [...new Set(melody.notes.map(({ note }) => note.startBeat))];\n}\n\n/**\n * Counter onsets complementing the melody: whole beats inside rests are always\n * taken, positions where the melody sustains are taken probabilistically, and\n * strong beats the melody also attacks are occasionally reinforced. A minimum\n * gap between accepted onsets keeps the line sparse.\n *\n * Each grid position is drawn for by where it falls rather than by how many\n * positions preceded it, so editing one bar of the melody leaves the onsets of\n * every other bar where they were.\n */\nfunction complementOnsets(\n melody: NoteEventIndex,\n ts: TimeSignature,\n spanStart: number,\n spanEnd: number,\n draw: Draw,\n): number[] {\n const onsets: number[] = [];\n const gridStart = Math.floor(spanStart / GRID_STEP) * GRID_STEP;\n let last = Number.NEGATIVE_INFINITY;\n for (let beat = gridStart; beat < spanEnd - EPS; beat += GRID_STEP) {\n const attacked = melodyAttacksAt(melody, beat);\n const sounding = melodyNoteAt(melody, beat) !== undefined;\n let place = false;\n if (!attacked && !sounding) {\n place = isWholeBeat(beat);\n } else if (!attacked) {\n place = draw.prob(isWholeBeat(beat) ? HOLD_ON_BEAT_PROB : HOLD_OFF_BEAT_PROB, 'hold', beat);\n } else if (isStrongBeat(beat, ts)) {\n place = draw.prob(REINFORCE_PROB, 'reinforce', beat);\n }\n if (place && beat - last >= MIN_ONSET_GAP - EPS) {\n onsets.push(beat);\n last = beat;\n }\n }\n return onsets;\n}\n\n/** Whether an interval class is a third or a sixth (an imperfect consonance). */\nfunction imperfectClass(ic: number): boolean {\n return ic === 3 || ic === 4 || ic === 8 || ic === 9;\n}\n\n/**\n * Whether two successive intervals are the same imperfect consonance, so the\n * voices are running in parallel thirds or parallel sixths.\n *\n * Thirds and sixths are grouped by size rather than by quality: a minor third\n * followed by a major third is the parallel-third texture an arranger hears,\n * and a bare pitch cannot tell the two qualities apart in any case.\n */\nfunction keepsImperfectInterval(before: number, now: number): boolean {\n if (!imperfectClass(before) || !imperfectClass(now)) {\n return false;\n }\n return before <= 4 === now <= 4;\n}\n\n/**\n * Preference score for one candidate counter pitch, under the weights the\n * profile carries. Every term names a situation the weight table prices:\n * consonance with the melody, chord-tone membership, the kind of motion the two\n * voices make, the size of the melodic move, and the drift from the register\n * centre. The weights decide which of those the caller's style actually wants.\n */\nfunction scoreCandidate(\n pitch: number,\n melPitch: number | undefined,\n melPrev: number | undefined,\n prevPitch: number | undefined,\n chord: Chord | null,\n center: number,\n weights: ProfileWeights,\n): number {\n let score = 0;\n const ic = melPitch === undefined ? undefined : Math.abs(pitch - melPitch) % 12;\n if (ic !== undefined) {\n if (imperfectClass(ic)) {\n score += weights.imperfectConsonance;\n } else if (ic === 0 || ic === 7) {\n score += weights.perfectConsonance;\n } else if (ic === 1 || ic === 2 || ic === 6 || ic === 10 || ic === 11) {\n // The fourth is left unpriced: whether it is a dissonance depends on what\n // is under it, which the safety verdict has already ruled on.\n score += weights.dissonance;\n }\n }\n if (chord && chordToneRole(pitch, chord) !== null) {\n score += weights.chordTone;\n }\n if (prevPitch !== undefined) {\n const move = pitch - prevPitch;\n const dist = Math.abs(move);\n score += dist * weights.melodicDistance;\n if (dist > 0 && dist <= 2) {\n score += weights.stepwise;\n }\n if (isForbiddenMelodicLeap(prevPitch, pitch)) {\n score += weights.forbiddenLeap;\n }\n if (melPitch !== undefined && melPrev !== undefined && ic !== undefined) {\n const melMove = melPitch - melPrev;\n if ((move > 0 && melMove < 0) || (move < 0 && melMove > 0)) {\n score += weights.contraryMotion;\n } else if (move === 0 || melMove === 0) {\n score += weights.obliqueMotion;\n } else if (keepsImperfectInterval(Math.abs(prevPitch - melPrev) % 12, ic)) {\n score += weights.parallelImperfect;\n } else {\n score += weights.similarMotion;\n }\n }\n }\n score += Math.abs(pitch - center) * weights.registerDrift;\n return score;\n}\n\n/** Event boundaries at which a held counter pitch can acquire a new context. */\nfunction heldNoteBoundaries(\n melody: NoteEventIndex,\n startBeat: number,\n endBeat: number,\n chordChangeBeats: number[],\n): number[] {\n const boundaries = new Set<number>([startBeat]);\n for (const onset of melody.onsetsBetween(startBeat, endBeat)) {\n boundaries.add(onset);\n }\n for (const beat of chordChangeBeats) {\n if (beat > startBeat + EPS && beat < endBeat - EPS) {\n boundaries.add(beat);\n }\n }\n // `chordAt` is an opaque callback. Probe the same half-beat grid used by the\n // complement rhythm so ordinary beat/bar chord changes are still found even\n // when the caller does not provide `chordChangeBeats`.\n const firstGrid = Math.floor(startBeat / GRID_STEP + 1) * GRID_STEP;\n for (let beat = firstGrid; beat < endBeat - EPS; beat += GRID_STEP) {\n boundaries.add(beat);\n }\n return [...boundaries].sort((a, b) => a - b);\n}\n\n/**\n * Everything about one boundary inside a held counter note that does not depend\n * on which pitch is being tried there.\n */\ntype BoundaryContext = {\n /** Melody voice sounding at the boundary, if any. */\n melodyVoice: VoiceSnapshot | undefined;\n /** `melodyVoice` in the array shape the safety query takes. */\n otherVoices: VoiceSnapshot[];\n /** Chord reported by the caller's callback at the boundary. */\n chord: Chord | null;\n /** Whether the boundary is a strong beat of the meter. */\n strongBeat: boolean;\n /** Whether the boundary is the counter note's own onset. */\n atCounterOnset: boolean;\n};\n\n/**\n * Resolve the candidate-independent context at every boundary a counter note\n * spans. This is computed once per onset rather than once per candidate pitch,\n * which is what keeps the caller's `chordAt` callback from being invoked once\n * per pitch in the register.\n */\nfunction boundaryContexts(\n melody: NoteEventIndex,\n harmony: { chordAt: (beat: number) => Chord | null; chordChangeBeats: number[] },\n startBeat: number,\n endBeat: number,\n ts: TimeSignature,\n): BoundaryContext[] {\n const boundaries = heldNoteBoundaries(melody, startBeat, endBeat, harmony.chordChangeBeats);\n return boundaries.map((boundary) => {\n const melodyVoice = melodySnapshotAt(melody, boundary);\n return {\n melodyVoice,\n otherVoices: melodyVoice === undefined ? [] : [melodyVoice],\n chord: harmony.chordAt(boundary),\n strongBeat: isStrongBeat(boundary, ts),\n atCounterOnset: Math.abs(boundary - startBeat) < EPS,\n };\n });\n}\n\n/**\n * Worst safety reached while `pitch` is held through its complete interval.\n * A wrong-side crossing is treated as unavailable. Safe candidates are later\n * preferred over Warning candidates; Dissonant candidates are never emitted.\n */\nfunction heldPitchSafety(\n pitch: number,\n contexts: BoundaryContext[],\n prevPitch: number | undefined,\n key: KeyScale,\n profile: 'strict' | 'pop',\n register: 'above' | 'below',\n low: number,\n high: number,\n): NoteSafety | null {\n let worst = NoteSafety.Safe;\n for (const context of contexts) {\n const melodyVoice = context.melodyVoice;\n if (\n melodyVoice !== undefined &&\n ((register === 'below' && pitch >= melodyVoice.pitch) ||\n (register === 'above' && pitch <= melodyVoice.pitch))\n ) {\n return null;\n }\n // Only `safety` is read, so the suggestion search is turned off rather than\n // run once per candidate and discarded.\n const result = evaluateSafety(\n {\n profile,\n candidatePitch: pitch,\n prevPitch: context.atCounterOnset ? prevPitch : pitch,\n chord: context.chord,\n key,\n otherVoices: context.otherVoices,\n strongBeat: context.strongBeat,\n vocalLow: low,\n vocalHigh: high,\n },\n { suggestions: false },\n );\n worst = Math.max(worst, result.safety) as NoteSafety;\n if (worst === NoteSafety.Dissonant) {\n return worst;\n }\n }\n return worst;\n}\n\n/**\n * Generate a counter melody against a lead line.\n *\n * Onsets are chosen per `rhythm`: `'follow'` places one counter note on each\n * melody onset; `'complement'` fills melody rests and sustains and reinforces\n * an occasional strong beat, spaced at least a beat apart. Each onset's pitch\n * is picked from the safe pitches in the counter register (chord tones and\n * consonant tensions against the sounding melody note), rejecting candidates\n * that would form a parallel perfect interval with the melody or sit on the\n * wrong side of it, then ranked by the profile's weights — which is where the\n * two styles part company: `'strict'` prefers contrary motion and penalizes\n * similar, `'pop'` rewards a run of parallel thirds or sixths and is content\n * with a pedal. Ties are broken by a seeded perturbation far\n * below any real score difference (see `TIE_BREAK_JITTER`), so the same\n * seed always yields the same line while a different seed can only reshuffle\n * candidates that were already equally good. Notes extend to the next counter\n * onset (the last to the melody's end) at a velocity slightly under the melody.\n *\n * @param opts The lead line, chord context callback, key, and generation knobs.\n * @returns The counter line as note events sorted by onset; `[]` for an empty melody.\n * @example\n * ```ts\n * import { generateCounterMelody, majorKey, parseChordSymbol } from '@libraz/libcantus';\n * const melody = [{ pitch: 72, startBeat: 0, durationBeat: 2 }];\n * const counter = generateCounterMelody({\n * melody,\n * chordAt: () => parseChordSymbol('C'),\n * key: majorKey(0),\n * }); // note events below the melody, in onset order\n * ```\n * @category Voicing & Counterpoint\n */\nexport function generateCounterMelody(opts: CounterMelodyOptions): NoteEvent[] {\n // Zero-length artefacts are accepted and ignored, matching the analysis\n // layer, so an array that passed through `analyzeArrangement` can be fed\n // straight in here.\n const timeline = opts.timeline;\n const chordAt = timeline === undefined ? opts.chordAt : (beat: number) => timeline.at(beat);\n if (chordAt === undefined) {\n throw new InvalidInputError('countermelody needs a timeline or a chordAt callback');\n }\n const chordChangeBeats =\n timeline === undefined\n ? (opts.chordChangeBeats ?? [])\n : timeline.segments.flatMap((segment) => [segment.startBeat, segment.endBeat]);\n const register = opts.register ?? 'below';\n // The counter line writes against the voice on its own side of the texture,\n // so a chord in the melody track resolves to that voice rather than to\n // whichever note the caller happened to store last.\n assertNoteEvents(opts.melody, 'countermelody melody', { allowNonPositiveDuration: true });\n const melody = createNoteEventIndex(soundingNotesOnly(opts.melody), {\n tieBreak: register === 'below' ? 'highest' : 'lowest',\n });\n const ts = opts.ts ?? DEFAULT_TS;\n assertTimeSignature(ts);\n const rhythm = assertOneOf(opts.rhythm ?? 'complement', ['complement', 'follow'], 'rhythm');\n const profile = assertOneOf(opts.profile ?? 'pop', ['strict', 'pop'], 'safety profile');\n const weights = profileWeights(profile, opts.weights);\n // The key is read into its plain form once, here at the boundary; the search\n // below is given the scale it resolved to.\n const key = toKeyScale(opts.key);\n const draw = resolveContext(opts.ctx).part('countermelody');\n // The options are checked before the empty-melody exit, so a call with a\n // malformed option is rejected whether or not the melody happens to sound.\n if (melody.notes.length === 0) {\n return [];\n }\n\n const spanStart = melody.notes[0]?.note.startBeat ?? 0;\n const spanEnd = melody.notes.reduce((end, n) => Math.max(end, n.endBeat), spanStart);\n assertGenerationBudget(Math.ceil((spanEnd - spanStart) / GRID_STEP), 'countermelody grid');\n const meanPitch = melody.notes.reduce((sum, n) => sum + n.note.pitch, 0) / melody.notes.length;\n const defaultCenter =\n register === 'below' ? meanPitch - REGISTER_OFFSET : meanPitch + REGISTER_OFFSET;\n const low = clampToMidi(\n opts.pitchLow ?? Math.round(defaultCenter) - DEFAULT_HALF_RANGE,\n 'countermelody pitchLow',\n );\n const high = clampToMidi(\n opts.pitchHigh ?? Math.round(defaultCenter) + DEFAULT_HALF_RANGE,\n 'countermelody pitchHigh',\n );\n if (low > high) {\n throw new InvalidInputError(\n `countermelody pitchLow must not exceed pitchHigh; received ${low} > ${high}`,\n );\n }\n assertGenerationBudget(high - low + 1, 'countermelody pitch candidates');\n assertGenerationBudget(chordChangeBeats.length, 'chord change beats');\n for (let index = 0; index < chordChangeBeats.length; index += 1) {\n assertRange(\n chordChangeBeats[index] ?? Number.NaN,\n 0,\n Number.MAX_SAFE_INTEGER,\n `chordChangeBeats[${index}]`,\n );\n }\n const center = (low + high) / 2;\n\n const onsets =\n rhythm === 'follow'\n ? followOnsets(melody)\n : complementOnsets(melody, ts, spanStart, spanEnd, draw);\n // Each candidate pitch is evaluated at every boundary its note spans, and the\n // boundaries partition the melody's span, so the whole search is bounded by\n // the total boundary count times the register width.\n const boundaryEstimate =\n onsets.length +\n Math.ceil((spanEnd - spanStart) / GRID_STEP) +\n melody.notes.length +\n chordChangeBeats.length;\n assertGenerationBudget(boundaryEstimate * (high - low + 1), 'countermelody search');\n const jitter = Array.from(\n { length: high - low + 1 },\n (_, index) => draw.at('jitter', index) * TIE_BREAK_JITTER,\n );\n\n const out: NoteEvent[] = [];\n let prevPitch: number | undefined;\n for (let onsetIndex = 0; onsetIndex < onsets.length; onsetIndex += 1) {\n const beat = onsets[onsetIndex];\n if (beat === undefined) {\n continue;\n }\n const nextOnset = onsets[onsetIndex + 1];\n const endBeat = nextOnset ?? Math.max(spanEnd, beat + GRID_STEP);\n const melNote = melodyNoteAt(melody, beat);\n const melPitch = melNote?.pitch;\n const melodyVoice = melodySnapshotAt(melody, beat);\n const melPrev = melodyVoice?.prevPitch;\n const contexts = boundaryContexts(melody, { chordAt, chordChangeBeats }, beat, endBeat, ts);\n const chord = contexts[0]?.chord ?? chordAt(beat);\n const otherVoices: VoiceSnapshot[] = melodyVoice !== undefined ? [melodyVoice] : [];\n\n const candidates = enumerateSafePitches(\n {\n profile,\n prevPitch,\n chord,\n key,\n otherVoices,\n strongBeat: isStrongBeat(beat, ts),\n vocalLow: low,\n vocalHigh: high,\n },\n low,\n high,\n );\n\n let bestPitch: number | undefined;\n let bestWorstSafety = NoteSafety.Dissonant;\n let bestScore = Number.NEGATIVE_INFINITY;\n for (const candidate of candidates) {\n const worstSafety = heldPitchSafety(\n candidate,\n contexts,\n prevPitch,\n key,\n profile,\n register,\n low,\n high,\n );\n if (worstSafety === null || worstSafety === NoteSafety.Dissonant) {\n continue;\n }\n if (melPitch !== undefined) {\n if (\n prevPitch !== undefined &&\n melPrev !== undefined &&\n // Parallel octaves are the perfect-class-zero case of this predicate,\n // so a single check covers both parallel fifths and octaves.\n createsParallelPerfect(prevPitch, candidate, melPrev, melPitch)\n ) {\n continue;\n }\n }\n const score =\n scoreCandidate(candidate, melPitch, melPrev, prevPitch, chord, center, weights) +\n (jitter[candidate - low] ?? 0);\n const tie =\n Math.abs(score - bestScore) <= EPS && candidate < (bestPitch ?? Number.POSITIVE_INFINITY);\n if (\n worstSafety < bestWorstSafety ||\n (worstSafety === bestWorstSafety && (score > bestScore + EPS || tie))\n ) {\n bestWorstSafety = worstSafety;\n bestScore = score;\n bestPitch = candidate;\n }\n }\n\n if (bestPitch === undefined) {\n // Do not emit an unchecked fallback. Ending the previous note at this\n // planned onset leaves an explicit rest when the constraints are\n // unsatisfiable instead of returning a plausibly named unsafe line.\n prevPitch = undefined;\n continue;\n }\n const pitch = bestPitch;\n const velocity = Math.max(\n 1,\n Math.min(127, Math.round((melNote?.velocity ?? DEFAULT_MELODY_VELOCITY) - VELOCITY_DROP)),\n );\n out.push({\n pitch: clampToMidi(pitch),\n startBeat: beat,\n durationBeat: endBeat - beat,\n velocity,\n });\n prevPitch = pitch;\n }\n return out;\n}\n\nexport type { ImitationAnswer, ImitationOptions } from './imitation.js';\nexport { imitate } from './imitation.js';\n","import type { SectionType } from './internal.js';\nimport { MoodCategory } from './internal.js';\n\n/** Ghost-note position within a beat: the \"e\" (1st 16th) or \"a\" (3rd 16th). */\nexport type GhostPosition = 'e' | 'a';\n\n/** Ghost density level ordinal (0 = none, 3 = heavy). */\ntype GhostDensityLevel = 0 | 1 | 2 | 3;\n\n/**\n * How much a groove ghosts, per section and mood.\n *\n * A row is read by mood category, in the order those categories are declared:\n * calm, standard, energetic.\n */\nexport const GHOST_DENSITY_TABLE: Readonly<Record<SectionType, readonly GhostDensityLevel[]>> =\n Object.freeze({\n intro: [0, 1, 1],\n a: [0, 1, 2],\n b: [1, 2, 2],\n chorus: [1, 2, 3],\n bridge: [1, 1, 2],\n outro: [0, 1, 1],\n });\n\n/** Trigger probability at each density level. */\nconst GHOST_LEVEL_PROBABILITY = Object.freeze([0, 0.15, 0.3, 0.45] as const);\n\nfunction levelToProbability(level: GhostDensityLevel): number {\n return GHOST_LEVEL_PROBABILITY[level];\n}\n\nfunction clampLevel(level: number): GhostDensityLevel {\n return Math.max(0, Math.min(3, level)) as GhostDensityLevel;\n}\n\nfunction adjustForBpm(level: GhostDensityLevel, bpm: number): GhostDensityLevel {\n if (bpm >= 160 && level !== 0) {\n return clampLevel(level - 1);\n }\n if (bpm <= 90 && level !== 3) {\n return clampLevel(level + 1);\n }\n return level;\n}\n\n/**\n * Ghost-note trigger probability for a section, mood, and tempo.\n *\n * How much of it survives is the ornament dial's business, applied by the\n * caller: this is the groove's own appetite for ghosts before any dial.\n */\nexport function getGhostDensity(mood: MoodCategory, section: SectionType, bpm: number): number {\n const row = GHOST_DENSITY_TABLE[section] ?? GHOST_DENSITY_TABLE.a;\n const level = adjustForBpm(row[mood] ?? 0, bpm);\n return levelToProbability(level);\n}\n\n/** Ghost-note velocity multiplier for a section and metric position. */\nexport function getGhostVelocity(section: SectionType, beatPosition: number): number {\n let base = 0.4;\n const even = beatPosition % 2 === 0 ? 0.05 : 0;\n switch (section) {\n case 'a':\n base = 0.35 + even;\n break;\n case 'chorus':\n base = 0.5 + even;\n break;\n case 'bridge':\n base = 0.25 + even;\n break;\n case 'b':\n base = 0.4 + even;\n break;\n case 'intro':\n case 'outro':\n base = 0.38;\n break;\n }\n return Math.max(0.25, Math.min(0.65, base));\n}\n\n/**\n * Probability of a ghost at a specific 16th position, higher when the ghost\n * leads straight into the backbeat.\n *\n * Ghosts are only placed on beats 1 and 3 (indices 0 and 2), so those are the\n * positions this answers for; the \"a\" of each is the one that anticipates the\n * snare.\n */\nexport function getGhostProbabilityAtPosition(beat: number, sixteenthInBeat: number): number {\n const leadsIntoBackbeat = (beat === 0 || beat === 2) && sixteenthInBeat === 3;\n return leadsIntoBackbeat ? 0.6 : 0.25;\n}\n\n/** Choose which ghost positions a groove favours. */\nexport function selectGhostPositions(mood: MoodCategory): GhostPosition[] {\n if (mood === MoodCategory.Energetic) {\n return ['e', 'a'];\n }\n return ['e'];\n}\n","import type { Draw } from '../context/index.js';\nimport { sustainsStrokes } from '../context/index.js';\nimport type { DrumRole, DrumStyle, SectionType } from './internal.js';\nimport { GM } from './internal.js';\n\n/** Hi-hat subdivision level. */\nexport type HiHatLevel = 'quarter' | 'eighth' | 'sixteenth';\n\n/** Hi-hat articulation type. */\nexport type HiHatType = 'closed' | 'pedal' | 'open' | 'halfOpen' | 'ride';\n\n/** BPM at or above which 16th-note hi-hats become 8ths for playability. */\nexport const HH_16TH_BPM_THRESHOLD = 150;\n\nconst FHH_VEL_MIN = 45;\nconst FHH_VEL_MAX = 60;\n/** Velocity boost applied to dynamic open hi-hat accents. */\nexport const OHH_VEL_BOOST = 7;\n\n/** True unless the role suppresses timekeeping cymbals entirely. */\nexport function shouldPlayHiHat(role: DrumRole): boolean {\n return role !== 'fxOnly';\n}\n\n/** Preferred timekeeping instrument for a role. */\nexport function roleHiHatInstrument(role: DrumRole, useRide: boolean): number {\n if (role === 'ambient') {\n return GM.RIDE;\n }\n if (role === 'minimal') {\n return GM.FHH;\n }\n return useRide ? GM.RIDE : GM.CHH;\n}\n\nfunction sparser(level: HiHatLevel): HiHatLevel {\n if (level === 'sixteenth') {\n return 'eighth';\n }\n if (level === 'eighth') {\n return 'quarter';\n }\n return 'quarter';\n}\n\nfunction denser(level: HiHatLevel): HiHatLevel {\n if (level === 'quarter') {\n return 'eighth';\n }\n if (level === 'eighth') {\n return 'sixteenth';\n }\n return 'sixteenth';\n}\n\n/** Beats between consecutive strokes at a hi-hat subdivision. */\nexport function hiHatStep(level: HiHatLevel): number {\n if (level === 'sixteenth') {\n return 0.25;\n }\n return level === 'eighth' ? 0.5 : 1;\n}\n\n/**\n * Choose the hi-hat subdivision for a section, style, rhythmic dial, and tempo.\n *\n * The style and section propose a level; the dial then moves it, and it moves\n * in one direction only. Both of its tests read a draw fixed by position — one\n * for thinning out, one for filling in — so as the dial rises the thinning\n * switches off before the filling switches on, and the level never falls.\n *\n * A difficulty ceiling can thin the level further: with a tempo to measure\n * against, a subdivision the player cannot keep up is a candidate to reject,\n * not a level to write and hope.\n *\n * The two dial draws are addressed by bar, so the dial moves the hats a bar at\n * a time rather than switching a whole section between two subdivisions.\n */\nexport function getHiHatLevel(\n section: SectionType,\n style: DrumStyle,\n rhythmic: number,\n bpm: number,\n draw: Draw,\n difficulty: number | undefined,\n bar: number,\n): HiHatLevel {\n const allow16th = bpm < HH_16TH_BPM_THRESHOLD;\n let base: HiHatLevel = 'eighth';\n let dialed = true;\n\n if (style === 'sparse') {\n base = section === 'chorus' ? 'eighth' : 'quarter';\n } else if (style === 'fourOnFloor') {\n base = allow16th && section === 'chorus' && draw.prob(0.25, 'hhLevel') ? 'sixteenth' : 'eighth';\n dialed = false;\n } else if (style === 'synth') {\n base = !allow16th || (section === 'a' && draw.prob(0.2, 'hhLevel')) ? 'eighth' : 'sixteenth';\n dialed = false;\n } else if (style === 'trap') {\n base = allow16th ? 'sixteenth' : 'eighth';\n dialed = false;\n } else if (style === 'latin') {\n base = allow16th && section === 'chorus' && draw.prob(0.3, 'hhLevel') ? 'sixteenth' : 'eighth';\n dialed = false;\n } else {\n switch (section) {\n case 'intro':\n base = 'quarter';\n break;\n case 'outro':\n base = 'eighth';\n break;\n case 'a':\n base = draw.prob(0.3, 'hhLevel') ? 'quarter' : 'eighth';\n break;\n case 'b':\n base = allow16th && draw.prob(0.25, 'hhLevel') ? 'sixteenth' : 'eighth';\n break;\n case 'chorus':\n base = allow16th && draw.prob(0.35, 'hhLevel') ? 'sixteenth' : 'eighth';\n break;\n case 'bridge':\n base = 'eighth';\n break;\n }\n }\n\n let level = base;\n if (dialed) {\n // The middle of the dial is the style's own level, so the two halves are\n // read separately: the lower half thins the level out, the upper half\n // fills it in, and each transition is spread across its half instead of\n // landing on one bucket boundary.\n if (draw.at('hhThin', bar) >= 2 * rhythmic) {\n level = sparser(level);\n }\n if (draw.at('hhFill', bar) < 2 * rhythmic - 1) {\n level = denser(level);\n }\n }\n\n if (!allow16th && level === 'sixteenth') {\n level = 'eighth';\n }\n while (level !== 'quarter' && !sustainsStrokes(hiHatStep(level), bpm, difficulty)) {\n level = sparser(level);\n }\n return level;\n}\n\n/**\n * Metric velocity multiplier for a 16th position within a beat.\n *\n * @param sixteenth Position within the beat, 0 to 3.\n * @param jitter Humanizing factor around 1, drawn by the caller for that\n * position so the same 16th of the same bar always sounds the same.\n */\nexport function hiHatVelocityMultiplier(sixteenth: number, jitter: number): number {\n switch (sixteenth) {\n case 0:\n return 0.95 * jitter;\n case 2:\n return 0.75 * jitter;\n case 1:\n return 0.55 * jitter;\n default:\n return 0.5 * jitter;\n }\n}\n\n/** Bars between dynamic open hi-hat accents (0 disables them). */\nexport function openHiHatBarInterval(section: SectionType, style: DrumStyle): number {\n if (style === 'sparse') {\n return section === 'chorus' ? 4 : 0;\n }\n switch (section) {\n case 'intro':\n return style === 'fourOnFloor' ? 4 : 0;\n case 'a':\n return style === 'fourOnFloor' || style === 'upbeat' ? 2 : 4;\n case 'b':\n return 2;\n case 'chorus':\n return style === 'rock' || style === 'fourOnFloor' ? 1 : 2;\n case 'bridge':\n return 0;\n case 'outro':\n return 4;\n }\n}\n\n/** Beat that receives the dynamic open hi-hat within a bar. */\nexport function openHiHatBeat(section: SectionType, draw: Draw, bar: number): number {\n if (section === 'chorus') {\n const choice = draw.range(0, 3, 'ohhBeat', bar);\n if (choice < 2) {\n return 3;\n }\n if (choice < 3) {\n return 1;\n }\n return 2;\n }\n return 3;\n}\n\n/** Whether the section uses an independent foot hi-hat pulse. */\nexport function shouldUseFootHiHat(section: SectionType, role: DrumRole): boolean {\n if (role === 'fxOnly') {\n return false;\n }\n switch (section) {\n case 'intro':\n case 'bridge':\n case 'outro':\n return true;\n default:\n return role === 'ambient' || role === 'minimal';\n }\n}\n\n/**\n * The hi-hat articulation a section leans on.\n *\n * This shapes dynamics only: the note a timekeeping hat actually sounds comes\n * from {@link roleHiHatInstrument}, which follows the voicing role, and from\n * the per-onset open-hat decisions. Feeding this into {@link hiHatNote} instead\n * would let the section override the role's own kit choice.\n */\nexport function sectionHiHatType(section: SectionType, role: DrumRole): HiHatType {\n if (role === 'ambient') {\n return 'ride';\n }\n if (role === 'minimal') {\n return 'pedal';\n }\n switch (section) {\n case 'intro':\n case 'a':\n return 'pedal';\n case 'b':\n return 'closed';\n case 'chorus':\n return 'open';\n case 'bridge':\n return 'ride';\n case 'outro':\n return 'halfOpen';\n }\n}\n\n/** GM note for a hi-hat articulation. */\nexport function hiHatNote(type: HiHatType): number {\n switch (type) {\n case 'pedal':\n return GM.FHH;\n case 'open':\n return GM.OHH;\n case 'ride':\n return GM.RIDE;\n default:\n return GM.CHH;\n }\n}\n\n/**\n * Velocity multiplier for a hi-hat articulation — the only effect\n * {@link sectionHiHatType} has on the output.\n */\nexport function hiHatTypeVelocityMultiplier(type: HiHatType): number {\n switch (type) {\n case 'halfOpen':\n return 0.75;\n case 'pedal':\n return 0.65;\n case 'open':\n return 1.0;\n case 'ride':\n return 0.9;\n default:\n return 0.85;\n }\n}\n\n/** Whether to accent with an open hi-hat at a given beat. */\nexport function shouldAddOpenHHAccent(\n section: SectionType,\n beat: number,\n bar: number,\n draw: Draw,\n): boolean {\n if (section !== 'chorus' && section !== 'b') {\n return false;\n }\n if (section === 'chorus') {\n if (beat === 1 || beat === 3) {\n return draw.prob(0.6, 'ohhAccent', bar, beat);\n }\n return false;\n }\n if (beat === 3 && bar % 2 === 1) {\n return draw.prob(0.4, 'ohhAccent', bar, beat);\n }\n return false;\n}\n\n/** Foot hi-hat velocity with slight humanization, drawn for its own position. */\nexport function footHiHatVelocity(draw: Draw, bar: number, beat: number): number {\n return draw.range(FHH_VEL_MIN, FHH_VEL_MAX, 'fhhVelocity', bar, beat);\n}\n\n/** Whether the section uses a ride cymbal instead of hi-hats. */\nexport function shouldUseRideForSection(section: SectionType, style: DrumStyle): boolean {\n if (style === 'rock' && section === 'chorus') {\n return true;\n }\n if (style === 'sparse') {\n return false;\n }\n return section === 'bridge';\n}\n\n/** Whether a bridge beat is carried by a cross-stick instead of the hi-hat. */\nexport function shouldUseBridgeCrossStick(section: SectionType, beat: number): boolean {\n return section === 'bridge' && (beat === 1 || beat === 3);\n}\n","import type { Feel } from './internal.js';\n\nconst SIXTEENTH = 0.25;\nconst EIGHTH = 0.5;\n/** Delay of an off-beat 16th at full swing (triplet minus straight), in beats. */\nconst DELTA_16 = 1 / 3 - 1 / 4; // 0.08333...\n/** Delay of an off-beat 8th at full swing, in beats. */\nconst DELTA_8 = (2 / 3 - 1 / 2) / 2 + (2 / 3 - 1 / 2) / 2; // 0.16667...\n\n/** Grid resolution for swing quantization. */\nexport type SwingResolution = 'eighth' | 'sixteenth';\n\n/**\n * Effective swing after applying the groove feel to a base swing amount.\n *\n * A feel names how far toward the triplet the off-beats sit, and the amount is\n * already that quantity — `'shuffle'` is the full triplet, `'swing'` a lighter\n * lean. Straight is straight whatever amount comes with it.\n */\nexport function effectiveSwing(feel: Feel, swingAmount: number): number {\n if (feel === 'straight') {\n return 0;\n }\n return Math.max(0, Math.min(1, swingAmount));\n}\n\n/**\n * The grid a resolution swings, as `[position, delay at full swing]` pairs\n * within one beat.\n *\n * The \"a\" 16th is the midpoint of the compressed second half of the beat\n * (between the swung \"and\" and the next downbeat), mirroring the \"e\" 16th in\n * the first half. Because DELTA_8 = 2 * DELTA_16, that midpoint reduces to a\n * single DELTA_16 delay off the straight position, so the \"a\" never overshoots\n * the downbeat and needs no clamp.\n */\nconst SWING_NODES: Readonly<Record<SwingResolution, readonly (readonly [number, number])[]>> =\n Object.freeze({\n eighth: [\n [0, 0],\n [EIGHTH, DELTA_8],\n [1, 0],\n ],\n sixteenth: [\n [0, 0],\n [SIXTEENTH, DELTA_16],\n [EIGHTH, DELTA_8],\n [3 * SIXTEENTH, DELTA_16],\n [1, 0],\n ],\n });\n\n/**\n * Push an off-beat position toward its triplet placement.\n *\n * On-beat positions are returned unchanged and off-beat 8ths and 16ths are\n * delayed proportionally to the swing amount. A position between two grid slots\n * — a Euclidean kick on a five-step bar, a pattern from a caller's own grid — is\n * delayed by an interpolation of its neighbours' delays rather than snapped to\n * one of them: swing is a warp of the beat, so the order of what was written\n * survives it and nothing ever arrives earlier than it was written.\n *\n * @param startBeat Absolute position in beats.\n * @param swing Swing amount in [0, 1] (already feel-adjusted).\n * @param resolution Whether to swing the 16th or only the 8th grid.\n * @returns The swung position in beats, never earlier than `startBeat`.\n */\nexport function quantizeSwing(\n startBeat: number,\n swing: number,\n resolution: SwingResolution,\n): number {\n const clamped = Math.max(0, Math.min(1, swing));\n if (clamped <= 0) {\n return startBeat;\n }\n const base = Math.floor(startBeat);\n const offset = startBeat - base;\n const nodes = SWING_NODES[resolution];\n\n for (let i = 1; i < nodes.length; i += 1) {\n const [from, fromDelay] = nodes[i - 1] ?? [0, 0];\n const [to, toDelay] = nodes[i] ?? [1, 0];\n if (offset < from || offset > to) {\n continue;\n }\n const span = to - from;\n const share = span <= 0 ? 0 : (offset - from) / span;\n const delay = fromDelay + (toDelay - fromDelay) * share;\n return startBeat + delay * clamped;\n }\n return startBeat;\n}\n","import type { Draw } from '../context/index.js';\nimport { sustainsStrokes } from '../context/index.js';\nimport {\n getGhostDensity,\n getGhostProbabilityAtPosition,\n getGhostVelocity,\n selectGhostPositions,\n} from './ghost.js';\nimport {\n footHiHatVelocity,\n type HiHatLevel,\n hiHatNote,\n hiHatTypeVelocityMultiplier,\n hiHatVelocityMultiplier,\n OHH_VEL_BOOST,\n roleHiHatInstrument,\n sectionHiHatType,\n shouldAddOpenHHAccent,\n shouldPlayHiHat,\n shouldUseBridgeCrossStick,\n} from './hihat.js';\nimport type { HitList } from './hit.js';\nimport type { DrumRole, DrumStyle, Feel, SectionType } from './internal.js';\nimport {\n BACKBEAT_LIFT,\n EIGHTH,\n GM,\n leanedBy,\n type MoodCategory,\n QUARTER,\n SIXTEENTH,\n} from './internal.js';\nimport type { KickPattern } from './kick.js';\nimport { effectiveSwing, quantizeSwing } from './swing.js';\n\n/** Per-section drum state shared across the bars of a section. */\nexport type SectionCtx = {\n style: DrumStyle;\n feel: Feel;\n /**\n * Whether the feel is the caller's own request rather than the one the style\n * implies. A named feel is taken at its word by every style.\n */\n feelRequested: boolean;\n densityMult: number;\n /** Rhythmic dial in [0, 1]: subdivision and syncopation. */\n rhythmic: number;\n /** Ornament dial in [0, 1]: how many ghosts and decorations survive. */\n ornament: number;\n /** Difficulty ceiling, or undefined when the caller set none. */\n difficulty: number | undefined;\n useGhostNotes: boolean;\n ghostBoost: boolean;\n useRide: boolean;\n useFootHh: boolean;\n role: DrumRole;\n ghostMood: MoodCategory;\n snareBeat3: boolean;\n};\n\n/** Per-beat context passed to every beat processor. */\nexport type BeatCtx = {\n track: HitList;\n beatTick: number;\n beat: number;\n velocity: number;\n section: SectionType;\n bpm: number;\n bar: number;\n sectionBars: number;\n inPrechorusLift: boolean;\n swingAmount: number;\n barHasOpenHh: boolean;\n openHhBeat: number;\n /** Hi-hat subdivision for this bar. */\n hhLevel: HiHatLevel;\n draw: Draw;\n};\n\n/**\n * How much of the feel's swing a style takes when the caller named no feel.\n *\n * These are the characters of the styles themselves: trap sits on a straight\n * (or triplet-hat) grid and latin only leans. They apply to the feel a style\n * implies, never to one the caller asked for — a request that comes back\n * unrecognisable is worse than one that comes back refused.\n */\nfunction styleSwingFactor(style: DrumStyle): number {\n if (style === 'trap') {\n return 0;\n }\n if (style === 'latin') {\n return 0.35;\n }\n return 1;\n}\n\n/**\n * The one swing amount every voice of a section shares.\n *\n * Kick, hi-hat, and ghost notes must land on the same grid: when the kick's\n * \"and\" is fully swung and the hi-hat's is not, the two voices separate by tens\n * of milliseconds at the same notated position, which is audible as flamming\n * rather than as groove.\n */\nfunction sectionSwing(sec: SectionCtx, swingAmount: number): number {\n const swing = effectiveSwing(sec.feel, swingAmount);\n return sec.feelRequested ? swing : swing * styleSwingFactor(sec.style);\n}\n\n/** Place a tick on the section's shared swung 16th grid. */\nexport function swing16(tick: number, sec: SectionCtx, swingAmount: number): number {\n return quantizeSwing(tick, sectionSwing(sec, swingAmount), 'sixteenth');\n}\n\n/**\n * Emit the kick for one beat.\n *\n * The grid is sixteenths. The downbeat of the beat sounds as written; every\n * other step is an off-beat kick, so it goes through the section's shared swung\n * grid and sits a little below the downbeat, as a player would place it. A kick\n * on an odd sixteenth is the one the ceiling can refuse: at that tempo the foot\n * does not get there, and the stroke is dropped rather than moved.\n */\nexport function generateKickForBeat(ctx: BeatCtx, sec: SectionCtx, kick: KickPattern): void {\n if (ctx.inPrechorusLift) {\n return;\n }\n const sixteenths = kick.length / 4;\n for (let step = 0; step < sixteenths; step += 1) {\n const index = ctx.beat * sixteenths + step;\n if (!kick[index]) {\n continue;\n }\n if (step === 0) {\n ctx.track.add(GM.BD, ctx.beatTick, EIGHTH, ctx.velocity);\n continue;\n }\n const offBeat = step % 2 === 1;\n if (offBeat && !sustainsStrokes(SIXTEENTH, ctx.bpm, sec.difficulty)) {\n continue;\n }\n const tick = swing16(ctx.beatTick + step * SIXTEENTH, sec, ctx.swingAmount);\n ctx.track.add(GM.BD, tick, offBeat ? SIXTEENTH : EIGHTH, ctx.velocity * 0.85);\n }\n}\n\n/** Emit the backbeat snare (or side-stick) for one beat. */\nexport function generateSnareForBeat(ctx: BeatCtx, sec: SectionCtx, isIntroFirst: boolean): void {\n if (ctx.inPrechorusLift) {\n return;\n }\n const snareOn = sec.snareBeat3 ? ctx.beat === 2 : ctx.beat === 1 || ctx.beat === 3;\n if (!snareOn || isIntroFirst) {\n return;\n }\n if (sec.useRide && shouldUseBridgeCrossStick(ctx.section, ctx.beat)) {\n return;\n }\n\n const backbeatVel = Math.min(127, ctx.velocity + BACKBEAT_LIFT);\n const promoteSparseChorus =\n sec.style === 'sparse' && ctx.section === 'chorus' && sec.role === 'full';\n\n if (sec.role === 'fxOnly') {\n return;\n }\n // `minimal` is one step quieter than `ambient` in every style: a side-stick\n // at a reduced velocity, never the full backbeat and never nothing at all.\n // Deciding it per style produced the loudest snare of any role in one branch\n // and no snare whatsoever in another.\n if (sec.role === 'minimal') {\n ctx.track.add(GM.SIDESTICK, ctx.beatTick, EIGHTH, ctx.velocity * 0.65);\n return;\n }\n if (promoteSparseChorus) {\n ctx.track.add(GM.SD, ctx.beatTick, EIGHTH, backbeatVel);\n } else if (sec.style === 'sparse' || sec.role === 'ambient') {\n ctx.track.add(GM.SIDESTICK, ctx.beatTick, EIGHTH, ctx.velocity * 0.8);\n } else {\n ctx.track.add(GM.SD, ctx.beatTick, EIGHTH, backbeatVel);\n }\n}\n\n/** Emit ghost snares at the \"e\"/\"a\" 16ths of beats 1 and 3. */\nexport function generateGhostNotesForBeat(ctx: BeatCtx, sec: SectionCtx): void {\n if (ctx.beat !== 0 && ctx.beat !== 2) {\n return;\n }\n // A ghost is decoration, so the ornament dial decides how many survive; the\n // ceiling decides whether 16ths are reachable at this tempo at all.\n if (!sustainsStrokes(SIXTEENTH, ctx.bpm, sec.difficulty)) {\n return;\n }\n const positions = selectGhostPositions(sec.ghostMood);\n let ghostProb = leanedBy(getGhostDensity(sec.ghostMood, ctx.section, ctx.bpm), sec.ornament);\n if (sec.ghostBoost) {\n ghostProb = Math.min(1, ghostProb * 1.4);\n }\n\n for (const pos of positions) {\n const sixteenthInBeat = pos === 'e' ? 1 : 3;\n const posProb = getGhostProbabilityAtPosition(ctx.beat, sixteenthInBeat);\n if (!ctx.draw.prob(ghostProb * posProb, 'ghost', ctx.bar, ctx.beat, pos)) {\n continue;\n }\n const variation = ctx.draw.float(0.85, 1.15, 'ghostVelocity', ctx.bar, ctx.beat, pos);\n const ghostBase = getGhostVelocity(ctx.section, ctx.beat / 2);\n let ghostVel = ctx.velocity * ghostBase * variation;\n if (pos === 'a') {\n ghostVel *= 0.9;\n }\n const offset = pos === 'e' ? SIXTEENTH : 3 * SIXTEENTH;\n const tick = swing16(ctx.beatTick + offset, sec, ctx.swingAmount);\n ctx.track.add(GM.SD, tick, SIXTEENTH, ghostVel);\n }\n}\n\n/** Emit the pre-chorus lift buildup for one beat. */\nexport function generatePreChorusBuildup(\n ctx: BeatCtx,\n sec: SectionCtx,\n isSectionLastBar: boolean,\n): void {\n if (sec.style === 'sparse') {\n if (isSectionLastBar && ctx.beat === 3) {\n ctx.track.add(GM.SD, ctx.beatTick, EIGHTH, Math.max(45, ctx.velocity * 0.75));\n ctx.track.add(GM.CRASH, ctx.beatTick + EIGHTH + SIXTEENTH, SIXTEENTH, ctx.velocity * 0.9);\n }\n return;\n }\n const barsInLift = 2;\n const barInLift = ctx.bar - (ctx.sectionBars - barsInLift);\n const progress = (barInLift * 4 + ctx.beat) / (barsInLift * 4);\n const buildupVel = ctx.velocity * (0.5 + 0.5 * progress);\n // The lift is a stream on one voice, so the ceiling decides how fine it may\n // be written: the offbeat goes first, and where even the quarter is out of\n // reach at this tempo the crash carries the phrase end alone.\n const takesEighths = sustainsStrokes(EIGHTH, ctx.bpm, sec.difficulty);\n if (takesEighths || sustainsStrokes(QUARTER, ctx.bpm, sec.difficulty)) {\n ctx.track.add(GM.SD, ctx.beatTick, EIGHTH, buildupVel);\n }\n if (takesEighths) {\n ctx.track.add(\n GM.SD,\n swing16(ctx.beatTick + EIGHTH, sec, ctx.swingAmount),\n EIGHTH,\n buildupVel * 0.85,\n );\n }\n if (isSectionLastBar && ctx.beat === 3) {\n ctx.track.add(GM.CRASH, ctx.beatTick + EIGHTH + SIXTEENTH, SIXTEENTH, ctx.velocity * 1.1);\n }\n}\n\n/** Emit the timekeeping hi-hat (or ride/foot) for one beat. */\nexport function generateHiHatForBeat(ctx: BeatCtx, sec: SectionCtx): void {\n if (!shouldPlayHiHat(sec.role)) {\n return;\n }\n\n const crossStick = sec.useRide && shouldUseBridgeCrossStick(ctx.section, ctx.beat);\n const hhInstrument = crossStick ? GM.SIDESTICK : roleHiHatInstrument(sec.role, sec.useRide);\n const hhType = sectionHiHatType(ctx.section, sec.role);\n const typeMult = hiHatTypeVelocityMultiplier(hhType);\n const isDynamicOpen = ctx.barHasOpenHh && ctx.beat === ctx.openHhBeat;\n const allowsOpenHiHat = sec.role !== 'ambient' && sec.role !== 'minimal';\n const dm = sec.densityMult;\n\n if (ctx.hhLevel === 'quarter') {\n const introRest = ctx.section === 'intro' && ctx.beat !== 0;\n if (!introRest) {\n if (isDynamicOpen && allowsOpenHiHat) {\n ctx.track.add(\n GM.OHH,\n ctx.beatTick,\n EIGHTH,\n ctx.velocity * dm * 0.75 * typeMult + OHH_VEL_BOOST,\n );\n } else {\n ctx.track.add(\n hhInstrument,\n ctx.beatTick,\n EIGHTH,\n Math.max(20, ctx.velocity * dm * 0.75 * typeMult),\n );\n }\n } else if (sec.useFootHh) {\n ctx.track.add(GM.FHH, ctx.beatTick, EIGHTH, footHiHatVelocity(ctx.draw, ctx.bar, ctx.beat));\n }\n return;\n }\n\n if (ctx.hhLevel === 'eighth') {\n for (let eighth = 0; eighth < 2; eighth += 1) {\n let hhTick = ctx.beatTick + eighth * EIGHTH;\n if (eighth === 1) {\n hhTick = swing16(hhTick, sec, ctx.swingAmount);\n }\n if (ctx.section === 'intro' && eighth === 1) {\n if (sec.useFootHh && ctx.beat % 2 === 0) {\n ctx.track.add(GM.FHH, hhTick, EIGHTH, footHiHatVelocity(ctx.draw, ctx.bar, ctx.beat));\n }\n continue;\n }\n const hhVel = Math.max(20, ctx.velocity * dm * typeMult * (eighth === 0 ? 0.9 : 0.65));\n if (isDynamicOpen && allowsOpenHiHat && eighth === 0) {\n ctx.track.add(GM.OHH, hhTick, EIGHTH, hhVel + OHH_VEL_BOOST);\n continue;\n }\n let useOpen = false;\n if (sec.style === 'fourOnFloor' && eighth === 1) {\n const openProb = Math.max(0.15, Math.min(0.8, 45 / ctx.bpm));\n useOpen =\n (ctx.beat === 1 || ctx.beat === 3) &&\n ctx.draw.prob(openProb, 'openHat', ctx.bar, ctx.beat, eighth);\n } else if (eighth === 0) {\n useOpen = shouldAddOpenHHAccent(ctx.section, ctx.beat, ctx.bar, ctx.draw);\n }\n if (useOpen && allowsOpenHiHat) {\n ctx.track.add(hiHatNote('open'), hhTick, EIGHTH, Math.max(20, hhVel * 1.1));\n } else {\n ctx.track.add(hhInstrument, hhTick, EIGHTH / 2, hhVel);\n }\n }\n return;\n }\n\n for (let sixteenth = 0; sixteenth < 4; sixteenth += 1) {\n let hhTick = ctx.beatTick + sixteenth * SIXTEENTH;\n // Every off-beat 16th goes through the shared grid, the \"and\" included:\n // leaving position 2 straight put the hat and the kick's \"and\" on different\n // ticks in the same bar.\n if (sixteenth !== 0) {\n hhTick = swing16(hhTick, sec, ctx.swingAmount);\n }\n const metricVel = hiHatVelocityMultiplier(\n sixteenth,\n ctx.draw.float(0.95, 1.05, 'hatVelocity', ctx.bar, ctx.beat, sixteenth),\n );\n const hhVel = Math.max(20, ctx.velocity * dm * typeMult * metricVel);\n if (isDynamicOpen && allowsOpenHiHat && sixteenth === 0) {\n ctx.track.add(GM.OHH, hhTick, SIXTEENTH, hhVel + OHH_VEL_BOOST);\n continue;\n }\n if (allowsOpenHiHat && ctx.beat === 3 && sixteenth === 3) {\n const openProb = Math.max(0.1, Math.min(0.4, 30 / ctx.bpm));\n if (ctx.draw.prob(openProb, 'openHat', ctx.bar, ctx.beat, sixteenth)) {\n ctx.track.add(GM.OHH, hhTick, SIXTEENTH, Math.max(20, hhVel * 1.2));\n continue;\n }\n }\n ctx.track.add(hhInstrument, hhTick, SIXTEENTH / 2, hhVel);\n }\n}\n","/**\n * Generate a euclidean onset pattern (Bjorklund's algorithm), distributing\n * `pulses` as evenly as possible across `steps`, then rotating.\n *\n * @param pulses Number of onsets, clamped to [0, steps].\n * @param steps Total steps (>= 1).\n * @param rotation Steps to rotate onsets toward later positions.\n * @returns One boolean per step; true marks an onset.\n */\nimport {\n assertGenerationBudget,\n assertInteger,\n assertPositiveInt,\n} from '../../core/validation/index.js';\n\nexport function euclideanRhythm(pulses: number, steps: number, rotation = 0): boolean[] {\n assertPositiveInt(steps, 'euclidean steps');\n assertInteger(pulses, 'euclidean pulses');\n assertInteger(rotation, 'euclidean rotation');\n assertGenerationBudget(steps, 'euclidean pattern steps');\n const p = Math.max(0, Math.min(pulses, steps));\n const base = bjorklund(p, steps);\n const shift = ((rotation % steps) + steps) % steps;\n if (shift === 0) {\n return base;\n }\n return base.map((_, i) => base[(i - shift + steps) % steps] ?? false);\n}\n\nfunction bjorklund(pulses: number, steps: number): boolean[] {\n if (pulses === 0) {\n return Array.from({ length: steps }, () => false);\n }\n if (pulses >= steps) {\n return Array.from({ length: steps }, () => true);\n }\n let head: boolean[][] = Array.from({ length: pulses }, () => [true]);\n let remainder: boolean[][] = Array.from({ length: steps - pulses }, () => [false]);\n while (remainder.length > 1) {\n const count = Math.min(head.length, remainder.length);\n const next: boolean[][] = [];\n for (let i = 0; i < count; i += 1) {\n next.push([...(head[i] ?? []), ...(remainder[i] ?? [])]);\n }\n remainder = head.length > count ? head.slice(count) : remainder.slice(count);\n head = next;\n }\n return [...head.flat(), ...remainder.flat()];\n}\n","/**\n * The fill vocabulary, as data.\n *\n * Every archetype is a list of strokes on the bar's grid and every selection is\n * a table indexed by a draw, so a fill can be added, replaced, or supplied by a\n * caller without touching a generator. The generator's part is to look one up.\n */\n\nimport type { Articulation } from '../../core/instrument/index.js';\nimport { type Draw, sustainsStrokes } from '../context/index.js';\nimport type { HitList } from './hit.js';\nimport type { DrumStyle, SectionEnergy, SectionType } from './internal.js';\nimport { BACKBEAT_LIFT, EIGHTH, FILL_ACCENT_LIFT, GM, SIXTEENTH } from './internal.js';\n\n/** The thirteen fill archetypes for section transitions, in declaration order. */\nexport const FILL_TYPES = Object.freeze([\n 'snareRoll',\n 'tomDescend',\n 'tomAscend',\n 'snareTomCombo',\n 'simpleCrash',\n 'linearFill',\n 'ghostToAccent',\n 'bdSnareAlternate',\n 'hiHatChoke',\n 'tomShuffle',\n 'breakdownFill',\n 'flamsAndDrags',\n 'halfTimeFill',\n] as const);\n\n/** The thirteen fill archetypes for section transitions. */\nexport type FillType = (typeof FILL_TYPES)[number];\n\n/**\n * How loud one stroke of a fill is.\n *\n * A fill's dynamics are written against the fill's own base velocity rather\n * than in absolute terms, so the same archetype sounds right in a verse and a\n * chorus: `fill` is the body of the fill, `accent` the stroke that lands. One\n * base for the whole gesture, not one per beat — a crescendo that restarts its\n * arithmetic at every beat line falls back in the middle of itself.\n */\nexport type FillVelocity = {\n base: 'fill' | 'accent';\n /** Factor on the base, for a crescendo or a ghosted stroke. */\n scale?: number;\n /** Fixed offset in MIDI velocity, for a stroke a little above or below. */\n offset?: number;\n};\n\n/** One stroke of a fill, positioned within the beat it belongs to. */\nexport type FillStroke = {\n /** General MIDI note number of the voice struck. */\n voice: number;\n /** Offset from the start of the beat, in quarter-note beats. */\n offset: number;\n /** Sounding length, in quarter-note beats. */\n duration: number;\n velocity: FillVelocity;\n /** How the stroke is played, when it is more than a plain hit. */\n articulation?: Articulation;\n};\n\n/**\n * A fill archetype: what each beat of the fill bar plays.\n *\n * Beats are indexed from the start of the bar. A beat with no entry is silent,\n * which is how the sparse archetypes leave the earlier beats to the groove.\n */\nexport type FillArchetype = {\n /** Strokes per beat of the bar, indexed 0..3. */\n atBeat: readonly (readonly FillStroke[])[];\n /**\n * Whether a bar longer than four beats keeps playing the last beat's strokes.\n * The archetypes are written against a four-beat bar; most read naturally when\n * extended, but the half-time figure is a bar-long gesture and stops there.\n */\n boundToBar?: boolean;\n};\n\nconst S = SIXTEENTH;\nconst E = EIGHTH;\n\n/** Body velocity with an optional factor and offset. */\nfunction fill(scale?: number, offset?: number): FillVelocity {\n return {\n base: 'fill',\n ...(scale === undefined ? {} : { scale }),\n ...(offset === undefined ? {} : { offset }),\n };\n}\n\n/** Accent velocity: the stroke the fill lands on. */\nconst accent: FillVelocity = { base: 'accent' };\n\n/** One stroke. */\nfunction at(\n voice: number,\n offset: number,\n duration: number,\n velocity: FillVelocity,\n articulation?: Articulation,\n): FillStroke {\n return { voice, offset, duration, velocity, ...(articulation ? { articulation } : {}) };\n}\n\n/**\n * The pickup beats every archetype shares: a fill that starts at beat 2 is\n * approached the same way whichever archetype follows.\n */\nconst PICKUP_BEAT_0: FillStroke[] = [at(GM.BD, 0, E, fill()), at(GM.SD, E, E, fill(undefined, -5))];\nconst PICKUP_BEAT_1: FillStroke[] = [\n at(GM.SD, 0, E, fill()),\n at(GM.TOM_H, E, E, fill(undefined, -3)),\n];\n\n/** A crescendo of `count` snare sixteenths starting from `from`. */\nfunction snareCrescendo(count: number, from: number): FillStroke[] {\n return Array.from({ length: count }, (_, i) => at(GM.SD, i * S, S, fill(from + 0.1 * i)));\n}\n\n/** An accelerating ghost-to-accent run of four snare sixteenths. */\nfunction ghostRun(): FillStroke[] {\n return Array.from({ length: 4 }, (_, i) => at(GM.SD, i * S, S, fill(0.4, i * 10)));\n}\n\n/** Build an archetype from its beat-2 and beat-3 strokes. */\nfunction archetype(\n beat2: readonly FillStroke[],\n beat3: readonly FillStroke[],\n boundToBar?: boolean,\n): FillArchetype {\n return {\n atBeat: [PICKUP_BEAT_0, PICKUP_BEAT_1, beat2, beat3],\n ...(boundToBar ? { boundToBar } : {}),\n };\n}\n\n/**\n * The fill dictionary.\n *\n * Provenance: every archetype here is a rudiment or a stock phrase-end gesture\n * — a snare crescendo, a descending tom run, a crash on the last off-beat — of\n * the kind every drummer plays and nobody owns. None reproduces a fill from a\n * particular recording.\n */\nexport const FILL_ARCHETYPES: Readonly<Record<FillType, FillArchetype>> = Object.freeze({\n // One crescendo of seven strokes across the two beats, not two of four and\n // three: the roll is a single gesture and its second half continues where its\n // first half left off.\n snareRoll: archetype(snareCrescendo(4, 0.6), [\n ...snareCrescendo(3, 1.0),\n at(GM.SD, 3 * S, S, accent),\n ]),\n tomDescend: archetype(\n [at(GM.SD, 0, E, fill()), at(GM.TOM_H, E, E, fill(undefined, -5))],\n [\n at(GM.TOM_H, 0, S, fill()),\n at(GM.TOM_M, S, S, fill(undefined, -3)),\n at(GM.TOM_M, E, S, fill(undefined, -5)),\n at(GM.TOM_L, E + S, S, accent),\n ],\n ),\n tomAscend: archetype(\n [at(GM.SD, 0, E, fill()), at(GM.TOM_L, E, E, fill(undefined, -5))],\n [\n at(GM.TOM_L, 0, S, fill()),\n at(GM.TOM_M, S, S, fill(undefined, 3)),\n at(GM.TOM_M, E, S, fill(undefined, 5)),\n at(GM.TOM_H, E + S, S, accent),\n ],\n ),\n snareTomCombo: archetype(\n [at(GM.SD, 0, E, fill()), at(GM.SD, E, S, fill(undefined, -5)), at(GM.TOM_H, E + S, S, fill())],\n [\n at(GM.TOM_M, 0, S, fill()),\n at(GM.SD, S, S, fill(undefined, -3)),\n at(GM.TOM_L, E, S, fill(undefined, 2)),\n at(GM.BD, E + S, S, accent),\n ],\n ),\n simpleCrash: archetype([], [at(GM.BD, E + S, S, accent), at(GM.CRASH, E + S, E, accent)]),\n linearFill: archetype(\n [\n at(GM.BD, 0, S, fill()),\n at(GM.SD, S, S, fill()),\n at(GM.TOM_H, 2 * S, S, fill()),\n at(GM.TOM_M, 3 * S, S, fill()),\n ],\n [\n at(GM.TOM_L, 0, S, fill(undefined, 3)),\n at(GM.SD, S, S, fill(undefined, 5)),\n at(GM.BD, 2 * S, S, fill(undefined, 7)),\n at(GM.SD, 3 * S, S, accent),\n ],\n ),\n ghostToAccent: archetype(ghostRun(), [at(GM.SD, 0, E, fill()), at(GM.SD, E, E, accent)]),\n bdSnareAlternate: archetype(\n [\n at(GM.BD, 0, S, fill()),\n at(GM.SD, S, S, fill()),\n at(GM.BD, 2 * S, S, fill(undefined, 3)),\n at(GM.SD, 3 * S, S, fill(undefined, 3)),\n ],\n [\n at(GM.BD, 0, S, fill(undefined, 5)),\n at(GM.SD, S, S, fill(undefined, 5)),\n at(GM.BD, 2 * S, S, accent),\n at(GM.SD, 3 * S, S, accent),\n ],\n ),\n hiHatChoke: archetype(\n [at(GM.OHH, 0, E, fill()), at(GM.OHH, E, E, fill(undefined, 5))],\n [at(GM.OHH, 0, S, fill(undefined, 8)), at(GM.CHH, S, S, accent), at(GM.SD, E, E, accent)],\n ),\n tomShuffle: archetype(\n [at(GM.TOM_H, 0, E, fill()), at(GM.TOM_M, E + S / 2, S, fill(undefined, -5))],\n [at(GM.TOM_M, 0, E, fill()), at(GM.TOM_L, E + S / 2, S, fill(undefined, 5))],\n ),\n breakdownFill: archetype([], [at(GM.SD, E, S, accent)]),\n flamsAndDrags: archetype(\n // The ornaments are attributes of the principal stroke, not extra onsets:\n // one flammed note rather than a grace note written a 64th early, one\n // dragged note rather than a pair of soft strokes before it.\n [at(GM.SD, 0, E, fill(), 'flam'), at(GM.SD, E + S, E, fill(), 'drag')],\n [at(GM.SD, 0, 1, accent, 'flam')],\n ),\n halfTimeFill: archetype(\n [at(GM.SD, 0, 1, accent), at(GM.BD, 0, 1, fill())],\n // At low energy the fill spans only beat 3, so this beat keeps the phrase\n // end from going silent: a broad half-time backbeat snare with a light\n // pickup into the next section.\n [at(GM.SD, 0, EIGHTH, accent), at(GM.SD, E, S, fill(0.6)), at(GM.SD, E + S, S, accent)],\n true,\n ),\n});\n\n/**\n * Whether a caller's material is a fill.\n *\n * A context carries one dictionary for the whole piece, so each generator has\n * to recognise its own material; a bass lick reaching the fill selector would\n * otherwise be rendered as silence.\n */\nexport function isFillArchetype(material: unknown): material is FillArchetype {\n return (\n typeof material === 'object' &&\n material !== null &&\n Array.isArray((material as FillArchetype).atBeat)\n );\n}\n\n/**\n * The archetype an id names.\n *\n * @param id Archetype name, built-in or supplied.\n * @param supplied The caller's archetypes, keyed by id.\n * @returns The archetype, or undefined when nothing carries that name.\n */\nexport function fillArchetypeFor(\n id: string,\n supplied?: ReadonlyMap<string, FillArchetype>,\n): FillArchetype | undefined {\n return supplied?.get(id) ?? FILL_ARCHETYPES[id as FillType];\n}\n\n/** Beat at which a fill begins, widening with section energy. */\nexport function getFillStartBeat(energy: SectionEnergy): number {\n switch (energy) {\n case 'low':\n return 3;\n case 'medium':\n return 2;\n case 'high':\n case 'peak':\n return 2;\n }\n}\n\n/**\n * The archetype offered at each draw, per transition context.\n *\n * A table is read by index, so an archetype named twice is simply twice as\n * likely; that is the whole weighting mechanism, and it is visible in the data\n * rather than buried in the branches of a switch.\n */\ntype FillTable = readonly FillType[];\n\n/** Sparse styles keep the phrase end small whatever the transition. */\nconst SPARSE_FILLS: FillTable = ['simpleCrash', 'breakdownFill'];\n\n/** Dropping into a low-energy section: keep the phrase end gentle. */\nconst LOW_ENERGY_FILLS: FillTable = ['simpleCrash', 'breakdownFill', 'halfTimeFill'];\n\n/** Big lead-ins to a chorus, from a high-energy style. */\nconst TO_CHORUS_HIGH_FILLS: FillTable = [\n 'tomDescend',\n 'tomDescend',\n 'snareRoll',\n 'linearFill',\n 'bdSnareAlternate',\n 'flamsAndDrags',\n 'tomShuffle',\n 'ghostToAccent',\n];\n\n/** Lead-ins to a chorus from the remaining styles. */\nconst TO_CHORUS_FILLS: FillTable = [\n 'snareTomCombo',\n 'snareTomCombo',\n 'tomDescend',\n 'ghostToAccent',\n 'hiHatChoke',\n 'linearFill',\n 'snareRoll',\n 'snareRoll',\n];\n\n/** Leaving the intro: lighter, building character. */\nconst FROM_INTRO_FILLS: FillTable = [\n 'snareRoll',\n 'snareRoll',\n 'simpleCrash',\n 'ghostToAccent',\n 'breakdownFill',\n 'halfTimeFill',\n];\n\n/** Generic medium/high/peak fills for a high-energy style. */\nconst GENERIC_HIGH_FILLS: FillTable = [\n 'tomDescend',\n 'snareRoll',\n 'tomAscend',\n 'snareTomCombo',\n 'linearFill',\n 'bdSnareAlternate',\n 'flamsAndDrags',\n 'tomShuffle',\n];\n\n/** Generic medium/high/peak fills for the remaining styles. */\nconst GENERIC_FILLS: FillTable = [\n 'snareRoll',\n 'snareRoll',\n 'snareTomCombo',\n 'ghostToAccent',\n 'hiHatChoke',\n 'halfTimeFill',\n 'breakdownFill',\n 'breakdownFill',\n];\n\n/**\n * The table a transition reads from.\n *\n * The transition-specific tables (into a chorus, out of an intro) are checked\n * before the generic energy tables so every archetype stays reachable; with\n * `from === to` the caller effectively asks for a within-section fill.\n */\nfunction fillTableFor(\n from: SectionType,\n to: SectionType,\n style: DrumStyle,\n nextEnergy: SectionEnergy,\n): FillTable {\n if (style === 'sparse') {\n return SPARSE_FILLS;\n }\n const highEnergy = style === 'rock' || style === 'fourOnFloor';\n if (nextEnergy === 'low') {\n return LOW_ENERGY_FILLS;\n }\n if (to === 'chorus') {\n return highEnergy ? TO_CHORUS_HIGH_FILLS : TO_CHORUS_FILLS;\n }\n if (from === 'intro') {\n return FROM_INTRO_FILLS;\n }\n return highEnergy ? GENERIC_HIGH_FILLS : GENERIC_FILLS;\n}\n\n/**\n * Pick a fill archetype for a section transition.\n *\n * @param from Section the fill leaves.\n * @param to Section the fill leads into.\n * @param style Internal drum style.\n * @param nextEnergy Energy of the section the fill leads into.\n * @param draw Position-addressed draws for the fill.\n * @param bar Bar the fill occupies; the choice is addressed by it, so a fill\n * elsewhere in the piece is unaffected by this one.\n * @param extra Ids a caller's dictionary contributes for this transition. They\n * are offered after the built-in table, so the built-in choices keep the draws\n * they had and a caller adds to the vocabulary rather than displacing it.\n * @param accepts Which archetypes may be drawn at all. A fill beyond the\n * difficulty ceiling is taken out of the table and the draw runs over what\n * remains, rather than being simplified into a fill nobody wrote.\n * @returns The id drawn, or undefined when the table has nothing this\n * transition accepts.\n */\nexport function selectFillType(\n from: SectionType,\n to: SectionType,\n style: DrumStyle,\n nextEnergy: SectionEnergy,\n draw: Draw,\n bar: number,\n extra: readonly string[] = [],\n accepts: (id: string) => boolean = () => true,\n): string | undefined {\n const table = fillTableFor(from, to, style, nextEnergy);\n // An id a built-in already carries is a replacement, not an addition: the\n // material is resolved through the caller's map wherever the table names it,\n // so appending it as well would give that one fill two ways of being drawn\n // and a weight no other entry has.\n const added = extra.filter((id) => FILL_ARCHETYPES[id as FillType] === undefined);\n const offered = added.length === 0 ? table : [...table, ...added];\n const candidates = offered.filter(accepts);\n if (candidates.length === 0) {\n return undefined;\n }\n return candidates[draw.range(0, candidates.length - 1, 'fill', bar)] ?? candidates[0];\n}\n\n/**\n * The strokes a fill plays on one beat of the fill bar.\n *\n * @param shape The archetype.\n * @param beat Beat index within the bar.\n * @returns The strokes, empty where the archetype leaves the beat to the groove.\n */\nfunction strokesAtBeat(shape: FillArchetype, beat: number): readonly FillStroke[] {\n const lastBeat = shape.atBeat.length - 1;\n const index = beat > lastBeat ? (shape.boundToBar ? -1 : lastBeat) : beat;\n return shape.atBeat[index] ?? [];\n}\n\n/**\n * Whether a fill is inside the difficulty ceiling at this tempo.\n *\n * The fill is measured as it will actually be played — from the beat it starts\n * on to the end of the bar — and voice by voice, since what stops a player is\n * one limb repeating faster than it can rather than the bar's total count. The\n * phrase end is the most exposed bar there is, so a ceiling that governs the\n * groove and not the fill governs nothing a listener would notice.\n *\n * @param shape The archetype.\n * @param fromBeat Beat the fill starts on.\n * @param barBeats Beats in the bar.\n * @param bpm Tempo, or undefined when the caller gave none.\n * @param difficulty The ceiling, or undefined for no ceiling.\n * @returns True when the fill is playable at the ceiling, and whenever either\n * the ceiling or the tempo is unknown.\n */\nexport function fillWithinCeiling(\n shape: FillArchetype,\n fromBeat: number,\n barBeats: number,\n bpm: number | undefined,\n difficulty: number | undefined,\n): boolean {\n if (difficulty === undefined || bpm === undefined) {\n return true;\n }\n const byVoice = new Map<number, number[]>();\n for (let beat = fromBeat; beat < barBeats; beat += 1) {\n for (const stroke of strokesAtBeat(shape, beat)) {\n const positions = byVoice.get(stroke.voice);\n if (positions) {\n positions.push(beat + stroke.offset);\n } else {\n byVoice.set(stroke.voice, [beat + stroke.offset]);\n }\n }\n }\n for (const positions of byVoice.values()) {\n positions.sort((a, b) => a - b);\n for (let i = 1; i < positions.length; i += 1) {\n const gap = (positions[i] ?? 0) - (positions[i - 1] ?? 0);\n if (gap > 0 && !sustainsStrokes(gap, bpm, difficulty)) {\n return false;\n }\n }\n }\n return true;\n}\n\n/**\n * Emit a fill for a single beat of the fill bar.\n *\n * @param track Hit accumulator.\n * @param beatTick Absolute beat position of this beat.\n * @param beat Beat index within the bar (0-3).\n * @param fillType Selected archetype, by name or as material of its own.\n * @param velocity Base velocity for the beat.\n */\nexport function generateFill(\n track: HitList,\n beatTick: number,\n beat: number,\n fillType: FillType | FillArchetype,\n velocity: number,\n): void {\n const shape =\n typeof fillType === 'string' ? FILL_ARCHETYPES[fillType] : (fillType as FillArchetype);\n if (!shape) {\n return;\n }\n const strokes = strokesAtBeat(shape, beat);\n const fillVel = velocity * 0.9;\n // The stroke a fill lands on is the loudest thing in the bar. Reading it as a\n // fraction of the beat velocity put it below the strokes leading into it and\n // some twenty units under the backbeat next to it, which is a phrase end\n // heard as a hole.\n const accentVel = velocity + BACKBEAT_LIFT + FILL_ACCENT_LIFT;\n for (const stroke of strokes) {\n const base = stroke.velocity.base === 'accent' ? accentVel : fillVel;\n const scaled = stroke.velocity.scale === undefined ? base : base * stroke.velocity.scale;\n track.add(\n stroke.voice,\n beatTick + stroke.offset,\n stroke.duration,\n scaled + (stroke.velocity.offset ?? 0),\n stroke.articulation,\n );\n }\n}\n","import type { Articulation } from '../../core/instrument/index.js';\nimport { clampVel, GM } from './internal.js';\n\n/**\n * The percussion voices the generator emits, as General MIDI note numbers.\n *\n * A {@link DrumHit} carries only its pitch, so splitting a kit across tracks —\n * kick to one, snare to another — means comparing against these numbers. They\n * are published so that comparison is a named constant covered by semver\n * rather than a hard-coded 36.\n *\n * @example\n * ```ts\n * import { DRUM_NOTES, generateDrums } from '@libraz/libcantus';\n * const hits = generateDrums({\n * bars: 1,\n * style: 'standard',\n * section: 'verse',\n * ctx: { bpm: 120, complexity: { rhythmic: 0.5 } },\n * });\n * const kick = hits.filter((hit) => hit.pitch === DRUM_NOTES.kick);\n * ```\n * @category Composition\n */\nexport const DRUM_NOTES = Object.freeze({\n kick: GM.BD,\n snare: GM.SD,\n sideStick: GM.SIDESTICK,\n handClap: GM.HANDCLAP,\n closedHiHat: GM.CHH,\n pedalHiHat: GM.FHH,\n openHiHat: GM.OHH,\n crash: GM.CRASH,\n ride: GM.RIDE,\n tambourine: GM.TAMBOURINE,\n highTom: GM.TOM_H,\n midTom: GM.TOM_M,\n lowTom: GM.TOM_L,\n shaker: GM.SHAKER,\n} as const);\n\n/**\n * A percussion voice name, one key of {@link DRUM_NOTES}.\n *\n * @category Composition\n */\nexport type DrumVoice = keyof typeof DRUM_NOTES;\n\n/** Voice name for each emitted note number, for {@link drumVoiceOf}. */\nconst VOICE_BY_NOTE = new Map<number, DrumVoice>(\n (Object.entries(DRUM_NOTES) as [DrumVoice, number][]).map(([voice, note]) => [note, voice]),\n);\n\n/**\n * The voice a percussion note number names.\n *\n * @param pitch The General MIDI note number.\n * @returns The voice, or undefined for a note this generator never emits.\n * @category Composition\n */\nexport function drumVoiceOf(pitch: number): DrumVoice | undefined {\n return VOICE_BY_NOTE.get(pitch);\n}\n\n/**\n * A single percussion onset.\n *\n * @category Composition\n */\nexport type DrumHit = {\n /** General MIDI note number; {@link drumVoiceOf} names it. */\n pitch: number;\n startBeat: number;\n durationBeat: number;\n velocity: number;\n /**\n * How the stroke is played, when it is more than a plain hit. A flam is one\n * onset carrying `'flam'`, not a pair of notes a fraction of a beat apart, so\n * the grace stroke is the reader's to place — as a second note, a key switch,\n * or a sample layer — and any pattern can carry one.\n */\n articulation?: Articulation;\n};\n\n/** Rounding used to key onsets by beat: a 128th note, finer than any grid used. */\nconst ONSET_KEY_SCALE = 128;\n\n/**\n * Accumulator for drum onsets with velocity clamping and simple lookups.\n *\n * Onsets are indexed by (pitch, quantized beat) as they are added. The lookups\n * run inside the per-bar loop, so scanning the accumulated hits made the whole\n * generator quadratic in bar count — a few thousand bars took seconds.\n */\nexport class HitList {\n readonly hits: DrumHit[] = [];\n /** Onset keys already occupied, as `pitch:quantizedBeat`. */\n readonly #onsets = new Set<string>();\n\n /** Append a hit, clamping velocity and dropping non-positive positions. */\n add(\n pitch: number,\n startBeat: number,\n durationBeat: number,\n velocity: number,\n articulation?: Articulation,\n ): void {\n const beat = Math.max(0, startBeat);\n const hit: DrumHit = {\n pitch,\n startBeat: beat,\n durationBeat,\n velocity: clampVel(velocity),\n };\n // Left off entirely rather than set to undefined, so a plain stroke stays\n // deep-equal to the same stroke written before articulations existed.\n if (articulation !== undefined) {\n hit.articulation = articulation;\n }\n this.hits.push(hit);\n this.#onsets.add(onsetKey(pitch, beat));\n }\n\n /** Whether a hit of this pitch already starts exactly at `startBeat`. */\n hasOnset(pitch: number, startBeat: number): boolean {\n return this.#onsets.has(onsetKey(pitch, startBeat));\n }\n\n /** True when a crash already sits within a 16th of `startBeat`. */\n hasCrashNear(startBeat: number): boolean {\n // The window is a 16th, and every grid in use is at least a 128th, so it is\n // enough to probe the quantized positions inside it.\n const steps = Math.round(0.25 * ONSET_KEY_SCALE);\n for (let step = 0; step < steps; step += 1) {\n if (this.#onsets.has(onsetKey(GM.CRASH, startBeat + step / ONSET_KEY_SCALE))) {\n return true;\n }\n }\n return false;\n }\n}\n\n/**\n * Key an onset by pitch and quantized beat.\n *\n * Shared so a generator can name onsets of its own — the strokes a caller asked\n * for exactly, which the difficulty ceiling leaves alone — in the same terms the\n * accumulator indexes them by.\n */\nexport function onsetKey(pitch: number, startBeat: number): string {\n return `${pitch}:${Math.round(startBeat * ONSET_KEY_SCALE)}`;\n}\n","/**\n * The kick vocabulary, as data on a sixteenth grid.\n *\n * The grid is sixteenths rather than eighths because most of what belongs in a\n * kick dictionary cannot be written on eighths at all: the funk and modern-pop\n * basics live on the sixteenths between the beats. The eighth-note patterns map\n * onto the even steps of the same grid, so they read exactly as before.\n */\n\nimport type { Draw } from '../context/index.js';\nimport { BAR_STEPS, BEAT_STEPS } from '../vocabulary/transform.js';\nimport {\n type DrumStyle,\n leanedBy,\n mapSection,\n type Section,\n type SectionType,\n} from './internal.js';\n\n/** Sixteenth steps in one bar of 4/4; the grid a kick pattern is written on. */\nexport const KICK_STEPS = BAR_STEPS;\n\n/**\n * Kick presence flags across the sixteen 16th-note slots of a bar.\n *\n * Index `n` is the `n`-th sixteenth from the downbeat, so beat 3 is index 8 and\n * the \"and\" of 2 is index 6.\n */\nexport type KickPattern = readonly boolean[];\n\n/** The sixteenth index of a beat's downbeat. */\nfunction beatStep(beat: number): number {\n return beat * BEAT_STEPS;\n}\n\n/**\n * One onset of a kick figure.\n *\n * A slot with no probability is part of the figure itself and always sounds.\n * One with a probability is where the rhythmic dial reaches the kick: the draw\n * is fixed by position and leaned on by the dial, so raising the dial fills\n * slots in without emptying any that were already there.\n */\nexport type KickSlot = {\n /** Sixteenth index within the bar, 0..15. */\n step: number;\n /** Chance the slot is taken before the dial leans on it. */\n probability?: number;\n /** Draw address for the slot; slots that may sound must name one. */\n slot?: string;\n /** Restrict the slot to bars of one parity, for a two-bar figure. */\n barParity?: 0 | 1;\n /** Sections the slot is added in; absent means every section. */\n sections?: Section[];\n};\n\n/** A style's kick figure: the slots it always plays, plus the ones it may. */\nexport type KickFigure = readonly KickSlot[];\n\nconst BEAT_1 = beatStep(0);\nconst BEAT_2 = beatStep(1);\nconst BEAT_3 = beatStep(2);\nconst BEAT_4 = beatStep(3);\nconst AND_2 = beatStep(1) + 2;\nconst AND_4 = beatStep(3) + 2;\n\n/**\n * The kick a section plays whatever the style: two half notes, so the phrase\n * ends without pushing.\n */\nconst OUTRO_FIGURE: KickFigure = [{ step: BEAT_1 }, { step: BEAT_3 }];\n\n/** The figure a style with no entry of its own plays. */\nconst DEFAULT_FIGURE: KickFigure = [\n { step: BEAT_1 },\n { step: BEAT_3 },\n { step: AND_2, probability: 0.5, slot: 'beat2and', sections: ['prechorus'] },\n { step: AND_2, probability: 0.55, slot: 'beat2and', sections: ['chorus'] },\n { step: AND_4, probability: 0.35, slot: 'beat4and', sections: ['chorus'] },\n];\n\n/**\n * The kick dictionary, keyed by internal style.\n *\n * Provenance: each figure is the plain pulse of its style — the four-on-the-\n * floor dance kick, the backbeat-answering \"and\" of two, the clave-leaning\n * latin figure — none of them traceable to a particular record.\n */\nexport const KICK_FIGURES: Readonly<Record<DrumStyle, KickFigure>> = Object.freeze({\n sparse: [{ step: BEAT_1 }, { step: BEAT_3, sections: ['chorus'], barParity: 1 }],\n fourOnFloor: [\n { step: BEAT_1 },\n { step: BEAT_2 },\n { step: BEAT_3 },\n { step: BEAT_4 },\n { step: AND_2, probability: 0.2, slot: 'beat2and', sections: ['chorus'] },\n ],\n upbeat: [\n { step: BEAT_1 },\n { step: BEAT_3 },\n { step: AND_2, probability: 0.7, slot: 'beat2and', sections: ['prechorus', 'chorus'] },\n { step: AND_4, probability: 0.6, slot: 'beat4and', sections: ['chorus'] },\n ],\n rock: [\n { step: BEAT_1 },\n { step: BEAT_3 },\n { step: AND_2, probability: 0.65, slot: 'beat2and', sections: ['chorus'] },\n { step: AND_4, probability: 0.4, slot: 'beat4and', sections: ['chorus'] },\n { step: AND_2, probability: 0.3, slot: 'beat2and', sections: ['prechorus'] },\n ],\n synth: [\n { step: BEAT_1 },\n { step: BEAT_3 },\n { step: AND_2, probability: 0.75, slot: 'beat2and', sections: ['prechorus', 'chorus'] },\n { step: AND_4, probability: 0.65, slot: 'beat4and', sections: ['chorus'] },\n ],\n trap: [\n { step: BEAT_1 },\n { step: AND_2, probability: 0.8, slot: 'beat2and' },\n { step: BEAT_3, probability: 0.3, slot: 'beat3' },\n { step: AND_4, probability: 0.7, slot: 'beat4and' },\n ],\n latin: [\n { step: BEAT_1 },\n { step: AND_2 },\n { step: BEAT_3 },\n { step: AND_4, probability: 0.5, slot: 'beat4and' },\n ],\n standard: DEFAULT_FIGURE,\n});\n\n/** An empty bar of the grid. */\nfunction emptyPattern(): boolean[] {\n return Array.from({ length: KICK_STEPS }, () => false);\n}\n\n/**\n * Whether a pre-chorus bar sits in the two-bar lift into the chorus.\n *\n * @param section Current section.\n * @param bar Bar index within the section.\n * @param sectionBars Total bars in the section.\n * @param nextIsChorus Whether the following section is a chorus.\n */\nexport function isInPreChorusLift(\n section: SectionType,\n bar: number,\n sectionBars: number,\n nextIsChorus: boolean,\n): boolean {\n if (section !== 'b' || !nextIsChorus || sectionBars < 3) {\n return false;\n }\n return bar >= sectionBars - 2;\n}\n\n/**\n * Realise a kick figure for one bar.\n *\n * @param figure The figure to play.\n * @param section Current section, matched against each slot's own sections.\n * @param bar Bar index, which addresses the draws and decides slot parity.\n * @param draw Position-addressed draws for the kick.\n * @param rhythmic Rhythmic dial in [0, 1].\n * @returns The bar's onsets on the sixteenth grid.\n */\nexport function realiseKickFigure(\n figure: KickFigure,\n section: SectionType,\n bar: number,\n draw: Draw,\n rhythmic: number,\n): KickPattern {\n const pattern = emptyPattern();\n for (const slot of figure) {\n if (slot.step < 0 || slot.step >= KICK_STEPS) {\n continue;\n }\n if (slot.sections && !slot.sections.some((name) => mapSection(name) === section)) {\n continue;\n }\n if (slot.barParity !== undefined && bar % 2 !== slot.barParity) {\n continue;\n }\n if (slot.probability === undefined) {\n pattern[slot.step] = true;\n continue;\n }\n if (draw.prob(leanedBy(slot.probability, rhythmic), 'kick', bar, slot.slot ?? slot.step)) {\n pattern[slot.step] = true;\n }\n }\n return pattern;\n}\n\n/**\n * Build a section- and style-aware kick pattern.\n *\n * @param section Current section.\n * @param style Internal drum style.\n * @param bar Bar index (drives variation for a few styles).\n * @param draw Position-addressed draws for the kick.\n * @param rhythmic Rhythmic dial in [0, 1].\n */\nexport function getKickPattern(\n section: SectionType,\n style: DrumStyle,\n bar: number,\n draw: Draw,\n rhythmic: number,\n): KickPattern {\n const figure = section === 'outro' ? OUTRO_FIGURE : (KICK_FIGURES[style] ?? DEFAULT_FIGURE);\n return realiseKickFigure(figure, section, bar, draw, rhythmic);\n}\n","import type { Draw } from '../context/index.js';\nimport { sustainsStrokes } from '../context/index.js';\nimport { type SectionCtx, swing16 } from './beat.js';\nimport { HH_16TH_BPM_THRESHOLD } from './hihat.js';\nimport type { HitList } from './hit.js';\nimport type { DrumRole, SectionType } from './internal.js';\nimport { EIGHTH, GM, PercMoodCategory, SIXTEENTH, sectionIndex } from './internal.js';\n\n/** Enabled auxiliary percussion voices for a section. */\nexport type PercussionConfig = {\n tambourine: boolean;\n shaker: boolean;\n handclap: boolean;\n shaker16th: boolean;\n};\n\ntype PercActivation = [tambourine: boolean, shaker: boolean, handclap: boolean];\n\n// Percussion activation per [mood category][section index]. Columns:\n// intro, a, b, chorus, bridge, interlude, outro, chant, mixbreak.\nconst F: PercActivation = [false, false, false];\nconst PERC_TABLE: PercActivation[][] = [\n /* calm */ [F, F, F, F, F, F, F, F, F],\n /* std */ [F, F, [false, true, false], [true, false, true], F, F, F, F, [true, false, true]],\n /* ener */ [\n F,\n [false, true, false],\n [false, true, false],\n [true, true, true],\n F,\n F,\n F,\n F,\n [true, true, true],\n ],\n /* idol */ [\n F,\n [false, true, false],\n [false, true, false],\n [true, true, true],\n F,\n F,\n F,\n F,\n [true, true, true],\n ],\n /* rock */ [F, F, F, [false, false, true], F, F, F, F, [false, false, true]],\n];\n\n/** Resolve which auxiliary percussion voices play for a section. */\nexport function getPercussionConfig(\n mood: PercMoodCategory,\n section: SectionType,\n): PercussionConfig {\n const row = PERC_TABLE[mood] ?? PERC_TABLE[PercMoodCategory.Standard];\n const act = row?.[sectionIndex(section)] ?? F;\n return {\n tambourine: act[0],\n shaker: act[1],\n handclap: act[2],\n shaker16th: act[1],\n };\n}\n\n/**\n * Emit one bar of auxiliary percussion (tambourine/shaker/handclap).\n *\n * The shaker's off-16ths are where the rhythmic dial reaches this voice: each\n * has its own draw, so turning the dial up fills the subdivision in one\n * position at a time rather than switching the whole bar between two patterns.\n */\nexport function generateAuxPercussionForBar(\n track: HitList,\n barStart: number,\n config: PercussionConfig,\n role: DrumRole,\n densityMult: number,\n draw: Draw,\n bpm: number,\n sec: SectionCtx,\n swingAmount: number,\n barBeats = 4,\n): void {\n if (role === 'minimal') {\n return;\n }\n\n if (config.tambourine) {\n for (let beat = 1; beat < barBeats; beat += 2) {\n const raw = 70 * densityMult * draw.float(0.9, 1.1, 'tambourine', barStart, beat);\n track.add(GM.TAMBOURINE, barStart + beat, EIGHTH, Math.max(40, Math.min(90, raw)));\n }\n }\n\n if (config.shaker) {\n const use16th =\n config.shaker16th &&\n bpm < HH_16TH_BPM_THRESHOLD &&\n sustainsStrokes(SIXTEENTH, bpm, sec.difficulty);\n if (use16th) {\n const velCurve = [0.75, 0.45, 0.6, 0.45];\n for (let beat = 0; beat < barBeats; beat += 1) {\n for (let sub = 0; sub < 4; sub += 1) {\n // The on-beat and mid-beat shakes carry the pattern; the two between\n // them are the dial's to add.\n if (sub % 2 === 1 && !draw.prob(sec.rhythmic, 'shaker16', barStart, beat, sub)) {\n continue;\n }\n const raw =\n 80 *\n (velCurve[sub] ?? 0.5) *\n densityMult *\n draw.float(0.9, 1.1, 'shaker', barStart, beat, sub);\n track.add(\n GM.SHAKER,\n swing16(barStart + beat + sub * SIXTEENTH, sec, swingAmount),\n SIXTEENTH,\n Math.max(25, Math.min(85, raw)),\n );\n }\n }\n } else {\n const velCurve = [0.75, 0.55];\n for (let beat = 0; beat < barBeats; beat += 1) {\n for (let sub = 0; sub < 2; sub += 1) {\n const raw =\n 80 *\n (velCurve[sub] ?? 0.6) *\n densityMult *\n draw.float(0.9, 1.1, 'shaker', barStart, beat, sub);\n track.add(\n GM.SHAKER,\n swing16(barStart + beat + sub * EIGHTH, sec, swingAmount),\n EIGHTH,\n Math.max(25, Math.min(85, raw)),\n );\n }\n }\n }\n }\n\n if (config.handclap) {\n for (let beat = 1; beat < barBeats; beat += 2) {\n const raw = 85 * densityMult * draw.float(0.9, 1.1, 'handclap', barStart, beat);\n track.add(GM.HANDCLAP, barStart + beat, EIGHTH, Math.max(50, Math.min(100, raw)));\n }\n }\n}\n","import type { Articulation, Limb, PercussionProfile } from '../../core/instrument/index.js';\nimport { LIMBS } from '../../core/instrument/index.js';\nimport { DRUM_NOTES, type DrumVoice } from './hit.js';\n\n/** Which limbs reach each voice, in the order a player would take them. */\nconst REACH_BY_VOICE: Readonly<Record<DrumVoice, readonly Limb[]>> = {\n kick: ['rightFoot'],\n pedalHiHat: ['leftFoot'],\n // The left hand rests on the snare and the right on the timekeeping voices;\n // either can cross over, which is what the second entry allows.\n snare: ['leftHand', 'rightHand'],\n sideStick: ['leftHand', 'rightHand'],\n handClap: ['leftHand', 'rightHand'],\n tambourine: ['leftHand', 'rightHand'],\n closedHiHat: ['rightHand', 'leftHand'],\n openHiHat: ['rightHand', 'leftHand'],\n ride: ['rightHand', 'leftHand'],\n crash: ['rightHand', 'leftHand'],\n highTom: ['rightHand', 'leftHand'],\n midTom: ['rightHand', 'leftHand'],\n lowTom: ['rightHand', 'leftHand'],\n shaker: ['rightHand', 'leftHand'],\n};\n\n/** The reach table keyed the way a hit is: by General MIDI note number. */\nfunction reachByNote(): Record<number, readonly Limb[]> {\n const reach: Record<number, readonly Limb[]> = {};\n for (const [voice, limbs] of Object.entries(REACH_BY_VOICE) as [DrumVoice, readonly Limb[]][]) {\n reach[DRUM_NOTES[voice]] = limbs;\n }\n return reach;\n}\n\n/** Ornaments a kit produces; the string techniques are not among them. */\nconst KIT_ARTICULATIONS: readonly Articulation[] = Object.freeze([\n 'accent',\n 'ghost',\n 'staccato',\n 'mute',\n 'flam',\n 'drag',\n 'roll',\n 'choke',\n 'open',\n] as const);\n\n/**\n * A drum kit played with four limbs, covering every voice\n * {@link generateDrums} emits.\n *\n * A kit is bounded by limbs rather than by range: two voices wanting the same\n * hand at the same instant is what makes a pattern unplayable, and a voice no\n * limb reaches is not on the kit at all.\n *\n * @example\n * ```ts\n * import { DRUM_KIT, generateDrums, playability } from '@libraz/libcantus';\n * const hits = generateDrums({\n * bars: 1,\n * style: 'funk',\n * section: 'chorus',\n * ctx: { bpm: 120, complexity: { rhythmic: 0.8 } },\n * });\n * const report = playability(hits, DRUM_KIT, 120);\n * ```\n * @category Composition\n */\nexport const DRUM_KIT: PercussionProfile = Object.freeze({\n kind: 'percussion',\n name: 'drum kit',\n limbs: LIMBS,\n reach: Object.freeze(reachByNote()),\n articulations: KIT_ARTICULATIONS,\n polyphony: 4,\n});\n","/**\n * The drum pattern dictionary: the bar-long figures that make a groove belong\n * to a genre rather than merely being correct.\n *\n * Provenance: every pattern here is the plain, unattributable pulse of its\n * genre — the figure taught as \"this is how that music goes\" — written out on\n * the sixteenth grid from that description. None reproduces the drum part of a\n * particular recording, and each entry says which ground it qualifies under.\n */\n\nimport { type Articulation, canSound, type Limb } from '../../core/instrument/index.js';\nimport { beatsPerBar, type TimeSignature } from '../../core/meter/index.js';\nimport {\n assertGenerationBudget,\n assertOneOf,\n assertPositiveInt,\n assertRange,\n assertTimeSignature,\n} from '../../core/validation/index.js';\nimport { type GenerationContextInput, resolveContext } from '../context/index.js';\nimport {\n BAR_STEPS,\n deform,\n GENRES,\n type Genre,\n type GridEvent,\n mergeVocabulary,\n pickVocabulary,\n STEP_BEATS,\n type Vocabulary,\n vocabularyOfKind,\n withinCeiling,\n} from '../vocabulary/index.js';\nimport { DRUM_NOTES, type DrumHit, type DrumVoice, HitList } from './hit.js';\nimport {\n DRUM_FEELS,\n feelSwingAmount,\n type GrooveFeel,\n PUBLIC_SECTIONS,\n type Section,\n} from './internal.js';\nimport { effectiveSwing, quantizeSwing } from './swing.js';\n\n/**\n * One stroke of a pattern, on the bar's sixteenth grid.\n *\n * Velocity is a factor of the passage's base velocity rather than a MIDI\n * number, so the same figure sits right in a verse and in a chorus.\n */\nexport type DrumStroke = GridEvent & {\n voice: DrumVoice;\n /** Sounding length in quarter-note beats; one sixteenth when absent. */\n duration?: number;\n articulation?: Articulation;\n /** Which limb plays the stroke, where the figure depends on it. */\n limb?: Limb;\n};\n\n/**\n * A bar-long drum figure.\n *\n * @category Composition\n */\nexport type DrumPattern = {\n /** Sixteenth steps in the figure; a 4/4 bar is sixteen. */\n steps: number;\n strokes: DrumStroke[];\n};\n\n/**\n * A drum figure with the conditions it fits.\n *\n * @category Composition\n */\nexport type DrumVocabulary = Vocabulary<DrumPattern>;\n\n/**\n * Whether a caller's material is a drum pattern.\n *\n * A context carries one dictionary for the whole piece, so each generator\n * recognises its own material: an entry meant for the bass is invisible here\n * rather than mis-read.\n *\n * @category Composition\n */\nexport function isDrumPattern(material: unknown): material is DrumPattern {\n const candidate = material as DrumPattern | null;\n return (\n typeof candidate === 'object' &&\n candidate !== null &&\n typeof candidate.steps === 'number' &&\n Array.isArray(candidate.strokes)\n );\n}\n\n/** One stroke. */\nfunction hit(\n voice: DrumVoice,\n step: number,\n velocity: number,\n extra?: { duration?: number; articulation?: Articulation; limb?: Limb },\n): DrumStroke {\n return { voice, step, velocity, ...extra };\n}\n\n/** A ghosted snare: decoration, so the ornament dial decides whether it stays. */\nfunction ghost(step: number): DrumStroke {\n return hit('snare', step, 0.32, { articulation: 'ghost' });\n}\n\n/** A run of strokes on one voice at a fixed spacing. */\nfunction pulse(voice: DrumVoice, every: number, velocity: number, from = 0): DrumStroke[] {\n const out: DrumStroke[] = [];\n for (let step = from; step < BAR_STEPS; step += every) {\n out.push(hit(voice, step, velocity));\n }\n return out;\n}\n\n/** A four-beat pattern on the sixteenth grid. */\nfunction bar(strokes: DrumStroke[]): DrumPattern {\n return { steps: BAR_STEPS, strokes };\n}\n\nconst FOUR_FOUR: TimeSignature = { numerator: 4, denominator: 4 };\n\n/**\n * The built-in drum patterns, in declaration order.\n *\n * @category Composition\n */\nexport const DRUM_PATTERNS: readonly DrumVocabulary[] = Object.freeze([\n {\n id: 'motownBackbeat',\n genre: 'motown',\n difficulty: 2,\n articulations: [],\n ts: FOUR_FOUR,\n tempoRange: [96, 136],\n material: bar([\n hit('kick', 0, 1),\n hit('kick', 6, 0.85),\n hit('kick', 8, 0.9),\n hit('kick', 14, 0.8),\n hit('snare', 4, 1),\n hit('snare', 12, 1),\n ...pulse('tambourine', 2, 0.5),\n ]),\n provenance: {\n basis: 'idiom',\n note: 'the label sound of a four-square backbeat under a tambourine on every eighth',\n },\n },\n {\n id: 'funkSixteenth',\n genre: 'funk',\n difficulty: 4,\n articulations: ['ghost'],\n ts: FOUR_FOUR,\n tempoRange: [84, 120],\n material: bar([\n hit('kick', 0, 1),\n hit('kick', 3, 0.8),\n hit('kick', 6, 0.85),\n hit('kick', 10, 0.8),\n hit('snare', 4, 1),\n hit('snare', 12, 1),\n ghost(2),\n ghost(7),\n ghost(9),\n ghost(15),\n ...pulse('closedHiHat', 1, 0.55),\n ]),\n provenance: {\n basis: 'idiom',\n note: 'the genre-defining sixteenth-note hi-hat with ghosted snares around the backbeat',\n },\n },\n {\n id: 'halfTimeShuffle',\n genre: 'blues',\n difficulty: 5,\n articulations: ['ghost'],\n ts: FOUR_FOUR,\n tempoRange: [72, 104],\n material: bar([\n hit('kick', 0, 1),\n hit('kick', 10, 0.85),\n hit('snare', 8, 1),\n ghost(3),\n ghost(7),\n ghost(11),\n ghost(15),\n ...pulse('closedHiHat', 2, 0.5),\n ]),\n provenance: {\n basis: 'idiom',\n note: 'the shuffle with its backbeat displaced to beat three; the triplet feel is the groove feel applied at render, not written into the grid',\n },\n },\n {\n id: 'bossaNova',\n genre: 'bossa',\n difficulty: 3,\n articulations: [],\n ts: FOUR_FOUR,\n tempoRange: [110, 160],\n material: bar([\n hit('kick', 0, 0.9),\n hit('kick', 6, 0.8),\n hit('kick', 8, 0.9),\n hit('kick', 14, 0.8),\n hit('sideStick', 0, 0.7),\n hit('sideStick', 3, 0.7),\n hit('sideStick', 6, 0.7),\n hit('sideStick', 10, 0.7),\n hit('sideStick', 12, 0.7),\n ...pulse('closedHiHat', 2, 0.45),\n ]),\n provenance: {\n basis: 'traditional',\n note: 'the Brazilian dance pattern: a rim-click clave over a two-beat bass figure',\n },\n },\n {\n id: 'samba',\n genre: 'samba',\n difficulty: 4,\n articulations: [],\n ts: FOUR_FOUR,\n tempoRange: [92, 140],\n material: bar([\n hit('kick', 0, 0.85),\n hit('kick', 3, 0.7),\n hit('kick', 4, 1),\n hit('kick', 7, 0.7),\n hit('kick', 8, 0.85),\n hit('kick', 11, 0.7),\n hit('kick', 12, 1),\n hit('kick', 15, 0.7),\n hit('sideStick', 4, 0.75),\n hit('sideStick', 12, 0.75),\n ...pulse('shaker', 1, 0.4),\n ]),\n provenance: {\n basis: 'traditional',\n note: 'the surdo accent on the second beat of each pair, under a continuous shaker',\n },\n },\n {\n id: 'gospelPocket',\n genre: 'gospel',\n difficulty: 4,\n articulations: ['ghost'],\n ts: FOUR_FOUR,\n tempoRange: [64, 104],\n material: bar([\n hit('kick', 0, 1),\n hit('kick', 6, 0.8),\n hit('kick', 10, 0.85),\n hit('kick', 14, 0.75),\n hit('snare', 4, 1),\n hit('snare', 12, 1),\n ghost(2),\n ghost(6),\n ghost(11),\n ghost(14),\n ...pulse('closedHiHat', 1, 0.5),\n ]),\n provenance: {\n basis: 'idiom',\n note: 'the deep pocket of the church backbeat, ghosted between the accents',\n },\n },\n {\n id: 'drumAndBassBreak',\n genre: 'dnb',\n difficulty: 4,\n articulations: ['ghost'],\n ts: FOUR_FOUR,\n tempoRange: [160, 180],\n material: bar([\n hit('kick', 0, 1),\n hit('kick', 10, 0.9),\n hit('snare', 4, 1),\n hit('snare', 11, 0.85),\n hit('snare', 12, 1),\n ghost(7),\n ghost(15),\n ...pulse('closedHiHat', 2, 0.45),\n ]),\n provenance: {\n basis: 'construction',\n note: 'the two-bar break skeleton of the genre, written out from its rhythmic grid',\n },\n },\n]);\n\n/**\n * Options controlling {@link placeDrumPattern}.\n *\n * @category Composition\n */\nexport type DrumPatternOptions = {\n /** Number of bars to generate. */\n bars: number;\n /**\n * The genre whose figures may be used. Genre is what selects the material;\n * the complexity dials then deform it and the difficulty ceiling rejects it.\n */\n genre: Genre;\n /** Section the pattern plays in, matched against each entry's own sections. */\n section?: Section;\n /** Time signature; defaults to 4/4. */\n ts?: TimeSignature;\n /**\n * The generation context. `bpm` is the tempo, `complexity.rhythmic` thins the\n * figure below its middle setting and syncopates it above,\n * `complexity.ornament` decides how many ghosts survive,\n * `complexity.difficulty` rejects a figure the player could not keep up at\n * this tempo, `vocabulary` brings figures of the caller's own, and `seed`\n * fixes which figure is chosen for each bar.\n *\n * @defaultValue `{ seed: 0, bpm: 120 }`\n */\n ctx?: GenerationContextInput;\n /**\n * Play the figure at half or double its written rate. This is a deformation\n * like the dials, not a different figure: the dictionary entry is the same.\n *\n * It is named for the note values it changes because `feel` names the swing\n * the figure is played with, here and in {@link generateDrums} alike.\n *\n * @defaultValue `'straight'`\n */\n rate?: 'straight' | 'half' | 'double';\n /**\n * The swing the figure is played with. Some entries are written on the\n * straight grid and are only themselves once a triplet feel is applied at\n * render — the half-time shuffle is the plain case — so this is the surface\n * those entries name when they say the feel comes from the performance.\n *\n * @defaultValue `'straight'`\n */\n feel?: GrooveFeel;\n /** Base velocity the figure's own factors are read against. */\n velocity?: number;\n /**\n * Maximum number of onsets {@link placeDrumPattern} may write. One figure is\n * placed per bar, so this is the guard against an unbounded caller rather\n * than a limit on any search.\n *\n * @defaultValue 1000000\n */\n budget?: number;\n};\n\n/** Base velocity a pattern's factors are read against when none is given. */\nconst DEFAULT_VELOCITY = 100;\n\n/** Tempo assumed when neither the context nor the options name one. */\nconst DEFAULT_BPM = 120;\n\n/** Generous upper bound on onsets one bar of a pattern emits. */\nconst MAX_STROKES_PER_BAR = 64;\n\n/**\n * Generate a genre-characteristic groove from the pattern dictionary.\n *\n * The three dials never fight, because each owns a different step: the genre\n * chooses which figures are candidates, the complexity dials deform the chosen\n * figure, and the difficulty ceiling rejects a figure whose closest pair of\n * strokes is faster than a player at that ceiling sustains. A rejected figure\n * is dropped from the candidates rather than simplified, since a simplified\n * figure is a different figure.\n *\n * @param opts Generation options.\n * @returns Percussion onsets in onset order, ties broken by pitch.\n * @throws If the bar count, tempo, or time signature is invalid, or the genre\n * is not one this library names.\n *\n * @example\n * ```ts\n * import { placeDrumPattern } from '@libraz/libcantus';\n * const hits = placeDrumPattern({ bars: 2, genre: 'bossa', ctx: { seed: 7, bpm: 130 } });\n * ```\n *\n * @category Composition\n */\nexport function placeDrumPattern(opts: DrumPatternOptions): DrumHit[] {\n assertPositiveInt(opts.bars, 'drum pattern bars');\n assertGenerationBudget(opts.bars * MAX_STROKES_PER_BAR, 'drum pattern hits', opts.budget);\n const genre = assertOneOf(opts.genre, GENRES, 'drum pattern genre');\n const section =\n opts.section === undefined\n ? undefined\n : assertOneOf(opts.section, PUBLIC_SECTIONS, 'drum pattern section');\n const ts = opts.ts ?? FOUR_FOUR;\n assertTimeSignature(ts, 'drum pattern time signature');\n if (opts.velocity !== undefined) {\n assertRange(opts.velocity, 1, 127, 'drum pattern velocity');\n }\n const feel = opts.feel === undefined ? 'straight' : assertOneOf(opts.feel, DRUM_FEELS, 'feel');\n const resolved = resolveContext(opts.ctx);\n const bpm = resolved.bpm ?? DEFAULT_BPM;\n const draw = resolved.part('drums');\n const baseVelocity = opts.velocity ?? DEFAULT_VELOCITY;\n const barBeats = beatsPerBar(ts);\n const kit = resolved.instrument('drums');\n const swing = effectiveSwing(feel, feelSwingAmount(feel));\n\n const dictionary = mergeVocabulary(\n DRUM_PATTERNS,\n vocabularyOfKind(resolved.vocabulary, isDrumPattern),\n );\n const track = new HitList();\n\n for (let bar = 0; bar < opts.bars; bar += 1) {\n const entry = pickVocabulary(\n dictionary,\n {\n genre,\n section,\n bpm,\n ts,\n difficulty: resolved.difficulty,\n // Naming a kit is the request that the part be playable on it, so a\n // figure calling for a technique the kit has no way to produce is not\n // offered at all rather than written and silently misread.\n ...(kit === undefined ? {} : { articulations: kit.articulations }),\n },\n draw,\n 'pattern',\n bar,\n );\n if (!entry) {\n continue;\n }\n const strokes = deform(\n entry.material.strokes,\n {\n ...(resolved.rhythmic === undefined ? {} : { rhythmic: resolved.rhythmic }),\n ...(resolved.ornament === undefined ? {} : { ornament: resolved.ornament }),\n isOrnament: (stroke) => (stroke as DrumStroke).articulation === 'ghost',\n rate: opts.rate ?? 'straight',\n spanSteps: entry.material.steps,\n },\n draw,\n 'pattern',\n bar,\n );\n // The ceiling judges what the dials actually produced, not what the\n // dictionary wrote: syncopating a figure can bring two strokes together.\n if (!withinCeiling(strokes, bpm, resolved.difficulty)) {\n continue;\n }\n const barStart = bar * barBeats;\n for (const stroke of strokes) {\n const pitch = DRUM_NOTES[stroke.voice];\n if (pitch === undefined) {\n continue;\n }\n track.add(\n pitch,\n quantizeSwing(barStart + stroke.step * STEP_BEATS, swing, 'sixteenth'),\n stroke.duration ?? STEP_BEATS,\n baseVelocity * stroke.velocity,\n stroke.articulation,\n );\n }\n }\n\n // Naming a kit is the request that the part be playable on it, so a voice the\n // kit does not have is an absent stroke rather than a hard one.\n const endBeat = opts.bars * barBeats;\n return track.hits\n .filter((h) => h.startBeat < endBeat && (kit === undefined || canSound(kit, h.pitch)))\n .sort((a, b) => a.startBeat - b.startBeat || a.pitch - b.pitch);\n}\n","import { InvalidInputError } from '../../core/errors/index.js';\nimport { canSound, type InstrumentProfile } from '../../core/instrument/index.js';\nimport { beatsPerBar, type TimeSignature } from '../../core/meter/index.js';\nimport {\n assertGenerationBudget,\n assertInteger,\n assertOneOf,\n assertPositiveInt,\n assertTimeSignature,\n} from '../../core/validation/index.js';\nimport { type GenerationContextInput, resolveContext, sustainsStrokes } from '../context/index.js';\nimport { selectVocabulary, vocabularyOfKind } from '../vocabulary/index.js';\nimport {\n type BeatCtx,\n generateGhostNotesForBeat,\n generateHiHatForBeat,\n generateKickForBeat,\n generatePreChorusBuildup,\n generateSnareForBeat,\n type SectionCtx,\n swing16,\n} from './beat.js';\nimport { euclideanRhythm } from './euclid.js';\nimport {\n FILL_ARCHETYPES,\n type FillArchetype,\n fillArchetypeFor,\n fillWithinCeiling,\n generateFill,\n getFillStartBeat,\n isFillArchetype,\n selectFillType,\n} from './fills.js';\nimport {\n footHiHatVelocity,\n getHiHatLevel,\n openHiHatBarInterval,\n openHiHatBeat,\n shouldPlayHiHat,\n shouldUseFootHiHat,\n shouldUseRideForSection,\n} from './hihat.js';\nimport { type DrumHit, HitList, onsetKey } from './hit.js';\nimport {\n backingScale,\n calculateVelocity,\n DEFAULT_RHYTHMIC,\n DRUM_FEELS,\n DRUM_ROLES,\n type DrumRole,\n type Feel,\n feelSwingAmount,\n GM,\n GROOVE_STYLES,\n type GrooveFeel,\n type GrooveStyle,\n ghostMoodCategory,\n mapSection,\n mapStyle,\n PUBLIC_SECTIONS,\n percMoodCategory,\n type Section,\n sectionDensityMultiplier,\n sectionEnergy,\n} from './internal.js';\nimport { getKickPattern, isInPreChorusLift, type KickPattern } from './kick.js';\nimport { generateAuxPercussionForBar, getPercussionConfig } from './percussion.js';\n\nexport type { FillArchetype, FillStroke, FillType, FillVelocity } from './fills.js';\nexport { FILL_ARCHETYPES, FILL_TYPES, isFillArchetype } from './fills.js';\n/**\n * A single drum onset emitted by {@link generateDrums}.\n *\n * @category Composition\n */\nexport type { DrumHit, DrumVoice } from './hit.js';\nexport { DRUM_NOTES, drumVoiceOf } from './hit.js';\n/**\n * Groove feel (the swing/straight rhythmic character) for {@link generateDrums}\n * and {@link placeDrumPattern}.\n *\n * @category Composition\n */\n/**\n * Drum voicing role and groove style identifiers for {@link generateDrums}.\n *\n * @category Composition\n */\nexport type { DrumRole, DrumStyle, GrooveFeel, GrooveStyle, Section } from './internal.js';\nexport type { KickFigure, KickPattern, KickSlot } from './kick.js';\nexport { KICK_FIGURES, KICK_STEPS } from './kick.js';\nexport { DRUM_KIT } from './kit.js';\nexport type {\n DrumPattern,\n DrumPatternOptions,\n DrumStroke,\n DrumVocabulary,\n} from './vocabulary.js';\nexport { DRUM_PATTERNS, isDrumPattern, placeDrumPattern } from './vocabulary.js';\n\n/**\n * A Euclidean (Bjorklund) kick pattern for {@link generateDrums}.\n *\n * @category Composition\n */\nexport type EuclideanKick = {\n /** Number of kick onsets, clamped to `[0, steps]`. */\n pulses: number;\n /**\n * Total steps in the bar (1..16).\n *\n * @defaultValue 16\n */\n steps?: number;\n /**\n * Steps to rotate the onsets toward later positions.\n *\n * @defaultValue 0\n */\n rotation?: number;\n};\n\n/**\n * Options controlling {@link generateDrums}.\n *\n * @category Composition\n */\nexport type DrumsOptions = {\n /**\n * Number of bars to generate.\n */\n bars: number;\n style: GrooveStyle;\n section: Section;\n /**\n * Time signature.\n *\n * Only 4/4 is accepted: every shape this generator writes — the backbeat, the\n * hi-hat subdivisions, the open-hat and crash beats, the beat a fill starts on\n * — is written against a four-beat bar, so another meter would come back with\n * 4/4 accents in a bar of the wrong length and no sign that anything was\n * wrong. A meter it cannot place is refused rather than mis-placed. Use\n * {@link generateRhythm} or {@link placeDrumPattern} for other meters.\n *\n * @defaultValue `{ numerator: 4, denominator: 4 }`\n */\n ts?: TimeSignature;\n /**\n * Replace the final bar with a fill.\n *\n * A pre-chorus leading into a chorus builds instead: the two-bar lift takes\n * precedence over the fill, since the buildup already marks the phrase end.\n * Set `nextSection` to anything but `'chorus'` to get the fill.\n *\n * @defaultValue false\n */\n fills?: boolean;\n /**\n * The swing the groove is played with. Naming one is taken at its word by\n * every style, including the ones whose own character is straight: a feel the\n * caller asked for and did not get is worse than one it has to ask for twice.\n * Left out, the style's own feel applies.\n */\n feel?: GrooveFeel;\n /**\n * Voicing role, from busiest to sparsest:\n *\n * - `'full'`: every voice, backbeat snare at full weight.\n * - `'ambient'`: side-stick backbeat, ride instead of hi-hat.\n * - `'minimal'`: side-stick backbeat quieter still, pedal hi-hat, no\n * auxiliary percussion.\n * - `'fxOnly'`: no kick, snare, ghost notes, timekeeping hi-hat, or fills —\n * only the fx and auxiliary voices.\n *\n * Roles express orchestration priority and peak dynamics, not a strict hit\n * count: style-specific ride, foot-hi-hat, and auxiliary patterns may make a\n * sparser role emit more individual onsets in a particular bar.\n *\n * @defaultValue `'full'`\n */\n role?: DrumRole;\n /**\n * The generation context: the tempo, the complexity dials, and the kit this\n * part is written for.\n *\n * `bpm` is the tempo in quarter-note beats per minute. `complexity.rhythmic`\n * is how busy the backing is, in [0, 1]; the dial is continuous, so every\n * value moves the result and raising it only adds onsets — the ones already\n * sounding stay where they were. `complexity.ornament` sets how many ghost\n * notes survive, `complexity.difficulty` caps how fast the generator writes,\n * `instruments.drums` — a percussion profile — restricts the output to voices\n * that kit actually has, and `seed` fixes every deterministic choice.\n *\n * @defaultValue `{ seed: 0, bpm: 120, complexity: { rhythmic: 0.5 } }`\n */\n ctx?: GenerationContextInput;\n /**\n * Section the final-bar fill leads into. Shapes which fill archetype is\n * chosen (into-chorus and out-of-intro fills differ from generic ones).\n * Defaults to `section`, i.e. a within-section fill.\n */\n nextSection?: Section;\n /**\n * When set, the kick follows this Euclidean rhythm instead of the\n * style/section pattern, giving direct access to evenly spread onsets.\n */\n euclideanKick?: EuclideanKick;\n /**\n * Maximum number of onsets {@link generateDrums} may write. Generation is\n * linear in bar count, so this is the guard against an unbounded caller\n * rather than a limit on any search.\n *\n * @defaultValue 1000000\n */\n budget?: number;\n};\n\n/** Generous upper bound on onsets emitted for a single bar, used for budgeting. */\nconst MAX_HITS_PER_BAR = 128;\n\n/** Tempo assumed when neither the context nor the options name one. */\nconst DEFAULT_BPM = 120;\n\n/**\n * Generate a drum performance as a flat list of onsets.\n *\n * Every voice (kick, snare, ghost snares, closed/open/foot hi-hats, ride, toms,\n * crash, and auxiliary percussion) is emitted as a {@link DrumHit} distinguished\n * by its General MIDI pitch. Groove style selects an internal style and feel;\n * `complexity.rhythmic` sets the backing-density level; `section` shapes kick,\n * hi-hat, ghost, and percussion density. 16th-note hi-hats drop to 8ths at or\n * above 150 BPM. When `fills` is true the final bar is replaced with a fill whose\n * archetype is shaped by `nextSection`; a fill that would emit nothing on its\n * beat falls back to the normal groove so the phrase end is never silent.\n * `euclideanKick` overrides the kick with an evenly-spread Euclidean pattern.\n * Output is fully determined by the options plus the context's seed.\n *\n * The patterns are 4/4 only — backbeats, hi-hat subdivisions, and fills are all\n * written against a four-beat bar — so beat positions are quarter notes and bar\n * `n` starts at beat `4n`.\n *\n * @param opts Generation options.\n * @returns Percussion onsets in onset order, ties broken by pitch.\n *\n * @example\n * ```ts\n * import { generateDrums } from '@libraz/libcantus';\n * const hits = generateDrums({\n * bars: 4,\n * style: 'standard',\n * section: 'chorus',\n * fills: true,\n * ctx: { seed: 0, bpm: 120, complexity: { rhythmic: 0.6 } },\n * });\n * // Fully determined by the options plus the context's seed (defaults to 0).\n * ```\n *\n * @category Composition\n */\nexport function generateDrums(opts: DrumsOptions): DrumHit[] {\n assertPositiveInt(opts.bars, 'drum bars');\n // Generation is linear in bar count — every lookup inside the bar loop is\n // indexed — so the estimate is the hit count itself.\n assertGenerationBudget(opts.bars * MAX_HITS_PER_BAR, 'drum hits', opts.budget);\n if (opts.euclideanKick) {\n const steps = opts.euclideanKick.steps ?? 16;\n assertInteger(steps, 'euclidean steps', 1, 16);\n assertInteger(opts.euclideanKick.pulses, 'euclidean pulses');\n assertInteger(opts.euclideanKick.rotation ?? 0, 'euclidean rotation');\n }\n // The string options are checked at runtime as well as at compile time: a\n // name from a config file or a JavaScript caller would otherwise be read\n // against a table with no entry for it and yield NaN velocities.\n const publicStyle = assertOneOf(opts.style, GROOVE_STYLES, 'drum style');\n const publicSection = assertOneOf(opts.section, PUBLIC_SECTIONS, 'drum section');\n if (opts.nextSection !== undefined) {\n assertOneOf(opts.nextSection, PUBLIC_SECTIONS, 'drum nextSection');\n }\n const track = new HitList();\n const resolved = resolveContext(opts.ctx);\n const draw = resolved.part('drums');\n const bpm = resolved.bpm ?? DEFAULT_BPM;\n const rhythmic = resolved.rhythmic ?? DEFAULT_RHYTHMIC;\n // Ghosts follow the ornament dial; with none named the rhythmic dial stands\n // in, so a caller who only ever moves `density` still gets one knob.\n const ornamentDial = resolved.ornament ?? rhythmic;\n const difficulty = resolved.difficulty;\n const mapping = mapStyle(publicStyle);\n const style = mapping.style;\n const feel: Feel =\n opts.feel === undefined ? mapping.feel : assertOneOf(opts.feel, DRUM_FEELS, 'drum feel');\n const role: DrumRole =\n opts.role === undefined ? 'full' : assertOneOf(opts.role, DRUM_ROLES, 'drum role');\n const ts = opts.ts ?? { numerator: 4, denominator: 4 };\n assertTimeSignature(ts, 'drum time signature');\n // The groove is written against a four-beat bar throughout. Accepting another\n // meter placed 4/4 accents inside a bar of a different length and reported\n // nothing, which is the one outcome a caller cannot detect.\n if (ts.numerator !== 4 || ts.denominator !== 4) {\n throw new InvalidInputError(\n `drum time signature must be 4/4; received ${ts.numerator}/${ts.denominator}`,\n );\n }\n const barBeats = beatsPerBar(ts);\n // fxOnly leaves only fx/aux voices: the main kick, snare, ghost, and fill\n // voices are suppressed just as timekeeping hi-hats already are.\n const playMainVoices = role !== 'fxOnly';\n const section = mapSection(publicSection);\n const swingAmount = feelSwingAmount(feel);\n const densityMult = sectionDensityMultiplier(section) * backingScale(rhythmic);\n\n const sec: SectionCtx = {\n style,\n feel,\n feelRequested: opts.feel !== undefined,\n densityMult,\n rhythmic,\n ornament: ornamentDial,\n difficulty,\n useGhostNotes:\n (section === 'b' || section === 'chorus' || section === 'bridge') && style !== 'sparse',\n ghostBoost: mapping.ghostBoost,\n useRide: shouldUseRideForSection(section, style),\n useFootHh: shouldUseFootHiHat(section, role),\n role,\n ghostMood: ghostMoodCategory(style),\n snareBeat3: mapping.snareBeat3,\n };\n\n const ohhBarInterval = openHiHatBarInterval(section, style);\n const energy = sectionEnergy(section);\n const fillStartBeat = getFillStartBeat(energy);\n const fillVelocity = calculateVelocity(section, fillStartBeat);\n const percMood = percMoodCategory(style);\n\n // The final-bar fill is shaped by the section it leads into. When no next\n // section is given the fill is treated as within-section (from === to).\n const nextSection = opts.nextSection ? mapSection(opts.nextSection) : section;\n const nextEnergy = sectionEnergy(nextSection);\n\n // Fills a caller brought for this piece. They are filtered by the conditions\n // they state — section, tempo, and the difficulty ceiling — and then offered\n // after the built-in table, so the built-in choices keep the draws they had.\n const callerFillEntries = selectVocabulary(\n vocabularyOfKind(resolved.vocabulary, isFillArchetype),\n { section: publicSection, bpm, difficulty },\n );\n const callerFills = new Map(callerFillEntries.map((entry) => [entry.id, entry.material]));\n const callerFillIds = callerFillEntries.map((entry) => entry.id);\n\n const euclidSteps = opts.euclideanKick?.steps ?? 16;\n const euclidKick = opts.euclideanKick\n ? euclideanRhythm(opts.euclideanKick.pulses, euclidSteps, opts.euclideanKick.rotation ?? 0)\n : undefined;\n\n const reuseSectionKick = (section === 'b' || section === 'chorus') && style !== 'sparse';\n let sectionKick: KickPattern | undefined;\n\n /** Onsets written from a pattern the caller named, which the ceiling spares. */\n const exemptOnsets = new Set<string>();\n\n for (let bar = 0; bar < opts.bars; bar += 1) {\n const barStart = bar * barBeats;\n const isLastBar = bar === opts.bars - 1;\n const hhLevel = getHiHatLevel(section, style, rhythmic, bpm, draw, difficulty, bar);\n\n if (bar === 0 && section === 'chorus') {\n track.add(GM.CRASH, barStart, 0.5, 100 * densityMult);\n }\n\n let barHasOpenHh = false;\n let openHhBeatIndex = 3;\n if (ohhBarInterval > 0 && bar % ohhBarInterval === ohhBarInterval - 1) {\n openHhBeatIndex = openHiHatBeat(section, draw, bar);\n barHasOpenHh = !track.hasCrashNear(barStart + openHhBeatIndex);\n }\n\n let kick: KickPattern | undefined;\n if (!euclidKick && reuseSectionKick) {\n sectionKick ??= getKickPattern(section, style, 0, draw, rhythmic);\n kick = sectionKick;\n } else if (!euclidKick) {\n kick = getKickPattern(section, style, bar, draw, rhythmic);\n }\n\n // The lift is a build into a chorus, so what follows decides it. Passing\n // `section === 'b'` made the predicate test the section against itself,\n // which is always true inside a pre-chorus: the lift then fired whatever\n // `nextSection` said, and suppressed the fill the caller asked for.\n const inLift = isInPreChorusLift(section, bar, opts.bars, nextSection === 'chorus');\n let currentFill: FillArchetype | undefined = FILL_ARCHETYPES.snareRoll;\n\n for (let beat = 0; beat < barBeats; beat += 1) {\n const beatTick = barStart + beat;\n const velocity = calculateVelocity(section, beat);\n\n if (opts.fills && isLastBar && !inLift && beat >= fillStartBeat) {\n if (beat === fillStartBeat) {\n // The ceiling applies to the phrase end as it does to the groove: an\n // archetype whose strokes run faster than a player at this ceiling\n // sustains is taken out of the table, and the draw runs over the rest.\n const chosen = selectFillType(\n section,\n nextSection,\n style,\n nextEnergy,\n draw,\n bar,\n callerFillIds,\n (id) => {\n const candidate = fillArchetypeFor(id, callerFills);\n return (\n candidate !== undefined &&\n fillWithinCeiling(candidate, fillStartBeat, barBeats, bpm, difficulty)\n );\n },\n );\n currentFill = chosen === undefined ? undefined : fillArchetypeFor(chosen, callerFills);\n }\n if (playMainVoices) {\n const before = track.hits.length;\n if (currentFill) {\n // One base velocity for the whole fill, read at the beat it starts\n // on: a crescendo written across two beats is one gesture, and\n // rereading the beat velocity partway through drops it.\n generateFill(track, beatTick, beat, currentFill, fillVelocity);\n }\n if (track.hits.length > before) {\n continue;\n }\n // Safety net: an archetype that contributes nothing on this beat would\n // leave a silent phrase end, so fall through to the normal groove.\n } else {\n // fxOnly deliberately emits no main voices on the fill beats.\n continue;\n }\n }\n\n const ctx: BeatCtx = {\n track,\n beatTick,\n beat,\n velocity,\n section,\n bpm,\n bar,\n sectionBars: opts.bars,\n inPrechorusLift: inLift,\n swingAmount,\n barHasOpenHh,\n openHhBeat: openHhBeatIndex,\n hhLevel,\n draw,\n };\n\n if (playMainVoices) {\n if (inLift) {\n generatePreChorusBuildup(ctx, sec, isLastBar);\n }\n if (euclidKick && !inLift) {\n const stepLength = barBeats / euclidSteps;\n for (let step = 0; step < euclidKick.length; step += 1) {\n const onset = step * stepLength;\n if (euclidKick[step] && Math.floor(onset) === beat) {\n const wholeBeat = Math.abs(onset - Math.round(onset)) < 1e-9;\n const tick = swing16(barStart + onset, sec, swingAmount);\n // Naming the pattern is the request that it be written as given,\n // so these strokes are the ones the ceiling does not touch.\n exemptOnsets.add(onsetKey(GM.BD, tick));\n track.add(GM.BD, tick, Math.min(0.5, stepLength), velocity * (wholeBeat ? 1 : 0.85));\n }\n }\n } else if (kick) {\n generateKickForBeat(ctx, sec, kick);\n }\n generateSnareForBeat(ctx, sec, section === 'intro' && bar === 0);\n if (sec.useGhostNotes && !inLift) {\n generateGhostNotesForBeat(ctx, sec);\n }\n }\n generateHiHatForBeat(ctx, sec);\n }\n\n if (sec.useFootHh && shouldPlayHiHat(role)) {\n for (let fhhBeat = 0; fhhBeat < barBeats; fhhBeat += 2) {\n const fhhTick = barStart + fhhBeat;\n if (!track.hasOnset(GM.FHH, fhhTick)) {\n track.add(GM.FHH, fhhTick, 0.5, footHiHatVelocity(draw, bar, fhhBeat));\n }\n }\n }\n\n let percussionConfig = getPercussionConfig(percMood, section);\n if (\n role === 'fxOnly' &&\n !percussionConfig.tambourine &&\n !percussionConfig.shaker &&\n !percussionConfig.handclap\n ) {\n percussionConfig = {\n tambourine: false,\n shaker: true,\n handclap: false,\n shaker16th: false,\n };\n }\n generateAuxPercussionForBar(\n track,\n barStart,\n percussionConfig,\n role,\n densityMult,\n draw,\n bpm,\n sec,\n swingAmount,\n barBeats,\n );\n }\n\n // Foot hi-hats and auxiliary percussion are appended after the beat loop, so\n // the accumulated list is not monotonic within a bar. A consumer writing MIDI\n // reads these in order and would emit a negative delta time.\n const endBeat = opts.bars * barBeats;\n const kit = resolved.instrument('drums');\n const written = track.hits\n .filter((hit) => hit.startBeat < endBeat && playableOn(kit, hit.pitch))\n .sort((a, b) => a.startBeat - b.startBeat || a.pitch - b.pitch);\n return underCeiling(written, bpm, difficulty, exemptOnsets);\n}\n\n/**\n * Drop the strokes a player at this ceiling could not reach in time.\n *\n * The ceiling is a property of the finished part rather than of any one voice\n * that proposed a stroke: the groove, the fill, the lift and the auxiliary\n * voices each judge their own material, and the bar where a fill meets the\n * groove before it belongs to neither of them. Applying it once at the end is\n * what makes \"no voice repeats faster than the ceiling sustains\" true of the\n * output instead of true of each generator separately.\n *\n * The earlier of two strokes too close together is the one kept, since it is\n * the one the passage was already committed to, and strokes at the same instant\n * are left alone — two voices layered on one onset are not a stream.\n *\n * @param hits The finished part, in onset order.\n * @param bpm Tempo.\n * @param difficulty The ceiling, or undefined for no ceiling.\n * @param exempt Onset keys the ceiling does not touch.\n * @returns The strokes that survive, in the order given.\n */\nfunction underCeiling(\n hits: readonly DrumHit[],\n bpm: number,\n difficulty: number | undefined,\n exempt: ReadonlySet<string>,\n): DrumHit[] {\n if (difficulty === undefined) {\n return [...hits];\n }\n const lastOnset = new Map<number, number>();\n const kept: DrumHit[] = [];\n for (const hit of hits) {\n const previous = lastOnset.get(hit.pitch);\n const reachable =\n previous === undefined ||\n exempt.has(onsetKey(hit.pitch, hit.startBeat)) ||\n sustainsStrokes(hit.startBeat - previous, bpm, difficulty);\n if (!reachable) {\n continue;\n }\n kept.push(hit);\n lastOnset.set(hit.pitch, hit.startBeat);\n }\n return kept;\n}\n\n/**\n * Whether a kit has the voice a hit asks for.\n *\n * Naming a kit is the request that the part be playable on it, so this holds\n * whatever the difficulty ceiling says: a voice the kit does not have is not a\n * hard stroke but an absent one. Without a kit there is no such constraint, and\n * the programmed case is unchanged.\n */\nfunction playableOn(kit: InstrumentProfile | undefined, pitch: number): boolean {\n return kit === undefined || canSound(kit, pitch);\n}\n","/**\n * Groove and humanization: nudging quantized note events toward a more human\n * feel, and capturing/replaying the timing-and-velocity \"feel\" of a\n * performance as a reusable groove template.\n *\n * Positions and durations are measured in quarter-note beats, matching the\n * library-wide convention. Randomness, where used, always goes through the\n * seeded PRNG from the `random` module so results are fully reproducible.\n */\n\nimport { InvalidInputError } from '../../core/errors/index.js';\nimport {\n beatsPerBar,\n formatTimeSignature,\n metricWeight,\n type TimeSignature,\n} from '../../core/meter/index.js';\nimport type { NoteEvent } from '../../core/types.js';\nimport {\n assertGenerationBudget,\n assertNoteEvents,\n assertPositiveInt,\n assertRange,\n assertTimeSignature,\n dropSilentNotes,\n} from '../../core/validation/index.js';\nimport { type GenerationContextInput, resolveContext } from '../context/index.js';\n\n/**\n * Options controlling {@link humanize}.\n *\n * @category Rhythm & Meter\n */\nexport type HumanizeOptions = {\n /**\n * Time signature used to derive metric accents.\n *\n * @defaultValue 4/4\n */\n ts?: TimeSignature;\n /**\n * Maximum timing jitter in quarter-note beats, applied as ±this value.\n *\n * @defaultValue 0.02\n */\n timing?: number;\n /**\n * Maximum velocity jitter (MIDI units), applied as ±this value.\n *\n * @defaultValue 8\n */\n velocity?: number;\n /**\n * How much louder strong beats get relative to weak ones, in MIDI velocity units.\n *\n * @defaultValue 12\n */\n accent?: number;\n /**\n * Velocity assumed for events with no `velocity` of their own.\n *\n * @defaultValue 80\n */\n baseVelocity?: number;\n /**\n * The generation context, so one project seed covers this call as well as the\n * generators that wrote the notes. Its `seed` fixes the jitter, so the same\n * seed always humanizes the same way.\n *\n * @defaultValue `{ seed: 0 }`\n */\n ctx?: GenerationContextInput;\n /**\n * The name this call draws under. Humanizing two parts from one context with\n * the same name gives notes at the same position and pitch the same jitter,\n * which is what a doubled line does not want; naming each part keeps them\n * independent.\n *\n * @defaultValue `'humanize'`\n */\n part?: string;\n};\n\n/** Default time signature for {@link humanize} when none is given. */\nconst DEFAULT_TS: TimeSignature = { numerator: 4, denominator: 4 };\n/** The namespace {@link humanize} draws under when the caller names none. */\nconst DEFAULT_PART = 'humanize';\n/**\n * Grid resolution assumed by {@link extractGrooveTemplate}. A sixteenth-note\n * grid is where the deviations a template records are audible.\n */\nconst DEFAULT_GROOVE_SUBDIVISION = 4;\nconst DEFAULT_TIMING = 0.02;\nconst DEFAULT_VELOCITY_JITTER = 8;\nconst DEFAULT_ACCENT = 12;\nconst DEFAULT_BASE_VELOCITY = 80;\nconst MAX_METRIC_WEIGHT = 3;\nconst MIN_VELOCITY = 1;\nconst MAX_VELOCITY = 127;\n\n/**\n * Humanize a sequence of note events by nudging their timing and velocity\n * with small, deterministic (seeded) randomness plus a metric-accent shape:\n * events on strong beats (see {@link metricWeight}) come out louder on\n * average than events on weak ones.\n *\n * Each returned event is a copy; `startBeat` is jittered within\n * `[-timing, +timing]` beats — not floored at 0, because a pickup sounds before\n * the downbeat and flooring it there would play two upbeats as one — and\n * `velocity` is the event's own velocity (or `baseVelocity` if it has none) plus an\n * accent term scaled by the event's metric weight plus a jitter within\n * `[-velocity, +velocity]`, clamped to `[1, 127]` and rounded. The metric\n * weight is computed from each event's original (pre-jitter) `startBeat`.\n * `pitch` and `durationBeat` pass through unchanged, and the output keeps the\n * input order.\n *\n * The jitter is drawn from the generation context, addressed by the event's own\n * onset and pitch rather than by its position in the array: an event added or\n * removed leaves every other event's jitter exactly where it was, and a part\n * humanized under its own name is independent of the others sharing the seed.\n *\n * @param events The events to humanize.\n * @param opts Humanization options.\n * @returns Humanized copies of `events`, in input order.\n * @example\n * ```ts\n * import { humanize } from '@libraz/libcantus';\n * const events = [{ pitch: 60, startBeat: 0, durationBeat: 1, velocity: 80 }];\n * humanize(events, { ctx: 1, timing: 0.03 }); // copies with jittered timing and accented velocity\n * humanize(events, { ctx: { seed: 42 }, part: 'lead' }); // under a project context\n * ```\n * Notes with a zero or negative duration never sound and are dropped, so the\n * result can be shorter than the input.\n *\n * @category Rhythm & Meter\n */\nexport function humanize(events: readonly NoteEvent[], opts: HumanizeOptions = {}): NoteEvent[] {\n const ts = opts.ts ?? DEFAULT_TS;\n assertTimeSignature(ts);\n // Zero-length artefacts are routine in MIDI imports and never sound, so they\n // are accepted and dropped here, exactly as the analysis layer does — the two\n // sides of a pipeline must not disagree about the same array.\n assertNoteEvents(events, 'humanize events', { allowNonPositiveDuration: true });\n const sounding = dropSilentNotes(events);\n const timing = opts.timing ?? DEFAULT_TIMING;\n const velocityJitter = opts.velocity ?? DEFAULT_VELOCITY_JITTER;\n const accent = opts.accent ?? DEFAULT_ACCENT;\n const baseVelocity = opts.baseVelocity ?? DEFAULT_BASE_VELOCITY;\n assertRange(timing, 0, Number.MAX_SAFE_INTEGER, 'humanize timing');\n assertRange(velocityJitter, 0, 127, 'humanize velocity jitter');\n assertRange(accent, 0, 127, 'humanize accent');\n assertRange(baseVelocity, 0, 127, 'humanize base velocity');\n const ctx = resolveContext(opts.ctx);\n const draw = ctx.part(opts.part ?? DEFAULT_PART);\n\n return sounding.map((event) => {\n // Addressed by the event rather than by how many events came before it, so\n // inserting a note leaves every other note's jitter where it was.\n const timingOffset = draw.float(-timing, timing, 'timing', event.startBeat, event.pitch);\n // Not clamped at 0: beat 0 is the first downbeat, so a pickup is written\n // before it, and flooring the jitter there would push every upbeat onto the\n // downbeat — two notes the input told apart, played as one.\n const startBeat = event.startBeat + timingOffset;\n\n const weight = metricWeight(event.startBeat, ts);\n const accentBoost = (weight / MAX_METRIC_WEIGHT) * accent;\n const velocityOffset = draw.float(\n -velocityJitter,\n velocityJitter,\n 'velocity',\n event.startBeat,\n event.pitch,\n );\n const rawVelocity = (event.velocity ?? baseVelocity) + accentBoost + velocityOffset;\n const velocity = Math.min(MAX_VELOCITY, Math.max(MIN_VELOCITY, Math.round(rawVelocity)));\n\n return {\n pitch: event.pitch,\n startBeat,\n durationBeat: event.durationBeat,\n velocity,\n };\n });\n}\n\n/**\n * The recorded feel at a single grid slot: an average timing deviation and velocity.\n *\n * @category Rhythm & Meter\n */\nexport type GrooveSlot = {\n /** Average `actualStartBeat - quantizedBeat` (in quarter-note beats) for events landing on this slot. */\n timingOffset: number;\n /**\n * Average velocity of events landing on this slot, or `null` if no event\n * with a velocity did. `null` (not 0) is the \"unrecorded\" sentinel so that a\n * genuine velocity of 0 is preserved and applied like any other value.\n */\n velocity: number | null;\n};\n\n/**\n * A per-bar grid of timing and velocity deviations captured from a performance.\n *\n * @category Rhythm & Meter\n */\nexport type GrooveTemplate = {\n /** Grid resolution: equal grid steps per quarter-note beat. */\n subdivision: number;\n /** Number of grid slots per bar (`beatsPerBar(ts) * subdivision`). */\n slotsPerBar: number;\n /** One entry per grid slot, in slot-index order (slot 0 is the downbeat). */\n slots: GrooveSlot[];\n /**\n * The time signature the template was extracted under. {@link applyGrooveTemplate}\n * requires the apply-time meter to match this so the per-bar grid lines up\n * instead of silently drifting. Optional for backward compatibility with\n * templates constructed without it (in which case no check is performed).\n */\n ts?: TimeSignature;\n};\n\n/**\n * Quantize a beat position to the nearest grid slot at the given subdivision,\n * within the bar that contains it.\n *\n * @param beat Absolute position in quarter-note beats.\n * @param barBeats Bar length in quarter-note beats.\n * @param subdivision Grid steps per quarter-note beat.\n * @param slotsPerBar Number of grid slots per bar.\n * @returns The quantized absolute beat and its 0-based slot index within the bar.\n */\nfunction quantizeToGrid(\n beat: number,\n barBeats: number,\n subdivision: number,\n slotsPerBar: number,\n): { quantizedBeat: number; slotIndex: number } {\n const barIndex = Math.floor(beat / barBeats);\n const modBeat = beat - barIndex * barBeats;\n let slotIndex = Math.round(modBeat * subdivision);\n let effectiveBarIndex = barIndex;\n // Rounding the last slot in a bar up to the grid step count means it\n // actually lands on the next bar's downbeat.\n if (slotIndex >= slotsPerBar) {\n slotIndex = 0;\n effectiveBarIndex += 1;\n }\n return {\n quantizedBeat: effectiveBarIndex * barBeats + slotIndex / subdivision,\n slotIndex,\n };\n}\n\n/**\n * Extract a groove template from a set of note events: for each grid slot,\n * the average timing deviation from the quantized grid and the average\n * velocity of events landing on it. Slots with no velocity-carrying event\n * record `velocity: null` (the \"unrecorded\" sentinel); empty slots default to\n * `{ timingOffset: 0, velocity: null }`. The time signature is stored on the\n * template so {@link applyGrooveTemplate} can verify the meter matches.\n *\n * @param events The (typically \"groovy\", human-played) events to analyze.\n * @param ts The time signature, used to compute the bar length.\n * @param subdivision Grid resolution: equal grid steps per quarter-note beat.\n * Defaults to a sixteenth-note grid, which is where the timing deviations a\n * template records — swing, push, drag — are audible.\n * @returns The extracted groove template.\n * @category Rhythm & Meter\n */\nexport function extractGrooveTemplate(\n events: readonly NoteEvent[],\n ts: TimeSignature,\n subdivision = DEFAULT_GROOVE_SUBDIVISION,\n): GrooveTemplate {\n assertTimeSignature(ts);\n assertNoteEvents(events, 'groove source events', { allowNonPositiveDuration: true });\n assertPositiveInt(subdivision, 'groove subdivision');\n const barBeats = beatsPerBar(ts);\n const slotsPerBar = Math.ceil(barBeats * subdivision);\n assertPositiveInt(slotsPerBar, 'groove template slots');\n assertGenerationBudget(slotsPerBar, 'groove template slots');\n const offsetSums = new Array<number>(slotsPerBar).fill(0);\n const velocitySums = new Array<number>(slotsPerBar).fill(0);\n const counts = new Array<number>(slotsPerBar).fill(0);\n const velocityCounts = new Array<number>(slotsPerBar).fill(0);\n\n for (const event of dropSilentNotes(events)) {\n const { quantizedBeat, slotIndex } = quantizeToGrid(\n event.startBeat,\n barBeats,\n subdivision,\n slotsPerBar,\n );\n offsetSums[slotIndex] = (offsetSums[slotIndex] ?? 0) + (event.startBeat - quantizedBeat);\n counts[slotIndex] = (counts[slotIndex] ?? 0) + 1;\n // Only events that carry a velocity contribute to the velocity average, so a\n // velocity-less event does not pull the slot's recorded velocity toward zero.\n if (event.velocity !== undefined) {\n velocitySums[slotIndex] = (velocitySums[slotIndex] ?? 0) + event.velocity;\n velocityCounts[slotIndex] = (velocityCounts[slotIndex] ?? 0) + 1;\n }\n }\n\n const slots: GrooveSlot[] = [];\n for (let i = 0; i < slotsPerBar; i += 1) {\n const count = counts[i] ?? 0;\n if (count === 0) {\n slots.push({ timingOffset: 0, velocity: null });\n continue;\n }\n const velocityCount = velocityCounts[i] ?? 0;\n slots.push({\n timingOffset: (offsetSums[i] ?? 0) / count,\n velocity: velocityCount > 0 ? (velocitySums[i] ?? 0) / velocityCount : null,\n });\n }\n\n return { subdivision, slotsPerBar, slots, ts };\n}\n\n/**\n * Apply a groove template to a set of (typically quantized) note events: each\n * event is snapped to its grid slot and then offset by that slot's recorded\n * timing deviation, pushing stiff timing toward the template's feel.\n * Velocity is set to the slot's recorded average when it recorded one (a\n * non-`null` velocity, including a genuine 0); otherwise the event's own\n * velocity is left untouched.\n * Non-positive-duration input artefacts are omitted, so output positions do\n * not retain a one-to-one correspondence with the input array.\n *\n * If the template carries a time signature (see {@link GrooveTemplate.ts}) it\n * must match `ts`, since a differing bar length would misalign the per-bar\n * grid and drift the feel; a mismatch throws rather than corrupting timing\n * silently.\n *\n * @param events The events to reshape.\n * @param template The groove template, from {@link extractGrooveTemplate}.\n * @param ts The time signature, used to compute the bar length.\n * @returns Reshaped copies of `events`, in input order.\n * @throws If `template.ts` is set and does not match `ts`.\n * @example\n * ```ts\n * import { extractGrooveTemplate, applyGrooveTemplate, parseTimeSignature } from '@libraz/libcantus';\n * const ts = parseTimeSignature('4/4');\n * // Learn the feel from a loosely played performance...\n * const groovy = [\n * { pitch: 36, startBeat: 0.02, durationBeat: 1, velocity: 100 },\n * { pitch: 38, startBeat: 1.06, durationBeat: 1, velocity: 70 },\n * ];\n * const template = extractGrooveTemplate(groovy, ts, 4);\n * // ...then impose it on stiff, quantized events.\n * const quantized = [{ pitch: 36, startBeat: 0, durationBeat: 1, velocity: 80 }];\n * applyGrooveTemplate(quantized, template, ts); // events nudged toward the captured feel\n * ```\n * @category Rhythm & Meter\n */\nexport function applyGrooveTemplate(\n events: readonly NoteEvent[],\n template: GrooveTemplate,\n ts: TimeSignature,\n): NoteEvent[] {\n assertTimeSignature(ts);\n assertNoteEvents(events, 'groove target events', { allowNonPositiveDuration: true });\n assertPositiveInt(template.subdivision, 'template subdivision');\n assertPositiveInt(template.slotsPerBar, 'template slotsPerBar');\n assertGenerationBudget(template.slots.length, 'template slots');\n if (template.slots.length !== template.slotsPerBar) {\n throw new InvalidInputError('template slots length must equal slotsPerBar');\n }\n if (\n template.ts &&\n (template.ts.numerator !== ts.numerator || template.ts.denominator !== ts.denominator)\n ) {\n throw new InvalidInputError(\n `Groove template meter ${formatTimeSignature(template.ts)} does not match ` +\n `apply-time meter ${formatTimeSignature(ts)}`,\n );\n }\n const barBeats = beatsPerBar(ts);\n return dropSilentNotes(events).map((event) => {\n const { quantizedBeat, slotIndex } = quantizeToGrid(\n event.startBeat,\n barBeats,\n template.subdivision,\n template.slotsPerBar,\n );\n const slot = template.slots[slotIndex];\n // A slot that lays back or pushes ahead of the beat moves the note it\n // lands on, downbeat included. Clamping at 0 would take the pushed feel off\n // the first note only, and would flatten a pickup onto the downbeat.\n const startBeat = quantizedBeat + (slot?.timingOffset ?? 0);\n const velocity =\n slot && slot.velocity !== null && slot.velocity !== undefined\n ? Math.round(slot.velocity)\n : event.velocity;\n\n return {\n pitch: event.pitch,\n startBeat,\n durationBeat: event.durationBeat,\n velocity,\n };\n });\n}\n","import type { TimeSignature } from '../../core/meter/index.js';\nimport { metricWeight, pulseBeats } from '../../core/meter/index.js';\nimport type { NoteEvent } from '../../core/types.js';\nimport { assertNoteEvents, assertTimeSignature } from '../../core/validation/index.js';\n\n/**\n * What a melody note contributes to the harmony: either a structural tone the\n * chord has to account for, or the ornament figure that explains it away.\n *\n * @category Reharmonization\n */\nexport type MelodyToneRole =\n | 'structural'\n | 'passing'\n | 'neighbor'\n | 'appoggiatura'\n | 'suspension'\n | 'anticipation'\n | 'escape';\n\n/**\n * One melody note's classification: its index in the classified melody, the\n * figure it forms, and whether that figure makes it ornamental.\n *\n * @category Reharmonization\n */\nexport type ClassifiedMelodyTone = {\n noteIndex: number;\n role: MelodyToneRole;\n /** True for every role but `'structural'`. */\n ornamental: boolean;\n};\n\n/** Maximum gap or overlap still heard as one note leading into the next. */\nconst ADJACENCY_TOLERANCE = 0.05;\n\n/**\n * Read an onset as the metric position it is playing, snapping it to the pulse\n * grid when it lies within the tolerance a performance stays inside.\n *\n * `metricWeight` answers for an exact grid position and reports no accent at\n * all a fraction of a beat away from one, while a {@link NoteEvent} is the\n * exchange format with a DAW or MIDI track — its onsets carry the timing of a\n * performance. Without this the same phrase would form one set of figures as\n * notated and another once played, and the chords chosen under it would change\n * with a few milliseconds of jitter. The tolerance is the one the adjacency test\n * already reads onsets with, so both sides of the classification agree about\n * what counts as the same beat.\n *\n * @param beat The onset in quarter-note beats.\n * @param pulse The metre's pulse length in quarter-note beats.\n */\nexport function snapToPulse(beat: number, pulse: number): number {\n const snapped = Math.round(beat / pulse) * pulse;\n return Math.abs(snapped - beat) <= ADJACENCY_TOLERANCE ? snapped : beat;\n}\n\n/** Largest interval still heard as a step; anything wider is a leap. */\nconst STEP_SEMITONES = 2;\n\nfunction isStep(a: number, b: number): boolean {\n const d = Math.abs(a - b);\n return d >= 1 && d <= STEP_SEMITONES;\n}\n\nfunction isLeap(a: number, b: number): boolean {\n return Math.abs(a - b) > STEP_SEMITONES;\n}\n\n/** Whether both notes move the same way, neither of them repeating a pitch. */\nfunction sameDirection(prev: number, note: number, next: number): boolean {\n return prev !== note && next !== note && note - prev > 0 === next - note > 0;\n}\n\n/**\n * Classify a melody's ornaments from the melody and the metre alone.\n *\n * This is deliberately chord-free: a harmonizer needs to know which notes the\n * chords must explain *before* it can choose them, so nothing here may consult a\n * harmony. What remains is the melodic and metric evidence — stepwise approach\n * and departure, repeated pitches, leaps, and the relative metric weight and\n * length of a note against its neighbours.\n *\n * The figures are recognized in the order suspension, passing, neighbor,\n * anticipation, appoggiatura, escape. A note is judged ornamental only when it\n * is metrically and rhythmically subordinate to the neighbours that frame it\n * (or, for the accented figures, when it leans on a stronger beat than its\n * approach and gives way to a resolution). A note without an adjacent\n * predecessor and successor has no figure to belong to and stays structural, so\n * the first and last notes of a phrase are always structural.\n *\n * @param melody The melody, in ascending onset order.\n * @param ts Time signature used to weight metric accents.\n * @returns One classification per input note, in input order.\n * @example\n * ```ts\n * import { classifyMelodyTones } from '@libraz/libcantus';\n * const melody = [60, 62, 64, 60].map((pitch, i) => ({\n * pitch,\n * startBeat: i,\n * durationBeat: 1,\n * }));\n * classifyMelodyTones(melody, { numerator: 4, denominator: 4 })[1];\n * // { noteIndex: 1, role: 'passing', ornamental: true }\n * ```\n * @category Reharmonization\n */\nexport function classifyMelodyTones(\n melody: readonly NoteEvent[],\n ts: TimeSignature,\n): ClassifiedMelodyTone[] {\n assertNoteEvents(melody, 'melody notes', { allowNonPositiveDuration: true });\n assertTimeSignature(ts);\n const pulse = pulseBeats(ts);\n return melody.map((_, index) => {\n const role = roleOfNote(melody, index, ts, pulse);\n return { noteIndex: index, role, ornamental: role !== 'structural' };\n });\n}\n\n/** Classify one note against the neighbours that immediately frame it. */\nfunction roleOfNote(\n melody: readonly NoteEvent[],\n index: number,\n ts: TimeSignature,\n pulse: number,\n): MelodyToneRole {\n const note = melody[index];\n const prev = melody[index - 1];\n const next = melody[index + 1];\n if (!note || !prev || !next) {\n return 'structural';\n }\n // A note separated from its neighbour by a rest is not led into or out of it,\n // and simultaneous onsets are cluster members rather than melodic neighbours.\n const intoNote = Math.abs(prev.startBeat + prev.durationBeat - note.startBeat);\n const outOfNote = Math.abs(note.startBeat + note.durationBeat - next.startBeat);\n if (intoNote > ADJACENCY_TOLERANCE || outOfNote > ADJACENCY_TOLERANCE) {\n return 'structural';\n }\n\n const weight = metricWeight(snapToPulse(note.startBeat, pulse), ts);\n const weightPrev = metricWeight(snapToPulse(prev.startBeat, pulse), ts);\n const weightNext = metricWeight(snapToPulse(next.startBeat, pulse), ts);\n // Subordinate: no stronger metrically and no longer than either neighbour, so\n // the ear hears the neighbours as the frame and this note as decoration.\n const subordinate =\n weight <= weightPrev &&\n weight <= weightNext &&\n note.durationBeat <= prev.durationBeat &&\n note.durationBeat <= next.durationBeat;\n // Leaning: an accented dissonance arrives on a stronger beat than the note it\n // comes from and hands over to a weaker resolution.\n const leaning = weight > weightPrev && weight >= weightNext;\n\n if (\n leaning &&\n prev.pitch === note.pitch &&\n note.pitch - next.pitch >= 1 &&\n isStep(note.pitch, next.pitch)\n ) {\n return 'suspension';\n }\n if (subordinate && isStep(prev.pitch, note.pitch) && isStep(note.pitch, next.pitch)) {\n if (sameDirection(prev.pitch, note.pitch, next.pitch)) {\n return 'passing';\n }\n if (prev.pitch === next.pitch) {\n return 'neighbor';\n }\n }\n if (\n subordinate &&\n note.pitch === next.pitch &&\n note.pitch !== prev.pitch &&\n weight < weightNext\n ) {\n return 'anticipation';\n }\n if (\n leaning &&\n isLeap(prev.pitch, note.pitch) &&\n isStep(note.pitch, next.pitch) &&\n !sameDirection(prev.pitch, note.pitch, next.pitch)\n ) {\n return 'appoggiatura';\n }\n if (\n subordinate &&\n isStep(prev.pitch, note.pitch) &&\n isLeap(note.pitch, next.pitch) &&\n !sameDirection(prev.pitch, note.pitch, next.pitch)\n ) {\n return 'escape';\n }\n return 'structural';\n}\n","import { isDiatonic, parallelKey } from '../../analyze/functional/index.js';\nimport type { TimeSignature } from '../../core/meter/index.js';\nimport { beatsPerBar, isStrongBeat, pulseBeats } from '../../core/meter/index.js';\nimport { pitchClassOf as pitchClass } from '../../core/pitch/index.js';\nimport type { KeyScale, NoteEvent } from '../../core/types.js';\nimport {\n assertFiniteNumber,\n assertGenerationBudget,\n assertNoteEvents,\n assertRange,\n assertTimeSignature,\n soundingNotesOnly,\n} from '../../core/validation/index.js';\nimport type { Chord, ChordQuality } from '../../theory/chord/index.js';\nimport { chordPitchClasses, diatonicTriad, makeChord } from '../../theory/chord/index.js';\nimport type { HarmonyRole } from '../../theory/harmony/index.js';\nimport { roleOf } from '../../theory/harmony/index.js';\nimport {\n isScaleTone,\n type KeyLike,\n majorKey,\n minorKey,\n scaleTonesInDegreeOrder,\n toKeyScale,\n} from '../../theory/scale/index.js';\nimport { type GenerationContextInput, resolveContext } from '../context/index.js';\nimport type { ChordSpan } from '../progression/index.js';\nimport { classifyMelodyTones, snapToPulse } from './nct.js';\n\nexport type { ClassifiedMelodyTone, MelodyToneRole } from './nct.js';\nexport { classifyMelodyTones } from './nct.js';\n\n/**\n * A melody note supplied to the harmonizer: a {@link NoteEvent}.\n *\n * @category Reharmonization\n */\nexport type MelodyNote = NoteEvent;\n\n/**\n * Options controlling {@link harmonizeMelody}.\n *\n * @category Reharmonization\n */\nexport type HarmonizePlacement = {\n /**\n * Move the melody into the key it is harmonized in. The search reports the\n * shift it chose in `transposeSemitones`; transposing the melody by that many\n * semitones puts it in the returned `key`, so melody and harmony always agree.\n *\n * Use it for a melody notated in one key that has to be sung or played in\n * another. It never changes register on its own — that is `octaveSearch`.\n *\n * @defaultValue false\n */\n transposeSearch: boolean;\n /**\n * Search octave placements as well, so a melody written too high or too low\n * is moved into a comfortable register. Octaves leave every pitch class where\n * it was, so this changes neither the key nor the chords.\n *\n * @defaultValue false\n */\n octaveSearch: boolean;\n};\n\n/**\n * Options controlling {@link harmonizeMelody}.\n *\n * Only `melody` is required; every other knob has the default a first call\n * wants.\n *\n * @category Reharmonization\n */\nexport type HarmonizeOptions = {\n /** The melody to harmonize, in ascending onset order. */\n melody: readonly MelodyNote[];\n /**\n * The key to harmonize in, or `'infer'` to estimate it from the melody's\n * pitch-class weighting — which is what a caller who has only a melody\n * wants, and so the default. A key name such as `'C major'` is read as that\n * key; `'infer'` names no key and always asks for the estimate.\n *\n * @defaultValue `'infer'`\n */\n key?: KeyLike | 'infer';\n /**\n * Length of each chord slot in beats. Harmonization places chords on a fixed\n * grid of this length — unlike {@link chordTimelineFromNotes}, which searches\n * for the boundaries an existing piece already implies. Any positive value is\n * honoured; a value fine enough to make the search explode is rejected by the\n * generation budget rather than rounded up.\n *\n * Left out, it is read from `ts`: half a bar where the half falls on a pulse,\n * and the whole bar where it does not, so a waltz changes chord on its\n * downbeats rather than across them. In 4/4 that is one chord per half bar.\n *\n * @defaultValue half a bar of `ts` — 2 in 4/4\n */\n harmonicRhythm?: number;\n /**\n * Time signature the melody is barred in. It weights the metric accents the\n * chords are chosen against and sets the default chord grid, so a waltz or a\n * jig is harmonized on its own beats rather than on a 4/4 reading of them.\n *\n * @defaultValue `4/4`\n */\n ts?: TimeSignature;\n /**\n * How far beyond the key's own triads the chord vocabulary reaches:\n * `'diatonic'` uses only them, `'secondaryDominant'` adds the dominants that\n * tonicize each degree, and `'borrowed'` adds the parallel mode's chords too.\n *\n * Sugar for `ctx: { complexity: { harmonic } }` at the three dial positions\n * these names have always stood for; a context names how far the vocabulary\n * reaches instead of which of three steps it stops at, and wins where both\n * are given.\n *\n * @defaultValue `'diatonic'`\n */\n reharmonize?: 'diatonic' | 'secondaryDominant' | 'borrowed';\n /**\n * Whether to search transpositions and octave placements for the melody.\n * Both are off by default, so the melody is harmonized where it was written.\n *\n * @defaultValue both false\n */\n placement?: HarmonizePlacement;\n /**\n * Beats at which the melody's phrases end, so a longer line closes at each of\n * them instead of only at its end.\n *\n * Naming a beat asks for a close there, and three things follow from it. The\n * beat divides the chord grid, so the slot the phrase closes in ends where the\n * phrase does rather than running on into the next one. The harmony moves into\n * that slot — the chord under a named close is never the chord that was\n * already sounding, which is what makes the close audible as one. And the note\n * the phrase comes to rest on is read as a structural tone rather than as an\n * ornament of the next phrase's first note, so a phrase resting on the tonic\n * is harmonized by the tonic.\n *\n * Which cadence that close forms is still the melody's to decide: a phrase\n * coming to rest on the tonic over an approach that can carry the dominant\n * cadences authentically, and one whose approach cannot is harmonized by what\n * it sounds.\n *\n * The melody always closes where it ends, whatever this says; these are the\n * closes *inside* it. `phrasesFromTimeline` finds the phrases of a line\n * already labelled with chords, and its `endBeat` values are what this\n * expects, so a caller harmonizes a whole piece in one call rather than\n * harmonizing each phrase and joining the results.\n *\n * A boundary landing exactly on a chord-slot boundary closes the slot before\n * it — the beat a phrase ends on is where the next phrase begins.\n *\n * @defaultValue none — one close, at the end\n */\n phraseEnds?: readonly number[];\n /**\n * The generation context. Its `complexity.harmonic` is how far the chord\n * vocabulary reaches beyond the key's own triads — 0 uses them alone, 0.5 has\n * every secondary dominant, 1 the parallel mode's chords as well — and its\n * `seed` drives the deterministic tie-break perturbation.\n *\n * @defaultValue `{ seed: 0 }`\n */\n ctx?: GenerationContextInput;\n};\n\n/**\n * The chosen transpose, key, chord path, and per-note roles.\n *\n * @category Reharmonization\n */\nexport type HarmonizeResult = {\n /**\n * How far the melody was moved, in semitones. Transposing the melody by this\n * much puts it in `key`, which is the key the chords are in.\n */\n transposeSemitones: number;\n key: KeyScale;\n /** One span per chord change, each starting on a harmonic-rhythm boundary. */\n chords: ChordSpan[];\n /** Each melody note's role in the chord sounding under it, once chosen. */\n melodyRoles: { noteIndex: number; role: HarmonyRole }[];\n};\n\n/** One chord the search may place in a slot, with everything it is scored by. */\nexport type Candidate = {\n rootPc: number;\n quality: ChordQuality;\n degree?: number;\n secondaryDominant: boolean;\n targetDegree?: number;\n base: number;\n /**\n * The candidate's pitch classes, resolved once. The emission cost is\n * evaluated for every transpose, segment, and candidate, and the chord's\n * pitch classes depend on none of those.\n */\n pcs: number[];\n};\n\n/**\n * One note as a segment's emission term sees it: how much of the segment it\n * sounds in and whether it lands on an accent. Neither depends on the candidate\n * chord or on the transposition being tried, so both are found once.\n */\ntype CostNote = {\n /** Index in the sounding melody. */\n index: number;\n /** Beats of the segment the note sounds in, floored so a short note still counts. */\n weight: number;\n /** Whether the note enters the segment on a metric accent. */\n strong: boolean;\n};\n\ntype Segment = {\n startBeat: number;\n endBeat: number;\n /**\n * The segment's own length in beats. Every term of the cost table is a rate per\n * beat, and a phrase end divides the grid where the caller named it, so a slot\n * cut short is scored by what it is worth rather than by what the harmonic\n * rhythm would have made it worth.\n */\n beats: number;\n /** Every sounding note overlapping the segment, by index in the sounding melody. */\n noteIndices: number[];\n /**\n * The notes that drive the segment's emission cost: its structural tones,\n * falling back to all of its notes when ornaments are all it has. A chord slot\n * with no note left to explain would be chosen by chord flow alone.\n */\n costNotes: CostNote[];\n /** Total emission weight of `costNotes`: what covering the segment is worth. */\n weight: number;\n /**\n * The last of `costNotes`, which is the tone a phrase closes on. Held as an\n * index because the pitch it carries moves with the transposition being tried.\n */\n closingIndex?: number;\n};\n\nconst FALLBACK: Candidate = {\n rootPc: 0,\n quality: 'maj',\n secondaryDominant: false,\n base: 0,\n pcs: chordPitchClasses(makeChord(0, 'maj')),\n};\n\n/**\n * Chord slot length assumed when the caller names none: half a bar of the metre\n * being harmonized in, or the whole bar where half of it does not fall on a\n * pulse.\n *\n * A grid is anchored at the melody's first slot boundary and repeats, so a slot\n * that is not a whole number of pulses long puts chord changes inside the bar\n * and drifts further from the barline with every repeat. In 3/4 half a bar is a\n * beat and a half; taking the bar instead is what makes a waltz change chord on\n * its downbeats.\n */\nfunction defaultHarmonicRhythm(ts: TimeSignature): number {\n const bar = beatsPerBar(ts);\n const half = bar / 2;\n return half % pulseBeats(ts) === 0 ? half : bar;\n}\n\n/** Placement assumed when the caller names none: harmonize the melody as written. */\nconst DEFAULT_PLACEMENT: HarmonizePlacement = { transposeSearch: false, octaveSearch: false };\n\n/** Default meter used to weight metric accents when none is supplied. */\nconst DEFAULT_METER: TimeSignature = { numerator: 4, denominator: 4 };\n\n/** Comfortable melodic range (MIDI) and the per-semitone cost of leaving it. */\nconst COMFORT_LOW = 55;\nconst COMFORT_HIGH = 79;\nconst TESSITURA_WEIGHT = 0.001;\n\n/**\n * The cost table, in one place because only the *ratios* between these terms\n * decide anything. Every term is on the same order of magnitude, so covering one\n * more melody note can never outweigh a functional progression: a chord that\n * leaves an accented structural tone unexplained pays about what a cadence or a\n * dominant resolution is worth, not several times more.\n *\n * **Every term is a rate per beat of music**, not per chord or per slot. The\n * emission term is charged per structural note by the beats it sounds; the\n * vocabulary term by the beats the slot's structural notes are worth; the\n * transition and phrase-end terms by the length of the slot they apply to. That\n * is what keeps the balance between melody fit and functional flow the same\n * whichever harmonic rhythm the caller asks for: with per-chord constants,\n * halving the slot length would double the total root-motion reward paid over\n * the same melody while its emission cost stayed put, and a fine harmonic rhythm\n * would spend chords the melody never asked for.\n *\n * ```text\n * emission per structural note, weighted by the beats it sounds in the segment\n * non-chord tone on a strong beat +4\n * non-chord tone on a weak beat +1\n * note outside the key, on top of the above +0.5\n * vocabulary per beat the slot's structural notes are worth\n * degree, by tonal weight: I 0, V 0.1, IV 0.15,\n * ii and vi 0.3, iii 0.45, vii 0.9\n * secondary dominant +0.5\n * borrowed chord +0.5\n * transition per beat of the slot it enters\n * changing chord at all +0.8\n * descending-fifth root motion -0.4\n * ascending-fifth root motion 0\n * root motion by step or third +0.15..0.2\n * root motion by tritone +0.4\n * V -> I -0.3\n * secondary dominant going anywhere but its target +1.0\n * secondary dominant of the chord before it +0.8\n * phrase end per beat of the phrase-final slot, when the melody's own\n * closing tone is the tonic\n * lands on the tonic -7\n * approached from V, on top of the above -0.5\n * placement per candidate placement of the melody\n * note outside the target key, per beat +3.0\n * semitone outside the comfortable range +0.001\n * ```\n *\n * Ornamental tones are removed before the emission term is computed (see\n * {@link classifyMelodyTones}), so these numbers weigh structural tones only.\n */\nconst NON_CHORD_TONE_STRONG = 4;\nconst NON_CHORD_TONE_WEAK = 1;\nconst NON_SCALE_TONE = 0.5;\n/**\n * What each degree costs to use, in degree order, standing for how much tonal\n * weight it carries: the tonic is free, the other primary triads are nearly so,\n * and the further a degree sits from them the more evidence the melody has to\n * supply. Without it every triad sharing a melody note is equally good and the\n * search wanders through the ones that chain by fifths. The same shape applies\n * in minor, where the slots are i, ii, III, iv, v, VI and VII.\n */\nconst DEGREE_BASE = [0, 0.3, 0.45, 0.15, 0.1, 0.3, 0.9];\n/**\n * What a chord from outside the key's own triads costs — more than any of them,\n * and more than the flow reward reaching it and leaving it can repay. A chromatic\n * chord is therefore never reached by chord flow alone: the melody has to sound\n * the note only that chord explains, which is worth several times as much.\n * Widening the vocabulary adds the chords the melody asks for and leaves a\n * melody with no accidental in it where it was.\n */\nconst SECONDARY_DOMINANT_BASE = 0.5;\nconst BORROWED_BASE = 0.5;\n/**\n * What changing chord costs at all, before the motion is judged. It is more than\n * the deepest discount any motion earns — a dominant resolving to its tonic —\n * so no chord change is ever cheaper than holding the chord already sounding.\n * Without it the discounts form a cycle a search can ride for free: I - IV - V -\n * I pays less than staying on I, and the melody has no say in it, so a harmonic\n * rhythm fine enough to fit the cycle in fills the whole melody with it.\n * Functional flow still orders the changes the melody does ask for, which is all\n * it is there to do.\n */\nconst CHORD_CHANGE = 0.8;\nconst DESCENDING_FIFTH = -0.4;\nconst ASCENDING_FIFTH = 0;\nconst DOMINANT_TO_TONIC = -0.3;\n/**\n * What a secondary dominant pays for not reaching the degree it tonicizes.\n * Reaching it earns no separate discount: the descending fifth an applied\n * dominant makes into its target is already priced, and paying it twice made\n * borrowing a chromatic chord and resolving it cheaper than staying in the key.\n */\nconst SECONDARY_UNRESOLVED = 1;\nconst SECONDARY_AFTER_TARGET = 0.8;\n/**\n * What other root motion costs, by the shorter distance in semitones between\n * the two roots: a third or a step asks a little, a tritone asks a lot. Motion\n * by a fifth is not priced here — it is the motion tonal harmony is built from\n * and is judged by direction instead, the descending one being the one a\n * progression is driven by.\n */\nconst ROOT_MOTION_COST = [0, 0.15, 0.15, 0.2, 0.2, 0, 0.4];\n/**\n * What closing a phrase on the tonic is worth, per beat of the closing slot. It\n * is worth more than everything that slot can otherwise decide — covering every\n * one of its structural tones, the widest vocabulary and root-motion difference\n * between two candidates — because a phrase whose melody has come to rest on the\n * tonic is closed, and a chord that covers one more note on a stronger beat does\n * not reopen it. The bonus is paid only where the melody's own closing tone is\n * the tonic, so a phrase ending anywhere else is still harmonized by what it\n * sounds.\n */\nconst PHRASE_TONIC = -7;\nconst PHRASE_AUTHENTIC = -0.5;\nconst OUT_OF_KEY_PER_BEAT = 3;\n\n/**\n * Magnitude of the seed-driven per-candidate perturbation. Kept far below the\n * smallest real cost difference (transition/emission terms are on the order of\n * 0.1 and up), so it can only decide between candidates or paths whose costs are\n * otherwise exactly equal. The seed therefore breaks ties deterministically and\n * never overrides melody fit or functional flow.\n */\nconst TIE_BREAK_JITTER = 1e-6;\n\n/** Whether a key's scale is minor: it has a minor third and no major third. */\nfunction isMinorKey(key: KeyScale): boolean {\n return ((key.modeMask12 >> 3) & 1) === 1 && ((key.modeMask12 >> 4) & 1) === 0;\n}\n\n/** The pitch class a semitone below the tonic, which a minor key cadences through. */\nfunction leadingTonePc(key: KeyScale): number {\n return (pitchClass(key.rootPc) + 11) % 12;\n}\n\n/**\n * Whether a pitch belongs to the key, counting the raised seventh of a minor\n * key.\n *\n * A minor key cadences through its harmonic-minor dominant, so its leading tone\n * is evidence for the key rather than a note foreign to it. Every term that asks\n * whether a note is in the key reads it this way — key inference, the emission\n * term's out-of-key surcharge, and the transposition search — so the dominant a\n * minor phrase closes with is not paid for twice.\n */\nfunction isKeyTone(pitch: number, key: KeyScale): boolean {\n return isScaleTone(pitch, key) || (isMinorKey(key) && pitchClass(pitch) === leadingTonePc(key));\n}\n\n/**\n * Estimate the best-fit key from a melody's pitch-class weighting.\n *\n * Both major and natural-minor tonics are scored, so a minor melody is\n * recognized as its own minor key rather than being folded into the relative\n * major (which shares identical pitch-class content). Each in-scale note adds\n * its duration weight; notes matching the candidate tonic add a further\n * half-weight, and that tonic emphasis is what separates a minor key from its\n * relative major. For minor candidates the raised leading tone (the\n * harmonic-minor seventh) also counts as in-scale, so cadential leading tones do\n * not penalize the true minor key. Ties are broken toward the earlier candidate,\n * and major is scored before minor at each tonic.\n */\nfunction inferKey(melody: readonly MelodyNote[]): KeyScale {\n let best: KeyScale = majorKey(0);\n let bestScore = Number.NEGATIVE_INFINITY;\n const candidates: KeyScale[] = [];\n for (let tonic = 0; tonic < 12; tonic += 1) {\n candidates.push(majorKey(tonic), minorKey(tonic));\n }\n for (const key of candidates) {\n const tonic = pitchClass(key.rootPc);\n let score = 0;\n for (const n of melody) {\n const w = Math.max(0.25, n.durationBeat);\n const pc = pitchClass(n.pitch);\n if (isKeyTone(n.pitch, key)) {\n score += w;\n }\n if (pc === tonic) {\n score += 0.5 * w;\n }\n }\n if (score > bestScore) {\n bestScore = score;\n best = key;\n }\n }\n return best;\n}\n\n/**\n * The degrees a secondary dominant conventionally tonicizes, in the order the\n * candidate list has always held them.\n */\nconst SECONDARY_DOMINANT_TARGETS = [2, 4, 5, 6];\n\n/**\n * The order those dominants open up in as the harmonic dial rises, by the\n * degree each tonicizes: the dominant's own dominant is the one an arranger\n * reaches for first, then the relative minor's, and the subdominant's last.\n * Kept apart from {@link SECONDARY_DOMINANT_TARGETS} so that widening the\n * vocabulary adds to the candidate list without reordering it.\n */\nconst SECONDARY_DOMINANT_ENTRY_ORDER = [5, 6, 2, 4];\n\n/**\n * The dial position at which the last secondary dominant has entered. Above it\n * the parallel mode's chords start arriving, so the two families open one after\n * the other rather than at once.\n */\nconst SECONDARY_DOMINANT_BAND = 0.5;\n\n/** Where each named reharmonization strength sits on the harmonic dial. */\nconst REHARMONIZE_DIAL: Record<NonNullable<HarmonizeOptions['reharmonize']>, number> = {\n diatonic: 0,\n secondaryDominant: SECONDARY_DOMINANT_BAND,\n borrowed: 1,\n};\n\n/**\n * How many members of a vocabulary family a dial fraction opens: none at or\n * below 0, all at 1, and one more each time the fraction crosses another\n * `1 / size` of the way up. That is what makes the dial continuous — a small\n * move opens one more chord rather than a whole family — while leaving the\n * family complete at the top of its band, so the named strengths reach exactly\n * the vocabulary they always did.\n */\nfunction admittedCount(fraction: number, size: number): number {\n return Math.max(0, Math.min(size, Math.ceil(fraction * size)));\n}\n\n/**\n * Enumerate candidate chords for the key, gated by the harmonic dial.\n *\n * Exported for the tests that pin the vocabulary a dial position opens; it is\n * not part of the package surface.\n */\nexport function buildCandidates(key: KeyScale, harmonic: number): Candidate[] {\n const tones = scaleTonesInDegreeOrder(key);\n const candidates: Candidate[] = tones.map((rootPc, index) => {\n // Scale degrees are 1-based across the library, while the array index is\n // not; the candidate records the degree, which is what reaches the caller.\n const degree = index + 1;\n const quality = diatonicTriad(degree, key).quality;\n return {\n rootPc,\n quality,\n degree,\n secondaryDominant: false,\n base: DEGREE_BASE[index] ?? 0.6,\n pcs: chordPitchClasses(makeChord(rootPc, quality)),\n };\n });\n\n // A minor key cadences through the harmonic-minor dominant, so the major triad\n // a fifth above the tonic belongs to the key's own vocabulary rather than to\n // any widening of it — the reading `generateProgression` already takes. Without\n // it the dominant-to-tonic and cadence terms are unreachable in minor, and a\n // minor melody can be harmonized but never closed. The natural-minor `v` stays\n // alongside it and carries the same degree, so the search chooses between them\n // on the melody. A major key already holds this chord as its diatonic V, and\n // the duplicate filter below drops the repeat.\n if (isMinorKey(key)) {\n const rootPc = (pitchClass(key.rootPc) + 7) % 12;\n candidates.push({\n rootPc,\n quality: 'maj',\n degree: 5,\n secondaryDominant: false,\n base: DEGREE_BASE[4] ?? 0.6,\n pcs: chordPitchClasses(makeChord(rootPc, 'maj')),\n });\n }\n\n // The degrees are kept in the same 1-based space as `Candidate.degree`, which\n // the voice-leading cost compares them against.\n const secondaryOpen = admittedCount(\n harmonic / SECONDARY_DOMINANT_BAND,\n SECONDARY_DOMINANT_TARGETS.length,\n );\n for (const target of SECONDARY_DOMINANT_TARGETS) {\n if (SECONDARY_DOMINANT_ENTRY_ORDER.indexOf(target) >= secondaryOpen) {\n continue;\n }\n const targetRoot = tones[target - 1] ?? 0;\n const rootPc = (targetRoot + 7) % 12;\n candidates.push({\n rootPc,\n quality: 'dom7',\n secondaryDominant: true,\n targetDegree: target,\n base: SECONDARY_DOMINANT_BASE,\n pcs: chordPitchClasses(makeChord(rootPc, 'dom7')),\n });\n }\n\n const parallel = parallelKey(key);\n const parallelTones = scaleTonesInDegreeOrder(parallel);\n const borrowed: Chord[] = [];\n for (let degree = 1; degree <= parallelTones.length; degree += 1) {\n const chord = diatonicTriad(degree, parallel);\n if (!isDiatonic(chord, key)) {\n borrowed.push(chord);\n }\n }\n const borrowedOpen = admittedCount(\n (harmonic - SECONDARY_DOMINANT_BAND) / (1 - SECONDARY_DOMINANT_BAND),\n borrowed.length,\n );\n for (const chord of borrowed.slice(0, borrowedOpen)) {\n candidates.push({\n rootPc: chord.rootPc,\n quality: chord.quality,\n secondaryDominant: false,\n base: BORROWED_BASE,\n pcs: chordPitchClasses(chord),\n });\n }\n\n const seen = new Set<string>();\n return candidates.filter((candidate) => {\n const id = `${candidate.rootPc}:${candidate.quality}`;\n if (seen.has(id)) {\n return false;\n }\n seen.add(id);\n return true;\n });\n}\n\n/**\n * Melody-fit cost of a candidate over a segment's structural notes.\n *\n * Only the segment's `costNotes` are charged: an ornament is explained by the\n * melodic figure it forms, not by the chord under it, so making the chord cover\n * it would be paying twice. Each note is weighted by the portion of its duration\n * that overlaps the segment, so a note sustained across a boundary contributes\n * to every segment it sounds in rather than only the one it starts in. The\n * candidate's own vocabulary cost is charged by the same total weight, so what a\n * chord costs to use and what it costs to leave a note unexplained are measured\n * against the same amount of music.\n */\nfunction emissionCost(\n seg: Segment,\n cand: Candidate,\n melody: readonly MelodyNote[],\n key: KeyScale,\n): number {\n const pcs = cand.pcs;\n let cost = cand.base * seg.weight;\n for (const { index, weight, strong } of seg.costNotes) {\n const note = melody[index];\n if (!note || pcs.includes(pitchClass(note.pitch))) {\n continue;\n }\n cost += (strong ? NON_CHORD_TONE_STRONG : NON_CHORD_TONE_WEAK) * weight;\n if (!isKeyTone(note.pitch, key)) {\n cost += NON_SCALE_TONE * weight;\n }\n }\n return cost;\n}\n\n/**\n * Register cost of a placed melody: a small penalty for notes pushed outside a\n * comfortable range. Kept far below emission/transition costs so it only breaks\n * ties between otherwise-equal octave placements.\n */\nfunction tessituraCost(melody: readonly MelodyNote[]): number {\n let cost = 0;\n for (const n of melody) {\n if (n.pitch < COMFORT_LOW) {\n cost += (COMFORT_LOW - n.pitch) * TESSITURA_WEIGHT;\n } else if (n.pitch > COMFORT_HIGH) {\n cost += (n.pitch - COMFORT_HIGH) * TESSITURA_WEIGHT;\n }\n }\n return cost;\n}\n\n/** Whether a candidate is the key's dominant, in a quality that can act as one. */\nfunction isDominantOf(cand: Candidate, tonicPc: number): boolean {\n return cand.rootPc === (tonicPc + 7) % 12 && (cand.quality === 'maj' || cand.quality === 'dom7');\n}\n\n/**\n * How badly a placed melody sits in the key it is about to be harmonized in.\n *\n * This is what makes a transposition search mean \"move the melody into this\n * key\": a shift is judged by the melody's own key membership, not by how many\n * chord tones a chord path can be talked into covering. The winning shift\n * therefore always agrees with the key the result reports.\n */\nfunction keyFitCost(melody: readonly MelodyNote[], key: KeyScale): number {\n let cost = 0;\n for (const n of melody) {\n if (!isKeyTone(n.pitch, key)) {\n cost += Math.max(0.25, n.durationBeat) * OUT_OF_KEY_PER_BEAT;\n }\n }\n return cost;\n}\n\n/**\n * Collapse runs of the same chord into one span.\n *\n * The harmonic rhythm sets the grid the search may change chords on, not a\n * requirement to change on every slot; repeating a slot's chord is the same\n * harmony held longer, and reporting it once is what a chart shows.\n */\nfunction mergeRepeats(spans: ChordSpan[]): ChordSpan[] {\n const merged: ChordSpan[] = [];\n for (const span of spans) {\n const prev = merged.at(-1);\n if (\n prev &&\n prev.rootPc === span.rootPc &&\n prev.quality === span.quality &&\n prev.degree === span.degree &&\n prev.secondaryDominant === span.secondaryDominant\n ) {\n continue;\n }\n merged.push(span);\n }\n return merged;\n}\n\n/**\n * Functional-flow cost of moving from one candidate chord to the next, per beat\n * of the slot the move enters.\n *\n * The caller scales it by that slot's length: root motion is worth what the\n * music it spans is worth, not what one grid boundary is worth, so dividing the\n * same melody into finer slots cannot buy more flow reward than the melody's\n * emission cost can answer for.\n */\nfunction transitionCost(prev: Candidate, cur: Candidate, tonicPc: number): number {\n let cost = prev.rootPc === cur.rootPc && prev.quality === cur.quality ? 0 : CHORD_CHANGE;\n const down = (prev.rootPc - cur.rootPc + 12) % 12;\n if (down === 7) {\n cost += DESCENDING_FIFTH;\n } else if (down === 5) {\n cost += ASCENDING_FIFTH;\n } else {\n cost += ROOT_MOTION_COST[Math.min(down, 12 - down)] ?? 0;\n }\n if (isDominantOf(prev, tonicPc) && cur.rootPc === tonicPc) {\n cost += DOMINANT_TO_TONIC;\n }\n if (prev.secondaryDominant && cur.degree !== prev.targetDegree) {\n cost += SECONDARY_UNRESOLVED;\n }\n // Reaching a chord's own dominant from that chord leads straight back where it\n // came from. Once it is cheaper to tonicize than to stay put, a search with no\n // memory will otherwise rock between the two forever.\n if (cur.secondaryDominant && prev.degree !== undefined && prev.degree === cur.targetDegree) {\n cost += SECONDARY_AFTER_TARGET;\n }\n return cost;\n}\n\n/**\n * Bonus for closing a phrase on the tonic, over the whole of what that slot is\n * worth.\n *\n * A melody that has come to rest on the tonic has closed, so the chord under it\n * is the tonic — whatever the rest of the slot sounds, and whatever chord the\n * slot before it settled on. The bonus is therefore weighed against everything\n * that slot can otherwise decide, which is why it is scaled by the slot's own\n * emission weight rather than being a constant one accented note can outbid.\n *\n * It is paid only where the melody's own closing tone is the tonic: a phrase\n * that comes to rest anywhere else — on the third, on the leading tone, on a\n * degree the tonic chord cannot support — has not closed, and is harmonized by\n * what it sounds.\n *\n * `prev` is the chord approaching the close, or null when the phrase is a single\n * chord long and there is nothing to approach it from.\n */\nfunction phraseEndBonus(\n prev: Candidate | null,\n cur: Candidate,\n tonicPc: number,\n seg: Segment,\n melody: readonly MelodyNote[],\n): number {\n if (cur.rootPc !== tonicPc || seg.closingIndex === undefined) {\n return 0;\n }\n const closing = melody[seg.closingIndex];\n if (!closing || pitchClass(closing.pitch) !== tonicPc) {\n return 0;\n }\n const weight = Math.max(seg.weight, seg.beats);\n return (\n (PHRASE_TONIC + (prev !== null && isDominantOf(prev, tonicPc) ? PHRASE_AUTHENTIC : 0)) * weight\n );\n}\n\n/** Tolerance for comparing a phrase boundary with a segment boundary, in beats. */\nconst BEAT_EPS = 1e-9;\n\n/**\n * The beats the chord grid may change on: the harmonic rhythm's own boundaries,\n * plus every named phrase end that falls inside the melody.\n *\n * A named end lands on a boundary that is already there for most callers, and\n * naming one that is not adds it rather than moving the grid, so the slots after\n * a phrase end stay where the harmonic rhythm put them.\n */\nfunction gridBounds(\n segmentStart: number,\n hr: number,\n gridCount: number,\n ends: readonly number[],\n): number[] {\n const bounds: number[] = [];\n for (let s = 0; s <= gridCount; s += 1) {\n bounds.push(segmentStart + s * hr);\n }\n const last = segmentStart + gridCount * hr;\n for (const end of ends) {\n if (end > segmentStart + BEAT_EPS && end < last - BEAT_EPS) {\n bounds.push(end);\n }\n }\n bounds.sort((a, b) => a - b);\n return bounds.filter(\n (beat, index) => index === 0 || beat - (bounds[index - 1] ?? beat) > BEAT_EPS,\n );\n}\n\n/**\n * The note each named phrase closes on: the last one to begin before the beat\n * the phrase ends at.\n */\nfunction closingNoteIndices(\n spans: readonly { startBeat: number; endBeat: number }[],\n ends: readonly number[],\n): Set<number> {\n const closing = new Set<number>();\n for (const end of ends) {\n let index = -1;\n let start = Number.NEGATIVE_INFINITY;\n for (const [i, span] of spans.entries()) {\n if (span.startBeat < end - BEAT_EPS && span.startBeat >= start) {\n start = span.startBeat;\n index = i;\n }\n }\n if (index >= 0) {\n closing.add(index);\n }\n }\n return closing;\n}\n\n/** The index of the segment a beat sounds in: the last boundary at or before it. */\nfunction segmentIndexAt(bounds: readonly number[], beat: number): number {\n let low = 0;\n let high = bounds.length - 2;\n let found = 0;\n while (low <= high) {\n const mid = (low + high) >> 1;\n if ((bounds[mid] ?? 0) <= beat) {\n found = mid;\n low = mid + 1;\n } else {\n high = mid - 1;\n }\n }\n return found;\n}\n\n/** Whether two candidates are the same harmony, which is what holding a chord means. */\nfunction sameHarmony(a: Candidate, b: Candidate): boolean {\n return a.rootPc === b.rootPc && a.quality === b.quality;\n}\n\n/**\n * Segments that close and therefore have to cadence.\n *\n * The melody handed to the harmonizer closes at its end, so its last segment\n * always cadences. A caller who knows where the phrases of a longer line fall —\n * `phrasesFromTimeline` finds them — names their ends as beats, and the segment\n * each end falls in cadences too, so one call harmonizes the whole line instead\n * of the caller harmonizing each phrase and joining the results. Every cadence\n * term reads the boundaries from here.\n *\n * The named ends are returned apart from the melody's own close because they ask\n * for more than it does: naming a beat asks for a cadence there, and a cadence\n * is a chord change into the close.\n */\nfunction phraseEndSegments(\n segments: readonly Segment[],\n ends: readonly number[],\n): { closing: Set<number>; named: Set<number> } {\n const closing = new Set(segments.length > 0 ? [segments.length - 1] : []);\n const named = new Set<number>();\n for (const end of ends) {\n // The phrase closes in the segment its last beat sounds in, which is the\n // last segment beginning before that beat — a boundary landing exactly on a\n // segment start belongs to the phrase that ended, not the one starting.\n let index = -1;\n for (let s = 0; s < segments.length; s += 1) {\n if ((segments[s]?.startBeat ?? 0) < end - BEAT_EPS) {\n index = s;\n }\n }\n if (index >= 0) {\n closing.add(index);\n named.add(index);\n }\n }\n return { closing, named };\n}\n\n/** Run one Viterbi harmonization pass over a fixed melody and key. */\nfunction harmonizeOnce(\n melody: readonly MelodyNote[],\n key: KeyScale,\n candidates: Candidate[],\n segments: Segment[],\n jitter: number[],\n phraseEnds: ReadonlySet<number>,\n articulated: ReadonlySet<number>,\n): { cost: number; path: number[] } {\n const tonicPc = pitchClass(key.rootPc);\n const n = candidates.length;\n const candAt = (i: number): Candidate => candidates[i] ?? FALLBACK;\n const seg0 = segments[0];\n\n // A phrase one segment long has no approach chord, so its close is judged on\n // the chord alone.\n let dp = candidates.map(\n (c, ci) =>\n (seg0 ? emissionCost(seg0, c, melody, key) : 0) +\n (seg0 && phraseEnds.has(0) ? phraseEndBonus(null, c, tonicPc, seg0, melody) : 0) +\n (jitter[ci] ?? 0),\n );\n const back: number[][] = [];\n\n for (let s = 1; s < segments.length; s += 1) {\n const seg = segments[s];\n if (!seg) {\n continue;\n }\n const closes = phraseEnds.has(s);\n // A beat the caller named as a phrase end is a cadence point, and a cadence\n // is harmonic motion into the close: the chord under it is not the chord\n // that was already sounding. The melody's own close is not held to this —\n // naming a beat is what asks for it — so a call that names none is scored\n // exactly as it was.\n const articulates = articulated.has(s);\n const next: number[] = [];\n const ptr: number[] = [];\n for (let c = 0; c < n; c += 1) {\n let best = Number.POSITIVE_INFINITY;\n let bestPrev = 0;\n for (let p = 0; p < n; p += 1) {\n if (articulates && sameHarmony(candAt(p), candAt(c))) {\n continue;\n }\n // The cadence bonus is part of the step into a phrase-final segment, not\n // an afterthought added to the finished path, so the approach chord is\n // chosen for the cadence it makes.\n const cost =\n (dp[p] ?? Number.POSITIVE_INFINITY) +\n transitionCost(candAt(p), candAt(c), tonicPc) * seg.beats +\n (closes ? phraseEndBonus(candAt(p), candAt(c), tonicPc, seg, melody) : 0);\n if (cost < best) {\n best = cost;\n bestPrev = p;\n }\n }\n next[c] = best + emissionCost(seg, candAt(c), melody, key) + (jitter[c] ?? 0);\n ptr[c] = bestPrev;\n }\n dp = next;\n back.push(ptr);\n }\n\n let bestCost = Number.POSITIVE_INFINITY;\n let bestEnd = 0;\n for (let c = 0; c < n; c += 1) {\n const total = dp[c] ?? Number.POSITIVE_INFINITY;\n if (total < bestCost) {\n bestCost = total;\n bestEnd = c;\n }\n }\n\n const path: number[] = new Array<number>(segments.length).fill(bestEnd);\n for (let s = segments.length - 2; s >= 0; s -= 1) {\n const ptr = back[s];\n const nextIdx = path[s + 1] ?? bestEnd;\n path[s] = ptr ? (ptr[nextIdx] ?? bestEnd) : bestEnd;\n }\n return { cost: bestCost, path };\n}\n\n/**\n * Harmonize a melody with a min-cost chord progression.\n *\n * The melody's ornaments are classified first, from the melody and the metre\n * alone (see {@link classifyMelodyTones}); the chords then have to explain the\n * structural tones only, so a passing tone no longer buys itself a chord. The\n * melody is segmented by `harmonicRhythm`; each segment is scored against the\n * candidate chords the harmonic dial opens — the key's own triads, then the\n * secondary dominants, then the parallel mode's chords, one at a time as\n * `ctx.complexity.harmonic` rises from 0 to 1 — by the fit of those structural\n * tones, and a Viterbi search picks the lowest-cost path using a\n * functional-flow transition cost and a cadence bonus at the end of the melody\n * it is given — one cadence, at the close, unless `phraseEnds` names the closes\n * inside a longer line, and then one at each of them as well. Runs of the same\n * chord are reported once, so the chord count follows the harmony rather than\n * the grid.\n *\n * With `placement.transposeSearch` the melody is moved into the key it is\n * harmonized in, and `transposeSemitones` reports how far; with\n * `placement.octaveSearch` it is moved by octaves into a comfortable register,\n * which leaves the key and the chords untouched. Both may be set, and the\n * reported shift is their sum.\n *\n * The `seed` drives a deterministic tie-break only: it perturbs candidates by a\n * magnitude far below any real cost difference (see `TIE_BREAK_JITTER`),\n * so it can decide between chords or paths of otherwise-equal cost but never\n * overrides melody fit or functional flow. For a well-determined melody the\n * result is identical across seeds; the same seed always yields the same result.\n *\n * @param opts The melody and, optionally, the generation context, key, harmonic\n * rhythm, reharmonization strength, placement search, meter, and seed. Only\n * `melody` is required.\n * @returns The chosen transpose, key, chord path, and per-note roles.\n * @example\n * ```ts\n * import { harmonizeMelody } from '@libraz/libcantus';\n * const result = harmonizeMelody({\n * melody: [\n * { pitch: 60, startBeat: 0, durationBeat: 1 },\n * { pitch: 64, startBeat: 1, durationBeat: 1 },\n * ],\n * });\n * result.chords; // one ChordSpan per chord change, on the harmonic-rhythm grid\n * ```\n * @category Reharmonization\n */\nexport function harmonizeMelody(opts: HarmonizeOptions): HarmonizeResult {\n assertNoteEvents(opts.melody, 'harmonize melody', { allowNonPositiveDuration: true });\n const phraseEnds = opts.phraseEnds ?? [];\n for (const end of phraseEnds) {\n assertFiniteNumber(end, 'harmonize phrase end');\n }\n const soundingMelody = soundingNotesOnly(opts.melody);\n const ts = opts.ts ?? DEFAULT_METER;\n assertTimeSignature(ts);\n const requestedKey = opts.key ?? 'infer';\n // The key is read into its plain form once, here at the boundary; the search\n // below is given the scale it resolved to.\n const key = requestedKey === 'infer' ? inferKey(soundingMelody) : toKeyScale(requestedKey);\n const placement = opts.placement ?? DEFAULT_PLACEMENT;\n const ctx = resolveContext(opts.ctx);\n // `reharmonize` names three points on the dial the context sets continuously,\n // so a caller who says `'secondaryDominant'` gets exactly the vocabulary that\n // name has always meant.\n const harmonic = ctx.harmonic ?? REHARMONIZE_DIAL[opts.reharmonize ?? 'diatonic'];\n // Nothing to harmonize: inventing a tonic bar here would silently insert a\n // ghost chord into a chart built by harmonizing sections and concatenating\n // them. The sibling generators return an empty result for empty input too.\n if (soundingMelody.length === 0) {\n return { transposeSemitones: 0, key, chords: [], melodyRoles: [] };\n }\n const candidates = buildCandidates(key, harmonic);\n const candAt = (i: number): Candidate => candidates[i] ?? FALLBACK;\n const draw = ctx.part('harmony');\n const jitter = candidates.map((_, index) => draw.at('jitter', index) * TIE_BREAK_JITTER);\n\n // Read every onset and release as the metric position it is playing. A melody\n // arrives as note events from a DAW or MIDI track, so its beats carry the\n // timing of a performance; without this a few milliseconds of jitter would put\n // a note in the slot before the one it belongs to, weigh a sliver of it as if\n // it sounded there, and change the chords chosen for the whole phrase.\n const pulse = pulseBeats(ts);\n const spans = soundingMelody.map((note) => ({\n startBeat: snapToPulse(note.startBeat, pulse),\n endBeat: snapToPulse(note.startBeat + note.durationBeat, pulse),\n }));\n\n const melodyStart = spans.reduce(\n (start, span) => Math.min(start, span.startBeat),\n Number.POSITIVE_INFINITY,\n );\n const melodyEnd = spans.reduce((end, span) => Math.max(end, span.endBeat), 0);\n // Any positive value is honoured: a silent clamp would make the option mean\n // something different here than it does on the analysis side. A value small\n // enough to make the search explode is caught by the budget assertions below,\n // not rounded away.\n const hr = assertRange(\n opts.harmonicRhythm ?? defaultHarmonicRhythm(ts),\n Number.MIN_VALUE,\n Number.MAX_SAFE_INTEGER,\n 'harmonic rhythm',\n );\n const segmentStart = Math.floor(melodyStart / hr) * hr;\n const gridCount = Math.max(1, Math.ceil((melodyEnd - segmentStart) / hr));\n assertGenerationBudget(gridCount, 'harmonic segments');\n // The grid the search may change chords on: the harmonic rhythm, anchored at\n // the melody's first slot boundary, divided again at every beat the caller\n // named as a phrase end. A named end falling inside a slot cuts it, so the\n // slot a phrase closes in ends where the phrase does instead of running on\n // into the next one, and the grid then resumes on its own boundaries — which\n // is what keeps the chord changes after the boundary on the barline.\n const bounds = gridBounds(segmentStart, hr, gridCount, phraseEnds);\n const segCount = bounds.length - 1;\n assertGenerationBudget(segCount, 'harmonic segments');\n const segments: Segment[] = Array.from({ length: segCount }, (_, s) => {\n const startBeat = bounds[s] ?? segmentStart;\n const endBeat = bounds[s + 1] ?? startBeat + hr;\n return {\n startBeat,\n endBeat,\n beats: endBeat - startBeat,\n noteIndices: [],\n costNotes: [],\n weight: 0,\n };\n });\n // Associate each note only with the windows it actually spans: each note is\n // visited once per window it covers, and no temporary note objects are\n // allocated.\n let memberships = 0;\n for (const [index, span] of spans.entries()) {\n const first = segmentIndexAt(bounds, span.startBeat);\n let lastExclusive = first;\n while (\n lastExclusive < segCount &&\n (bounds[lastExclusive] ?? 0) < span.endBeat - Number.EPSILON\n ) {\n lastExclusive += 1;\n }\n memberships += Math.max(0, lastExclusive - first);\n assertGenerationBudget(memberships, 'note-to-segment memberships');\n for (let segment = first; segment < lastExclusive; segment += 1) {\n segments[segment]?.noteIndices.push(index);\n }\n }\n\n // Classify the ornaments before any chord exists, and keep only the structural\n // tones in each slot's emission term. Transposing a melody moves every note\n // alike, so the figures are the same at every placement and are found once —\n // and so is the metric weight of each note in each slot it sounds in.\n const tones = classifyMelodyTones(soundingMelody, ts);\n // The classifier reads a note against the notes on either side of it, and\n // knows nothing of phrases: the melody's own last note is structural because\n // nothing follows it, but the note closing a phrase inside the line has the\n // next phrase's first note after it and is heard as an ornament of it — a\n // close on the tonic between two supertonics reads as a lower neighbour. A\n // phrase's last note is structural for the same reason the melody's last note\n // is, so the closes the caller named are put back.\n const phraseClosingNotes = closingNoteIndices(spans, phraseEnds);\n for (const segment of segments) {\n const structural = segment.noteIndices.filter(\n (idx) => tones[idx]?.ornamental !== true || phraseClosingNotes.has(idx),\n );\n const costIndices = structural.length > 0 ? structural : segment.noteIndices;\n let closingStart = Number.NEGATIVE_INFINITY;\n for (const index of costIndices) {\n const span = spans[index];\n if (!span) {\n continue;\n }\n const overlapStart = Math.max(span.startBeat, segment.startBeat);\n const overlap = Math.min(span.endBeat, segment.endBeat) - overlapStart;\n if (overlap <= 0) {\n continue;\n }\n const weight = Math.max(0.25, overlap);\n segment.costNotes.push({ index, weight, strong: isStrongBeat(overlapStart, ts) });\n segment.weight += weight;\n // The tone a phrase closes on is the last one to start, which is not the\n // last one listed when a note held from earlier is still sounding under it.\n if (span.startBeat >= closingStart) {\n closingStart = span.startBeat;\n segment.closingIndex = index;\n }\n }\n }\n\n const { closing: closingSegments, named: namedEndSegments } = phraseEndSegments(\n segments,\n phraseEnds,\n );\n // A cadence needs two chords to be told apart, so the articulation rule can\n // only be applied where the vocabulary offers a second harmony to move to.\n const articulatedSegments =\n new Set(candidates.map((c) => `${c.rootPc}:${c.quality}`)).size > 1\n ? namedEndSegments\n : new Set<number>();\n\n // Two independent axes: the semitone shift that puts the melody in the key it\n // is harmonized in, and the octave that puts it in a comfortable register.\n // Octaves preserve pitch classes, so they can only move the register.\n const semitoneShifts: number[] = [0];\n if (placement.transposeSearch) {\n for (let s = -6; s <= 6; s += 1) {\n if (s !== 0) {\n semitoneShifts.push(s);\n }\n }\n }\n const octaveShifts: number[] = placement.octaveSearch ? [0, -12, 12] : [0];\n const transposes = semitoneShifts.flatMap((s) => octaveShifts.map((o) => s + o));\n assertGenerationBudget(\n transposes.length * segCount * candidates.length * candidates.length,\n 'harmonization placement search',\n );\n\n let bestCost = Number.POSITIVE_INFINITY;\n let bestTs = 0;\n let bestPath: number[] = [];\n for (const shift of transposes) {\n const shifted = soundingMelody.map((n) => ({ ...n, pitch: n.pitch + shift }));\n const { cost, path } = harmonizeOnce(\n shifted,\n key,\n candidates,\n segments,\n jitter,\n closingSegments,\n articulatedSegments,\n );\n const total = cost + keyFitCost(shifted, key) + tessituraCost(shifted);\n if (total < bestCost) {\n bestCost = total;\n bestTs = shift;\n bestPath = path;\n }\n }\n\n const chords = mergeRepeats(\n bestPath.map((ci, s) => {\n const cand = candAt(ci);\n const chord: ChordSpan = {\n rootPc: cand.rootPc,\n quality: cand.quality,\n startBeat: segments[s]?.startBeat ?? segmentStart + s * hr,\n };\n if (cand.degree !== undefined) {\n chord.degree = cand.degree;\n }\n if (cand.secondaryDominant) {\n chord.secondaryDominant = true;\n }\n return chord;\n }),\n );\n\n const melodyRoles = opts.melody.map((note, noteIndex) => {\n const segIdx = segmentIndexAt(bounds, note.startBeat);\n const cand = candAt(bestPath[segIdx] ?? 0);\n const chord: Chord = makeChord(cand.rootPc, cand.quality);\n return { noteIndex, role: roleOf(note.pitch + bestTs, chord).role };\n });\n\n return { transposeSemitones: bestTs, key, chords, melodyRoles };\n}\n","import type { ChordTimeline } from '../../analyze/timeline/index.js';\nimport { InvalidInputError } from '../../core/errors/index.js';\nimport type { TimeSignature } from '../../core/meter/index.js';\nimport { beatsPerBar } from '../../core/meter/index.js';\nimport { pitchClassOf as pitchClass } from '../../core/pitch/index.js';\nimport type { KeyScale, NoteEvent } from '../../core/types.js';\nimport {\n assertFiniteNumber,\n assertGenerationBudget,\n assertInteger,\n assertNoteEvents,\n assertPositiveInt,\n clampToMidi,\n} from '../../core/validation/index.js';\nimport type { Chord } from '../../theory/chord/index.js';\nimport { chordPitchClasses } from '../../theory/chord/index.js';\nimport {\n isScaleTone,\n type KeyLike,\n shiftByScaleDegrees,\n toKeyScale,\n} from '../../theory/scale/index.js';\nimport { type ChordLike, toChordData } from '../../theory/symbol/index.js';\nimport { type GenerationContextInput, resolveContextWith } from '../context/index.js';\n\n/**\n * A transformation applicable to a motif cell.\n *\n * @category Composition\n */\nexport type MotifTransform =\n | 'transposeDiatonic'\n | 'transposeChromatic'\n | 'invert'\n | 'retrograde'\n | 'augment'\n | 'diminish'\n | 'sequence';\n\n/**\n * A single note within a motif cell.\n *\n * @category Composition\n */\nexport type MotifNote = {\n pitch: number;\n startBeat: number;\n durationBeat: number;\n};\n\n/**\n * A short melodic cell.\n *\n * @category Composition\n */\nexport type MotifCell = {\n notes: MotifNote[];\n};\n\n/**\n * Melodic contour shape for {@link generateMotif}.\n *\n * @category Composition\n */\nexport type MotifContour = 'arch' | 'ascending' | 'descending' | 'wave';\n\n/**\n * Options controlling {@link generateMotif}.\n *\n * @category Composition\n */\nexport type MotifOptions = {\n /** The key the line is written in; a key name such as `'C major'` is read as that key. */\n key: KeyLike;\n /**\n * Chord the downbeats are pulled to, if any; it may be written as a chord\n * symbol as well as given as chord data.\n */\n chord?: ChordLike | null;\n /** Length of the motif in bars of `ts`. */\n bars: number;\n /**\n * Meter the bars are counted in. It sets the bar length, so a motif shares a\n * bar grid with the other generators instead of assuming four beats.\n *\n * @defaultValue 4/4\n */\n ts?: TimeSignature;\n /**\n * Melodic contour shape the line follows.\n *\n * @defaultValue `'arch'`\n */\n contour?: MotifContour;\n /**\n * Probability in [0, 1] that a note is nudged by a single diatonic step. The\n * nudge direction is balanced (up or down with equal odds), so it adds\n * variety without biasing the line off its contour. Default 0, which\n * reproduces the requested contour exactly (no drift, tails return to tonic).\n *\n * Sugar for `ctx: { complexity: { ornament } }` — a nudge off the contour is\n * decoration rather than rhythm; the context wins where both are given. Being\n * the same dial, it takes the same values: one outside [0, 1] is rejected here\n * exactly as it is there, rather than being folded to the nearest end.\n *\n * @defaultValue 0\n * @throws If it is not a finite number in [0, 1].\n */\n jitter?: number;\n /**\n * The generation context. Its `complexity.ornament` is this generator's\n * jitter, and its `seed` fixes which notes are nudged.\n *\n * @defaultValue `{ seed: 0 }`\n */\n ctx?: GenerationContextInput;\n};\n\n/**\n * Smallest span, in beats, treated as a distinct tile step. A positive but\n * near-zero cell span is clamped to this so tiling cannot stall.\n */\nconst MIN_TILE_SPAN = 1 / 256;\n\n/** Meter assumed when none is supplied. */\nconst DEFAULT_TS: TimeSignature = { numerator: 4, denominator: 4 };\n\n/** Tolerance for beat-position comparisons. */\nconst EPS = 1e-9;\n\n/** Whether a beat position falls on a bar line of a `barBeats`-long bar. */\nfunction isDownbeat(startBeat: number, barBeats: number): boolean {\n const bar = startBeat / barBeats;\n return Math.abs(bar - Math.round(bar)) < EPS;\n}\n\n/** Nearest chord tone to `pitch`, preferring the lower pitch on a tie. */\nfunction nearestChordTone(pitch: number, chord: Chord): number {\n const pcs = chordPitchClasses(chord);\n for (let d = 0; d <= 12; d += 1) {\n if (pcs.includes(pitchClass(pitch - d))) {\n return pitch - d;\n }\n if (pcs.includes(pitchClass(pitch + d))) {\n return pitch + d;\n }\n }\n return pitch;\n}\n\n/** Widest move, in semitones, a developed note is displaced by. */\nconst MAX_DEVELOP_SHIFT = 12;\n\n/**\n * Pick the pitch a developed note takes.\n *\n * The note moves to the nearest pitch its position allows — a chord tone where\n * the position carries structural weight, any tone of the key elsewhere — and\n * skips a pitch a different note of the same tile already took, so two pitches\n * that differ in the cell still differ in the development. A tie is broken in\n * the direction the melody was already moving, which keeps a step a step\n * instead of folding it back onto the note before it.\n *\n * @param pitch The note's pitch in the cell.\n * @param allows Whether a candidate pitch is one this position may take.\n * @param direction Sign of the melodic motion into the note.\n * @param taken Pitches already placed in this tile, by the cell pitch that took\n * them.\n * @returns The developed pitch, or `pitch` when the position allows nothing\n * within reach.\n */\nfunction developedPitch(\n pitch: number,\n allows: (candidate: number) => boolean,\n direction: number,\n taken: Map<number, number>,\n): number {\n const ahead = direction >= 0 ? 1 : -1;\n for (let d = 0; d <= MAX_DEVELOP_SHIFT; d += 1) {\n for (const candidate of d === 0 ? [pitch] : [pitch + ahead * d, pitch - ahead * d]) {\n if (candidate < 0 || candidate > 127 || !allows(candidate)) {\n continue;\n }\n const owner = taken.get(candidate);\n if (owner === undefined || owner === pitch) {\n return candidate;\n }\n }\n }\n return pitch;\n}\n\n/** Earliest onset in a cell; the time origin for timing transforms. */\nfunction cellOrigin(notes: MotifNote[]): number {\n let origin = Number.POSITIVE_INFINITY;\n for (const note of notes) {\n origin = Math.min(origin, note.startBeat);\n }\n return Number.isFinite(origin) ? origin : 0;\n}\n\n/** Latest offset in a cell, without spreading an unbounded caller array. */\nfunction cellEnd(notes: MotifNote[]): number {\n let end = Number.NEGATIVE_INFINITY;\n for (const note of notes) {\n end = Math.max(end, note.startBeat + note.durationBeat);\n }\n return Number.isFinite(end) ? end : 0;\n}\n\n/** Total beat span covered by a cell (from first onset to last offset). */\nfunction cellSpan(cell: MotifCell): number {\n if (cell.notes.length === 0) {\n return 0;\n }\n const start = cellOrigin(cell.notes);\n const end = cellEnd(cell.notes);\n return end - start;\n}\n\nfunction clone(cell: MotifCell): MotifCell {\n return { notes: cell.notes.map((n) => ({ ...n })) };\n}\n\n/** Diatonic scale-degree offsets shaping a contour of `count` notes. */\nfunction contourOffsets(contour: MotifContour, count: number): number[] {\n const offsets: number[] = [];\n for (let i = 0; i < count; i += 1) {\n switch (contour) {\n case 'ascending':\n offsets.push(i);\n break;\n case 'descending':\n offsets.push(-i);\n break;\n case 'arch':\n // Symmetric rise-and-fall: a palindrome that starts and ends on the\n // tonic (offset 0) for both odd and even lengths.\n offsets.push(Math.min(i, count - 1 - i));\n break;\n case 'wave':\n offsets.push([0, 1, 0, -1][i % 4] ?? 0);\n break;\n }\n }\n return offsets;\n}\n\n/**\n * Generate a short seed motif over `bars`.\n *\n * The melody follows the requested contour in diatonic steps from the tonic,\n * snapped to the key. When a chord is supplied, notes landing on bar downbeats\n * are pulled to the nearest chord tone. An optional `jitter` adds balanced\n * per-note variation without biasing the contour; it is off by default, so the\n * contour is reproduced exactly. Output is deterministic for a given seed.\n *\n * @param opts Key, optional chord, length, contour, jitter, and context.\n * @returns The generated motif cell.\n *\n * @example\n * ```ts\n * import { generateMotif, majorKey } from '@libraz/libcantus';\n * const cell = generateMotif({ key: majorKey(0), bars: 2, contour: 'arch' });\n * // cell.notes is a deterministic MotifCell (seed defaults to 0)\n * ```\n *\n * @category Composition\n */\nexport function generateMotif(opts: MotifOptions): MotifCell {\n const contour = opts.contour ?? 'arch';\n const bars = assertPositiveInt(opts.bars, 'motif bars');\n const barBeats = beatsPerBar(opts.ts ?? DEFAULT_TS);\n const totalBeats = bars * barBeats;\n const noteCount = Math.max(3, bars * 2);\n assertGenerationBudget(noteCount, 'motif notes');\n const beatsPerNote = totalBeats / noteCount;\n // The dial is handed over as it was given: `jitter` is the sugar for the\n // context's ornament dial, so it has to accept and reject exactly what the\n // context does. Clamping here would make the sugar take a value its own\n // desugared form refuses, which is a different option wearing the same name.\n const ctx = resolveContextWith(opts.ctx, { ornament: opts.jitter });\n const jitterProb = ctx.ornament ?? 0;\n const draw = ctx.part('motif');\n // Key and chord are read into their plain form once, here at the boundary;\n // the loop below works on the plain forms alone.\n const key = toKeyScale(opts.key);\n const chord = opts.chord ? toChordData(opts.chord) : null;\n const tonic = pitchClass(key.rootPc) + 60;\n const offsets = contourOffsets(contour, noteCount);\n\n const notes: MotifNote[] = [];\n for (let i = 0; i < noteCount; i += 1) {\n // Opt-in, direction-balanced jitter: disabled by default so the contour is\n // preserved exactly. When enabled it nudges up or down with equal odds,\n // avoiding the upward bias that used to drift arch/wave tails off the\n // tonic. Each note's draw belongs to its own index, so raising the dial\n // nudges further notes while the ones already nudged keep their nudge.\n let jitter = 0;\n if (draw.prob(jitterProb, 'jitter', i)) {\n jitter = draw.prob(0.5, 'direction', i) ? 1 : -1;\n }\n let pitch = shiftByScaleDegrees(tonic, (offsets[i] ?? 0) + jitter, key);\n const startBeat = i * beatsPerNote;\n if (chord && isDownbeat(startBeat, barBeats)) {\n pitch = nearestChordTone(pitch, chord);\n }\n notes.push({\n pitch: clampToMidi(pitch, 'generated motif pitch'),\n startBeat,\n durationBeat: beatsPerNote,\n });\n }\n return { notes };\n}\n\n/**\n * Turn a motif cell into note events.\n *\n * A {@link MotifNote} already carries the {@link NoteEvent} fields, but a cell\n * wraps them in an object; this unwraps it, so a motif can be humanized,\n * grooved, or written out like any other line.\n *\n * @param cell The motif to convert.\n * @returns The cell's notes, copied, in the order they are stored.\n * @example\n * ```ts\n * import { generateMotif, humanize, majorKey, motifToNoteEvents } from '@libraz/libcantus';\n * humanize(motifToNoteEvents(generateMotif({ key: majorKey(0), bars: 2 })));\n * ```\n * @category Composition\n */\nexport function motifToNoteEvents(cell: MotifCell): NoteEvent[] {\n return cell.notes.map((note) => ({ ...note }));\n}\n\n/**\n * Apply a transformation to a motif cell.\n *\n * `invert` reflects pitches about the first note (chromatic, self-inverse);\n * `retrograde` mirrors onsets about the cell span, preserving rests (self-inverse);\n * `augment`/`diminish` scale time by `amount ?? 2` and its reciprocal;\n * `transposeChromatic` adds `amount` semitones; `transposeDiatonic` shifts by\n * `amount` scale degrees when a `key` is given, or — with no `key` — falls back\n * to a chromatic shift of `amount` semitones; `sequence` appends a shifted copy\n * (by `amount` diatonic degrees with a `key`, or by `amount` semitones without\n * one, since it delegates to `transposeDiatonic`).\n *\n * @param cell The cell to transform.\n * @param t The transformation.\n * @param amount Optional parameter (semitones, degrees, or time factor).\n * @param key Key context for the diatonic transforms, as a key name such as\n * `'C major'` or as a key/scale; without it, `transposeDiatonic` and\n * `sequence` shift chromatically by semitones.\n * @returns The transformed cell.\n * @throws If a transformed pitch would fall outside the MIDI range 0..127.\n *\n * @example\n * ```ts\n * import { generateMotif, transformMotif, majorKey } from '@libraz/libcantus';\n * const cell = generateMotif({ key: majorKey(0), bars: 1 });\n * const inverted = transformMotif(cell, 'invert');\n * ```\n *\n * @category Composition\n */\nexport function transformMotif(\n cell: MotifCell,\n t: MotifTransform,\n amount?: number,\n key?: KeyLike,\n): MotifCell {\n // The key is read into its plain form once, here at the boundary; `sequence`\n // re-enters through `transformCell` so it is never read a second time.\n return transformCell(cell, t, amount, key === undefined ? undefined : toKeyScale(key));\n}\n\n/** The transform on an already-resolved key, checked. */\nfunction transformCell(\n cell: MotifCell,\n t: MotifTransform,\n amount?: number,\n key?: KeyScale,\n): MotifCell {\n return assertInRange(transformUnchecked(cell, t, amount, key));\n}\n\n/**\n * Reject a cell whose pitches have left the MIDI range.\n *\n * Transposing far enough, or inverting about a low pivot, walks a motif off\n * either end of the range. Returning such a note would carry an unplayable\n * pitch through `developMotif` and into whatever consumes the result, so it is\n * a caller error rather than something to clamp — clamping would distort the\n * very intervals the transform exists to preserve.\n */\nfunction assertInRange(cell: MotifCell): MotifCell {\n for (const note of cell.notes) {\n if (note.pitch < 0 || note.pitch > 127) {\n throw new InvalidInputError(\n `transformed motif pitch ${note.pitch} is outside the MIDI range 0..127`,\n );\n }\n }\n return cell;\n}\n\n/** The transform itself, before the range check. */\nfunction transformUnchecked(\n cell: MotifCell,\n t: MotifTransform,\n amount?: number,\n key?: KeyScale,\n): MotifCell {\n assertNoteEvents(cell.notes, 'motif notes');\n if (amount !== undefined) {\n assertFiniteNumber(amount, 'motif transform amount');\n }\n if (\n (t === 'transposeChromatic' || t === 'transposeDiatonic' || t === 'sequence') &&\n amount !== undefined\n ) {\n assertInteger(\n amount,\n `${t === 'transposeChromatic' ? 'chromatic' : 'diatonic'} transform amount`,\n );\n }\n if ((t === 'augment' || t === 'diminish') && amount !== undefined && amount <= 0) {\n throw new InvalidInputError('time transform amount must be positive');\n }\n const notes = cell.notes;\n switch (t) {\n case 'transposeChromatic': {\n const semis = amount ?? 0;\n return { notes: notes.map((n) => ({ ...n, pitch: n.pitch + semis })) };\n }\n case 'transposeDiatonic': {\n const degrees = amount ?? 1;\n return {\n notes: notes.map((n) => ({\n ...n,\n // With a key, shift by scale degrees; without one, fall back to a\n // chromatic shift of `degrees` semitones. A pitch between two scale\n // tones keeps its distance above the one below it, so the chromatic\n // notes a motif is written with — a blues lick, a semitone neighbour —\n // come through as themselves, and shifting by no degrees returns the\n // cell it was given.\n pitch: key ? shiftByScaleDegrees(n.pitch, degrees, key) : n.pitch + degrees,\n })),\n };\n }\n case 'invert': {\n const pivot = notes[0]?.pitch ?? 0;\n return { notes: notes.map((n) => ({ ...n, pitch: 2 * pivot - n.pitch })) };\n }\n case 'retrograde': {\n if (notes.length === 0) {\n return { notes: [] };\n }\n const start = cellOrigin(notes);\n const end = cellEnd(notes);\n return {\n notes: notes.map((n) => ({\n pitch: n.pitch,\n startBeat: start + (end - (n.startBeat + n.durationBeat)),\n durationBeat: n.durationBeat,\n })),\n };\n }\n case 'augment':\n return scaleTime(cell, amount ?? 2);\n case 'diminish':\n return scaleTime(cell, 1 / (amount ?? 2));\n case 'sequence': {\n const degrees = amount ?? 2;\n const span = cellSpan(cell);\n // Delegates to transposeDiatonic: a diatonic shift when `key` is given,\n // otherwise a chromatic shift of `degrees` semitones.\n const copy = transformCell(cell, 'transposeDiatonic', degrees, key);\n const shifted = copy.notes.map((n) => ({ ...n, startBeat: n.startBeat + span }));\n return { notes: [...clone(cell).notes, ...shifted] };\n }\n }\n}\n\n/** Scale a cell's timing about its origin by `factor`. */\nfunction scaleTime(cell: MotifCell, factor: number): MotifCell {\n const origin = cellOrigin(cell.notes);\n return {\n notes: cell.notes.map((n) => ({\n pitch: n.pitch,\n startBeat: origin + (n.startBeat - origin) * factor,\n durationBeat: n.durationBeat * factor,\n })),\n };\n}\n\n/**\n * Lay a motif across `bars` and snap it to a chord timeline.\n *\n * The cell is tiled back-to-back to fill the requested span. Notes that carry\n * structural weight — the head of each tile and every bar line — are pulled to\n * the nearest chord tone of the segment sounding at that onset, so the\n * developed line spells the underlying harmony. The notes between them are kept\n * in the key as passing and neighbour tones, and two pitches that differ in the\n * cell still differ in the development, so the development is still recognizably\n * the motif rather than a chord arpeggiated over the cell's rhythm.\n *\n * @param cell The source motif.\n * @param timeline Chord segments to snap against.\n * @param key Key context, as a key name such as `'C major'` or as a key/scale;\n * the notes off the structural positions are kept in it.\n * @param bars Number of bars to fill.\n * @param ts Meter the bars are counted in; defaults to 4/4.\n * @returns The developed, harmony-aware cell.\n *\n * @example\n * ```ts\n * import { chordTimelineFromChords, developMotif, generateMotif, majorKey } from '@libraz/libcantus';\n * const key = majorKey(0);\n * const timeline = chordTimelineFromChords([{ rootPc: 0, quality: 'maj', startBeat: 0 }], 8);\n * const developed = developMotif(generateMotif({ key, bars: 1 }), timeline, key, 2);\n * developed.notes.map((n) => n.pitch); // [60, 62, 60, 60, 62, 60] — the C-D-C cell, twice\n * ```\n *\n * @category Composition\n */\nexport function developMotif(\n cell: MotifCell,\n timeline: ChordTimeline,\n key: KeyLike,\n bars: number,\n ts: TimeSignature = DEFAULT_TS,\n): MotifCell {\n assertPositiveInt(bars, 'development bars');\n assertNoteEvents(cell.notes, 'motif notes');\n // The key is read into its plain form once, here at the boundary; the tiling\n // below is given the scale it resolved to.\n const scale = toKeyScale(key);\n const span = cellSpan(cell);\n const barBeats = beatsPerBar(ts);\n const totalBeats = bars * barBeats;\n const origin = cellOrigin(cell.notes);\n const out: MotifNote[] = [];\n\n if (span <= 0) {\n return clone(cell);\n }\n\n // Clamp a degenerate near-zero span so the tile count stays finite, and step\n // the offset multiplicatively so rounding error cannot accumulate over tiles.\n const tileSpan = Math.max(span, MIN_TILE_SPAN);\n const tileCount = Math.ceil(totalBeats / tileSpan);\n assertGenerationBudget(tileCount * cell.notes.length, 'developed motif notes');\n for (let k = 0; k < tileCount; k += 1) {\n const offset = k * tileSpan;\n // Placed pitch by cell pitch, so a tile cannot fold two of the cell's\n // pitches onto one. It is per tile: each tile is the same cell again, and\n // the same cell pitch under the same harmony wants the same answer.\n const taken = new Map<number, number>();\n let previous: number | undefined;\n for (const n of cell.notes) {\n const startBeat = offset + (n.startBeat - origin);\n if (startBeat >= totalBeats) {\n continue;\n }\n const chord = timeline.at(startBeat);\n // The head of the cell and the bar lines are where the ear reads the\n // harmony; everything else is free to pass between them.\n const structural = n.startBeat === origin || isDownbeat(startBeat, barBeats);\n const chordTones = chord && structural ? chordPitchClasses(chord) : null;\n const allows = chordTones\n ? (candidate: number) => chordTones.includes(pitchClass(candidate))\n : (candidate: number) => isScaleTone(candidate, scale);\n const direction = previous === undefined ? 0 : n.pitch - previous;\n const pitch = developedPitch(n.pitch, allows, direction, taken);\n taken.set(pitch, n.pitch);\n previous = n.pitch;\n out.push({ pitch, startBeat, durationBeat: n.durationBeat });\n }\n }\n return { notes: out };\n}\n","/**\n * Ornaments applied to material that already exists.\n *\n * Decoration is not a property of generation: this layer takes notes and gives\n * notes back, the way {@link humanize} does, so a line from any source — a\n * generator, a MIDI import, a caller's own array — can be decorated without\n * being generated again by whichever generator happens to write ghost notes.\n *\n * The decoration is carried as an {@link Articulation}, not baked into extra\n * onsets: a flam is one note marked `'flam'`, so the grace stroke stays the\n * reader's to voice and nothing downstream has to guess which of two onsets was\n * the ornament.\n */\n\nimport type { Articulation } from '../../core/instrument/index.js';\nimport { isStrongBeat, type TimeSignature } from '../../core/meter/index.js';\nimport type { NoteEvent } from '../../core/types.js';\nimport {\n assertNoteEvents,\n assertOneOf,\n assertRange,\n assertTimeSignature,\n dropSilentNotes,\n} from '../../core/validation/index.js';\nimport {\n type GenerationContextInput,\n resolveContextWith,\n sustainsStrokes,\n} from '../context/index.js';\n\n/**\n * Every ornament idiom, in declaration order.\n *\n * @category Composition\n */\nexport const ORNAMENT_STYLES = Object.freeze(['ghost', 'flam', 'drag', 'slide', 'accent'] as const);\n\n/**\n * The kind of ornament to apply.\n *\n * - `'ghost'`: soften weak-position notes into ghosted strokes.\n * - `'flam'`: mark accented notes as flammed — the strong positions `'accent'`\n * also takes, voiced as a grace stroke instead of a velocity lift.\n * - `'drag'`: mark notes leading into a strong position as dragged. A weak-position\n * note qualifies only when the next onset in the material falls on a strong one.\n * - `'slide'`: mark a note reached by a step or a leap as slid into.\n * - `'accent'`: mark strong-position notes as accented, and lift their velocity.\n *\n * @category Composition\n */\nexport type OrnamentStyle = (typeof ORNAMENT_STYLES)[number];\n\n/**\n * Options controlling {@link ornament}.\n *\n * @category Composition\n */\nexport type OrnamentOptions = {\n /**\n * The ornament to apply.\n *\n * @defaultValue `'ghost'`\n */\n style?: OrnamentStyle;\n /**\n * How much of the eligible material is decorated, in [0, 1]. Raising it only\n * ornaments more notes; the notes already ornamented keep the ornament they\n * had. Defaults to `complexity.ornament` from the context, or 0.5.\n */\n amount?: number;\n /**\n * Time signature, used to tell strong positions from weak ones.\n *\n * @defaultValue 4/4\n */\n ts?: TimeSignature;\n /**\n * The generation context. Its `complexity.ornament` sets the amount, its\n * `complexity.difficulty` — with a tempo — keeps ornaments off passages too\n * fast to play them in, and its `seed` fixes which notes are decorated.\n *\n * @defaultValue `{ seed: 0 }`\n */\n ctx?: GenerationContextInput;\n};\n\nconst DEFAULT_TS: TimeSignature = { numerator: 4, denominator: 4 };\nconst DEFAULT_STYLE: OrnamentStyle = 'ghost';\nconst DEFAULT_AMOUNT = 0.5;\n/** Velocity a ghosted stroke keeps, as a fraction of what it had. */\nconst GHOST_VELOCITY_SCALE = 0.45;\n/** Velocity an accent adds, in MIDI units. */\nconst ACCENT_VELOCITY_BOOST = 12;\n/** Velocity assumed for a note that carries none. */\nconst DEFAULT_VELOCITY = 80;\nconst MIN_VELOCITY = 1;\nconst MAX_VELOCITY = 127;\n/** Interval, in semitones, from which a move reads as slid into rather than fingered. */\nconst SLIDE_MIN_SEMITONES = 2;\n\n/** Whether a note is a candidate for an ornament of this style. */\nfunction eligible(\n style: OrnamentStyle,\n note: NoteEvent,\n previous: NoteEvent | undefined,\n next: NoteEvent | undefined,\n ts: TimeSignature,\n): boolean {\n const strong = isStrongBeat(note.startBeat, ts);\n switch (style) {\n case 'ghost':\n return !strong;\n case 'flam':\n case 'accent':\n return strong;\n case 'drag':\n // A drag leads somewhere: it is the weak note before a strong one, so the\n // next onset in the material has to be the strong position it runs into.\n return !strong && next !== undefined && isStrongBeat(next.startBeat, ts);\n case 'slide':\n return previous !== undefined && Math.abs(note.pitch - previous.pitch) >= SLIDE_MIN_SEMITONES;\n }\n}\n\n/** The articulation a style writes. */\nfunction articulationOf(style: OrnamentStyle): Articulation {\n return style;\n}\n\n/** Velocity after an ornament, or the note's own where the ornament changes none. */\nfunction ornamentedVelocity(style: OrnamentStyle, note: NoteEvent): number | undefined {\n if (note.velocity === undefined && style !== 'ghost' && style !== 'accent') {\n return undefined;\n }\n const velocity = note.velocity ?? DEFAULT_VELOCITY;\n if (style === 'ghost') {\n return clampVelocity(velocity * GHOST_VELOCITY_SCALE);\n }\n if (style === 'accent') {\n return clampVelocity(velocity + ACCENT_VELOCITY_BOOST);\n }\n return velocity;\n}\n\nfunction clampVelocity(velocity: number): number {\n return Math.min(MAX_VELOCITY, Math.max(MIN_VELOCITY, Math.round(velocity)));\n}\n\n/**\n * Decorate existing notes with an ornament.\n *\n * Which notes are eligible follows from the style and the meter — ghosts fall on\n * weak positions, drags on the weak position before a strong one, flams and\n * accents on strong ones, slides on notes reached by a leap — and `amount`\n * decides how many of those eligible notes are taken. The choice is addressed by\n * each note's own position, so raising `amount` adds ornaments without moving the\n * ones already there, and no onset is ever added or shifted: a decorated note is\n * the same note carrying an {@link Articulation}. A note that already carries one\n * is left alone, so ornament passes can be layered.\n *\n * With a difficulty ceiling and a tempo in the context, a passage whose strokes\n * come too fast for that ceiling is left plain — the ornament is exactly the\n * kind of candidate a ceiling exists to reject.\n *\n * @param notes The material to decorate.\n * @param opts Style, amount, meter, and context.\n * @returns Copies of the sounding notes, in input order, with ornaments applied.\n * @example\n * ```ts\n * import { generateBassLine, majorKey, makeChord, ornament } from '@libraz/libcantus';\n * const line = generateBassLine({\n * segments: [{ startBeat: 0, endBeat: 4, chord: makeChord(0, 'maj') }],\n * key: majorKey(0),\n * style: 'pop',\n * });\n * ornament(line, { style: 'ghost', amount: 0.4, ctx: 3 });\n * ```\n * Notes with a zero or negative duration never sound and are dropped, so the\n * result can be shorter than the input.\n *\n * @category Composition\n */\nexport function ornament(notes: readonly NoteEvent[], opts: OrnamentOptions = {}): NoteEvent[] {\n assertNoteEvents(notes, 'ornament notes', { allowNonPositiveDuration: true });\n const style = assertOneOf(opts.style ?? DEFAULT_STYLE, ORNAMENT_STYLES, 'ornament style');\n const ts = opts.ts ?? DEFAULT_TS;\n assertTimeSignature(ts);\n if (opts.amount !== undefined) {\n assertRange(opts.amount, 0, 1, 'ornament amount');\n }\n const ctx = resolveContextWith(opts.ctx, { ornament: opts.amount });\n const amount = ctx.ornament ?? DEFAULT_AMOUNT;\n const draw = ctx.part('ornament');\n\n // Zero-length artefacts are routine in MIDI imports and never sound, so they\n // are accepted and dropped here, exactly as humanize and the analysis layer do.\n const sounding = dropSilentNotes(notes);\n const ordered = [...sounding].sort((a, b) => a.startBeat - b.startBeat || a.pitch - b.pitch);\n const previousOf = new Map<NoteEvent, NoteEvent | undefined>();\n for (let index = 0; index < ordered.length; index += 1) {\n const note = ordered[index];\n if (note) {\n previousOf.set(note, index === 0 ? undefined : ordered[index - 1]);\n }\n }\n const nextOf = nextOnsets(ordered);\n const gaps = strokeGaps(ordered);\n\n return sounding.map((note) => {\n const decorated: NoteEvent = { ...note };\n if (\n note.articulation !== undefined ||\n !eligible(style, note, previousOf.get(note), nextOf.get(note), ts)\n ) {\n return decorated;\n }\n // The ceiling only ever rejects: an ornament this fast is a candidate the\n // player at that level would not take.\n if (!sustainsStrokes(gaps.get(note) ?? Number.POSITIVE_INFINITY, ctx.bpm, ctx.difficulty)) {\n return decorated;\n }\n if (!draw.prob(amount, style, note.startBeat, note.pitch)) {\n return decorated;\n }\n decorated.articulation = articulationOf(style);\n const velocity = ornamentedVelocity(style, note);\n if (velocity !== undefined) {\n decorated.velocity = velocity;\n }\n return decorated;\n });\n}\n\n/**\n * The first note of the next onset after each note.\n *\n * The next onset rather than the next note: the notes of a chord share a\n * position and say nothing about where the line is heading.\n */\nfunction nextOnsets(ordered: readonly NoteEvent[]): Map<NoteEvent, NoteEvent | undefined> {\n const nextOf = new Map<NoteEvent, NoteEvent | undefined>();\n let later: NoteEvent | undefined;\n for (let index = ordered.length - 1; index >= 0; index -= 1) {\n const note = ordered[index];\n if (!note) continue;\n if (later !== undefined && later.startBeat === note.startBeat) {\n nextOf.set(note, nextOf.get(later));\n } else {\n nextOf.set(note, later);\n }\n later = note;\n }\n return nextOf;\n}\n\n/**\n * The tightest gap around each onset, in beats.\n *\n * Both neighbours count: a stroke is hard to place because of how close it sits\n * to the one before it as much as the one after, and reading only the following\n * onset would let the last note of a fast passage pass as unhurried.\n */\nfunction strokeGaps(ordered: readonly NoteEvent[]): Map<NoteEvent, number> {\n const distinct = [...new Set(ordered.map((note) => note.startBeat))].sort((a, b) => a - b);\n const gapAt = new Map<number, number>();\n for (let index = 0; index < distinct.length; index += 1) {\n const beat = distinct[index] ?? 0;\n const before = index === 0 ? Number.POSITIVE_INFINITY : beat - (distinct[index - 1] ?? 0);\n const after =\n index === distinct.length - 1 ? Number.POSITIVE_INFINITY : (distinct[index + 1] ?? 0) - beat;\n gapAt.set(beat, Math.min(before, after));\n }\n const gaps = new Map<NoteEvent, number>();\n for (const note of ordered) {\n gaps.set(note, gapAt.get(note.startBeat) ?? Number.POSITIVE_INFINITY);\n }\n return gaps;\n}\n","import { InvalidInputError } from '../../core/errors/index.js';\nimport type { KeyScale } from '../../core/types.js';\nimport {\n assertDegree,\n assertGenerationBudget,\n assertOneOf,\n assertPositiveInt,\n} from '../../core/validation/index.js';\nimport type { ChordQuality, ChordSpan } from '../../theory/chord/index.js';\nimport { chordQualities, diatonicTriad } from '../../theory/chord/index.js';\nimport { type KeyLike, scaleTonesInDegreeOrder, toKeyScale } from '../../theory/scale/index.js';\nimport { type GenerationContextInput, resolveContext } from '../context/index.js';\n\nexport type { ChordSpan } from '../../theory/chord/index.js';\n\n/**\n * Broad production style a progression preset suits.\n *\n * @category Composition\n */\nexport type ProgStyle = 'minimal' | 'dance' | 'idol' | 'rock';\n\n/**\n * Harmonic-function role of a progression as a whole.\n *\n * @category Composition\n */\nexport type ProgFunction = 'loop' | 'tensionBuild' | 'cadenceStrong' | 'stable';\n\n/**\n * A named chord-progression preset expressed in scale degrees.\n *\n * @category Composition\n */\nexport type ProgressionPreset = {\n id: string;\n name: string;\n /**\n * The chord roots, in order, as {@link ProgressionDegree} codes: 0..6 for the\n * key's own scale degrees and {@link BORROWED_DEGREES} for the borrowed ones.\n */\n degrees: ProgressionDegree[];\n functional: ProgFunction;\n styles: ProgStyle[];\n};\n\n/**\n * The borrowed (non-diatonic) chord roots a preset can name, as their semitone\n * code for a borrowed degree. These are stable identifiers, not semitone\n * offsets: their pitch-class offsets are resolved internally.\n *\n * Scale degrees 1..7 address the key's own chords; these codes continue the\n * numbering for the chromatic chords a pop progression borrows, so a preset is\n * one flat list of degree codes.\n *\n * @example\n * ```ts\n * import { BORROWED_DEGREES, generateProgression, majorKey } from '@libraz/libcantus';\n * generateProgression({\n * key: majorKey(0),\n * style: 'rock',\n * bars: 4,\n * preset: { degrees: [1, BORROWED_DEGREES.bVII, 4, 1] },\n * });\n * ```\n * @category Composition\n */\nexport const BORROWED_DEGREES = Object.freeze({\n /** Flat submediant: bVI. */\n bVI: 8,\n /** Flat subtonic: bVII. */\n bVII: 10,\n /** Flat mediant: bIII. */\n bIII: 11,\n /** Minor subdominant borrowed from the parallel minor: iv. */\n iv: 12,\n /** Neapolitan: bII. */\n bII: 13,\n /** Sharp subdominant, the diminished #IV. */\n sharpIV: 14,\n} as const);\n\n/**\n * A chord root in a preset: a scale degree 1..7, or one of\n * {@link BORROWED_DEGREES}.\n *\n * @category Composition\n */\nexport type ProgressionDegree = number;\n\n/**\n * Options controlling {@link generateProgression}.\n *\n * @category Composition\n */\nexport type ProgressionOptions = {\n /**\n * The key the degrees are resolved against; a key name such as `'C major'` is\n * read as that key.\n */\n key: KeyLike;\n /**\n * Which pool of built-in presets to choose from. Ignored when `preset` names\n * the progression outright.\n */\n style: ProgStyle;\n bars: number;\n /** Pick a specific built-in preset by id instead of choosing one by style. */\n presetId?: string;\n /**\n * Use this progression rather than a built-in one. Only `degrees` is\n * required; see {@link BORROWED_DEGREES} for the non-diatonic codes.\n */\n preset?: Partial<ProgressionPreset> & { degrees: ProgressionDegree[] };\n ext?: ChordQuality | 'auto';\n /**\n * The generation context.\n *\n * Its `complexity.harmonic` sets how much of the progression is replaced by\n * the secondary dominant of what follows: 0 — the default — leaves it alone,\n * 0.5 takes about half of what the voice-leading rules allow, and 1 takes\n * every one of them. Its `seed` fixes both the preset choice and which chords\n * are replaced, so the same seed always yields the same progression.\n *\n * @defaultValue `{ seed: 0 }`\n */\n ctx?: GenerationContextInput;\n};\n\nconst PRESETS: ProgressionPreset[] = [\n {\n id: 'fourChordPop',\n name: 'Four Chord Pop',\n degrees: [1, 5, 6, 4],\n functional: 'loop',\n styles: ['minimal', 'dance', 'idol'],\n },\n {\n id: 'pop1',\n name: 'Pop 1',\n degrees: [1, 6, 4, 5],\n functional: 'loop',\n styles: ['minimal', 'dance', 'idol'],\n },\n {\n id: 'axis',\n name: 'Axis',\n degrees: [6, 4, 1, 5],\n functional: 'loop',\n styles: ['minimal', 'dance', 'idol', 'rock'],\n },\n {\n id: 'pop2',\n name: 'Pop 2',\n degrees: [4, 1, 5, 6],\n functional: 'loop',\n styles: ['minimal', 'dance', 'idol'],\n },\n {\n id: 'classic',\n name: 'Classic',\n degrees: [1, 4, 5, 1],\n functional: 'cadenceStrong',\n styles: ['dance', 'idol', 'rock'],\n },\n {\n id: 'pop3',\n name: 'Pop 3',\n degrees: [1, 4, 6, 5],\n functional: 'loop',\n styles: ['minimal', 'dance', 'idol'],\n },\n {\n id: 'royalRoad',\n name: 'Royal Road',\n degrees: [4, 5, 3, 6],\n functional: 'tensionBuild',\n styles: ['dance', 'idol'],\n },\n {\n id: 'minor1',\n name: 'Minor 1',\n degrees: [6, 5, 4, 5],\n functional: 'tensionBuild',\n styles: ['idol', 'rock'],\n },\n {\n id: 'minor2',\n name: 'Minor 2',\n degrees: [6, 4, 5, 1],\n functional: 'tensionBuild',\n styles: ['idol', 'rock'],\n },\n {\n id: 'pop4',\n name: 'Pop 4',\n degrees: [1, 5, 3, 4],\n functional: 'loop',\n styles: ['minimal', 'dance', 'idol'],\n },\n {\n id: 'pop5',\n name: 'Pop 5',\n degrees: [1, 3, 4, 5],\n functional: 'stable',\n styles: ['minimal', 'dance', 'idol'],\n },\n {\n id: 'rock1',\n name: 'Rock 1',\n degrees: [1, 10, 4, 1],\n functional: 'tensionBuild',\n styles: ['rock'],\n },\n {\n id: 'rock2',\n name: 'Rock 2',\n degrees: [1, 4, 10, 1],\n functional: 'tensionBuild',\n styles: ['rock'],\n },\n {\n id: 'extended4',\n name: 'Extended 4',\n degrees: [1, 5, 6, 3],\n functional: 'stable',\n styles: ['minimal', 'dance'],\n },\n {\n id: 'minor3',\n name: 'Minor 3',\n degrees: [6, 1, 5, 4],\n functional: 'loop',\n styles: ['dance', 'idol'],\n },\n {\n id: 'aeolianPop',\n name: 'Aeolian Pop',\n degrees: [6, 8, 10, 1],\n functional: 'tensionBuild',\n styles: ['minimal', 'dance', 'idol', 'rock'],\n },\n {\n id: 'animeHighEnergy1',\n name: 'Anime High Energy 1',\n degrees: [6, 3, 4, 1],\n functional: 'loop',\n styles: ['dance', 'idol'],\n },\n {\n id: 'jazzPop',\n name: 'Jazz Pop',\n degrees: [2, 5, 1, 6],\n functional: 'cadenceStrong',\n styles: ['minimal', 'dance'],\n },\n {\n id: 'animeHighEnergy2',\n name: 'Anime High Energy 2',\n degrees: [6, 2, 5, 1],\n functional: 'cadenceStrong',\n styles: ['dance', 'idol'],\n },\n {\n id: 'cityPop',\n name: 'City Pop',\n degrees: [1, 6, 2, 5],\n functional: 'stable',\n styles: ['minimal', 'dance'],\n },\n {\n id: 'extended5',\n name: 'Extended 5',\n degrees: [1, 5, 6, 3, 4],\n functional: 'loop',\n styles: ['minimal', 'dance', 'idol'],\n },\n {\n id: 'neapolitanPop',\n name: 'Neapolitan Pop',\n degrees: [6, 12, 13, 5, 1],\n functional: 'cadenceStrong',\n styles: ['minimal', 'dance', 'idol'],\n },\n];\n\n/** Share of the eligible chords replaced when the caller names no dial. */\nconst DEFAULT_HARMONIC = 0;\n\n/** Chromatic semitone offset from the tonic for borrowed (non-diatonic) degrees. */\nconst BORROWED_OFFSET: Record<number, number> = {\n [BORROWED_DEGREES.bVI]: 8,\n [BORROWED_DEGREES.bVII]: 10,\n [BORROWED_DEGREES.bIII]: 3,\n [BORROWED_DEGREES.iv]: 5,\n [BORROWED_DEGREES.bII]: 1,\n [BORROWED_DEGREES.sharpIV]: 6,\n};\n\n/**\n * All built-in progression presets.\n *\n * @category Composition\n */\nexport function progressions(): ProgressionPreset[] {\n return PRESETS.map((p) => ({ ...p, degrees: [...p.degrees], styles: [...p.styles] }));\n}\n\n/**\n * Presets whose style list includes the given style.\n *\n * @category Composition\n */\nexport function progressionsByStyle(style: ProgStyle): ProgressionPreset[] {\n return progressions().filter((p) => p.styles.includes(style));\n}\n\n/** Root pitch class of a scale degree in the given key, including borrowed degrees. */\nfunction degreeToRootPc(degree: number, key: KeyScale): number {\n if (degree >= 1 && degree <= 7) {\n const tones = scaleTonesInDegreeOrder(key);\n return tones.length > 0\n ? (tones[(degree - 1) % tones.length] ?? key.rootPc % 12)\n : key.rootPc % 12;\n }\n const offset = BORROWED_OFFSET[degree] ?? 0;\n return ((((key.rootPc % 12) + offset) % 12) + 12) % 12;\n}\n\n/**\n * Diatonic (or borrowed) triad quality of a degree in the given key.\n *\n * Diatonic degrees (1-7) take their scale-correct triad quality, so non-major\n * keys yield diatonic chords. Borrowed degrees keep their fixed chromatic\n * qualities (`#IV` diminished, `iv` minor, the rest major).\n */\nfunction autoQuality(degree: number, key: KeyScale, harmonicDominant: boolean): ChordQuality {\n if (degree >= 1 && degree <= 7) {\n const isMinor = ((key.modeMask12 >> 3) & 1) === 1 && ((key.modeMask12 >> 4) & 1) === 0;\n // A cadence-oriented progression needs a leading tone in minor too: use\n // the conventional harmonic-minor V rather than the natural-minor v.\n if (harmonicDominant && isMinor && degree === 5) {\n return 'maj';\n }\n return diatonicTriad(degree, key).quality;\n }\n if (degree === 14) {\n return 'dim';\n }\n if (degree === 12) {\n return 'min';\n }\n return 'maj';\n}\n\n/** One resolved step of a preset cycle, with the preset degree it came from. */\ntype CycleStep = { source: number; degree?: number; rootPc: number; quality: ChordQuality };\n\n/**\n * Resolve a preset's degrees against a key, dropping any step that lands on the\n * chord already sounding.\n *\n * Presets are written in scale degrees plus a handful of chromatic borrowings\n * measured from the tonic, and the two can name the same chord: `bVI` is the\n * sixth degree of a minor key, so `vi bVI bVII I` — a major-key device — turns\n * into `bVI bVI bVII i` there, sounding one chord twice. Collapsing the repeat\n * keeps the progression moving. Only a repeat produced by *different* degrees is\n * collapsed: a preset that names the same degree twice — `I IV V I` closing on\n * its tonic — means it. The wrap from the last step back to the first is treated\n * the same way, since the cycle repeats to fill the bars.\n */\nfunction resolveCycle(\n degrees: readonly number[],\n key: KeyScale,\n ext: ProgressionOptions['ext'],\n harmonicDominant: boolean,\n): CycleStep[] {\n const steps: CycleStep[] = [];\n for (const degree of degrees) {\n const step: CycleStep = {\n source: degree,\n rootPc: degreeToRootPc(degree, key),\n quality:\n ext !== undefined && ext !== 'auto' ? ext : autoQuality(degree, key, harmonicDominant),\n };\n if (degree >= 1 && degree <= 7) {\n step.degree = degree;\n }\n const previous = steps[steps.length - 1];\n if (previous !== undefined && previous.source !== step.source && sameStep(previous, step)) {\n continue;\n }\n steps.push(step);\n }\n const first = steps[0];\n const last = steps[steps.length - 1];\n if (\n steps.length > 1 &&\n first !== undefined &&\n last !== undefined &&\n first.source !== last.source &&\n sameStep(first, last)\n ) {\n steps.pop();\n }\n return steps.length > 0\n ? steps\n : [{ source: 0, degree: 0, rootPc: degreeToRootPc(0, key), quality: 'maj' }];\n}\n\n/** Whether two resolved steps name the same chord. */\nfunction sameStep(a: CycleStep, b: CycleStep): boolean {\n return a.rootPc === b.rootPc && a.quality === b.quality;\n}\n\n/**\n * The preset {@link generateProgression} would choose for a style and seed.\n *\n * The choice is otherwise invisible: the generator returns chords, not the\n * preset it drew them from, so a caller who wants to show or reproduce it has\n * no way to name it.\n *\n * @param style The style pool to choose from.\n * @param seed The same seed the generator would be given.\n * @returns The preset that seed selects.\n * @throws If no preset claims the style.\n * @example\n * ```ts\n * import { pickProgressionPreset } from '@libraz/libcantus';\n * pickProgressionPreset('dance', 3).name;\n * ```\n * @category Composition\n */\nexport function pickProgressionPreset(style: ProgStyle, seed = 0): ProgressionPreset {\n // A style no preset claims is a caller error, exactly as an unknown\n // presetId is: falling back to the whole pool would answer a typo with a\n // plausible but stylistically unrelated progression.\n const pool = PRESETS.filter((preset) => preset.styles.includes(style));\n if (pool.length === 0) {\n throw new InvalidInputError(`Unknown progression style: ${style}`);\n }\n const index = resolveContext(seed)\n .part('progression')\n .range(0, pool.length - 1, 'preset');\n return pool[index] ?? (PRESETS[0] as ProgressionPreset);\n}\n\n/**\n * Generate a chord progression laid out one chord per bar.\n *\n * A preset is chosen by `presetId` when given, otherwise deterministically from\n * the presets matching `style`, seeded by the context's `seed`. An unknown\n * `presetId`, or a `style` no preset claims, is a caller error and throws rather\n * than silently falling back to a random preset. The preset's degrees cycle to\n * fill `bars`; each bar is four beats, so `startBeat` is `barIndex * 4`. Chord\n * roots come from the key's diatonic scale-degree mapping. When `ext` is\n * omitted or `'auto'`, each chord takes its diatonic triad quality; otherwise\n * `ext` is forced on every chord — except a reharmonized chord, which is a\n * secondary dominant and is therefore always a `dom7`. With\n * `complexity.harmonic` above 0, some chords are deterministically replaced with\n * the secondary dominant (V7) of the following chord, flagged with\n * `secondaryDominant`.\n *\n * @param opts Generation options.\n * @returns One chord per bar in timeline order.\n * @throws If `presetId` matches no built-in preset, or `style` matches none.\n *\n * @example\n * ```ts\n * import { generateProgression, majorKey } from '@libraz/libcantus';\n * const chords = generateProgression({ key: majorKey(0), style: 'dance', bars: 4 });\n * // Deterministic for a given seed (defaults to 0); one ChordSpan per bar.\n * const richer = generateProgression({\n * key: majorKey(0),\n * style: 'dance',\n * bars: 4,\n * ctx: { seed: 3, complexity: { harmonic: 0.5 } },\n * });\n * // Some chords are now the secondary dominant of the chord that follows.\n * ```\n *\n * @category Composition\n */\nexport function generateProgression(opts: ProgressionOptions): ChordSpan[] {\n assertPositiveInt(opts.bars, 'progression bars');\n assertGenerationBudget(opts.bars, 'progression chords');\n // The key is read into its plain form once, here at the boundary; the degree\n // resolution below is given the scale it resolved to.\n const key = toKeyScale(opts.key);\n const ctx = resolveContext(opts.ctx);\n const seed = ctx.seed;\n let preset: ProgressionPreset | undefined;\n if (opts.preset !== undefined) {\n if (opts.preset.degrees.length === 0) {\n throw new InvalidInputError('progression preset must name at least one degree');\n }\n preset = {\n id: opts.preset.id ?? 'custom',\n name: opts.preset.name ?? 'Custom',\n degrees: opts.preset.degrees.map((degree, index) => {\n assertDegree(degree, `progression preset degrees[${index}]`);\n const supported =\n (degree >= 1 && degree <= 7) ||\n (Object.values(BORROWED_DEGREES) as number[]).includes(degree);\n if (!supported) {\n throw new InvalidInputError(\n `progression preset degrees[${index}] is not a supported progression degree; received ${degree}`,\n );\n }\n return degree;\n }),\n functional: opts.preset.functional ?? 'loop',\n styles: opts.preset.styles ?? [opts.style],\n };\n }\n if (preset === undefined && opts.presetId !== undefined) {\n preset = PRESETS.find((p) => p.id === opts.presetId);\n if (preset === undefined) {\n throw new InvalidInputError(`Unknown progression preset: ${opts.presetId}`);\n }\n }\n preset ??= pickProgressionPreset(opts.style, seed);\n const ext =\n opts.ext === undefined\n ? 'auto'\n : assertOneOf(opts.ext, ['auto', ...chordQualities()], 'progression extension');\n const cycle = resolveCycle(\n preset?.degrees ?? [0],\n key,\n ext,\n preset?.functional === 'cadenceStrong',\n );\n const chords: ChordSpan[] = [];\n for (let bar = 0; bar < opts.bars; bar += 1) {\n const step = cycle[bar % cycle.length];\n const chord: ChordSpan = {\n rootPc: step?.rootPc ?? 0,\n quality: step?.quality ?? 'maj',\n startBeat: bar * 4,\n };\n if (step?.degree !== undefined) {\n chord.degree = step.degree;\n }\n chords.push(chord);\n }\n\n const harmonic = ctx.harmonic ?? DEFAULT_HARMONIC;\n if (harmonic > 0) {\n const tonicPc = (((key.rootPc % 12) + 12) % 12) as number;\n const draw = ctx.part('progression');\n let tonicStatements = chords.filter((chord) => chord.rootPc === tonicPc).length;\n for (let i = 0; i < chords.length - 1; i += 1) {\n const cur = chords[i];\n const next = chords[i + 1];\n if (!cur || !next) {\n continue;\n }\n const domRoot = (next.rootPc + 7) % 12;\n const targetsTonic = (next.rootPc - tonicPc + 12) % 12 === 0;\n const isLastTonicStatement = cur.rootPc === tonicPc && tonicStatements <= 1;\n const alreadySecondary = cur.rootPc === domRoot && cur.quality === 'dom7';\n // A secondary dominant inserted at i-1 resolves onto this chord; replacing\n // it here would orphan that dominant.\n const isResolutionTarget = chords[i - 1]?.secondaryDominant === true;\n if (targetsTonic || isLastTonicStatement || alreadySecondary || isResolutionTarget) {\n continue;\n }\n if (draw.prob(harmonic, 'reharmonize', i)) {\n if (cur.rootPc === tonicPc) {\n tonicStatements -= 1;\n }\n chords[i] = {\n rootPc: domRoot,\n quality: 'dom7',\n startBeat: cur.startBeat,\n secondaryDominant: true,\n };\n }\n }\n }\n\n return chords;\n}\n","/**\n * Reharmonization: chord substitution, modal-interchange palettes, and negative\n * harmony. These transforms take an existing chord (or a key) and propose\n * alternative harmonies that preserve a chosen relationship — a shared function,\n * a common tone, or a reflection across the key axis.\n *\n * Roots are pitch classes (0..11); no spelled key signature is required. Every\n * proposed chord nevertheless carries the spelling hints of the key it was asked\n * for, so a flat-side key names its chromatic chords with flats (the tritone\n * substitute of G7 in C major is Db7, not C#7).\n */\n\nimport { detectChordBest } from '../../analyze/detect/index.js';\nimport {\n type BorrowedSource,\n borrowedSource,\n chordToRoman,\n functionOf,\n type HarmonicFunction,\n isDiatonic,\n parallelKey,\n} from '../../analyze/functional/index.js';\nimport {\n midiToNote,\n pitchClassOf as mod12,\n type Note,\n noteToPitchClass,\n transposeNote,\n} from '../../core/pitch/index.js';\nimport type { KeyScale } from '../../core/types.js';\nimport {\n type Chord,\n type ChordQuality,\n chordPitchClasses,\n diatonicTriad,\n makeChord,\n type PitchSpelling,\n} from '../../theory/chord/index.js';\nimport {\n isScaleTone,\n type KeyLike,\n scaleTonesInDegreeOrder,\n toKeyScale,\n} from '../../theory/scale/index.js';\nimport { spellPitchClass, spellScale } from '../../theory/spelling/index.js';\nimport { type ChordLike, toChordData } from '../../theory/symbol/index.js';\n\n/**\n * The kind of substitution relationship a candidate realizes.\n *\n * An applied dominant is not among them: which dominant applies is decided by\n * the harmony that follows, and {@link substituteChord} is given one chord and a\n * key. Build one with `secondaryDominantOf` where the target is known, or let\n * `harmonizeMelody` open its vocabulary with `reharmonize: 'secondaryDominant'`\n * where the progression is being chosen as a whole.\n *\n * @category Reharmonization\n */\nexport type SubstitutionType = 'tritone' | 'relative' | 'borrowed' | 'chromaticMediant';\n\n/**\n * A proposed chord substitution with its relationship, numeral, and function.\n *\n * @category Reharmonization\n */\nexport type Substitution = {\n chord: Chord;\n type: SubstitutionType;\n roman: string;\n function: HarmonicFunction;\n};\n\n/**\n * Options controlling which substitutions {@link substituteChord} returns.\n *\n * @category Reharmonization\n */\nexport type SubstituteOptions = {\n /**\n * Melody pitch classes that must remain chord tones. When given, only\n * substitutions whose pitch classes contain every listed pitch class are\n * kept, so the melody stays consonant against the new harmony.\n */\n melodyPcs?: number[];\n};\n\n/** Whether the chord's template carries a given interval (mod 12). */\nfunction hasInterval(chord: Chord, semitone: number): boolean {\n return chord.intervals.some((iv) => mod12(iv) === semitone);\n}\n\n/** Whether a chord is a dominant type: a major third with a minor seventh. */\nfunction isDominantType(chord: Chord): boolean {\n return hasInterval(chord, 4) && hasInterval(chord, 10);\n}\n\n/** Count the pitch classes shared between two pitch-class sets. */\nfunction commonToneCount(a: number[], b: number[]): number {\n const set = new Set(b);\n return a.filter((pc) => set.has(pc)).length;\n}\n\n/** The plain triad quality underlying a chord's own quality. */\nfunction triadQualityOf(chord: Chord): ChordQuality {\n const semis = new Set(chord.intervals.map((interval) => mod12(interval)));\n const hasThird = semis.has(4) ? 'maj' : semis.has(3) ? 'min' : undefined;\n if (hasThird === undefined) {\n return chord.quality; // sus, power, and other non-tertian chords stand alone\n }\n if (semis.has(6) && !semis.has(7)) {\n return hasThird === 'min' ? 'dim' : 'majb5';\n }\n if (semis.has(8) && !semis.has(7)) {\n return 'aug';\n }\n return hasThird;\n}\n\n/** The diatonic triads of a key, one per scale degree. */\nfunction diatonicTriadsOf(key: KeyScale): Chord[] {\n const degrees = scaleTonesInDegreeOrder(key).length;\n const triads: Chord[] = [];\n for (let degree = 1; degree <= degrees; degree += 1) {\n triads.push(diatonicTriad(degree, key));\n }\n return triads;\n}\n\n/** Whether two chords share the same root pitch class and quality. */\nfunction sameChord(a: Chord, b: Chord): boolean {\n return mod12(a.rootPc) === mod12(b.rootPc) && a.quality === b.quality;\n}\n\n/** Root offsets (semitones) that form a third above or below a root. */\nconst THIRD_OFFSETS = [3, 4, 8, 9] as const;\n\n/** Middle C: the octave the two bare tonic candidates are named in. */\nconst MIDDLE_C = 60;\n\n/** Widest alteration a {@link PitchSpelling} hint may carry: a double accidental. */\nconst MAX_HINT_ALTER = 2;\n\n/** Reduce a spelled note to the bare letter/alter a chord records as a hint. */\nfunction bareSpelling(note: Note): PitchSpelling {\n return { letter: note.letter, alter: note.alter };\n}\n\n/** Total accidentals a spelled tonic produces across a key's whole scale. */\nfunction accidentalLoad(tonic: Note, key: KeyScale): number {\n return spellScale(tonic, key).reduce((sum, note) => sum + Math.abs(note.alter), 0);\n}\n\n/**\n * The tonic spelling a key is written on.\n *\n * A `KeyScale` carries a root pitch class and no letter, so the side has to be\n * chosen: take whichever spelling writes the scale with the fewest accidentals,\n * and flats on a tie. That is what makes pitch class 1 major spell as Db major\n * rather than C# major, while pitch class 6 minor still spells as F# minor.\n */\nfunction keyTonicOf(key: KeyScale): Note {\n const rootPc = mod12(key.rootPc);\n const sharp = bareSpelling(midiToNote(MIDDLE_C + rootPc, 'sharp'));\n const flat = bareSpelling(midiToNote(MIDDLE_C + rootPc, 'flat'));\n if (sharp.letter === flat.letter) {\n return sharp;\n }\n return accidentalLoad(flat, key) <= accidentalLoad(sharp, key) ? flat : sharp;\n}\n\n/**\n * Copy a chord with `root` recorded as its root spelling, plus the key's own\n * spelling of the bass when one is set.\n *\n * A hint is only worth carrying while it still names the pitch class it belongs\n * to, so a root spelling that does not — or that would need more than a double\n * accidental — gives way to the key's spelling of that pitch class.\n */\nfunction spelledOnRoot(chord: Chord, root: Note, tonic: Note, key: KeyScale): Chord {\n const usable =\n noteToPitchClass(root) === mod12(chord.rootPc) && Math.abs(root.alter) <= MAX_HINT_ALTER;\n const spelled: Chord = {\n ...chord,\n rootSpelling: bareSpelling(usable ? root : spellPitchClass(chord.rootPc, tonic, key)),\n };\n if (chord.bassPc !== undefined) {\n spelled.bassSpelling = bareSpelling(spellPitchClass(chord.bassPc, tonic, key));\n }\n return spelled;\n}\n\n/** Copy a chord with the key's own spelling of its root and bass attached. */\nfunction spelledInKey(chord: Chord, tonic: Note, key: KeyScale): Chord {\n return spelledOnRoot(chord, spellPitchClass(chord.rootPc, tonic, key), tonic, key);\n}\n\n/**\n * The root spelling of a tritone substitute: the flattened supertonic of the\n * chord the substituted dominant resolves to, since the substitute is that\n * chord's bII7. G7 resolves to C, so its substitute is written Db7 — never C#7,\n * whichever letter the pitch class alone would suggest.\n *\n * A pitch class the key already spells as a scale degree keeps that letter\n * instead: the same substitution read in B major is the II7 written C#7.\n */\nfunction tritoneSubstituteRoot(dominant: Chord, tonic: Note, key: KeyScale): Note {\n const substituteRootPc = mod12(dominant.rootPc + 6);\n if (isScaleTone(substituteRootPc, key)) {\n return spellPitchClass(substituteRootPc, tonic, key);\n }\n const resolution = spellPitchClass(mod12(dominant.rootPc + 5), tonic, key);\n return transposeNote(resolution, 1);\n}\n\n/**\n * Propose substitutions for a chord within a key.\n *\n * Candidates are generated by several relationships and deduplicated against the\n * original chord and each other:\n *\n * - `tritone`: for a dominant-type chord, the dominant seventh a tritone away.\n * - `relative`: a diatonic triad a third away that shares two common tones.\n * - `borrowed`: a triad from the parallel mode with the same harmonic function.\n * - `chromaticMediant`: a major/minor triad a third away sharing one common\n * tone.\n *\n * These four are the whole vocabulary. An applied dominant is not proposed\n * because the chord it applies to is the one that follows, which a call about a\n * single chord cannot see; `secondaryDominantOf` builds one from a named target.\n *\n * Each result carries its Roman numeral and harmonic function in `key`. When\n * `opts.melodyPcs` is given, only substitutions whose pitch classes contain all\n * of those pitch classes are returned.\n *\n * Every substitution is spelled the way `key` writes it, as `rootSpelling` and\n * `bassSpelling` hints, so a flat-side key keeps flat names: in C major the\n * tritone substitute of G7 formats as `Db7` and its chromatic mediants as `Bb`\n * and `Eb`. Where the substitution's function and the bare pitch class disagree,\n * the function decides — a tritone substitute is a bII7 and so is written on the\n * flattened supertonic of the chord it resolves to, and a borrowed chord takes\n * the spelling of the mode it is borrowed from.\n *\n * @param chord The chord to reharmonize, as a chord symbol or as chord data.\n * @param key The prevailing key, as a key name or as a key/scale.\n * @param opts Optional melody-preservation constraint.\n * @returns The deduplicated substitution candidates.\n * @example\n * ```ts\n * import { substituteChord, makeChord, majorKey, formatChordSymbol } from '@libraz/libcantus';\n * const subs = substituteChord(makeChord(7, 'dom7'), majorKey(0));\n * subs\n * .filter((sub) => sub.type === 'tritone')\n * .map((sub) => formatChordSymbol(sub.chord)); // ['Db7'] — the bII7 of C major, not 'C#7'\n * ```\n * @category Reharmonization\n */\nexport function substituteChord(\n chord: ChordLike,\n key: KeyLike,\n opts?: SubstituteOptions,\n): Substitution[] {\n // Chord and key are read into their plain form once, here at the boundary;\n // everything below works on the plain forms alone.\n const source = toChordData(chord);\n const scale = toKeyScale(key);\n // The relative test counts common tones against the chord's triad, not its\n // full pitch-class set: an exact count of two would otherwise be decided by\n // how many tensions the input carries, so C proposes both Em and Am while\n // Cmaj7 proposes only Am — penalising the candidate that overlaps *more*.\n const originalTriad = chordPitchClasses(makeChord(source.rootPc, triadQualityOf(source)));\n const candidates: { chord: Chord; type: SubstitutionType }[] = [];\n const tonic = keyTonicOf(scale);\n\n // Tritone substitution: only for dominant-type chords.\n if (isDominantType(source)) {\n const substitute = makeChord(mod12(source.rootPc + 6), 'dom7');\n candidates.push({\n chord: spelledOnRoot(substitute, tritoneSubstituteRoot(source, tonic, scale), tonic, scale),\n type: 'tritone',\n });\n }\n\n // Relative: a diatonic triad a third away sharing two common tones.\n for (const triad of diatonicTriadsOf(scale)) {\n const offset = mod12(triad.rootPc - source.rootPc);\n if (\n (THIRD_OFFSETS as readonly number[]).includes(offset) &&\n commonToneCount(originalTriad, chordPitchClasses(triad)) === 2\n ) {\n candidates.push({ chord: spelledInKey(triad, tonic, scale), type: 'relative' });\n }\n }\n\n // Borrowed: a parallel-mode triad sharing the original's harmonic function.\n // It is written the way the mode it comes from writes it, which is also how\n // the key names the borrowing: C major borrows Eb and Ab from C minor.\n const targetFunction = functionOf(source, scale);\n const parallel = parallelKey(scale);\n for (const triad of diatonicTriadsOf(parallel)) {\n if (functionOf(triad, scale) === targetFunction && !isDiatonic(triad, scale)) {\n candidates.push({ chord: spelledInKey(triad, tonic, parallel), type: 'borrowed' });\n }\n }\n\n // Chromatic mediants: major/minor triads a third away sharing one common tone.\n for (const offset of THIRD_OFFSETS) {\n for (const quality of ['maj', 'min'] as const) {\n const mediant = makeChord(mod12(source.rootPc + offset), quality);\n if (\n commonToneCount(originalTriad, chordPitchClasses(mediant)) === 1 &&\n !isDiatonic(mediant, scale)\n ) {\n candidates.push({ chord: spelledInKey(mediant, tonic, scale), type: 'chromaticMediant' });\n }\n }\n }\n\n const melodyPcs = opts?.melodyPcs?.map(mod12);\n const seen = new Set<string>();\n const results: Substitution[] = [];\n for (const candidate of candidates) {\n if (sameChord(candidate.chord, source)) {\n continue;\n }\n const dedupeKey = `${mod12(candidate.chord.rootPc)}:${candidate.chord.quality}`;\n if (seen.has(dedupeKey)) {\n continue;\n }\n seen.add(dedupeKey);\n if (melodyPcs !== undefined) {\n const pcs = new Set(chordPitchClasses(candidate.chord));\n if (!melodyPcs.every((pc) => pcs.has(pc))) {\n continue;\n }\n }\n results.push({\n chord: candidate.chord,\n type: candidate.type,\n roman: chordToRoman(candidate.chord, scale),\n function: functionOf(candidate.chord, scale),\n });\n }\n return results;\n}\n\n/**\n * A borrowed chord in the modal-interchange palette of a key.\n *\n * @category Reharmonization\n */\nexport type BorrowedChord = {\n chord: Chord;\n roman: string;\n source: BorrowedSource;\n};\n\n/**\n * The modal-interchange palette of a key.\n *\n * Lists the diatonic triads of the parallel key that are not diatonic to\n * `key` (the classic borrowed chords such as `iv`, `bVI`, and `bVII` in a major\n * key), plus the Neapolitan (a major triad on the flat second degree). Each\n * carries its Roman numeral and {@link borrowedSource} relative to `key`.\n *\n * Every chord is spelled as the mode it is borrowed from writes it, recorded as\n * a `rootSpelling` hint: C major borrows `Fm`, `Ab` and `Bb` from C minor and\n * takes `Db` as its Neapolitan, while a minor key borrows in the other direction\n * (A minor borrows `A`, `D` and `F#m` from A major, with `Bb` as its\n * Neapolitan). The major dominant of a minor key — `E` in A minor — is not among\n * them: raising the seventh degree is an alteration inside the key rather than a\n * chord taken from the parallel mode.\n *\n * @param key The prevailing key, as a key name or as a key/scale.\n * @returns The borrowed-chord palette.\n * @example\n * ```ts\n * import { modalInterchangePalette, majorKey, formatChordSymbol } from '@libraz/libcantus';\n * const palette = modalInterchangePalette(majorKey(0));\n * palette.map((borrowed) => formatChordSymbol(borrowed.chord));\n * // ['Cm', 'Ddim', 'Eb', 'Fm', 'Gm', 'Ab', 'Bb', 'Db'] — flat-side, not 'D#'/'G#'/'A#'\n * ```\n * @category Reharmonization\n */\nexport function modalInterchangePalette(key: KeyLike): BorrowedChord[] {\n // The key is read into its plain form once, here at the boundary.\n const scale = toKeyScale(key);\n const palette: BorrowedChord[] = [];\n const seen = new Set<string>();\n const add = (chord: Chord) => {\n const dedupeKey = `${mod12(chord.rootPc)}:${chord.quality}`;\n if (seen.has(dedupeKey)) {\n return;\n }\n seen.add(dedupeKey);\n palette.push({\n chord,\n roman: chordToRoman(chord, scale),\n source: borrowedSource(chord, scale),\n });\n };\n\n const tonic = keyTonicOf(scale);\n const parallel = parallelKey(scale);\n for (const triad of diatonicTriadsOf(parallel)) {\n if (!isDiatonic(triad, scale) && borrowedSource(triad, scale) !== null) {\n add(spelledInKey(triad, tonic, parallel));\n }\n }\n // The Neapolitan (major triad on b2) sits outside both parallel modes, so it\n // is added explicitly — unless b2 is already diatonic (e.g. in a Phrygian key,\n // where it is a native chord rather than a borrowing). Being a bII, it is\n // written a minor second above the tonic: Db in C major, C in B major.\n const neapolitan = makeChord(mod12(scale.rootPc + 1), 'maj');\n if (!isDiatonic(neapolitan, scale)) {\n add(spelledOnRoot(neapolitan, transposeNote(tonic, 1), tonic, scale));\n }\n return palette;\n}\n\n/**\n * Reflect a chord across the tonic–dominant axis of a key (negative harmony).\n *\n * Every pitch class `p` maps to `(2*T + 7 - p) mod 12`, where `T` is the key\n * tonic — the reflection that swaps the tonic with the dominant. The mirrored\n * pitch classes (including the bass, if set) are reassembled into a chord by\n * {@link detectChordBest}; when no chord is recognized, the mirrored root and\n * the original quality are used as a fallback.\n *\n * The result is spelled the way `key` writes it, as `rootSpelling` and\n * `bassSpelling` hints, so the reflection of a chord in a flat-side key keeps\n * flat names: in Eb major the mirror of Bb formats as `Abm`, not `G#m`.\n *\n * @param chord The chord to reflect, as a chord symbol or as chord data.\n * @param key The prevailing key, as a key name or as a key/scale.\n * @returns The negative-harmony counterpart of the chord.\n * @example\n * ```ts\n * import { negativeHarmonyMirror, makeChord, majorKey } from '@libraz/libcantus';\n * negativeHarmonyMirror(makeChord(7, 'maj'), majorKey(0));\n * // G major reflected across C's axis => F minor (rootPc 5, quality 'min')\n * ```\n * @category Reharmonization\n */\nexport function negativeHarmonyMirror(chord: ChordLike, key: KeyLike): Chord {\n // Chord and key are read into their plain form once, here at the boundary.\n const source = toChordData(chord);\n const scale = toKeyScale(key);\n const tonicPc = mod12(scale.rootPc);\n const tonic = keyTonicOf(scale);\n const mirror = (p: number) => mod12(2 * tonicPc + 7 - p);\n const pcs = chordPitchClasses(source).map(mirror);\n const bassPc = source.bassPc !== undefined ? mirror(source.bassPc) : undefined;\n // Place the mirrored bass an octave below so it is recognized as the lowest\n // note and preserved through detection.\n const pitches = bassPc !== undefined ? [bassPc - 12, ...pcs] : pcs;\n const detected = detectChordBest(pitches);\n if (detected) {\n // Without an input bass the mirrored tones are octave-less, so detection may\n // read the lowest pitch class as a slash bass and report a spurious\n // inversion. Restore the recognized chord to root position in that case.\n if (bassPc === undefined && detected.bassPc !== undefined) {\n return spelledInKey(makeChord(detected.rootPc, detected.quality), tonic, scale);\n }\n return spelledInKey(detected, tonic, scale);\n }\n const quality: ChordQuality = source.quality;\n return spelledInKey(makeChord(mirror(source.rootPc), quality, bassPc), tonic, scale);\n}\n","/**\n * Deterministic rhythm generation over a metric grid.\n *\n * Builds a fixed-resolution grid across one or more bars of a time signature\n * and decides, per grid slot, whether it becomes a note onset. Onset\n * probability follows the metric-accent hierarchy from the `meter` module, so\n * strong positions (downbeats, secondary pulses) are favored over weak\n * off-pulse subdivisions. Given a seed the output is fully reproducible.\n *\n * Positions and durations are measured in quarter-note beats, matching the\n * library-wide convention.\n */\n\nimport { beatsPerBar, metricWeight, type TimeSignature } from '../../core/meter/index.js';\nimport type { NoteEvent } from '../../core/types.js';\nimport {\n assertFiniteNumber,\n assertGenerationBudget,\n assertPositiveInt,\n assertRange,\n} from '../../core/validation/index.js';\nimport { type GenerationContextInput, resolveContext } from '../context/index.js';\n\n/**\n * A single rhythmic note: an onset position and how long it sounds.\n *\n * @category Rhythm & Meter\n */\nexport type RhythmEvent = {\n /** Onset position in quarter-note beats, absolute from the start of the span. */\n position: number;\n /** Duration in quarter-note beats, extending to the next onset. */\n duration: number;\n};\n\n/** Velocity given to converted rhythm events when the caller names none. */\nconst DEFAULT_RHYTHM_VELOCITY = 96;\n\n/**\n * Turn rhythm events into note events at a fixed pitch.\n *\n * {@link RhythmEvent} names its fields `position`/`duration` because a rhythm\n * has no pitch; every other generator in the library speaks {@link NoteEvent}.\n * This is the bridge, so a generated rhythm can be humanized, grooved, or\n * written out like any other line.\n *\n * @param events The rhythm events to convert.\n * @param pitch The MIDI pitch to give every note.\n * @param velocity Velocity for every note; defaults to a mezzo-forte 96.\n * @returns The note events, in the order given.\n * @example\n * ```ts\n * import { generateRhythm, humanize, rhythmToNoteEvents } from '@libraz/libcantus';\n * const rhythm = generateRhythm({ numerator: 4, denominator: 4 }, { bars: 2 });\n * humanize(rhythmToNoteEvents(rhythm, 38));\n * ```\n * @category Rhythm & Meter\n */\nexport function rhythmToNoteEvents(\n events: RhythmEvent[],\n pitch: number,\n velocity: number = DEFAULT_RHYTHM_VELOCITY,\n): NoteEvent[] {\n assertFiniteNumber(pitch, 'pitch');\n assertRange(velocity, 0, 127, 'velocity');\n return events.map((event) => ({\n pitch,\n startBeat: event.position,\n durationBeat: event.duration,\n velocity,\n }));\n}\n\n/**\n * Options controlling {@link generateRhythm}.\n *\n * @category Rhythm & Meter\n */\nexport type RhythmOptions = {\n /**\n * Number of bars to generate.\n *\n * @defaultValue 1\n */\n bars?: number;\n /**\n * Grid resolution as the number of equal grid steps per quarter-note beat.\n * The default of 2 yields an eighth-note grid; 4 yields a sixteenth-note\n * grid, 3 an eighth-note triplet grid, and so on.\n *\n * @defaultValue 2\n */\n subdivision?: number;\n /**\n * The generation context.\n *\n * Its `complexity.rhythmic` is this generator's overall onset density: it\n * scales the per-slot onset probability, so higher values fill more grid\n * slots. A value outside [0, 1] is rejected. Its `seed` fixes which slots are\n * taken, so the same seed always yields the same pattern.\n *\n * @defaultValue `{ seed: 0, complexity: { rhythmic: 0.5 } }`\n */\n ctx?: GenerationContextInput;\n};\n\nconst DEFAULT_SUBDIVISION = 2;\nconst DEFAULT_DENSITY = 0.5;\nconst DEFAULT_BARS = 1;\n\n/**\n * Map a metric weight (0–3 from {@link metricWeight}) to a base onset\n * probability before density scaling. Strong positions are much more likely to\n * carry an onset than weak ones; the curve is intentionally small and explicit\n * so its behavior is easy to inspect and test.\n *\n * @param weight The metric weight, 0 (off-pulse) to 3 (downbeat).\n * @returns A base onset probability in [0, 1].\n * @category Rhythm & Meter\n */\nexport function onsetWeightCurve(weight: number): number {\n switch (weight) {\n case 3:\n return 1;\n case 2:\n return 0.8;\n case 1:\n return 0.55;\n default:\n return 0.25;\n }\n}\n\n/**\n * Generate a deterministic rhythmic pattern over `bars` bars of a time\n * signature. A grid is built at the chosen subdivision; each slot becomes an\n * onset when the draw belonging to that slot falls under its metric weight\n * scaled by the density dial. Because the draw belongs to the slot rather than\n * to the order the slots were visited, raising the dial only adds onsets:\n * everything already sounding stays where it was. Every bar's downbeat is always\n * an onset, so a pattern of any length has a reliable pulse independent of the\n * seed. Each event's duration extends to the next onset, and the last event\n * extends to the end of the span.\n *\n * @param ts The time signature.\n * @param opts Generation options.\n * @returns Onset events sorted by position, non-overlapping, covering the span.\n * @example\n * ```ts\n * import { parseTimeSignature, generateRhythm } from '@libraz/libcantus';\n * const ts = parseTimeSignature('4/4');\n * generateRhythm(ts, { ctx: { seed: 42, complexity: { rhythmic: 0.6 } } });\n * // onset events over one bar\n * ```\n * @category Rhythm & Meter\n */\nexport function generateRhythm(ts: TimeSignature, opts: RhythmOptions = {}): RhythmEvent[] {\n const bars = opts.bars ?? DEFAULT_BARS;\n const subdivision = opts.subdivision ?? DEFAULT_SUBDIVISION;\n const ctx = resolveContext(opts.ctx);\n const density = ctx.rhythmic ?? DEFAULT_DENSITY;\n\n assertPositiveInt(bars, 'rhythm bars');\n assertPositiveInt(subdivision, 'rhythm subdivision');\n\n const barBeats = beatsPerBar(ts);\n const spanBeats = barBeats * bars;\n const step = 1 / subdivision;\n const slotsPerBar = Math.ceil(barBeats * subdivision);\n const slotCount = slotsPerBar * bars;\n assertGenerationBudget(slotCount, 'rhythm grid slots');\n const draw = ctx.part('rhythm');\n\n const positions: number[] = [];\n for (let bar = 0; bar < bars; bar += 1) {\n const barStart = bar * barBeats;\n const barEnd = barStart + barBeats;\n for (let slot = 0; slot < slotsPerBar; slot += 1) {\n const position = barStart + slot * step;\n if (position >= barEnd - Number.EPSILON) {\n break;\n }\n // Force every bar's downbeat so the pulse is reliable regardless of seed.\n if (slot === 0) {\n positions.push(position);\n continue;\n }\n const weight = metricWeight(position, ts);\n const probability = onsetWeightCurve(weight) * density;\n if (draw.prob(probability, 'onset', bar, slot)) {\n positions.push(position);\n }\n }\n }\n\n const events: RhythmEvent[] = [];\n for (let i = 0; i < positions.length; i += 1) {\n const position = positions[i] ?? 0;\n const nextPosition = positions[i + 1] ?? spanBeats;\n events.push({ position, duration: nextPosition - position });\n }\n return events;\n}\n\n/**\n * Onset density of a generated pattern: the mean number of onsets per bar.\n *\n * @param events Events from {@link generateRhythm}.\n * @param ts The time signature.\n * @returns The onset count divided by the number of bars the events span.\n * @category Rhythm & Meter\n */\nexport function rhythmDensity(events: RhythmEvent[], ts: TimeSignature): number {\n if (events.length === 0) {\n return 0;\n }\n const last = events[events.length - 1];\n const spanBeats = (last?.position ?? 0) + (last?.duration ?? 0);\n const bars = spanBeats / beatsPerBar(ts);\n return bars > 0 ? events.length / bars : 0;\n}\n"]}
|