@libraz/libcantus 0.9.4 → 0.9.5
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 -18
- package/README_ja.md +268 -0
- package/dist/analyze/index.cjs +7 -7
- package/dist/analyze/index.d.cts +9 -99
- package/dist/analyze/index.d.ts +9 -99
- package/dist/analyze/index.js +6 -6
- package/dist/{chunk-36N73KZ3.cjs → chunk-2A2QMA74.cjs} +80 -65
- package/dist/chunk-2A2QMA74.cjs.map +1 -0
- package/dist/{chunk-YLEMGEFH.js → chunk-2PTFGVD7.js} +50 -35
- package/dist/chunk-2PTFGVD7.js.map +1 -0
- package/dist/{chunk-ZEJR4TKL.cjs → chunk-357ZDPVU.cjs} +36 -47
- package/dist/chunk-357ZDPVU.cjs.map +1 -0
- package/dist/{chunk-EHVEBWM5.js → chunk-3KX76RLG.js} +136 -37
- package/dist/chunk-3KX76RLG.js.map +1 -0
- package/dist/{chunk-RC6GBMCD.cjs → chunk-4VZ5MZAM.cjs} +155 -111
- package/dist/chunk-4VZ5MZAM.cjs.map +1 -0
- package/dist/{chunk-RK35RIN7.js → chunk-7EONYVI5.js} +115 -28
- package/dist/chunk-7EONYVI5.js.map +1 -0
- package/dist/{chunk-GQSGS2N4.js → chunk-7JEP3MFY.js} +5 -16
- package/dist/chunk-7JEP3MFY.js.map +1 -0
- package/dist/{chunk-7NKDPF74.js → chunk-AID75RN3.js} +203 -104
- package/dist/chunk-AID75RN3.js.map +1 -0
- package/dist/{chunk-FNDWXRRI.cjs → chunk-BSLTLGM3.cjs} +55 -37
- package/dist/chunk-BSLTLGM3.cjs.map +1 -0
- package/dist/{chunk-NP4NA2FB.cjs → chunk-CU6VG2YM.cjs} +39 -36
- package/dist/chunk-CU6VG2YM.cjs.map +1 -0
- package/dist/{chunk-NYYWMV2C.js → chunk-DZMHIGI4.js} +27 -17
- package/dist/chunk-DZMHIGI4.js.map +1 -0
- package/dist/{chunk-I24MV65U.js → chunk-EDOTEABK.js} +18 -15
- package/dist/{chunk-I24MV65U.js.map → chunk-EDOTEABK.js.map} +1 -1
- package/dist/{chunk-NKIBQ6PT.js → chunk-HN2OEUN2.js} +7 -4
- package/dist/chunk-HN2OEUN2.js.map +1 -0
- package/dist/{chunk-D2FAJBYL.js → chunk-KBTTEX7Q.js} +30 -12
- package/dist/chunk-KBTTEX7Q.js.map +1 -0
- package/dist/{chunk-CAN76NCU.cjs → chunk-KQN2ZYND.cjs} +147 -102
- package/dist/chunk-KQN2ZYND.cjs.map +1 -0
- package/dist/{chunk-4HLLZPXT.cjs → chunk-MPTVJIVG.cjs} +350 -251
- package/dist/chunk-MPTVJIVG.cjs.map +1 -0
- package/dist/{chunk-RUMU3UA6.cjs → chunk-MTMZTMWM.cjs} +12 -12
- package/dist/{chunk-RUMU3UA6.cjs.map → chunk-MTMZTMWM.cjs.map} +1 -1
- package/dist/{chunk-KMHOICIY.js → chunk-NNB67XA2.js} +90 -45
- package/dist/chunk-NNB67XA2.js.map +1 -0
- package/dist/{chunk-LW53AHQU.cjs → chunk-OL3IHHSJ.cjs} +228 -141
- package/dist/chunk-OL3IHHSJ.cjs.map +1 -0
- package/dist/{chunk-7MGZ7D62.cjs → chunk-PI3UXRWK.cjs} +43 -13
- package/dist/chunk-PI3UXRWK.cjs.map +1 -0
- package/dist/{chunk-XEBG3G53.js → chunk-R2VVFAIN.js} +42 -12
- package/dist/chunk-R2VVFAIN.js.map +1 -0
- package/dist/{chunk-Q6OP4U43.cjs → chunk-WFDXOLUB.cjs} +50 -40
- package/dist/chunk-WFDXOLUB.cjs.map +1 -0
- package/dist/{chunk-NVE34YGZ.cjs → chunk-WTAN4ACG.cjs} +212 -113
- package/dist/chunk-WTAN4ACG.cjs.map +1 -0
- package/dist/{chunk-FCANXBAT.cjs → chunk-YFJXS3AU.cjs} +8 -5
- package/dist/chunk-YFJXS3AU.cjs.map +1 -0
- package/dist/{chunk-Q2XUP227.js → chunk-YUE6TJ4M.js} +89 -45
- package/dist/chunk-YUE6TJ4M.js.map +1 -0
- package/dist/{chunk-HKGYCQ34.js → chunk-ZGLJM4A4.js} +2 -2
- package/dist/core/index.cjs +15 -5
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.d.cts +14 -4
- package/dist/core/index.d.ts +14 -4
- package/dist/core/index.js +14 -4
- package/dist/{function-DNugAOrt.d.cts → function-CxixTB02.d.cts} +98 -7
- package/dist/{function-BtiZb9i9.d.ts → function-DTCt675z.d.ts} +98 -7
- package/dist/generate/index.cjs +10 -10
- package/dist/generate/index.d.cts +21 -21
- package/dist/generate/index.d.ts +21 -21
- package/dist/generate/index.js +9 -9
- package/dist/{index-tJhkmQgE.d.cts → index-B2Ulxxpo.d.cts} +3 -2
- package/dist/{index-Be74mcon.d.cts → index-BkYJ0xMq.d.cts} +2 -2
- package/dist/{index-6VR8-kit.d.ts → index-BuaB-ypt.d.ts} +3 -2
- package/dist/{index-70p168VM.d.ts → index-CAN7v-kw.d.ts} +9 -2
- package/dist/{index-C83mZI1P.d.ts → index-COw1Ky9g.d.ts} +1 -1
- package/dist/{index-tSQh-xwY.d.ts → index-Cfx8xgH0.d.ts} +1 -1
- package/dist/{index-C5UBbQ4G.d.ts → index-DRtIhlJj.d.ts} +2 -2
- package/dist/{index-BtXT6UQ0.d.cts → index-alrRninU.d.cts} +9 -2
- package/dist/{index-y_ak9_Hi.d.cts → index-oTQbYYIs.d.cts} +1 -1
- package/dist/{index-OG65ocOr.d.ts → index-tR9jTn26.d.cts} +4 -1
- package/dist/{index-OG65ocOr.d.cts → index-tR9jTn26.d.ts} +4 -1
- package/dist/{index-D6b6na69.d.cts → index-x2vcsduE.d.cts} +1 -1
- package/dist/index.cjs +24 -14
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +11 -11
- package/dist/index.d.ts +11 -11
- package/dist/index.js +23 -13
- package/dist/model/index.cjs +7 -7
- package/dist/model/index.d.cts +48 -16
- package/dist/model/index.d.ts +48 -16
- package/dist/model/index.js +6 -6
- package/dist/{styled-DIRkk9Uz.d.ts → styled-Bl0ZK28h.d.ts} +40 -3
- package/dist/{styled-DJbkJQfj.d.cts → styled-Crv6SA4S.d.cts} +40 -3
- package/dist/theory/index.cjs +6 -6
- package/dist/theory/index.d.cts +9 -9
- package/dist/theory/index.d.ts +9 -9
- package/dist/theory/index.js +5 -5
- package/package.json +12 -9
- package/dist/chunk-36N73KZ3.cjs.map +0 -1
- package/dist/chunk-4HLLZPXT.cjs.map +0 -1
- package/dist/chunk-7MGZ7D62.cjs.map +0 -1
- package/dist/chunk-7NKDPF74.js.map +0 -1
- package/dist/chunk-CAN76NCU.cjs.map +0 -1
- package/dist/chunk-D2FAJBYL.js.map +0 -1
- package/dist/chunk-EHVEBWM5.js.map +0 -1
- package/dist/chunk-FCANXBAT.cjs.map +0 -1
- package/dist/chunk-FNDWXRRI.cjs.map +0 -1
- package/dist/chunk-GQSGS2N4.js.map +0 -1
- package/dist/chunk-KMHOICIY.js.map +0 -1
- package/dist/chunk-LW53AHQU.cjs.map +0 -1
- package/dist/chunk-NKIBQ6PT.js.map +0 -1
- package/dist/chunk-NP4NA2FB.cjs.map +0 -1
- package/dist/chunk-NVE34YGZ.cjs.map +0 -1
- package/dist/chunk-NYYWMV2C.js.map +0 -1
- package/dist/chunk-Q2XUP227.js.map +0 -1
- package/dist/chunk-Q6OP4U43.cjs.map +0 -1
- package/dist/chunk-RC6GBMCD.cjs.map +0 -1
- package/dist/chunk-RK35RIN7.js.map +0 -1
- package/dist/chunk-XEBG3G53.js.map +0 -1
- package/dist/chunk-YLEMGEFH.js.map +0 -1
- package/dist/chunk-ZEJR4TKL.cjs.map +0 -1
- /package/dist/{chunk-HKGYCQ34.js.map → chunk-ZGLJM4A4.js.map} +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["/home/runner/work/libcantus/libcantus/dist/chunk-MPTVJIVG.cjs","../src/generate/bass/index.ts","../src/generate/countermelody/index.ts","../src/generate/drums/internal.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/index.ts","../src/generate/groove/index.ts","../src/generate/harmonize/index.ts","../src/generate/motif/index.ts","../src/generate/progression/index.ts","../src/generate/rhythm/index.ts"],"names":["DEFAULT_TS","EPS"],"mappings":"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","file":"/home/runner/work/libcantus/libcantus/dist/chunk-MPTVJIVG.cjs","sourcesContent":[null,"/**\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 {\n beatsPerBar,\n isStrongBeat,\n pulseBeats,\n type TimeSignature,\n} from '../../core/meter/index.js';\nimport { pitchClassOf as pitchClass } from '../../core/pitch/index.js';\nimport { createRng } from '../../core/random/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 { nearestScaleTone } from '../../theory/scale/index.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 /** Chord placement to follow; need not be pre-sorted. */\n segments: readonly BassSegment[];\n /** Key/scale context, used for diatonic approach tones in `walking`. */\n key: KeyScale;\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 * Seed for the deterministic PRNG.\n *\n * @defaultValue 0\n */\n seed?: number;\n};\n\nconst DEFAULT_TS: TimeSignature = { numerator: 4, denominator: 4 };\nconst DEFAULT_STYLE: BassStyle = 'root';\nconst DEFAULT_OCTAVE = 2;\n\n/** Velocity for notes on metrically strong positions. */\nconst STRONG_VELOCITY = 100;\n/** Velocity for notes on weak positions. */\nconst WEAK_VELOCITY = 80;\n/** Probability of a weak-beat pickup in the `pop` style. */\nconst PICKUP_PROB = 0.35;\n\nconst 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 */\nfunction 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. */\nfunction 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 */\nfunction 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. */\nfunction 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. */\nfunction 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\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 rng: ReturnType<typeof createRng>;\n notes: RawNote[];\n prevMidi: number;\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 midi = clampToMidi(\n midiOverride ?? placePc(pc, ctx.prevMidi, ctx.low),\n 'generated bass pitch',\n );\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): 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.rng.prob(PICKUP_PROB)) {\n if (ctx.rng.prob(0.5)) {\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/** 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 * 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 is emitted\n * with an explicit MIDI value, bypassing the placement clamp, and a target on\n * the band edge would otherwise put it a semitone outside — below MIDI 0 at the\n * lowest accepted octave.\n */\nfunction approachNote(ctx: BuildContext, target: number, from: number): number {\n const dir = from <= target ? -1 : 1;\n const chromatic = target + dir;\n if (ctx.rng.prob(0.5)) {\n return foldIntoBand(chromatic, ctx.low);\n }\n const cand = nearestScaleTone(target + dir * 2, ctx.key);\n const step = Math.abs(cand - target);\n return foldIntoBand(step >= 1 && step <= 2 ? cand : chromatic, ctx.low);\n}\n\n/** Shift a pitch by whole octaves until it lies in the band `[low, low + 12]`. */\nfunction 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/** A quarter-note line of chord tones that leads by step into each chord change. */\nfunction buildWalking(ctx: BuildContext, seg: BassSegment, next: BassSegment | undefined): 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);\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 * @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');\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 const segments = [...opts.segments].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');\n const low = octave * 12 + 12;\n\n const ctx: BuildContext = {\n ts,\n low,\n key: opts.key,\n rng: createRng(opts.seed ?? 0),\n notes: [],\n prevMidi: low,\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);\n break;\n case 'walking':\n buildWalking(ctx, seg, segments[s + 1]);\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","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 { Rng } from '../../core/random/index.js';\nimport { createRng } from '../../core/random/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 { VoiceSnapshot } from '../../theory/safety/index.js';\nimport { enumerateSafePitches, evaluateSafety, NoteSafety } from '../../theory/safety/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 /** Key/scale context for scale-tone decisions. */\n key: KeyScale;\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 * Safety profile applied to candidate pitches.\n *\n * @defaultValue 'pop'\n */\n profile?: 'strict' | 'pop';\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 * PRNG seed. It places the `'complement'` onsets and breaks ties between\n * equally good candidate pitches; the same seed always yields the same line.\n * Where the 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 0\n */\n seed?: number;\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 */\nfunction complementOnsets(\n melody: NoteEventIndex,\n ts: TimeSignature,\n spanStart: number,\n spanEnd: number,\n rng: Rng,\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 = rng.prob(isWholeBeat(beat) ? HOLD_ON_BEAT_PROB : HOLD_OFF_BEAT_PROB);\n } else if (isStrongBeat(beat, ts)) {\n place = rng.prob(REINFORCE_PROB);\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/**\n * Preference score for one candidate counter pitch. Rewards imperfect\n * consonance with the melody, chord-tone membership, contrary or oblique\n * motion, and stepwise movement; penalizes wide or forbidden leaps, weak-beat\n * clashes, and drifting from the register centre.\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): number {\n let score = 0;\n if (melPitch !== undefined) {\n const ic = Math.abs(pitch - melPitch) % 12;\n if (ic === 3 || ic === 4 || ic === 8 || ic === 9) {\n score += 2; // imperfect consonance: thirds and sixths\n } else if (ic === 0 || ic === 7) {\n score += 0.5; // perfect consonance: allowed but less colourful\n } else if (ic === 1 || ic === 2 || ic === 6 || ic === 10 || ic === 11) {\n score -= 2; // dissonant even on weak beats\n }\n }\n if (chord && chordToneRole(pitch, chord) !== null) {\n score += 1;\n }\n if (prevPitch !== undefined) {\n const move = pitch - prevPitch;\n const dist = Math.abs(move);\n score -= dist * 0.3;\n if (dist > 0 && dist <= 2) {\n score += 1; // stepwise motion\n }\n if (isForbiddenMelodicLeap(prevPitch, pitch)) {\n score -= 6;\n }\n if (melPitch !== undefined && melPrev !== undefined) {\n const melMove = melPitch - melPrev;\n if ((move > 0 && melMove < 0) || (move < 0 && melMove > 0)) {\n score += 2; // contrary motion\n } else if (move === 0 || melMove === 0) {\n score += 1; // oblique motion\n } else {\n score -= 1; // similar motion\n }\n }\n }\n score -= Math.abs(pitch - center) * 0.02;\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 scored to favour contrary or oblique motion, imperfect\n * consonance, and stepwise movement. 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 TypeError('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 rng = createRng(opts.seed ?? 0);\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, rng);\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({ length: high - low + 1 }, () => rng.next() * TIE_BREAK_JITTER);\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: opts.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 opts.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) +\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","/** 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 beat and one sixteenth expressed as fractions of a beat. */\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/** Reduced section set the engine reasons about. */\nexport type SectionType = 'intro' | 'a' | 'b' | 'chorus' | 'bridge' | 'outro';\n\n/** Backing density bucket derived from the public density knob. */\nexport type BackingDensity = 'thin' | 'normal' | 'thick';\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/** Public section identifiers. */\nexport type PublicSection = (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: PublicSection): 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/** Bucket the 0..1 density knob into a backing-density level. */\nexport function mapDensity(density: number): BackingDensity {\n if (density < 0.34) {\n return 'thin';\n }\n if (density < 0.67) {\n return 'normal';\n }\n return 'thick';\n}\n\n/** Swing amount (0..1) implied by a groove feel. */\nexport function feelSwingAmount(feel: Feel): number {\n if (feel === 'swing') {\n return 0.33;\n }\n if (feel === 'shuffle') {\n return 0.5;\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/** 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","import type { BackingDensity, SectionType } from './internal.js';\nimport { MoodCategory, sectionIndex } 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// Ghost density level per [section index][mood category].\nconst GHOST_DENSITY_TABLE: GhostDensityLevel[][] = [\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 /* interlude */ [0, 1, 1],\n /* outro */ [0, 1, 1],\n];\n\nfunction levelToProbability(level: GhostDensityLevel): number {\n switch (level) {\n case 0:\n return 0;\n case 1:\n return 0.15;\n case 2:\n return 0.3;\n case 3:\n return 0.45;\n }\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/** Ghost-note trigger probability for a section, mood, density, and tempo. */\nexport function getGhostDensity(\n mood: MoodCategory,\n section: SectionType,\n backingDensity: BackingDensity,\n bpm: number,\n): number {\n const row = GHOST_DENSITY_TABLE[sectionIndex(section)] ?? GHOST_DENSITY_TABLE[1];\n const level = adjustForBpm((row?.[mood] ?? 0) as GhostDensityLevel, bpm);\n\n if (backingDensity === 'thin' && level !== 0) {\n return levelToProbability(clampLevel(level - 1));\n }\n if (backingDensity === 'thick' && level !== 3) {\n return levelToProbability(clampLevel(level + 1));\n }\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 { DrumRole, DrumStyle, SectionType } from './internal.js';\nimport { GM } from './internal.js';\nimport type { DrumRng } from './rng.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/** Choose the hi-hat subdivision for a section/style/density/BPM. */\nexport function getHiHatLevel(\n section: SectionType,\n style: DrumStyle,\n backingDensity: 'thin' | 'normal' | 'thick',\n bpm: number,\n rng: DrumRng,\n): HiHatLevel {\n const allow16th = bpm < HH_16TH_BPM_THRESHOLD;\n let base: HiHatLevel = 'eighth';\n\n if (style === 'sparse') {\n base = section === 'chorus' ? 'eighth' : 'quarter';\n } else if (style === 'fourOnFloor') {\n if (allow16th && section === 'chorus' && rng.prob(0.25)) {\n return 'sixteenth';\n }\n return 'eighth';\n } else if (style === 'synth') {\n if (!allow16th) {\n return 'eighth';\n }\n if (section === 'a' && rng.prob(0.2)) {\n return 'eighth';\n }\n return 'sixteenth';\n } else if (style === 'trap') {\n return allow16th ? 'sixteenth' : 'eighth';\n } else if (style === 'latin') {\n if (allow16th && section === 'chorus' && rng.prob(0.3)) {\n return 'sixteenth';\n }\n return 'eighth';\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 = rng.prob(0.3) ? 'quarter' : 'eighth';\n break;\n case 'b':\n base = allow16th && rng.prob(0.25) ? 'sixteenth' : 'eighth';\n break;\n case 'chorus':\n base = allow16th && rng.prob(0.35) ? 'sixteenth' : 'eighth';\n break;\n case 'bridge':\n base = 'eighth';\n break;\n }\n }\n\n if (backingDensity === 'thin') {\n base = sparser(base);\n } else if (backingDensity === 'thick') {\n base = denser(base);\n }\n\n if (!allow16th && base === 'sixteenth') {\n base = 'eighth';\n }\n return base;\n}\n\n/** Metric velocity multiplier for a 16th position within a beat. */\nexport function hiHatVelocityMultiplier(sixteenth: number, rng: DrumRng): number {\n let baseValue: number;\n switch (sixteenth) {\n case 0:\n baseValue = 0.95;\n break;\n case 2:\n baseValue = 0.75;\n break;\n case 1:\n baseValue = 0.55;\n break;\n default:\n baseValue = 0.5;\n break;\n }\n return baseValue * rng.float(0.95, 1.05);\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, rng: DrumRng): number {\n if (section === 'chorus') {\n const choice = rng.range(0, 3);\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 rng: DrumRng,\n): boolean {\n if (section !== 'chorus' && section !== 'b') {\n return false;\n }\n if (section === 'chorus') {\n if (beat === 1 || beat === 3) {\n return rng.prob(0.6);\n }\n return false;\n }\n if (beat === 3 && bar % 2 === 1) {\n return rng.prob(0.4);\n }\n return false;\n}\n\n/** Foot hi-hat velocity with slight humanization. */\nexport function footHiHatVelocity(rng: DrumRng): number {\n return rng.range(FHH_VEL_MIN, FHH_VEL_MAX);\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;\nconst HALF_16TH = 0.125;\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/** Effective swing after applying the groove feel to a base swing amount. */\nexport function effectiveSwing(feel: Feel, swingAmount: number): number {\n if (feel === 'straight') {\n return 0;\n }\n if (feel === 'shuffle') {\n return Math.min(1, swingAmount * 1.5);\n }\n return swingAmount;\n}\n\n/**\n * Push an off-beat position toward its triplet placement.\n *\n * On-beat positions are returned unchanged; off-beat 8ths and 16ths are delayed\n * proportionally to the swing amount.\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.\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\n if (resolution === 'sixteenth') {\n if (offset >= HALF_16TH && offset < SIXTEENTH + HALF_16TH) {\n return base + SIXTEENTH + DELTA_16 * clamped;\n }\n if (offset >= SIXTEENTH + HALF_16TH && offset < EIGHTH + HALF_16TH) {\n return base + EIGHTH + DELTA_8 * clamped;\n }\n if (offset >= EIGHTH + HALF_16TH && offset < 3 * SIXTEENTH + HALF_16TH) {\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\n // in the first half. Because DELTA_8 = 2 * DELTA_16, that midpoint reduces\n // to a single DELTA_16 delay off the straight position, so the \"a\" never\n // overshoots the downbeat and needs no clamp. At the default shuffle feel\n // (swing 0.75) this lands near 0.8125 rather than the doubly-swung 0.9375.\n return base + 3 * SIXTEENTH + DELTA_16 * clamped;\n }\n return startBeat;\n }\n\n if (offset >= SIXTEENTH && offset < 3 * SIXTEENTH) {\n return base + EIGHTH + DELTA_8 * clamped;\n }\n return startBeat;\n}\n","import {\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 { BackingDensity, DrumRole, DrumStyle, Feel, SectionType } from './internal.js';\nimport { EIGHTH, GM, type MoodCategory, SIXTEENTH } from './internal.js';\nimport type { KickPattern } from './kick.js';\nimport type { DrumRng } from './rng.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 densityMult: number;\n backingDensity: BackingDensity;\n hhLevel: HiHatLevel;\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 rng: DrumRng;\n};\n\n/**\n * How much of the feel's swing a style actually takes. Trap sits on a straight\n * (or triplet-hat) grid, latin only leans; everything else swings by half.\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 0.5;\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 return effectiveSwing(sec.feel, swingAmount) * 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/** Emit the kick for one beat. */\nexport function generateKickForBeat(ctx: BeatCtx, sec: SectionCtx, kick: KickPattern): void {\n if (ctx.inPrechorusLift) {\n return;\n }\n const on = [kick.beat1, kick.beat2, kick.beat3, kick.beat4][ctx.beat] ?? false;\n const and = [kick.beat1and, kick.beat2and, kick.beat3and, kick.beat4and][ctx.beat] ?? false;\n\n if (on) {\n ctx.track.add(GM.BD, ctx.beatTick, EIGHTH, ctx.velocity);\n }\n if (and) {\n const andTick = swing16(ctx.beatTick + EIGHTH, sec, ctx.swingAmount);\n ctx.track.add(GM.BD, andTick, 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 + 16);\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 const positions = selectGhostPositions(sec.ghostMood);\n let ghostProb = getGhostDensity(sec.ghostMood, ctx.section, sec.backingDensity, ctx.bpm);\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.rng.prob(ghostProb * posProb)) {\n continue;\n }\n const variation = ctx.rng.float(0.85, 1.15);\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 ctx.track.add(GM.SD, ctx.beatTick, EIGHTH, buildupVel);\n ctx.track.add(\n GM.SD,\n swing16(ctx.beatTick + EIGHTH, sec, ctx.swingAmount),\n EIGHTH,\n buildupVel * 0.85,\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 (sec.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.rng));\n }\n return;\n }\n\n if (sec.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.rng));\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 = (ctx.beat === 1 || ctx.beat === 3) && ctx.rng.prob(openProb);\n } else if (eighth === 0) {\n useOpen = shouldAddOpenHHAccent(ctx.section, ctx.beat, ctx.bar, ctx.rng);\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(sixteenth, ctx.rng);\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.rng.prob(openProb)) {\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","import type { HitList } from './hit.js';\nimport type { DrumStyle, SectionEnergy, SectionType } from './internal.js';\nimport { EIGHTH, GM, SIXTEENTH } from './internal.js';\nimport type { DrumRng } from './rng.js';\n\n/** The thirteen fill archetypes for section transitions. */\nexport type FillType =\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\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 * Pick a fill archetype for a section transition.\n *\n * The transition-specific archetypes (into a chorus, out of an intro) are\n * checked before the generic energy fills so every variation stays reachable;\n * with `from === to` the caller effectively asks for a within-section fill.\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 rng Deterministic PRNG.\n */\nexport function selectFillType(\n from: SectionType,\n to: SectionType,\n style: DrumStyle,\n nextEnergy: SectionEnergy,\n rng: DrumRng,\n): FillType {\n if (style === 'sparse') {\n return rng.range(0, 1) === 0 ? 'simpleCrash' : 'breakdownFill';\n }\n\n const toChorus = to === 'chorus';\n const fromIntro = from === 'intro';\n const highEnergy = style === 'rock' || style === 'fourOnFloor';\n\n // Dropping into a low-energy section: keep the phrase end gentle.\n if (nextEnergy === 'low') {\n switch (rng.range(0, 2)) {\n case 0:\n return 'simpleCrash';\n case 1:\n return 'breakdownFill';\n default:\n return 'halfTimeFill';\n }\n }\n\n // Big lead-ins to a chorus take precedence over the generic energy fills.\n if (toChorus) {\n const choice = rng.range(0, 7);\n if (highEnergy) {\n switch (choice) {\n case 0:\n case 1:\n return 'tomDescend';\n case 2:\n return 'snareRoll';\n case 3:\n return 'linearFill';\n case 4:\n return 'bdSnareAlternate';\n case 5:\n return 'flamsAndDrags';\n case 6:\n return 'tomShuffle';\n default:\n return 'ghostToAccent';\n }\n }\n switch (choice) {\n case 0:\n case 1:\n return 'snareTomCombo';\n case 2:\n return 'tomDescend';\n case 3:\n return 'ghostToAccent';\n case 4:\n return 'hiHatChoke';\n case 5:\n return 'linearFill';\n default:\n return 'snareRoll';\n }\n }\n\n // Leaving the intro: lighter, building character.\n if (fromIntro) {\n switch (rng.range(0, 5)) {\n case 0:\n case 1:\n return 'snareRoll';\n case 2:\n return 'simpleCrash';\n case 3:\n return 'ghostToAccent';\n case 4:\n return 'breakdownFill';\n default:\n return 'halfTimeFill';\n }\n }\n\n // Generic medium/high/peak fills, split by style energy.\n const choice = rng.range(0, 7);\n if (highEnergy) {\n switch (choice) {\n case 0:\n return 'tomDescend';\n case 1:\n return 'snareRoll';\n case 2:\n return 'tomAscend';\n case 3:\n return 'snareTomCombo';\n case 4:\n return 'linearFill';\n case 5:\n return 'bdSnareAlternate';\n case 6:\n return 'flamsAndDrags';\n default:\n return 'tomShuffle';\n }\n }\n\n switch (choice) {\n case 0:\n case 1:\n return 'snareRoll';\n case 2:\n return 'snareTomCombo';\n case 3:\n return 'ghostToAccent';\n case 4:\n return 'hiHatChoke';\n case 5:\n return 'halfTimeFill';\n default:\n return 'breakdownFill';\n }\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 fill archetype.\n * @param velocity Base velocity for the beat.\n */\nexport function generateFill(\n track: HitList,\n beatTick: number,\n beat: number,\n fillType: FillType,\n velocity: number,\n): void {\n const fillVel = velocity * 0.9;\n const accentVel = velocity * 0.95;\n const S = SIXTEENTH;\n const E = EIGHTH;\n\n if (beat === 0) {\n track.add(GM.BD, beatTick, E, fillVel);\n track.add(GM.SD, beatTick + E, E, fillVel - 5);\n return;\n }\n if (beat === 1) {\n track.add(GM.SD, beatTick, E, fillVel);\n track.add(GM.TOM_H, beatTick + E, E, fillVel - 3);\n return;\n }\n\n switch (fillType) {\n case 'snareRoll':\n if (beat === 2) {\n for (let i = 0; i < 4; i += 1) {\n track.add(GM.SD, beatTick + i * S, S, fillVel * (0.6 + 0.1 * i));\n }\n } else {\n for (let i = 0; i < 3; i += 1) {\n track.add(GM.SD, beatTick + i * S, S, fillVel * (0.7 + 0.1 * i));\n }\n track.add(GM.SD, beatTick + 3 * S, S, accentVel);\n }\n break;\n\n case 'tomDescend':\n if (beat === 2) {\n track.add(GM.SD, beatTick, E, fillVel);\n track.add(GM.TOM_H, beatTick + E, E, fillVel - 5);\n } else {\n track.add(GM.TOM_H, beatTick, S, fillVel);\n track.add(GM.TOM_M, beatTick + S, S, fillVel - 3);\n track.add(GM.TOM_M, beatTick + E, S, fillVel - 5);\n track.add(GM.TOM_L, beatTick + E + S, S, accentVel);\n }\n break;\n\n case 'tomAscend':\n if (beat === 2) {\n track.add(GM.SD, beatTick, E, fillVel);\n track.add(GM.TOM_L, beatTick + E, E, fillVel - 5);\n } else {\n track.add(GM.TOM_L, beatTick, S, fillVel);\n track.add(GM.TOM_M, beatTick + S, S, fillVel + 3);\n track.add(GM.TOM_M, beatTick + E, S, fillVel + 5);\n track.add(GM.TOM_H, beatTick + E + S, S, accentVel);\n }\n break;\n\n case 'snareTomCombo':\n if (beat === 2) {\n track.add(GM.SD, beatTick, E, fillVel);\n track.add(GM.SD, beatTick + E, S, fillVel - 5);\n track.add(GM.TOM_H, beatTick + E + S, S, fillVel);\n } else {\n track.add(GM.TOM_M, beatTick, S, fillVel);\n track.add(GM.SD, beatTick + S, S, fillVel - 3);\n track.add(GM.TOM_L, beatTick + E, S, fillVel + 2);\n track.add(GM.BD, beatTick + E + S, S, accentVel);\n }\n break;\n\n case 'simpleCrash':\n if (beat === 3) {\n track.add(GM.BD, beatTick + E + S, S, accentVel);\n track.add(GM.CRASH, beatTick + E + S, E, accentVel);\n }\n break;\n\n case 'linearFill':\n if (beat === 2) {\n track.add(GM.BD, beatTick, S, fillVel);\n track.add(GM.SD, beatTick + S, S, fillVel);\n track.add(GM.TOM_H, beatTick + 2 * S, S, fillVel);\n track.add(GM.TOM_M, beatTick + 3 * S, S, fillVel);\n } else {\n track.add(GM.TOM_L, beatTick, S, fillVel + 3);\n track.add(GM.SD, beatTick + S, S, fillVel + 5);\n track.add(GM.BD, beatTick + 2 * S, S, fillVel + 7);\n track.add(GM.SD, beatTick + 3 * S, S, accentVel);\n }\n break;\n\n case 'ghostToAccent':\n if (beat === 2) {\n const ghost = fillVel * 0.4;\n track.add(GM.SD, beatTick, S, ghost);\n track.add(GM.SD, beatTick + S, S, ghost + 10);\n track.add(GM.SD, beatTick + 2 * S, S, ghost + 20);\n track.add(GM.SD, beatTick + 3 * S, S, ghost + 30);\n } else {\n track.add(GM.SD, beatTick, E, fillVel);\n track.add(GM.SD, beatTick + E, E, accentVel);\n }\n break;\n\n case 'bdSnareAlternate':\n if (beat === 2) {\n track.add(GM.BD, beatTick, S, fillVel);\n track.add(GM.SD, beatTick + S, S, fillVel);\n track.add(GM.BD, beatTick + 2 * S, S, fillVel + 3);\n track.add(GM.SD, beatTick + 3 * S, S, fillVel + 3);\n } else {\n track.add(GM.BD, beatTick, S, fillVel + 5);\n track.add(GM.SD, beatTick + S, S, fillVel + 5);\n track.add(GM.BD, beatTick + 2 * S, S, accentVel);\n track.add(GM.SD, beatTick + 3 * S, S, accentVel);\n }\n break;\n\n case 'hiHatChoke':\n if (beat === 2) {\n track.add(GM.OHH, beatTick, E, fillVel);\n track.add(GM.OHH, beatTick + E, E, fillVel + 5);\n } else {\n track.add(GM.OHH, beatTick, S, fillVel + 8);\n track.add(GM.CHH, beatTick + S, S, accentVel);\n track.add(GM.SD, beatTick + E, E, accentVel);\n }\n break;\n\n case 'tomShuffle':\n if (beat === 2) {\n track.add(GM.TOM_H, beatTick, E, fillVel);\n track.add(GM.TOM_M, beatTick + E + S / 2, S, fillVel - 5);\n } else {\n track.add(GM.TOM_M, beatTick, E, fillVel);\n track.add(GM.TOM_L, beatTick + E + S / 2, S, fillVel + 5);\n }\n break;\n\n case 'breakdownFill':\n if (beat === 3) {\n track.add(GM.SD, beatTick + E, S, accentVel);\n }\n break;\n\n case 'flamsAndDrags':\n if (beat === 2) {\n track.add(GM.SD, beatTick - S / 4, S / 4, fillVel * 0.5);\n track.add(GM.SD, beatTick, E, fillVel);\n track.add(GM.SD, beatTick + E, S / 2, fillVel * 0.6);\n track.add(GM.SD, beatTick + E + S / 2, S / 2, fillVel * 0.6);\n track.add(GM.SD, beatTick + E + S, E, fillVel);\n } else {\n track.add(GM.SD, beatTick - S / 4, S / 4, fillVel * 0.5);\n track.add(GM.SD, beatTick, 1, accentVel);\n }\n break;\n\n case 'halfTimeFill':\n if (beat === 2) {\n track.add(GM.SD, beatTick, 1, accentVel);\n track.add(GM.BD, beatTick, 1, fillVel);\n } else if (beat === 3) {\n // At low energy the fill spans only beat 3, so this branch keeps the\n // phrase end from going silent: a broad half-time backbeat snare with a\n // light pickup into the next section.\n track.add(GM.SD, beatTick, EIGHTH, accentVel);\n track.add(GM.SD, beatTick + E, S, fillVel * 0.6);\n track.add(GM.SD, beatTick + E + S, S, accentVel);\n }\n break;\n }\n}\n","import { 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({ bars: 1, bpm: 120, style: 'standard', section: 'verse', density: 0.5 });\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\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(pitch: number, startBeat: number, durationBeat: number, velocity: number): void {\n const beat = Math.max(0, startBeat);\n this.hits.push({\n pitch,\n startBeat: beat,\n durationBeat,\n velocity: clampVel(velocity),\n });\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/** Key an onset by pitch and quantized beat. */\nfunction onsetKey(pitch: number, startBeat: number): string {\n return `${pitch}:${Math.round(startBeat * ONSET_KEY_SCALE)}`;\n}\n","import type { DrumStyle, SectionType } from './internal.js';\nimport type { DrumRng } from './rng.js';\n\n/** Kick presence flags across the eight 8th-note slots of a bar. */\nexport type KickPattern = {\n beat1: boolean;\n beat1and: boolean;\n beat2: boolean;\n beat2and: boolean;\n beat3: boolean;\n beat3and: boolean;\n beat4: boolean;\n beat4and: boolean;\n};\n\nconst EMPTY: KickPattern = {\n beat1: false,\n beat1and: false,\n beat2: false,\n beat2and: false,\n beat3: false,\n beat3and: false,\n beat4: false,\n beat4and: 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 * 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 rng Deterministic PRNG for probabilistic accents.\n */\nexport function getKickPattern(\n section: SectionType,\n style: DrumStyle,\n bar: number,\n rng: DrumRng,\n): KickPattern {\n const p: KickPattern = { ...EMPTY };\n\n if (section === 'outro') {\n p.beat1 = true;\n p.beat3 = true;\n return p;\n }\n\n switch (style) {\n case 'sparse':\n p.beat1 = true;\n if (section === 'chorus' && bar % 2 === 1) {\n p.beat3 = true;\n }\n break;\n\n case 'fourOnFloor':\n p.beat1 = p.beat2 = p.beat3 = p.beat4 = true;\n if (section === 'chorus' && rng.prob(0.2)) {\n p.beat2and = true;\n }\n break;\n\n case 'upbeat':\n p.beat1 = true;\n p.beat3 = true;\n if (section === 'b' || section === 'chorus') {\n p.beat2and = rng.prob(0.7);\n }\n if (section === 'chorus') {\n p.beat4and = rng.prob(0.6);\n }\n break;\n\n case 'rock':\n p.beat1 = true;\n p.beat3 = true;\n if (section === 'chorus') {\n p.beat2and = rng.prob(0.65);\n p.beat4and = rng.prob(0.4);\n } else if (section === 'b') {\n p.beat2and = rng.prob(0.3);\n }\n break;\n\n case 'synth':\n p.beat1 = true;\n p.beat3 = true;\n if (section === 'b' || section === 'chorus') {\n p.beat2and = rng.prob(0.75);\n }\n if (section === 'chorus') {\n p.beat4and = rng.prob(0.65);\n }\n break;\n\n case 'trap':\n p.beat1 = true;\n p.beat2and = rng.prob(0.8);\n p.beat3 = rng.prob(0.3);\n p.beat4and = rng.prob(0.7);\n break;\n\n case 'latin':\n p.beat1 = true;\n p.beat2and = true;\n p.beat3 = true;\n p.beat4and = rng.prob(0.5);\n break;\n\n default:\n p.beat1 = true;\n p.beat3 = true;\n if (section === 'b') {\n p.beat2and = rng.prob(0.5);\n } else if (section === 'chorus') {\n p.beat2and = rng.prob(0.55);\n p.beat4and = rng.prob(0.35);\n }\n break;\n }\n\n return p;\n}\n","import { 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';\nimport type { DrumRng } from './rng.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/** Emit one bar of auxiliary percussion (tambourine/shaker/handclap). */\nexport function generateAuxPercussionForBar(\n track: HitList,\n barStart: number,\n config: PercussionConfig,\n role: DrumRole,\n densityMult: number,\n rng: DrumRng,\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 * rng.float(0.9, 1.1);\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 = config.shaker16th && bpm < HH_16TH_BPM_THRESHOLD;\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 const raw = 80 * (velCurve[sub] ?? 0.5) * densityMult * rng.float(0.9, 1.1);\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 = 80 * (velCurve[sub] ?? 0.6) * densityMult * rng.float(0.9, 1.1);\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 * rng.float(0.9, 1.1);\n track.add(GM.HANDCLAP, barStart + beat, EIGHTH, Math.max(50, Math.min(100, raw)));\n }\n }\n}\n","import { beatsPerBar, type TimeSignature } from '../../core/meter/index.js';\nimport {\n assertGenerationBudget,\n assertInteger,\n assertOneOf,\n assertPositiveInt,\n assertRange,\n assertTimeSignature,\n} from '../../core/validation/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 { type FillType, generateFill, getFillStartBeat, selectFillType } 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 } from './hit.js';\nimport {\n calculateVelocity,\n DRUM_FEELS,\n DRUM_ROLES,\n type DrumRole,\n type Feel,\n feelSwingAmount,\n GM,\n GROOVE_STYLES,\n type GrooveStyle,\n ghostMoodCategory,\n mapDensity,\n mapSection,\n mapStyle,\n PUBLIC_SECTIONS,\n type PublicSection,\n percMoodCategory,\n sectionDensityMultiplier,\n sectionEnergy,\n} from './internal.js';\nimport { getKickPattern, isInPreChorusLift, type KickPattern } from './kick.js';\nimport { generateAuxPercussionForBar, getPercussionConfig } from './percussion.js';\nimport { createRng } from './rng.js';\n\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/**\n * Groove feel (the swing/straight rhythmic character) for {@link generateDrums}.\n *\n * @category Composition\n */\nexport type GrooveFeel = Feel;\n\n/**\n * Drum voicing role and groove style identifiers for {@link generateDrums}.\n *\n * @category Composition\n */\nexport type { DrumRole, GrooveStyle } from './internal.js';\n\n/**\n * Public section identifiers for {@link generateDrums}.\n *\n * @category Composition\n */\nexport type Section = PublicSection;\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 bpm: number;\n style: GrooveStyle;\n section: Section;\n density: number;\n /** Time signature; defaults to 4/4. */\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 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 * Seed for the deterministic PRNG.\n *\n * @defaultValue 0\n */\n seed?: number;\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\n/** Generous upper bound on onsets emitted for a single bar, used for budgeting. */\nconst MAX_HITS_PER_BAR = 128;\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 * `density` sets the backing-density level; `section` shapes kick, hi-hat, ghost,\n * and percussion density. 16th-note hi-hats drop to 8ths at or above 150 BPM.\n * When `fills` is true the final bar is replaced with a fill whose archetype is\n * shaped by `nextSection`; a fill that would emit nothing on its beat falls back\n * to the normal groove so the phrase end is never silent. `euclideanKick`\n * overrides the kick with an evenly-spread Euclidean pattern. Output is fully\n * determined by the options plus `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({ bars: 4, bpm: 120, style: 'standard', section: 'chorus', density: 0.6, fills: true });\n * // Fully determined by the options plus seed (defaults to 0).\n * ```\n *\n * @category Composition\n */\nexport function generateDrums(opts: DrumsOptions): DrumHit[] {\n assertPositiveInt(opts.bars, 'drum bars');\n assertRange(opts.bpm, Number.MIN_VALUE, 1000, 'drum bpm');\n assertRange(opts.density, 0, 1, 'drum density');\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');\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 rng = createRng(opts.seed ?? 0);\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 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 backingDensity = mapDensity(opts.density);\n const swingAmount = feelSwingAmount(feel);\n\n let densityMult = sectionDensityMultiplier(section);\n if (backingDensity === 'thin') {\n densityMult *= 0.75;\n } else if (backingDensity === 'thick') {\n densityMult *= 1.15;\n }\n\n const sec: SectionCtx = {\n style,\n feel,\n densityMult,\n backingDensity,\n hhLevel: getHiHatLevel(section, style, backingDensity, opts.bpm, rng),\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 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 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 for (let bar = 0; bar < opts.bars; bar += 1) {\n const barStart = bar * barBeats;\n const isLastBar = bar === opts.bars - 1;\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, rng);\n barHasOpenHh = !track.hasCrashNear(barStart + openHhBeatIndex);\n }\n\n let kick: KickPattern | undefined;\n if (!euclidKick && reuseSectionKick) {\n sectionKick ??= getKickPattern(section, style, 0, rng);\n kick = sectionKick;\n } else if (!euclidKick) {\n kick = getKickPattern(section, style, bar, rng);\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: FillType = '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 currentFill = selectFillType(section, nextSection, style, nextEnergy, rng);\n }\n if (playMainVoices) {\n const before = track.hits.length;\n generateFill(track, beatTick, beat, currentFill, velocity);\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: opts.bpm,\n bar,\n sectionBars: opts.bars,\n inPrechorusLift: inLift,\n swingAmount,\n barHasOpenHh,\n openHhBeat: openHhBeatIndex,\n rng,\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 track.add(\n GM.BD,\n swing16(barStart + onset, sec, swingAmount),\n Math.min(0.5, stepLength),\n velocity * (wholeBeat ? 1 : 0.85),\n );\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(rng));\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 rng,\n opts.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 return track.hits\n .filter((hit) => hit.startBeat < endBeat)\n .sort((a, b) => a.startBeat - b.startBeat || a.pitch - b.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 { createRng } from '../../core/random/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';\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 * Seed for the deterministic PRNG.\n *\n * @defaultValue 0\n */\n seed?: number;\n};\n\n/** Default time signature for {@link humanize} when none is given. */\nconst DEFAULT_TS: TimeSignature = { numerator: 4, denominator: 4 };\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 and clamped to be non-negative, and `velocity`\n * 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 * @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, { seed: 1, timing: 0.03 }); // copies with jittered timing and accented velocity\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 rng = createRng(opts.seed ?? 0);\n\n return sounding.map((event) => {\n const timingOffset = rng.float(-timing, timing);\n const startBeat = Math.max(0, event.startBeat + timingOffset);\n\n const weight = metricWeight(event.startBeat, ts);\n const accentBoost = (weight / MAX_METRIC_WEIGHT) * accent;\n const velocityOffset = rng.float(-velocityJitter, velocityJitter);\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 const startBeat = Math.max(0, 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 { isDiatonic, parallelKey } from '../../analyze/functional/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 { pitchClassOf as pitchClass } from '../../core/pitch/index.js';\nimport { createRng } from '../../core/random/index.js';\nimport type { KeyScale, NoteEvent } from '../../core/types.js';\nimport {\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 majorKey,\n minorKey,\n NATURAL_MINOR_MASK,\n scaleTonesInDegreeOrder,\n} from '../../theory/scale/index.js';\nimport type { ChordSpan } from '../progression/index.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 * Search a range of transpositions and keep the best-fitting one. The result\n * reports the shift it chose in `transposeSemitones`.\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.\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.\n *\n * @defaultValue `'infer'`\n */\n key?: KeyScale | 'infer';\n /**\n * Length of each chord slot in beats. Any positive value is honoured, as in\n * {@link chordTimelineFromNotes}; a value fine enough to make the search\n * explode is rejected by the generation budget rather than rounded up.\n *\n * @defaultValue 2\n */\n harmonicRhythm?: number;\n /**\n * Time signature used to weight metric accents; defaults to 4/4. A waltz or\n * a jig harmonized against a 4/4 accent grid gets its chords placed on the\n * wrong beats, and the error accumulates bar by bar.\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 * @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 * Seed for the deterministic tie-break perturbation.\n *\n * @defaultValue 0\n */\n seed?: number;\n};\n\n/**\n * The chosen transpose, key, chord path, and per-note roles.\n *\n * @category Reharmonization\n */\nexport type HarmonizeResult = {\n transposeSemitones: number;\n key: KeyScale;\n chords: ChordSpan[];\n melodyRoles: { noteIndex: number; role: HarmonyRole }[];\n};\n\ntype 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\ntype Segment = { startBeat: number; endBeat: number; noteIndices: number[] };\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/** Chord slot length assumed when the caller names none: one chord per half bar. */\nconst DEFAULT_HARMONIC_RHYTHM = 2;\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 * 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.3 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/**\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 const isMinor = key.modeMask12 === NATURAL_MINOR_MASK;\n const leadingTone = (tonic + 11) % 12;\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 (isScaleTone(n.pitch, key) || (isMinor && pc === leadingTone)) {\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/** Enumerate candidate chords for the key, gated by reharmonization strength. */\nfunction buildCandidates(key: KeyScale, reharmonize: HarmonizeOptions['reharmonize']): Candidate[] {\n const tones = scaleTonesInDegreeOrder(key);\n const candidates: Candidate[] = tones.map((rootPc, degree) => {\n const quality = diatonicTriad(degree, key).quality;\n return {\n rootPc,\n quality,\n degree,\n secondaryDominant: false,\n base: 0,\n pcs: chordPitchClasses(makeChord(rootPc, quality)),\n };\n });\n\n if (reharmonize !== 'diatonic') {\n for (const target of [1, 3, 4, 5]) {\n const targetRoot = tones[target] ?? 0;\n const rootPc = (targetRoot + 7) % 12;\n candidates.push({\n rootPc,\n quality: 'dom7',\n secondaryDominant: true,\n targetDegree: target,\n base: 1.5,\n pcs: chordPitchClasses(makeChord(rootPc, 'dom7')),\n });\n }\n }\n\n if (reharmonize === 'borrowed') {\n const parallel = parallelKey(key);\n const parallelTones = scaleTonesInDegreeOrder(parallel);\n for (let degree = 0; degree < parallelTones.length; degree += 1) {\n const chord = diatonicTriad(degree, parallel);\n if (!isDiatonic(chord, key)) {\n candidates.push({\n rootPc: chord.rootPc,\n quality: chord.quality,\n secondaryDominant: false,\n base: 1.25,\n pcs: chordPitchClasses(chord),\n });\n }\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 notes.\n *\n * Each note is weighted by the portion of its duration that overlaps the\n * segment, so a note sustained across a boundary contributes to every segment\n * it sounds in rather than only the one it starts in.\n */\nfunction emissionCost(\n seg: Segment,\n cand: Candidate,\n melody: MelodyNote[],\n key: KeyScale,\n ts: TimeSignature,\n): number {\n const pcs = cand.pcs;\n let cost = cand.base;\n for (const idx of seg.noteIndices) {\n const note = melody[idx];\n if (!note) {\n continue;\n }\n const overlapStart = Math.max(note.startBeat, seg.startBeat);\n const overlap = Math.min(note.startBeat + note.durationBeat, seg.endBeat) - overlapStart;\n if (overlap <= 0) {\n continue;\n }\n const w = Math.max(0.25, overlap);\n if (pcs.includes(pitchClass(note.pitch))) {\n continue;\n }\n const strong = isStrongBeat(overlapStart, ts);\n cost += (strong ? 10 : 2) * w;\n if (!isScaleTone(note.pitch, key)) {\n cost += w;\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: 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/** Functional-flow cost of moving from one candidate chord to the next. */\nfunction transitionCost(prev: Candidate, cur: Candidate, tonicPc: number): number {\n let cost = 0;\n if ((prev.rootPc - cur.rootPc + 12) % 12 === 7) {\n cost -= 2; // descending-fifth root motion\n }\n if (\n prev.rootPc === (tonicPc + 7) % 12 &&\n (prev.quality === 'maj' || prev.quality === 'dom7') &&\n cur.rootPc === tonicPc\n ) {\n cost -= 3; // V -> I\n }\n if (prev.secondaryDominant) {\n cost += cur.degree === prev.targetDegree ? -3 : 2;\n }\n const dist = Math.min((prev.rootPc - cur.rootPc + 12) % 12, (cur.rootPc - prev.rootPc + 12) % 12);\n cost += dist * 0.3;\n return cost;\n}\n\n/** Run one Viterbi harmonization pass over a fixed melody and key. */\nfunction harmonizeOnce(\n melody: MelodyNote[],\n key: KeyScale,\n candidates: Candidate[],\n segments: Segment[],\n jitter: number[],\n ts: TimeSignature,\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 let dp = candidates.map(\n (c, ci) => (seg0 ? emissionCost(seg0, c, melody, key, ts) : 0) + (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 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 const cost =\n (dp[p] ?? Number.POSITIVE_INFINITY) + transitionCost(candAt(p), candAt(c), tonicPc);\n if (cost < best) {\n best = cost;\n bestPrev = p;\n }\n }\n next[c] = best + emissionCost(seg, candAt(c), melody, key, ts) + (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 cadence = candAt(c).rootPc === tonicPc ? -2 : 0;\n const total = (dp[c] ?? Number.POSITIVE_INFINITY) + cadence;\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 is segmented by `harmonicRhythm`; each segment is scored against\n * diatonic (and, per `reharmonize`, secondary-dominant and borrowed) candidate\n * chords by melody fit, and a Viterbi search picks the lowest-cost path using a\n * functional-flow transition cost. When `placement.transposeSearch`/`octaveSearch`\n * is set, the whole melody is transposed across a range and the best\n * `(transpose, progression)` pair is returned; a small tessitura cost breaks\n * ties toward placements that keep the melody in a comfortable register.\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 key, harmonic rhythm,\n * reharmonization strength, placement search, meter, and seed. Only `melody`\n * 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 harmonic-rhythm segment\n * ```\n * @category Reharmonization\n */\nexport function harmonizeMelody(opts: HarmonizeOptions): HarmonizeResult {\n assertNoteEvents(opts.melody, 'harmonize melody', { allowNonPositiveDuration: true });\n const soundingMelody = soundingNotesOnly(opts.melody);\n const noteIndex = createNoteEventIndex(soundingMelody);\n const ts = opts.ts ?? DEFAULT_METER;\n assertTimeSignature(ts);\n const requestedKey = opts.key ?? 'infer';\n const key = requestedKey === 'infer' ? inferKey(soundingMelody) : requestedKey;\n const placement = opts.placement ?? DEFAULT_PLACEMENT;\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 (noteIndex.notes.length === 0) {\n return { transposeSemitones: 0, key, chords: [], melodyRoles: [] };\n }\n const candidates = buildCandidates(key, opts.reharmonize ?? 'diatonic');\n const candAt = (i: number): Candidate => candidates[i] ?? FALLBACK;\n const rng = createRng(opts.seed ?? 0);\n const jitter = candidates.map(() => rng.next() * TIE_BREAK_JITTER);\n\n const melodyStart = noteIndex.notes.reduce(\n (start, indexed) => Math.min(start, indexed.note.startBeat),\n Number.POSITIVE_INFINITY,\n );\n const melodyEnd = noteIndex.notes.reduce((end, indexed) => Math.max(end, indexed.endBeat), 0);\n // Any positive value is honoured, matching chordTimelineFromNotes: a silent\n // clamp here would make the same option name mean different things on the\n // analysis and generation sides. A value small enough to make the search\n // explode is caught by the budget assertions below, not rounded away.\n const hr = assertRange(\n opts.harmonicRhythm ?? DEFAULT_HARMONIC_RHYTHM,\n Number.MIN_VALUE,\n Number.MAX_SAFE_INTEGER,\n 'harmonic rhythm',\n );\n const segmentStart = Math.floor(melodyStart / hr) * hr;\n const segCount = Math.max(1, Math.ceil((melodyEnd - segmentStart) / hr));\n assertGenerationBudget(segCount, 'harmonic segments');\n const segments: Segment[] = Array.from({ length: segCount }, (_, s) => ({\n startBeat: segmentStart + s * hr,\n endBeat: segmentStart + (s + 1) * hr,\n noteIndices: [],\n }));\n // Associate each note only with the windows it actually spans. This replaces\n // the former per-window full melody scan and creates no temporary note objects.\n let memberships = 0;\n for (const indexed of noteIndex.notes) {\n const first = Math.max(0, Math.floor((indexed.note.startBeat - segmentStart) / hr));\n const lastExclusive = Math.min(\n segCount,\n Math.ceil((indexed.endBeat - segmentStart) / hr - Number.EPSILON),\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(indexed.originalIndex);\n }\n }\n\n const transposes: number[] = [0];\n if (placement.transposeSearch) {\n for (let s = -6; s <= 6; s += 1) {\n if (s !== 0) {\n transposes.push(s);\n }\n }\n }\n if (placement.octaveSearch) {\n for (const o of [-12, 12]) {\n if (!transposes.includes(o)) {\n transposes.push(o);\n }\n }\n }\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 let bestMelody = opts.melody;\n for (const shift of transposes) {\n const shifted = opts.melody.map((n) => ({ ...n, pitch: n.pitch + shift }));\n const { cost, path } = harmonizeOnce(shifted, key, candidates, segments, jitter, ts);\n const total = cost + tessituraCost(shifted);\n if (total < bestCost) {\n bestCost = total;\n bestTs = shift;\n bestPath = path;\n bestMelody = shifted;\n }\n }\n\n const chords: ChordSpan[] = 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 const melodyRoles = bestMelody.map((note, noteIndex) => {\n const segIdx = Math.min(segments.length - 1, Math.max(0, Math.floor(note.startBeat / hr)));\n const cand = candAt(bestPath[segIdx] ?? 0);\n const chord: Chord = makeChord(cand.rootPc, cand.quality);\n return { noteIndex, role: roleOf(note.pitch, 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 { createRng } from '../../core/random/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 { isScaleTone, nearestScaleTone } from '../../theory/scale/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 key: KeyScale;\n chord?: Chord | 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 * @defaultValue 0\n */\n jitter?: number;\n /**\n * Seed for the deterministic PRNG.\n *\n * @defaultValue 0\n */\n seed?: number;\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 in-scale pitch strictly above `pitch`. */\nfunction upScaleTone(pitch: number, key: KeyScale): number {\n for (let d = 1; d <= 12; d += 1) {\n if (isScaleTone(pitch + d, key)) {\n return pitch + d;\n }\n }\n return pitch + 12;\n}\n\n/** Nearest in-scale pitch strictly below `pitch`. */\nfunction downScaleTone(pitch: number, key: KeyScale): number {\n for (let d = 1; d <= 12; d += 1) {\n if (isScaleTone(pitch - d, key)) {\n return pitch - d;\n }\n }\n return pitch - 12;\n}\n\n/** Shift a pitch by a number of diatonic scale degrees. */\nfunction stepDiatonic(pitch: number, degrees: number, key: KeyScale): number {\n let p = nearestScaleTone(pitch, key);\n const steps = Math.abs(Math.trunc(degrees));\n for (let i = 0; i < steps; i += 1) {\n p = degrees >= 0 ? upScaleTone(p, key) : downScaleTone(p, key);\n }\n return p;\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/** 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 seed.\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 const rng = createRng(opts.seed ?? 0);\n const requestedJitter = assertFiniteNumber(opts.jitter ?? 0, 'motif jitter');\n const jitterProb = Math.min(1, Math.max(0, requestedJitter));\n const tonic = pitchClass(opts.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 tonic.\n let jitter = 0;\n if (jitterProb > 0 && rng.next() < jitterProb) {\n jitter = rng.next() < 0.5 ? 1 : -1;\n }\n let pitch = stepDiatonic(tonic, (offsets[i] ?? 0) + jitter, opts.key);\n const startBeat = i * beatsPerNote;\n if (opts.chord && isDownbeat(startBeat, barBeats)) {\n pitch = nearestChordTone(pitch, opts.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; without it,\n * `transposeDiatonic` and `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?: 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.\n pitch: key ? stepDiatonic(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 = transformMotif(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; each note is then\n * pulled to the nearest chord tone of the segment sounding at its onset, so the\n * developed line spells the underlying harmony.\n *\n * @param cell The source motif.\n * @param timeline Chord segments to snap against.\n * @param key Key context (used to keep snapped pitches sensible).\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 * ```\n *\n * @category Composition\n */\nexport function developMotif(\n cell: MotifCell,\n timeline: ChordTimeline,\n key: KeyScale,\n bars: number,\n ts: TimeSignature = DEFAULT_TS,\n): MotifCell {\n assertPositiveInt(bars, 'development bars');\n assertNoteEvents(cell.notes, 'motif notes');\n const span = cellSpan(cell);\n const totalBeats = bars * beatsPerBar(ts);\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 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 let pitch = n.pitch;\n if (chord) {\n const isMember = chordPitchClasses(chord).includes(pitchClass(pitch));\n pitch = isMember ? pitch : nearestChordTone(pitch, chord);\n } else {\n pitch = nearestScaleTone(pitch, key);\n }\n out.push({ pitch, startBeat, durationBeat: n.durationBeat });\n }\n }\n return { notes: out };\n}\n","import { InvalidInputError } from '../../core/errors/index.js';\nimport { createRng } from '../../core/random/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 { scaleTonesInDegreeOrder } from '../../theory/scale/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 0..6 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: [0, BORROWED_DEGREES.bVII, 3, 0] },\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 0..6, 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 key: KeyScale;\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 reharmonize?: boolean;\n /**\n * Seed for the deterministic preset choice and reharmonization.\n *\n * @defaultValue 0\n */\n seed?: number;\n};\n\nconst PRESETS: ProgressionPreset[] = [\n {\n id: 'fourChordPop',\n name: 'Four Chord Pop',\n degrees: [0, 4, 5, 3],\n functional: 'loop',\n styles: ['minimal', 'dance', 'idol'],\n },\n {\n id: 'pop1',\n name: 'Pop 1',\n degrees: [0, 5, 3, 4],\n functional: 'loop',\n styles: ['minimal', 'dance', 'idol'],\n },\n {\n id: 'axis',\n name: 'Axis',\n degrees: [5, 3, 0, 4],\n functional: 'loop',\n styles: ['minimal', 'dance', 'idol', 'rock'],\n },\n {\n id: 'pop2',\n name: 'Pop 2',\n degrees: [3, 0, 4, 5],\n functional: 'loop',\n styles: ['minimal', 'dance', 'idol'],\n },\n {\n id: 'classic',\n name: 'Classic',\n degrees: [0, 3, 4, 0],\n functional: 'cadenceStrong',\n styles: ['dance', 'idol', 'rock'],\n },\n {\n id: 'pop3',\n name: 'Pop 3',\n degrees: [0, 3, 5, 4],\n functional: 'loop',\n styles: ['minimal', 'dance', 'idol'],\n },\n {\n id: 'royalRoad',\n name: 'Royal Road',\n degrees: [3, 4, 2, 5],\n functional: 'tensionBuild',\n styles: ['dance', 'idol'],\n },\n {\n id: 'minor1',\n name: 'Minor 1',\n degrees: [5, 4, 3, 4],\n functional: 'tensionBuild',\n styles: ['idol', 'rock'],\n },\n {\n id: 'minor2',\n name: 'Minor 2',\n degrees: [5, 3, 4, 0],\n functional: 'tensionBuild',\n styles: ['idol', 'rock'],\n },\n {\n id: 'pop4',\n name: 'Pop 4',\n degrees: [0, 4, 2, 3],\n functional: 'loop',\n styles: ['minimal', 'dance', 'idol'],\n },\n {\n id: 'pop5',\n name: 'Pop 5',\n degrees: [0, 2, 3, 4],\n functional: 'stable',\n styles: ['minimal', 'dance', 'idol'],\n },\n {\n id: 'rock1',\n name: 'Rock 1',\n degrees: [0, 10, 3, 0],\n functional: 'tensionBuild',\n styles: ['rock'],\n },\n {\n id: 'rock2',\n name: 'Rock 2',\n degrees: [0, 3, 10, 0],\n functional: 'tensionBuild',\n styles: ['rock'],\n },\n {\n id: 'extended4',\n name: 'Extended 4',\n degrees: [0, 4, 5, 2],\n functional: 'stable',\n styles: ['minimal', 'dance'],\n },\n {\n id: 'minor3',\n name: 'Minor 3',\n degrees: [5, 0, 4, 3],\n functional: 'loop',\n styles: ['dance', 'idol'],\n },\n {\n id: 'aeolianPop',\n name: 'Aeolian Pop',\n degrees: [5, 8, 10, 0],\n functional: 'tensionBuild',\n styles: ['minimal', 'dance', 'idol', 'rock'],\n },\n {\n id: 'animeHighEnergy1',\n name: 'Anime High Energy 1',\n degrees: [5, 2, 3, 0],\n functional: 'loop',\n styles: ['dance', 'idol'],\n },\n {\n id: 'jazzPop',\n name: 'Jazz Pop',\n degrees: [1, 4, 0, 5],\n functional: 'cadenceStrong',\n styles: ['minimal', 'dance'],\n },\n {\n id: 'animeHighEnergy2',\n name: 'Anime High Energy 2',\n degrees: [5, 1, 4, 0],\n functional: 'cadenceStrong',\n styles: ['dance', 'idol'],\n },\n {\n id: 'cityPop',\n name: 'City Pop',\n degrees: [0, 5, 1, 4],\n functional: 'stable',\n styles: ['minimal', 'dance'],\n },\n {\n id: 'extended5',\n name: 'Extended 5',\n degrees: [0, 4, 5, 2, 3],\n functional: 'loop',\n styles: ['minimal', 'dance', 'idol'],\n },\n {\n id: 'neapolitanPop',\n name: 'Neapolitan Pop',\n degrees: [5, 12, 13, 4, 0],\n functional: 'cadenceStrong',\n styles: ['minimal', 'dance', 'idol'],\n },\n];\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 >= 0 && degree <= 6) {\n const tones = scaleTonesInDegreeOrder(key);\n return tones.length > 0 ? (tones[degree % tones.length] ?? key.rootPc % 12) : 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 (0-6) 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 >= 0 && degree <= 6) {\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 === 4) {\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 >= 0 && degree <= 6) {\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 rng = createRng(seed);\n const index = Math.floor(rng.next() * pool.length) % pool.length;\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 `seed`. An unknown `presetId`, or a\n * `style` no preset claims, is a caller error and throws rather than silently\n * 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 chord replaced by `reharmonize`,\n * which is a secondary dominant and is therefore always a `dom7`; those carry\n * `secondaryDominant: true`. When `reharmonize` is set, some chords are\n * deterministically replaced with the secondary dominant (V7) of the following\n * chord, flagged with `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 * ```\n *\n * @category Composition\n */\nexport function generateProgression(opts: ProgressionOptions): ChordSpan[] {\n assertPositiveInt(opts.bars, 'progression bars');\n assertGenerationBudget(opts.bars, 'progression chords');\n const seed = opts.seed ?? 0;\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 >= 0 && degree <= 6) ||\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 opts.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 if (opts.reharmonize) {\n const tonicPc = (((opts.key.rootPc % 12) + 12) % 12) as number;\n const rrng = createRng((seed ^ 0x9e3779b9) >>> 0);\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 (rrng.next() < 0.5) {\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 * 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 { createRng } from '../../core/random/index.js';\nimport type { NoteEvent } from '../../core/types.js';\nimport {\n assertFiniteNumber,\n assertGenerationBudget,\n assertPositiveInt,\n assertRange,\n} from '../../core/validation/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 * Seed for the deterministic PRNG.\n *\n * @defaultValue 0\n */\n seed?: number;\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 * Overall onset density in [0, 1]. Scales the per-slot onset probability, so\n * higher values fill more grid slots. Values outside [0, 1] are rejected.\n *\n * @defaultValue 0.5\n */\n density?: number;\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 with a probability derived from its metric weight scaled by `density`\n * and sampled from the seeded PRNG. Every bar's downbeat is always an onset, so\n * a pattern of any length has a reliable pulse independent of the seed. Each\n * event's duration extends to the next onset, and the last event extends to the\n * end of the span. `density` must be in [0, 1].\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, { seed: 42, density: 0.6 }); // onset events over one bar\n * ```\n * @category Rhythm & Meter\n */\nexport function generateRhythm(ts: TimeSignature, opts: RhythmOptions = {}): RhythmEvent[] {\n const seed = opts.seed ?? 0;\n const bars = opts.bars ?? DEFAULT_BARS;\n const subdivision = opts.subdivision ?? DEFAULT_SUBDIVISION;\n const density = assertRange(opts.density ?? DEFAULT_DENSITY, 0, 1, 'rhythm 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 rng = createRng(seed);\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 (rng.prob(probability)) {\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"]}
|