@libraz/libcantus 1.0.0 → 1.0.1
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/dist/analyze/index.cjs +2 -2
- package/dist/analyze/index.js +1 -1
- package/dist/{chunk-GOZG57ER.js → chunk-2LCPM3EG.js} +8 -4
- package/dist/chunk-2LCPM3EG.js.map +1 -0
- package/dist/{chunk-M4CWTJWJ.cjs → chunk-6XH6QZYQ.cjs} +33 -33
- package/dist/{chunk-M4CWTJWJ.cjs.map → chunk-6XH6QZYQ.cjs.map} +1 -1
- package/dist/{chunk-3JXCW6DX.cjs → chunk-B6XNKWZV.cjs} +8 -4
- package/dist/chunk-B6XNKWZV.cjs.map +1 -0
- package/dist/{chunk-4A2RMUCC.js → chunk-PKFNUHDY.js} +2 -2
- package/dist/index.cjs +3 -3
- package/dist/index.js +2 -2
- package/dist/model/index.cjs +3 -3
- package/dist/model/index.js +2 -2
- package/package.json +1 -1
- package/dist/chunk-3JXCW6DX.cjs.map +0 -1
- package/dist/chunk-GOZG57ER.js.map +0 -1
- /package/dist/{chunk-4A2RMUCC.js.map → chunk-PKFNUHDY.js.map} +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/analyze/grid.ts","../src/analyze/histogram.ts","../src/analyze/keys/index.ts","../src/analyze/timeline/index.ts","../src/analyze/voice/index.ts","../src/analyze/arrange/internal.ts","../src/analyze/arrange/tracks.ts","../src/analyze/arrange/session.ts","../src/analyze/arrange/tension.ts","../src/analyze/form/internal.ts","../src/analyze/form/hypermeter.ts","../src/analyze/melody/index.ts","../src/analyze/form/phrase.ts","../src/analyze/form/section.ts","../src/analyze/reduction/index.ts"],"sourcesContent":["/**\n * Where a slot grid starts when the music begins before the downbeat.\n *\n * Chord inference and key inference slice the same piece into slots of\n * different lengths, so they have to agree about where the piece starts or the\n * two answers describe different spans. They read the origin from here for that\n * reason: the same input can only have one first beat.\n */\n\nconst EPS = 1e-9;\n\n/**\n * How far below beat 0 an onset may sit and still be read as beat 0.\n *\n * A recording or an export puts the first note a millibeat early; an upbeat\n * puts it a beat early. Only the second is a pickup, and reading the first as\n * one hands the analysis a whole slot of silence before the music. The bound is\n * the tolerance the rest of the library uses for played-not-quantized timing.\n */\nconst PICKUP_JITTER = 0.05;\n\n/**\n * The grid a span is analyzed in: equal slots of `slotBeats` from `origin`, and\n * the beat the music itself starts on.\n */\nexport type GridOrigin = {\n /**\n * First beat of slot 0. Always a whole number of slots from beat 0, so the\n * grid's boundaries keep falling on the bar lines rather than being pushed\n * off them by the length of the upbeat.\n */\n origin: number;\n /**\n * First beat that sounds. Never earlier than `origin` and never later than 0,\n * so a pickup that is not a whole number of slots long is analyzed in a slot\n * that begins before it without being reported as sounding there.\n */\n startBeat: number;\n};\n\n/**\n * Place the slot grid for a span whose earliest onset is `firstOnset`.\n *\n * @param firstOnset The earliest onset of a sounding note, or 0 when none is\n * earlier than the downbeat.\n * @param slotBeats Length of one slot in beats.\n * @returns The grid origin and the beat the music starts on.\n */\nexport function gridOriginOf(firstOnset: number, slotBeats: number): GridOrigin {\n // A pickup sounds before beat 0, so the grid has to start there too — but a\n // whole number of slots before it, or every slot boundary after the pickup\n // would sit off the bar lines by the length of the upbeat. Dropping those\n // notes instead would throw away the very bar that establishes the key.\n if (firstOnset >= -PICKUP_JITTER) {\n return { origin: 0, startBeat: 0 };\n }\n return { origin: Math.floor(firstOnset / slotBeats + EPS) * slotBeats, startBeat: firstOnset };\n}\n","/**\n * Pitch-class weight histograms over a span of notes.\n *\n * Chord inference and key inference both ask the same question of a stretch of\n * music — how much of each pitch class is sounding, and how prominently — so\n * they read it from one place. Sharing the histogram is what lets a chord\n * boundary and a key boundary be compared on the same scale.\n */\n\nimport type { MeterLike } from '../core/meter/index.js';\nimport { metricWeight } from '../core/meter/index.js';\nimport { pitchClassOf as pitchClass } from '../core/pitch/index.js';\nimport type { NoteEvent } from '../core/types.js';\nimport { assertGenerationBudget } from '../core/validation/index.js';\n\nconst EPS = 1e-9;\n\n/** An equal-slot grid: slot `i` covers `[origin + i * slotBeats, + slotBeats)`. */\nexport type SlotGrid = {\n /** First beat of slot 0. */\n origin: number;\n /** Length of one slot in beats. */\n slotBeats: number;\n};\n\n/**\n * Sort notes into the slots of an equal grid they overlap.\n *\n * A per-slot histogram asks one question once per slot, so the notes are\n * grouped once rather than rescanned for every slot: scanning them per slot\n * costs the note count times the slot count, and at a fixed note density the\n * slot count grows with the piece, so the total grows with its square. Both the\n * chord search and the key search cut a piece into slots, and they group their\n * notes here so neither can drift back to rescanning.\n *\n * Notes are kept by identity, so a caller may deduplicate a note spanning\n * several slots with a `Set`.\n *\n * @param notes The notes to group; the parts of one falling outside the grid\n * are dropped.\n * @param grid The grid to group them on.\n * @param slotCount How many slots the grid holds.\n * @param opts `name` labels the budget error, `budget` bounds the total number\n * of note-to-slot memberships.\n * @returns One array per slot, each holding the notes overlapping that slot in\n * input order.\n * @throws If the memberships exceed `budget`.\n */\nexport function bucketNotesBySlot(\n notes: readonly NoteEvent[],\n grid: SlotGrid,\n slotCount: number,\n opts: { name: string; budget?: number },\n): NoteEvent[][] {\n const buckets: NoteEvent[][] = Array.from({ length: slotCount }, () => []);\n let memberships = 0;\n for (const note of notes) {\n const first = Math.max(0, Math.floor((note.startBeat - grid.origin) / grid.slotBeats));\n const lastExclusive = Math.min(\n slotCount,\n Math.ceil((note.startBeat + note.durationBeat - grid.origin) / grid.slotBeats),\n );\n memberships += Math.max(0, lastExclusive - first);\n assertGenerationBudget(memberships, opts.name, opts.budget);\n for (let slot = first; slot < lastExclusive; slot += 1) {\n buckets[slot]?.push(note);\n }\n }\n return buckets;\n}\n\n/** A window's pitch-class weight histogram and the summaries drawn from it. */\nexport type WindowWeights = {\n /** Weight per pitch class, indexed 0-11. */\n weights: number[];\n /** Sum of every pitch class's weight. */\n totalWeight: number;\n /** The single largest pitch-class weight. */\n maxWeight: number;\n /** Lowest sounding MIDI pitch, or +Infinity when the window is empty. */\n lowestPitch: number;\n};\n\n/**\n * Weigh the pitch classes sounding across `[windowStart, windowEnd)`.\n *\n * A note contributes its overlap with the window, scaled by velocity and, when\n * its onset falls inside the window, by a metric-accent bonus. Weighing by\n * overlap rather than by full duration is what makes the result additive over\n * adjacent windows: a note held across a boundary counts in each window only\n * for the part of it that sounds there.\n *\n * The accents are read from the meter itself rather than from the signature in\n * force at the window's start, so an onset under a meter change is weighed\n * against the bar it actually falls in and a pickup at a negative beat is\n * weighed as the upbeat it is.\n *\n * @param notes The notes to weigh; those not overlapping the window are ignored.\n * @param windowStart First beat of the window.\n * @param windowEnd End of the window, exclusive.\n * @param meter A single signature, or the piece's meter map, supplying the\n * metric accents.\n * @returns The histogram and its summaries.\n */\nexport function windowWeights(\n notes: readonly NoteEvent[],\n windowStart: number,\n windowEnd: number,\n meter: MeterLike,\n): WindowWeights {\n const weights = new Array<number>(12).fill(0);\n let lowestPitch = Number.POSITIVE_INFINITY;\n for (const note of notes) {\n const noteEnd = note.startBeat + note.durationBeat;\n const overlap = Math.min(noteEnd, windowEnd) - Math.max(note.startBeat, windowStart);\n if (overlap <= EPS) {\n continue;\n }\n const velocityFactor = note.velocity !== undefined ? note.velocity / 127 : 1;\n const onsetInWindow = note.startBeat >= windowStart - EPS && note.startBeat < windowEnd - EPS;\n const accent = onsetInWindow ? 1 + metricWeight(note.startBeat, meter) / 3 : 1;\n const pc = pitchClass(note.pitch);\n weights[pc] = (weights[pc] ?? 0) + overlap * velocityFactor * accent;\n if (note.pitch < lowestPitch) {\n lowestPitch = note.pitch;\n }\n }\n let totalWeight = 0;\n let maxWeight = 0;\n for (const w of weights) {\n totalWeight += w;\n maxWeight = Math.max(maxWeight, w);\n }\n return { weights, totalWeight, maxWeight, lowestPitch };\n}\n","/**\n * The key as it changes over time.\n *\n * A piece that modulates has no single key, so a whole-piece answer is wrong\n * for every bar after the modulation. This module answers instead with a run of\n * regions, each carrying the key in force over one span, how it relates to the\n * key before it, and — when chords are available — the chord the modulation\n * turned on.\n */\n\nimport type { MeterLike, MeterMap, TimeSignature } from '../../core/meter/index.js';\nimport { beatsPerBarAt, metricWeight, resolveMeters } from '../../core/meter/index.js';\nimport { pitchClassOf as pitchClass } from '../../core/pitch/index.js';\nimport type { KeyScale, NoteEvent } from '../../core/types.js';\nimport {\n assertGenerationBudget,\n assertNoteEvents,\n assertRange,\n} from '../../core/validation/index.js';\nimport type { Chord, ChordSegment } from '../../theory/chord/index.js';\nimport { chordPitchClasses } from '../../theory/chord/index.js';\nimport type { KeyRelation } from '../../theory/scale/index.js';\nimport {\n isScaleTone,\n keyRelationBetween,\n majorKey,\n minorKey,\n spelledKeyOf,\n} from '../../theory/scale/index.js';\nimport type { KeyProfileName, KeyProfilePair } from '../detect/index.js';\nimport { profileScore, resolveKeyProfile } from '../detect/profiles.js';\nimport type { PivotChord } from '../functional/index.js';\nimport { pivotChords } from '../functional/index.js';\nimport { gridOriginOf } from '../grid.js';\nimport type { WindowWeights } from '../histogram.js';\nimport { bucketNotesBySlot, windowWeights } from '../histogram.js';\n\nconst EPS = 1e-9;\n\n/** How many pitch classes a histogram is indexed by. */\nconst PITCH_CLASSES = 12;\n\n/** The histogram a slot outside the grid contributes: nothing at all. */\nconst EMPTY_WEIGHTS: WindowWeights = {\n weights: new Array<number>(PITCH_CLASSES).fill(0),\n totalWeight: 0,\n maxWeight: 0,\n lowestPitch: Number.POSITIVE_INFINITY,\n};\n\n/** The largest value {@link metricWeight} returns (a downbeat). */\nconst MAX_METRIC_WEIGHT = 3;\n\n/**\n * A span of the piece over which one key is in force.\n *\n * @category Arrangement & Analysis\n */\nexport type KeyRegion = {\n /** First beat of the region. */\n startBeat: number;\n /** End of the region, exclusive. */\n endBeat: number;\n /** The key in force across the region. */\n key: KeyScale;\n /**\n * How well the region's music fits the key, in [0, 1]: the correlation\n * between the span's pitch-class distribution and the key's profile, with\n * negative correlations reported as 0.\n *\n * One statistic whichever entry point built the region, so a threshold or a\n * comparison means the same thing across them. Where the region came from\n * chords rather than notes, the distribution is each chord's tones weighted\n * by how long the chord holds.\n */\n confidence: number;\n /**\n * How this key relates to the previous region's — `'dominant'` for a move to\n * the dominant, `'relative'` for a move to the relative minor, and so on.\n * Absent on the first region, and absent when the two keys stand in none of\n * the named relations.\n *\n * Never `'same'`: a region is the maximal span of one key, so consecutive\n * regions never share one and a region carrying this field is a place the\n * piece moved.\n */\n modulation?: KeyRelation;\n /**\n * The chord the modulation turned on, when one was identified. Only the\n * chord-aware entry points fill this in: a pivot is a chord that reads in\n * both keys, so it cannot be named without knowing which chord sounded.\n */\n pivot?: PivotChord;\n};\n\n/**\n * Options shared by the key-region entry points.\n *\n * @category Arrangement & Analysis\n */\nexport type KeyTimelineOptions = {\n /**\n * A single time signature held across the whole span, as sugar for a\n * one-element `meters`; defaults to 4/4. Giving both is an input error.\n *\n * @defaultValue `4/4`\n */\n ts?: TimeSignature;\n /**\n * The meter as it changes over the span. Bar lines, downbeats and the metric\n * accents the search discounts a modulation by all follow the signature in\n * force at the beat in question, so a piece that changes meter is not read in\n * the one it opened in.\n *\n * @defaultValue 4/4 throughout\n */\n meters?: MeterMap;\n /**\n * Expected length of one key area in beats; defaults to four bars of the\n * opening meter.\n *\n * This is a prior, not a window: the longer a key is expected to hold, the\n * more evidence a modulation needs before the search will report one. Keys\n * change far more slowly than chords do, which is why the default is measured\n * in bars rather than beats.\n *\n * @defaultValue four bars of the opening meter\n */\n expectedKeyBeats?: number;\n /**\n * Shortest key area the search may report, in beats; defaults to one bar of\n * the opening meter. Values above `expectedKeyBeats` are clamped to it.\n *\n * @defaultValue one bar of the opening meter\n */\n minKeyBeats?: number;\n /**\n * End of the analyzed span in beats; defaults to the end of the last note or\n * chord segment.\n *\n * @defaultValue the end of the input\n */\n totalBeats?: number;\n /**\n * Key profile the regions are scored against; defaults to `'krumhansl'`.\n * Same meaning as the option of the same name on the key detectors.\n *\n * Every entry point reports `confidence` as the correlation between a\n * region's pitch-class distribution and this profile, so the option is what\n * that number is measured against. Which keys {@link detectModulations}\n * reports is settled by how each chord reads in a key rather than by a\n * pitch-class profile, so there the profile scores the answer without\n * choosing it.\n *\n * @defaultValue `'krumhansl'`\n */\n profile?: KeyProfileName | KeyProfilePair;\n /**\n * Upper bound on the work this call may do.\n *\n * @defaultValue {@link DEFAULT_GENERATION_BUDGET}\n */\n budget?: number;\n};\n\n/**\n * Cost of one modulation, as a fraction of one expected key area's evidence.\n *\n * The scores this is weighed against are correlations, and neighbouring keys\n * correlate strongly with the same music — a bar of plain C major still scores\n * 0.45 as G major. The evidence that actually separates two keys is therefore a\n * fraction of the raw score, and the cost is set against that margin rather\n * than against the score itself. Charging the full score would make no\n * modulation between related keys ever worth reporting, which is precisely the\n * modulation tonal music spends most of its time making.\n */\nconst MODULATION_COST = 0.15;\n\n/**\n * How loudly a slot hears the slot before it when the key is read.\n *\n * Key moves more slowly than harmony, so a single bar is not enough to settle\n * one. Below 1 the bar still speaks for itself, which is what keeps a boundary\n * on the bar the music turns on rather than a bar either side of it.\n */\nconst PAST_CONTEXT_SHARE = 0.5;\n\n/**\n * How loudly a slot hears the slot after it.\n *\n * Quieter than the past, because a key is established by what has already\n * sounded and only confirmed by what follows. Hearing the future as loudly as\n * the past makes the search hand the closing chord of one key area to the next,\n * which reads a cadence's own resolution as the first bar of the key it was\n * cadencing away from.\n */\nconst FUTURE_CONTEXT_SHARE = 0.25;\n\n/** How much of the modulation cost a maximally strong beat waives. */\nconst STRONG_BEAT_DISCOUNT = 0.4;\n\n/**\n * How much each step around the circle of fifths adds to the modulation cost.\n *\n * A move to the dominant or the relative is what tonal music mostly does; a\n * move to the tritone is what it mostly does not. Charging by distance is what\n * stops a passing chromatic bar from being read as a remote modulation.\n */\nconst FIFTHS_DISTANCE_COST = 0.18;\n\n/** The 24 major and minor keys, in the order the search indexes them. */\nconst KEY_CANDIDATES: readonly { key: KeyScale; rootPc: number; minor: boolean }[] = (() => {\n const candidates: { key: KeyScale; rootPc: number; minor: boolean }[] = [];\n for (let rootPc = 0; rootPc < 12; rootPc += 1) {\n candidates.push({ key: majorKey(rootPc), rootPc, minor: false });\n candidates.push({ key: minorKey(rootPc), rootPc, minor: true });\n }\n return candidates;\n})();\n\n/** Position of a key on the circle of fifths, as a signed count of accidentals. */\nfunction fifthsOf(rootPc: number, minor: boolean): number {\n // A minor key carries its relative major's signature, three semitones up.\n const majorRoot = pitchClass(minor ? rootPc + 3 : rootPc);\n const raw = (majorRoot * 7) % 12;\n return raw > 6 ? raw - 12 : raw;\n}\n\n/** Steps around the circle of fifths between two candidates, 0 to 6. */\nfunction fifthsDistance(a: number, b: number): number {\n const diff = Math.abs(a - b) % 12;\n return Math.min(diff, 12 - diff);\n}\n\n/** Circle-of-fifths distance between every ordered pair of candidates. */\nconst CANDIDATE_DISTANCE: readonly (readonly number[])[] = KEY_CANDIDATES.map((from) =>\n KEY_CANDIDATES.map((to) =>\n fifthsDistance(fifthsOf(from.rootPc, from.minor), fifthsOf(to.rootPc, to.minor)),\n ),\n);\n\n/** A slot of the search: what sounds in it, and how strongly. */\ntype KeySlot = {\n startBeat: number;\n endBeat: number;\n /** Score of each candidate over this slot, already weighted by prominence. */\n scores: number[];\n /** Total prominence of the slot, used to calibrate the modulation cost. */\n weight: number;\n};\n\n/**\n * Choose one key per slot, trading each slot's fit against a cost per\n * modulation.\n *\n * Unlike the chord search, the cost here depends on which key is left as well\n * as which is entered — a modulation to the dominant is ordinary and one to the\n * tritone is not — so the previous stage cannot collapse to a single cheapest\n * predecessor and every pair is considered. With 24 candidates that is 576\n * comparisons per slot, which is nothing next to building the slots.\n */\nfunction chooseKeys(slots: readonly KeySlot[], changeCost: number, meter: MeterLike): number[] {\n const count = KEY_CANDIDATES.length;\n if (slots.length === 0) {\n return [];\n }\n let costs = (slots[0]?.scores ?? []).map((score) => -score);\n const cameFrom: Int32Array[] = [];\n for (let i = 1; i < slots.length; i += 1) {\n const slot = slots[i];\n const previous = costs;\n const accent = metricWeight(slot?.startBeat ?? 0, meter);\n const metricFactor = 1 - (STRONG_BEAT_DISCOUNT * accent) / MAX_METRIC_WEIGHT;\n const next = new Array<number>(count).fill(0);\n const back = new Int32Array(count);\n for (let to = 0; to < count; to += 1) {\n // Holding the key is free; every other predecessor pays by distance.\n let best = previous[to] ?? Number.POSITIVE_INFINITY;\n let bestFrom = to;\n for (let from = 0; from < count; from += 1) {\n if (from === to) {\n continue;\n }\n const distance = CANDIDATE_DISTANCE[from]?.[to] ?? 0;\n const penalty = changeCost * metricFactor * (1 + FIFTHS_DISTANCE_COST * distance);\n const cost = (previous[from] ?? Number.POSITIVE_INFINITY) + penalty;\n if (cost < best) {\n best = cost;\n bestFrom = from;\n }\n }\n back[to] = bestFrom;\n next[to] = best - (slot?.scores[to] ?? 0);\n }\n costs = next;\n cameFrom.push(back);\n }\n\n let best = 0;\n for (let c = 1; c < count; c += 1) {\n if ((costs[c] ?? Number.POSITIVE_INFINITY) < (costs[best] ?? Number.POSITIVE_INFINITY)) {\n best = c;\n }\n }\n const path = new Array<number>(slots.length);\n let current = best;\n for (let i = slots.length - 1; i >= 0; i -= 1) {\n path[i] = current;\n if (i > 0) {\n current = cameFrom[i - 1]?.[current] ?? current;\n }\n }\n return path;\n}\n\n/** A run of slots the search gave one key, before relations and pivots. */\ntype SlotRegion = {\n startBeat: number;\n endBeat: number;\n candidate: number;\n /** First slot of the run. */\n from: number;\n /** One past its last slot. */\n toExclusive: number;\n};\n\n/**\n * Group a per-slot key choice into regions, without relations or pivots.\n *\n * A region is the maximal span over which one key is in force, so two\n * consecutive runs of the same key are one region whether or not anything\n * sounded between them: silence changes no key, and a region ending at a rest\n * would report the key returning to itself as a modulation. The earlier region\n * therefore reaches the later one rather than stopping at the last slot that\n * sounded — the same reading that lets the last region run to the end of the\n * analyzed span.\n */\nfunction regionsFromPath(slots: readonly KeySlot[], path: readonly number[]): SlotRegion[] {\n const regions: SlotRegion[] = [];\n for (let i = 0; i < slots.length; i += 1) {\n const last = regions[regions.length - 1];\n const slot = slots[i];\n if (slot === undefined) {\n continue;\n }\n if (last !== undefined && last.candidate === path[i]) {\n last.endBeat = slot.endBeat;\n last.toExclusive = i + 1;\n continue;\n }\n regions.push({\n startBeat: slot.startBeat,\n endBeat: slot.endBeat,\n candidate: path[i] ?? 0,\n from: i,\n toExclusive: i + 1,\n });\n }\n return regions;\n}\n\n/**\n * Fold every region shorter than `minBeats` into the neighbour that explains it\n * best, and return the amended per-slot choice.\n *\n * The note path gets this for nothing: its slots are `minKeyBeats` long, so no\n * region it groups can come out shorter than one. The chord path takes its\n * slots from the chords instead, and a chord is as short as the harmony is —\n * without this a single passing chord is reported as a key area of its own,\n * which is exactly what raising `minKeyBeats` is asked to stop.\n *\n * A region with no neighbour adjoining it is left alone: nothing can absorb a\n * run that stands across a gap in the chords, and shortening the reported span\n * would claim a key holds where no chord sounded.\n */\nfunction enforceMinimumRegion(\n slots: readonly KeySlot[],\n path: readonly number[],\n minBeats: number,\n): number[] {\n const chosen = [...path];\n // Every pass merges two regions into one, so the region count strictly falls\n // and the loop ends.\n for (;;) {\n const regions = regionsFromPath(slots, chosen);\n let shortest: SlotRegion | undefined;\n let before: SlotRegion | undefined;\n let after: SlotRegion | undefined;\n for (let i = 0; i < regions.length; i += 1) {\n const region = regions[i];\n if (region === undefined || region.endBeat - region.startBeat >= minBeats - EPS) {\n continue;\n }\n const previous = regions[i - 1];\n const next = regions[i + 1];\n const left =\n previous !== undefined && Math.abs(previous.endBeat - region.startBeat) < EPS\n ? previous\n : undefined;\n const right =\n next !== undefined && Math.abs(next.startBeat - region.endBeat) < EPS ? next : undefined;\n if (left === undefined && right === undefined) {\n continue;\n }\n if (\n shortest !== undefined &&\n region.endBeat - region.startBeat >= shortest.endBeat - shortest.startBeat\n ) {\n continue;\n }\n shortest = region;\n before = left;\n after = right;\n }\n if (shortest === undefined) {\n return chosen;\n }\n const merging = shortest;\n // The shorter region joins the neighbouring key that reads its chords best,\n // so a tonicization is absorbed by the key it decorates rather than by\n // whichever side happens to come first.\n const scoreOver = (candidate: number): number => {\n let total = 0;\n for (let i = merging.from; i < merging.toExclusive; i += 1) {\n total += slots[i]?.scores[candidate] ?? 0;\n }\n return total;\n };\n const left = before?.candidate;\n const right = after?.candidate;\n const winner =\n left === undefined\n ? (right ?? merging.candidate)\n : right === undefined || scoreOver(left) >= scoreOver(right)\n ? left\n : right;\n for (let i = merging.from; i < merging.toExclusive; i += 1) {\n chosen[i] = winner;\n }\n }\n}\n\n/**\n * Attach each region's relation to the one before it.\n *\n * The relation is asked of the conventionally spelled keys, not of raw pitch\n * classes, because that is the only way `'enharmonic'` can be told from\n * `'same'` — and telling them apart is the whole point of naming the relation.\n *\n * A key standing in the `'same'` relation to the one before it is no\n * modulation, so it is not written: a region boundary marks a change of key,\n * and a caller filtering on `modulation` is asking where the piece moved.\n */\nfunction withRelations(regions: KeyRegion[]): KeyRegion[] {\n for (let i = 1; i < regions.length; i += 1) {\n const previous = regions[i - 1];\n const current = regions[i];\n if (previous === undefined || current === undefined) {\n continue;\n }\n const relation = keyRelationBetween(spelledKeyOf(previous.key), spelledKeyOf(current.key));\n if (relation !== null && relation !== 'same') {\n current.modulation = relation;\n }\n }\n return regions;\n}\n\n/**\n * Infer where the key changes from raw notes.\n *\n * The span is examined in `minKeyBeats` slots. Each slot's pitch-class\n * distribution is correlated against all 24 major and minor key profiles, and\n * the run of keys that best explains the piece is chosen, paying a cost per\n * modulation that rises with the distance travelled around the circle of\n * fifths and falls on strong beats. Adjacent slots agreeing on a key become one\n * region.\n *\n * The metric accents that discount a modulation follow the signature in force\n * at the beat in question, so a piece that changes meter is read in the meter\n * it is actually in. The first downbeat is beat 0, so a pickup is written at\n * negative beats and its notes argue for the key like any others rather than\n * falling outside the span.\n *\n * Pivot chords are not reported here: naming one requires knowing which chord\n * sounded at the boundary, which raw notes do not say. Use\n * {@link detectModulations} when a chord timeline is in hand.\n *\n * @param notes The notes to analyze (any number of tracks, flattened).\n * @param opts Analysis options; see {@link KeyTimelineOptions}.\n * @returns The key regions in time order; empty when nothing sounds.\n * @example\n * ```ts\n * import { keyTimelineFromNotes } from '@libraz/libcantus';\n * const notes = [\n * { pitch: 60, startBeat: 0, durationBeat: 4 },\n * { pitch: 64, startBeat: 4, durationBeat: 4 },\n * ];\n * const regions = keyTimelineFromNotes(notes);\n * regions[0]?.key; // the key in force at the start\n * ```\n * @category Arrangement & Analysis\n */\nexport function keyTimelineFromNotes(\n notes: readonly NoteEvent[],\n opts: KeyTimelineOptions = {},\n): KeyRegion[] {\n const meters = resolveMeters(opts, 'key timeline meters');\n const bar = beatsPerBarAt(0, meters);\n const expectedKeyBeats = opts.expectedKeyBeats ?? bar * 4;\n assertRange(expectedKeyBeats, Number.MIN_VALUE, Number.MAX_SAFE_INTEGER, 'expectedKeyBeats');\n const minKeyBeats = opts.minKeyBeats ?? bar;\n assertRange(minKeyBeats, Number.MIN_VALUE, Number.MAX_SAFE_INTEGER, 'minKeyBeats');\n const budget = opts.budget;\n assertNoteEvents(notes, 'key timeline notes', { allowNonPositiveDuration: true, budget });\n const sounding = notes.filter((note) => note.durationBeat > 0);\n const lastEnd = sounding.reduce((end, n) => Math.max(end, n.startBeat + n.durationBeat), 0);\n const totalBeats = opts.totalBeats ?? lastEnd;\n assertRange(totalBeats, 0, Number.MAX_SAFE_INTEGER, 'key timeline totalBeats');\n\n const slotBeats = Math.min(minKeyBeats, expectedKeyBeats);\n const firstOnset = sounding.reduce((first, n) => Math.min(first, n.startBeat), 0);\n // The key slots are longer than the chord slots, so the two grids start on\n // different beats — but the music starts on one beat, and that is the beat\n // both report, so a chord timeline and its keys cover the same span.\n const { origin, startBeat: musicStart } = gridOriginOf(firstOnset, slotBeats);\n const slotCount = Math.max(0, Math.ceil((totalBeats - origin) / slotBeats - EPS));\n assertGenerationBudget(slotCount, 'key timeline slots', budget);\n if (slotCount === 0 || sounding.length === 0) {\n return [];\n }\n\n const profile = resolveKeyProfile(opts.profile);\n // Each note is sorted into its slots once. Weighing every slot against the\n // whole note list instead would cost the note count times the slot count,\n // and the slot count grows with the piece.\n const slotNotes = bucketNotesBySlot(sounding, { origin, slotBeats }, slotCount, {\n name: 'key timeline note-to-slot memberships',\n budget,\n });\n // A slot's own histogram, which is also what its neighbours hear of it: the\n // window a slot looks back or forward over is exactly the adjacent slot.\n const slotWeights: WindowWeights[] = [];\n for (let i = 0; i < slotCount; i += 1) {\n const startBeat = origin + i * slotBeats;\n slotWeights.push(\n windowWeights(\n slotNotes[i] ?? [],\n startBeat,\n Math.min(startBeat + slotBeats, totalBeats),\n meters,\n ),\n );\n }\n const slots: KeySlot[] = [];\n let weightSum = 0;\n let soundingSlots = 0;\n for (let i = 0; i < slotCount; i += 1) {\n const startBeat = origin + i * slotBeats;\n const endBeat = Math.min(startBeat + slotBeats, totalBeats);\n // One bar in isolation says very little — a bar of G major triad inside C\n // major looks exactly like a bar of tonic G — so the neighbouring bars are\n // heard too. They are heard more quietly than the bar itself, which is what\n // keeps the boundary where the music actually turns instead of smearing it\n // across the bars either side.\n const own = slotWeights[i] ?? EMPTY_WEIGHTS;\n const before = slotWeights[i - 1] ?? EMPTY_WEIGHTS;\n const after = slotWeights[i + 1] ?? EMPTY_WEIGHTS;\n const weights = own.weights.map(\n (weight, pc) =>\n weight +\n PAST_CONTEXT_SHARE * (before.weights[pc] ?? 0) +\n FUTURE_CONTEXT_SHARE * (after.weights[pc] ?? 0),\n );\n // Prominence comes from the slot itself, so a sparse slot does not inherit\n // the weight of its loud neighbours when the modulation cost is calibrated.\n const totalWeight = own.totalWeight;\n // A correlation is dimensionless; multiplying by the slot's prominence puts\n // the fit and the modulation cost into the same units, so a loud, long slot\n // carries more of the argument than a sparse one.\n const scores = KEY_CANDIDATES.map(\n (candidate) =>\n profileScore(weights, candidate.minor ? profile.minor : profile.major, candidate.rootPc) *\n totalWeight,\n );\n slots.push({ startBeat, endBeat, scores, weight: totalWeight });\n if (totalWeight > EPS) {\n weightSum += totalWeight;\n soundingSlots += 1;\n }\n }\n if (soundingSlots === 0) {\n return [];\n }\n\n const meanSlotWeight = weightSum / soundingSlots;\n const changeCost = MODULATION_COST * meanSlotWeight * (expectedKeyBeats / slotBeats);\n const path = chooseKeys(slots, changeCost, meters);\n const grouped = regionsFromPath(slots, path);\n\n const regions = grouped.map((region) => {\n const candidate = KEY_CANDIDATES[region.candidate];\n const key = candidate?.key ?? majorKey(0);\n // Only the first region can start before the music: the slot holding the\n // pickup begins a whole slot before beat 0, and nothing sounds in the part\n // of it that precedes the upbeat.\n const startBeat = Math.max(region.startBeat, musicStart);\n // The histogram over a region is the histogram over its slots: a note's\n // weight is its overlap with the window, so the parts add up. Nothing\n // sounds before the first onset, so clipping the reported start to it\n // removes no weight.\n // The histogram over a region is the histogram over its slots: a note's\n // weight is its overlap with the window, so the parts add up. Nothing\n // sounds before the first onset, so clipping the reported start to it\n // removes no weight.\n const weights = new Array<number>(PITCH_CLASSES).fill(0);\n for (let i = region.from; i < region.toExclusive; i += 1) {\n const slot = slotWeights[i];\n if (slot === undefined) {\n continue;\n }\n for (let pc = 0; pc < PITCH_CLASSES; pc += 1) {\n weights[pc] = (weights[pc] ?? 0) + (slot.weights[pc] ?? 0);\n }\n }\n const correlation = profileScore(\n weights,\n candidate?.minor ? profile.minor : profile.major,\n candidate?.rootPc ?? 0,\n );\n return {\n startBeat,\n endBeat: region.endBeat,\n key,\n confidence: Math.max(0, correlation),\n };\n });\n return withRelations(regions);\n}\n\n/** How much of a chord's weight a key must explain before it counts as fitting. */\nconst CHORD_TONE_CREDIT = 1;\n\n/** Extra credit for a chord that is the key's dominant — the strongest signal. */\nconst DOMINANT_CREDIT = 0.7;\n\n/** Extra credit for a chord that is the key's tonic. */\nconst TONIC_CREDIT = 0.4;\n\n/** Extra credit for a tonic that its own dominant resolved onto. */\nconst CADENCE_CREDIT = 0.6;\n\n/**\n * Cost of one modulation on the chord path, in beats of chord evidence.\n *\n * Higher than {@link MODULATION_COST} because chord fits are not correlations:\n * a chord either belongs to a key or does not, so the margin between two\n * readings is a large fraction of the score rather than a small one. Most of a\n * diatonic progression reads plausibly in its subdominant as well as its tonic\n * — C F G C is I IV V I in C and V I II V in F — so without a cost of this size\n * the search reports a modulation every time the harmony touches IV.\n */\nconst CHORD_MODULATION_COST = 0.6;\n\n/** Score how strongly one chord argues for one key. */\nfunction chordFitsKey(chord: Chord, candidate: { key: KeyScale; rootPc: number }): number {\n const tones = chordPitchClasses(chord);\n if (tones.length === 0) {\n return 0;\n }\n let inKey = 0;\n for (const pc of tones) {\n if (isScaleTone(pc, candidate.key)) {\n inKey += 1;\n }\n }\n // A tone outside the key counts against it rather than merely failing to\n // count for it: the F# in a D major triad is the reason to hear G rather\n // than C, and a rule that only rewarded the D and the A would miss it.\n let score = CHORD_TONE_CREDIT * ((inKey - (tones.length - inKey)) / tones.length);\n // A dominant seventh names its key almost by itself, which is what makes a\n // secondary dominant readable as a modulation rather than as a wrong note.\n const rootDegree = pitchClass(chord.rootPc - candidate.rootPc);\n if (rootDegree === 7 && (chord.quality === 'dom7' || chord.quality === 'maj')) {\n score += DOMINANT_CREDIT * (chord.quality === 'dom7' ? 1 : 0.5);\n }\n if (rootDegree === 0) {\n score += TONIC_CREDIT;\n }\n return score;\n}\n\n/**\n * Infer where the key changes from a chord sequence, naming the pivot chords.\n *\n * Each chord segment is one slot of the same search {@link keyTimelineFromNotes}\n * runs, scored by how well the chord belongs to each key rather than by a\n * pitch-class profile — a dominant seventh argues for its key far more strongly\n * than its three or four pitch classes alone would suggest. Where the key\n * changes, the chord sounding into the boundary is reported as the pivot if it\n * reads in both keys.\n *\n * The chords supply the slots, so `minKeyBeats` cannot size them as it does in\n * the note path; a region shorter than it is folded into the neighbouring key\n * that reads its chords best instead, which is the same guarantee — a shorter\n * region is reported only where the analyzed span itself ends, or across a gap\n * in the chords that no neighbour adjoins. `profile` scores each region's\n * confidence, as it does in the note path, but takes no part in choosing which\n * keys are reported: that is settled by how the chords read.\n *\n * @param chords The chord segments to analyze, in time order; pass a chord\n * timeline's `segments`.\n * @param opts Analysis options; see {@link KeyTimelineOptions}.\n * @returns The key regions in time order; empty when no chords were given.\n * @example\n * ```ts\n * import { chordTimelineFromNotes, detectModulations } from '@libraz/libcantus';\n * const notes = [\n * { pitch: 60, startBeat: 0, durationBeat: 4 },\n * { pitch: 64, startBeat: 0, durationBeat: 4 },\n * { pitch: 67, startBeat: 0, durationBeat: 4 },\n * ];\n * const { timeline } = chordTimelineFromNotes(notes);\n * const regions = detectModulations(timeline.segments);\n * ```\n * @category Arrangement & Analysis\n */\nexport function detectModulations(\n chords: readonly ChordSegment[],\n opts: KeyTimelineOptions = {},\n): KeyRegion[] {\n const meters = resolveMeters(opts, 'modulation meters');\n const bar = beatsPerBarAt(0, meters);\n const expectedKeyBeats = opts.expectedKeyBeats ?? bar * 4;\n assertRange(expectedKeyBeats, Number.MIN_VALUE, Number.MAX_SAFE_INTEGER, 'expectedKeyBeats');\n const minKeyBeats = opts.minKeyBeats ?? bar;\n assertRange(minKeyBeats, Number.MIN_VALUE, Number.MAX_SAFE_INTEGER, 'minKeyBeats');\n assertGenerationBudget(chords.length, 'modulation chord segments', opts.budget);\n const sorted = [...chords]\n .filter((segment) => segment.endBeat > segment.startBeat)\n .sort((a, b) => a.startBeat - b.startBeat);\n const lastChordEnd = sorted.reduce((end, segment) => Math.max(end, segment.endBeat), 0);\n const totalBeats = opts.totalBeats ?? lastChordEnd;\n assertRange(totalBeats, 0, Number.MAX_SAFE_INTEGER, 'modulation totalBeats');\n // The analyzed span ends where the caller says it does, so a chord held past\n // the end of the piece argues for its key only over the part that sounds.\n const ordered = sorted\n .map((segment) => ({ ...segment, endBeat: Math.min(segment.endBeat, totalBeats) }))\n .filter((segment) => segment.endBeat > segment.startBeat);\n if (ordered.length === 0) {\n return [];\n }\n\n const slots: KeySlot[] = ordered.map((segment) => {\n const length = segment.endBeat - segment.startBeat;\n return {\n startBeat: segment.startBeat,\n endBeat: segment.endBeat,\n // A chord that holds for two bars argues twice as hard as one that\n // passes in half a bar, exactly as a heavier slot does in the note path.\n scores: KEY_CANDIDATES.map((candidate) => chordFitsKey(segment.chord, candidate) * length),\n weight: length,\n };\n });\n // A chord preceded by its own dominant is the clearest statement a key gets,\n // and it is the pair that makes it — so the cadential credit is folded into\n // the arriving chord, where the search can read it as ordinary slot evidence.\n for (let i = 1; i < slots.length; i += 1) {\n const arriving = ordered[i]?.chord;\n const approaching = ordered[i - 1]?.chord;\n const slot = slots[i];\n if (arriving === undefined || approaching === undefined || slot === undefined) {\n continue;\n }\n // Only a dominant seventh earns this. A plain major triad a fifth above is\n // just as readily heard as the chord it moves to being the subdominant —\n // C to F says nothing about whether the key is C or F. What makes a cadence\n // name its key is the tritone, and only the seventh has one.\n const isDominantMotion =\n pitchClass(approaching.rootPc - arriving.rootPc) === 7 && approaching.quality === 'dom7';\n if (!isDominantMotion) {\n continue;\n }\n const length = slot.endBeat - slot.startBeat;\n for (let c = 0; c < KEY_CANDIDATES.length; c += 1) {\n const candidate = KEY_CANDIDATES[c];\n if (candidate !== undefined && pitchClass(arriving.rootPc - candidate.rootPc) === 0) {\n slot.scores[c] = (slot.scores[c] ?? 0) + CADENCE_CREDIT * length;\n }\n }\n }\n // Here a slot's weight is its length in beats outright, so one expected key\n // area's worth of evidence is just `expectedKeyBeats` and no mean is needed.\n const changeCost = CHORD_MODULATION_COST * expectedKeyBeats;\n const path = chooseKeys(slots, changeCost, meters);\n // A shortest region longer than a key is expected to hold is a contradiction,\n // and the prior is the one to keep — the note path clamps it the same way.\n const grouped = regionsFromPath(\n slots,\n enforceMinimumRegion(slots, path, Math.min(minKeyBeats, expectedKeyBeats)),\n );\n\n const profile = resolveKeyProfile(opts.profile);\n const regions: KeyRegion[] = grouped.map((region) => {\n const candidate = KEY_CANDIDATES[region.candidate];\n const key = candidate?.key ?? majorKey(0);\n // The confidence is the statistic the note path reports, not the chord fit\n // the search ran on: a chord fit says which key won, and a correlation says\n // how much of the region the winner explains. Reporting the search's own\n // score here pegged every diatonic passage at 1. A chord sounds all of its\n // tones for all of its length, so the distribution is its pitch classes\n // weighted by how much of the region it holds.\n const weights = new Array<number>(PITCH_CLASSES).fill(0);\n for (const segment of ordered) {\n const overlap =\n Math.min(segment.endBeat, region.endBeat) - Math.max(segment.startBeat, region.startBeat);\n if (overlap <= EPS) {\n continue;\n }\n for (const pc of chordPitchClasses(segment.chord)) {\n weights[pc] = (weights[pc] ?? 0) + overlap;\n }\n }\n const correlation = profileScore(\n weights,\n candidate?.minor ? profile.minor : profile.major,\n candidate?.rootPc ?? 0,\n );\n return {\n startBeat: region.startBeat,\n endBeat: region.endBeat,\n key,\n confidence: Math.max(0, correlation),\n };\n });\n // The analyzed span runs to `totalBeats`, so the last key holds to the end of\n // it rather than to the last chord: silence changes no key, and the note path\n // covers its own silent tail the same way.\n const last = regions[regions.length - 1];\n if (last !== undefined && totalBeats > last.endBeat) {\n last.endBeat = totalBeats;\n }\n\n return attachPivots(withRelations(regions), ordered);\n}\n\n/** Semitones above the root a third may sit at: minor, then major. */\nconst THIRD_SEMITONES = [3, 4];\n\n/** Semitones above the root a fifth may sit at: diminished, perfect, augmented. */\nconst FIFTH_SEMITONES = [6, 7, 8];\n\n/**\n * The triad a chord is heard as, or null when it is heard as none.\n *\n * A pivot is a triad, and a chart writes that triad with whatever the players\n * put over it: the `Am7` sounding into a boundary is the A minor triad the\n * modulation turns on, and so is the `Am` and the `Am9`. Only the tones inside\n * the octave are read, so an extension never stands in for a chord member, and\n * the slash bass is not read at all, so an inversion is the same triad. A chord\n * whose third or fifth is replaced rather than played — a suspension, a power\n * chord — is heard as no triad and pivots on nothing.\n *\n * @param chord The chord that sounded.\n * @returns Its root, third and fifth as pitch classes, ascending, or null.\n */\nfunction triadCore(chord: Chord): number[] | null {\n const root = pitchClass(chord.rootPc);\n let third: number | undefined;\n let fifth: number | undefined;\n for (const interval of chord.intervals) {\n if (interval < 0 || interval >= 12) {\n continue;\n }\n if (third === undefined && THIRD_SEMITONES.includes(interval)) {\n third = interval;\n } else if (fifth === undefined && FIFTH_SEMITONES.includes(interval)) {\n fifth = interval;\n }\n }\n if (third === undefined || fifth === undefined) {\n return null;\n }\n return [root, pitchClass(root + third), pitchClass(root + fifth)].sort((a, b) => a - b);\n}\n\n/** Whether two ascending pitch-class lists name the same tones. */\nfunction sameTones(a: readonly number[], b: readonly number[]): boolean {\n return a.length === b.length && a.every((pc, index) => pc === b[index]);\n}\n\n/**\n * Name the chord each modulation turned on, where the chords support one.\n *\n * The pivot is the last chord to sound before the boundary, reported only when\n * it is diatonic to both keys — which is exactly what makes it a pivot rather\n * than simply the chord that happened to be playing.\n *\n * The chord is matched by the triad it is heard as rather than by its quality\n * name, so a seventh, an added tone or an inversion over the boundary chord\n * names the same pivot the bare triad does. That is what the candidate list\n * means by offering triads alone: see {@link pivotChords}.\n *\n * Not part of the public surface: it takes regions that some other pass already\n * decided, so exposing it would invite callers to pair regions and chords that\n * were never analysed together.\n *\n * @param regions The regions to annotate, modified in place and returned.\n * @param chords The chords that sounded, in time order.\n * @returns The same regions.\n */\nexport function attachPivots(regions: KeyRegion[], chords: readonly ChordSegment[]): KeyRegion[] {\n for (let i = 1; i < regions.length; i += 1) {\n const previous = regions[i - 1];\n const current = regions[i];\n if (previous === undefined || current === undefined) {\n continue;\n }\n let last: ChordSegment | undefined;\n for (const segment of chords) {\n if (segment.endBeat <= current.startBeat + EPS) {\n last = segment;\n }\n }\n if (last === undefined) {\n continue;\n }\n const core = triadCore(last.chord);\n if (core === null) {\n continue;\n }\n const root = pitchClass(last.chord.rootPc);\n const pivot = pivotChords(previous.key, current.key).find(\n (entry) => entry.chord.rootPc === root && sameTones(chordPitchClasses(entry.chord), core),\n );\n if (pivot !== undefined) {\n current.pivot = pivot;\n }\n }\n return regions;\n}\n\n/**\n * Build a `keyAt(beat)` lookup over a run of regions.\n *\n * Regions are disjoint and in beat order, so the covering one is found by\n * binary search. Beats outside every region — before the first note, or inside\n * a rest long enough to have ended a region — answer with the fallback rather\n * than with nothing, because every beat of an analysis has to be read against\n * some key.\n *\n * Not part of the public surface: it is an internal convenience for passes that\n * already hold both the regions and the beats.\n *\n * @param regions The regions to search, in time order.\n * @param fallback The key to answer with outside every region.\n * @returns The lookup.\n */\nexport function keyLookup(\n regions: readonly KeyRegion[],\n fallback: KeyScale,\n): (beat: number) => KeyScale {\n return (beat) => {\n let low = 0;\n let high = regions.length;\n while (low < high) {\n const middle = Math.floor((low + high) / 2);\n if ((regions[middle]?.startBeat ?? Number.POSITIVE_INFINITY) <= beat) {\n low = middle + 1;\n } else {\n high = middle;\n }\n }\n const candidate = regions[low - 1];\n return candidate !== undefined && beat < candidate.endBeat ? candidate.key : fallback;\n };\n}\n\n/**\n * The single key that holds for most of a run of regions.\n *\n * Callers that only want one key — a key signature to print, a scale to offer —\n * need the one the piece spends the most time in, not the one it happens to\n * start on.\n *\n * @param regions The regions to weigh, as returned by the key-region entry points.\n * @returns The longest-held key, or null when there are no regions.\n * @example\n * ```ts\n * import { keyTimelineFromNotes, prevailingKeyOf } from '@libraz/libcantus';\n * const notes = [{ pitch: 60, startBeat: 0, durationBeat: 4 }];\n * const key = prevailingKeyOf(keyTimelineFromNotes(notes));\n * ```\n * @category Arrangement & Analysis\n */\nexport function prevailingKeyOf(regions: readonly KeyRegion[]): KeyScale | null {\n // Held time is summed per key, not per region: a key returned to after a\n // digression prevails over one stated once at length.\n const totals = new Map<string, { key: KeyScale; length: number }>();\n for (const region of regions) {\n const id = `${pitchClass(region.key.rootPc)}:${region.key.modeMask12}`;\n const entry = totals.get(id);\n const length = Math.max(0, region.endBeat - region.startBeat);\n if (entry === undefined) {\n totals.set(id, { key: region.key, length });\n } else {\n entry.length += length;\n }\n }\n let best: KeyScale | null = null;\n let bestLength = -1;\n for (const entry of totals.values()) {\n // Ties keep the earlier key, which is the one the piece established first.\n if (entry.length > bestLength) {\n bestLength = entry.length;\n best = entry.key;\n }\n }\n return best;\n}\n","import { createNoteEventIndex } from '../../core/event-index/index.js';\nimport type { MeterMap, TimeSignature } from '../../core/meter/index.js';\nimport { beatsPerBarAt, metricWeight, resolveMeters } from '../../core/meter/index.js';\nimport { pitchClassOf as pitchClass } from '../../core/pitch/index.js';\nimport type { KeyScale, NoteEvent } from '../../core/types.js';\nimport type { NoteEventAssertOptions } from '../../core/validation/index.js';\nimport {\n assertFiniteNumber,\n assertGenerationBudget,\n assertNoteEvents,\n assertRange,\n} from '../../core/validation/index.js';\nimport type { Chord, ChordQuality, ChordSpan } from '../../theory/chord/index.js';\nimport { chordFromSpan, chordPitchClasses, makeChord } from '../../theory/chord/index.js';\nimport { isScaleTone, majorKey } from '../../theory/scale/index.js';\nimport type { ChordMatch } from '../detect/index.js';\nimport { detectChord } from '../detect/index.js';\nimport { augmentedSixthFromPitchClasses } from '../functional/augmented-sixth.js';\nimport type { CadenceResult } from '../functional/index.js';\nimport { detectCadence } from '../functional/index.js';\nimport { gridOriginOf } from '../grid.js';\nimport type { SlotGrid, WindowWeights } from '../histogram.js';\nimport { bucketNotesBySlot, windowWeights } from '../histogram.js';\nimport type { KeyRegion } from '../keys/index.js';\nimport { attachPivots, keyLookup, keyTimelineFromNotes, prevailingKeyOf } from '../keys/index.js';\nimport type { KeyContext } from '../voice/index.js';\n\nexport type { ChordSegment } from '../../theory/chord/index.js';\n\nimport type { ChordSegment } from '../../theory/chord/index.js';\n\n/**\n * A beat-indexed sequence of chord segments.\n *\n * @category Arrangement & Analysis\n */\nexport type ChordTimeline = {\n /** The chord sounding at a beat, or null when no segment covers it. */\n at: (beat: number) => Chord | null;\n segments: ChordSegment[];\n};\n\n/**\n * Build a chord timeline from placed chords.\n *\n * Each chord spans from its `startBeat` to the next chord's `startBeat`; the last\n * chord runs to `totalBeats`. Segments with no positive length (from duplicate\n * onsets or a last chord at or past `totalBeats`) are dropped. `at(beat)` returns\n * the covering segment's chord, or null when the beat lies outside every segment.\n *\n * @param chords Placed chords in time order.\n * @param totalBeats End of the timeline in beats.\n * @returns A queryable chord timeline.\n * @category Arrangement & Analysis\n */\nexport function chordTimelineFromChords(\n chords: readonly ChordSpan[],\n totalBeats: number,\n): ChordTimeline {\n assertRange(totalBeats, 0, Number.MAX_SAFE_INTEGER, 'timeline totalBeats');\n assertGenerationBudget(chords.length, 'timeline chords');\n for (let index = 0; index < chords.length; index += 1) {\n // Unbounded below, like a note onset: a chord may sound in the pickup.\n assertRange(\n chords[index]?.startBeat ?? Number.NaN,\n -Number.MAX_SAFE_INTEGER,\n Number.MAX_SAFE_INTEGER,\n `chords[${index}].startBeat`,\n );\n }\n const sorted = [...chords]\n .filter((chord) => chord.startBeat < totalBeats)\n .sort((a, b) => a.startBeat - b.startBeat);\n const segments: ChordSegment[] = sorted\n .map((gc, i) => {\n const next = sorted[i + 1];\n const nextBeat = next ? next.startBeat : totalBeats;\n const endBeat = Math.min(totalBeats, Math.max(gc.startBeat, nextBeat));\n return {\n startBeat: gc.startBeat,\n endBeat,\n chord: chordFromSpan(gc),\n };\n })\n .filter((seg) => seg.endBeat > seg.startBeat);\n\n return { at: segmentLookup(segments), segments };\n}\n\n/**\n * Build the `at(beat)` lookup over a segment list.\n *\n * Segments are disjoint and in beat order, so the covering one is found by\n * binary search. A linear scan here is the inner loop of arrangement analysis,\n * which queries it once per note per chord change.\n */\nfunction segmentLookup(segments: ChordSegment[]): (beat: number) => Chord | null {\n return (beat) => {\n assertFiniteNumber(beat, 'timeline query beat');\n let low = 0;\n let high = segments.length;\n while (low < high) {\n const middle = Math.floor((low + high) / 2);\n if ((segments[middle]?.startBeat ?? Number.POSITIVE_INFINITY) <= beat) {\n low = middle + 1;\n } else {\n high = middle;\n }\n }\n const candidate = segments[low - 1];\n return candidate !== undefined && beat >= candidate.startBeat && beat < candidate.endBeat\n ? candidate.chord\n : null;\n };\n}\n\nconst EPS = 1e-9;\n\n/** Fraction of the strongest pitch-class weight below which a pc is noise. */\nconst NOISE_THRESHOLD_RATIO = 0.2;\n\n/** Maximum number of pitch classes fed to chord detection per window. */\nconst MAX_DETECTION_PCS = 6;\n\n/** Confidence multiplier applied when the chosen chord match is inexact. */\nconst INEXACT_CONFIDENCE_FACTOR = 0.85;\n\n/** Score bonus for a match whose tones are all in the key. */\nconst DIATONIC_BONUS = 0.5;\n\n/** Score bonus for an exact match (no extra and no missing tones). */\nconst EXACT_BONUS = 0.5;\n\n/**\n * Score penalty per sounding pitch class a match leaves unexplained.\n *\n * Larger than {@link DIATONIC_BONUS} deliberately: key membership may separate\n * two readings that account for the same notes, but it may not buy the\n * discarding of a note that sounded. Were the two the same size, a dominant\n * voiced without its fifth would read as the triad the key happens to contain —\n * C E Bb in C major as `C`, which drops the tritone the chord exists for and\n * asserts a G nobody played.\n */\nconst EXTRA_TONE_PENALTY = 0.6;\n\n/**\n * Score penalty per chord tone the voicing omits.\n *\n * {@link detectChord} admits one absent tone and only the perfect fifth, so this\n * is the cost of a shell voicing rather than of a misreading — small, because\n * dropping the fifth is ordinary practice, and non-zero, so a complete voicing\n * still outranks an incomplete reading of the same notes.\n */\nconst OMITTED_TONE_PENALTY = 0.15;\n\n/**\n * Standing a match's root earns from being the sounding bass, on the 0..1 scale\n * a root's share of the window's weight is measured on.\n *\n * The bass is the strongest single indicator of a root there is, so a candidate\n * it puts in root position is not judged on how loudly that root happens to be\n * sounding: a shell voicing whose seventh is the loudest thing in the window\n * still has its root underneath it, and reading it from the loudest pitch class\n * instead re-roots the chord onto a tone that is merely prominent.\n *\n * Short of 1 because the bass is evidence rather than proof — a pitch class that\n * actually carries the window's weight still outranks a bass that does not,\n * which is what keeps a chord voiced in inversion from being re-rooted onto its\n * own bass note.\n */\nconst BASS_ROOT_STANDING = 0.95;\n\n/**\n * How {@link chordTimelineFromNotes} decides where one chord ends and the next\n * begins.\n *\n * `'dynamic'` searches for the boundaries the notes actually imply, so a bar\n * holding two chords yields two segments and a bar holding one yields one.\n * `'grid'` cuts a segment every `harmonicRhythm` beats regardless of what\n * sounds, which is only right when the harmonic rhythm is known to be fixed.\n *\n * @category Arrangement & Analysis\n */\nexport type ChordSegmentation = 'dynamic' | 'grid';\n\n/**\n * Options controlling {@link chordTimelineFromNotes}.\n *\n * @category Arrangement & Analysis\n */\nexport type ChordTimelineOptions = {\n /**\n * Key context held across the whole span. Omit it to have the key searched\n * for over time with {@link keyTimelineFromNotes}, which is what lets a piece\n * that modulates be analysed against the key actually in force.\n */\n key?: KeyScale;\n /**\n * A single time signature held across the whole span, as sugar for a\n * one-element `meters`; defaults to 4/4. Giving both is an input error.\n *\n * @defaultValue `4/4`\n */\n ts?: TimeSignature;\n /**\n * The meter as it changes over the span. Bar lines, downbeats and metric\n * weight all follow the signature in force at the beat in question, so a\n * piece that changes meter is not read in the one it opened in.\n *\n * @defaultValue 4/4 throughout\n */\n meters?: MeterMap;\n /**\n * Length of the pickup in beats, when the piece starts with one.\n *\n * The first downbeat is beat 0 whatever precedes it, so an upbeat is written\n * at negative beats — a one-beat pickup in 4/4 sounds at beat -1. Declaring\n * its length rejects a note that starts before the pickup does; leave it\n * unset to accept any finite onset.\n *\n * @defaultValue no declared pickup\n */\n pickupBeats?: number;\n /**\n * Where chord boundaries may fall; defaults to `'dynamic'`.\n *\n * @defaultValue `'dynamic'`\n */\n segmentation?: ChordSegmentation;\n /**\n * Expected chord length in beats; defaults to the length of the opening bar.\n *\n * Under `'grid'` segmentation this is the exact window length. Under\n * `'dynamic'` it is a prior: the longer a chord is expected to last, the more\n * evidence a change needs before the search will place one.\n *\n * @defaultValue the length of the opening bar\n */\n harmonicRhythm?: number;\n /**\n * Shortest chord the `'dynamic'` search may report, in beats; defaults to one\n * beat. Lower it to catch changes on off-beats, at the cost of proportionally\n * more work. Values above `harmonicRhythm` are clamped to it, and the option\n * is ignored under `'grid'` segmentation.\n *\n * The search reads it as a resolution rather than as an exact slot length: it\n * rounds down to a whole division of `harmonicRhythm`, so `0.6` against a\n * four-beat chord is read in sevenths of it. That keeps the beats one\n * expected chord gives way to the next on the grid whatever resolution is\n * asked for, and with them every change a coarser setting found.\n *\n * @defaultValue `1`\n */\n minChordBeats?: number;\n /**\n * End of the analyzed span in beats; defaults to the end of the last note.\n *\n * @defaultValue the end of the last note\n */\n totalBeats?: number;\n /**\n * Upper bound on the work this call may do — note counts, windows, and\n * candidate counts are each checked against it before anything is allocated.\n *\n * Raise it to analyse a piece larger than the default allows; the default is\n * {@link DEFAULT_GENERATION_BUDGET}, chosen so a runaway input fails fast\n * rather than blocking the thread.\n *\n * @defaultValue {@link DEFAULT_GENERATION_BUDGET}\n */\n budget?: number;\n};\n\n/**\n * The result of {@link chordTimelineFromNotes}.\n *\n * @category Arrangement & Analysis\n */\nexport type ChordTimelineResult = {\n /** The inferred timeline, with adjacent identical chords merged. */\n timeline: ChordTimeline;\n /**\n * The key regions the analysis ran against, in time order. A piece that does\n * not modulate yields one region covering the whole span; a given `key`\n * yields exactly one region too, since the caller has already answered the\n * question. Either way the first region starts where the analysis does, which\n * is before beat 0 when the piece opens with a pickup. Pivot chords are filled\n * in where the chords support one.\n */\n keys: KeyRegion[];\n /**\n * The key held longest across {@link ChordTimelineResult.keys} — the one to\n * print on a key signature or hand to a generator that takes a single key.\n */\n prevailingKey: KeyScale;\n /** One confidence value in [0, 1] per segment, in segment order. */\n segmentConfidence: number[];\n};\n\n/** A window's inferred chord and its confidence, or null for an empty window. */\ntype WindowChord = {\n chord: Chord;\n confidence: number;\n};\n\n/**\n * Score a chord match against a window's pitch-class weights: a root that\n * carries weight or stands in the bass, in-key tones, and exactness all raise\n * the score; a sounding pitch class the chord does not name and a chord tone the\n * voicing omits both lower it.\n *\n * The two costs are not the same size. What sounded is the evidence, so leaving\n * a pitch class out of the reading costs more than the key can make up, while\n * the absent perfect fifth {@link detectChord} allows costs little — that is\n * what keeps a shell voicing named by the chord it plays rather than by the\n * triad its key prefers.\n */\nfunction scoreMatch(\n match: ChordMatch,\n weights: number[],\n maxWeight: number,\n key: KeyScale,\n): number {\n const rootWeight = maxWeight > 0 ? (weights[match.rootPc] ?? 0) / maxWeight : 0;\n // `inversion === 0` is exactly \"the sounding bass is this candidate's root\".\n // It is null when the window offered no usable bass, so a bass that was\n // filtered out as noise lifts nothing.\n const rootStanding =\n match.inversion === 0 ? Math.max(rootWeight, BASS_ROOT_STANDING) : rootWeight;\n const tones = chordPitchClasses(makeChord(match.rootPc, match.quality));\n let score = rootStanding;\n if (tones.every((pc) => isScaleTone(pc, key))) {\n score += DIATONIC_BONUS;\n }\n if (match.exact) {\n score += EXACT_BONUS;\n }\n score -= EXTRA_TONE_PENALTY * match.extraPcs.length;\n score -= OMITTED_TONE_PENALTY * match.missingPcs.length;\n return score;\n}\n\n/** Confidence of a chord for a window: chord-tone weight over total weight. */\nfunction chordConfidence(\n chord: Chord,\n weights: number[],\n totalWeight: number,\n exact: boolean,\n): number {\n const tones = new Set(chordPitchClasses(chord));\n let chordWeight = 0;\n for (let pc = 0; pc < 12; pc += 1) {\n if (tones.has(pc)) {\n chordWeight += weights[pc] ?? 0;\n }\n }\n const raw = totalWeight > 0 ? chordWeight / totalWeight : 0;\n const confidence = Math.min(1, Math.max(0, raw));\n return exact ? confidence : confidence * INEXACT_CONFIDENCE_FACTOR;\n}\n\n/**\n * Infer the chord sounding in one window from the notes overlapping it.\n *\n * Builds a pitch-class weight histogram (overlap duration x velocity x\n * metric-accent bonus for onsets inside the window), keeps the significantly\n * weighted pitch classes, and picks the best {@link detectChord} match by the\n * standing of its root — the weight it carries, or the bass it stands in — plus\n * key membership, exactness, and how much of the window it accounts for.\n */\nfunction analyzeWindow(\n notes: readonly NoteEvent[],\n windowStart: number,\n windowEnd: number,\n meters: MeterMap,\n key: KeyScale,\n): WindowChord | null {\n const { weights, totalWeight, maxWeight, lowestPitch } = windowWeights(\n notes,\n windowStart,\n windowEnd,\n meters,\n );\n if (totalWeight <= EPS) {\n return null;\n }\n\n // Keep pitch classes with meaningful weight, strongest first.\n const ranked = weights\n .map((weight, pc) => ({ pc, weight }))\n .filter(({ weight }) => weight > 0)\n .sort((a, b) => b.weight - a.weight || a.pc - b.pc);\n const selected = ranked\n .filter(({ weight }) => weight >= maxWeight * NOISE_THRESHOLD_RATIO)\n .slice(0, MAX_DETECTION_PCS)\n .map(({ pc }) => pc);\n\n // Feed detection with the window's true bass as the lowest pitch so inversion\n // detection works. If that pitch class was removed as noise, detection must\n // become unordered instead: promoting the strongest remaining tone to bass\n // manufactures a slash chord that the input never established.\n const bassPc = pitchClass(lowestPitch);\n const hasBass = selected.includes(bassPc);\n const detectionPitches = hasBass\n ? selected.map((pc) => (pc === bassPc ? pc : pc + 12))\n : selected;\n\n const matches = detectChord(detectionPitches, { input: hasBass ? 'midi' : 'pitchClass' });\n let bestMatch: ChordMatch | null = null;\n let bestScore = Number.NEGATIVE_INFINITY;\n for (const match of matches) {\n const score = scoreMatch(match, weights, maxWeight, key);\n if (score > bestScore) {\n bestScore = score;\n bestMatch = match;\n }\n }\n if (bestMatch) {\n // An augmented sixth is a bass and an interval rather than a stack of\n // thirds, so no tertian match can carry one: over the lowered submediant\n // the same pitch classes are read as bVI7, whose seventh is spelled a\n // semitone below where the augmented sixth is. The reading is made here,\n // where the sounding bass and the key are both known, and the chord it\n // yields carries the spelling that keeps it.\n const augmented = hasBass ? augmentedSixthFromPitchClasses(selected, bassPc, key) : null;\n if (augmented !== null) {\n // Its tones are exactly the window's, so it explains them exactly.\n return {\n chord: augmented,\n confidence: chordConfidence(augmented, weights, totalWeight, true),\n };\n }\n const chord = makeChord(bestMatch.rootPc, bestMatch.quality, bestMatch.bassPc);\n return { chord, confidence: chordConfidence(chord, weights, totalWeight, bestMatch.exact) };\n }\n\n return null;\n}\n\n/** Whether two chords are the same root, quality, and bass. */\nfunction sameChord(a: Chord, b: Chord): boolean {\n return a.rootPc === b.rootPc && a.quality === b.quality && a.bassPc === b.bassPc;\n}\n\n/**\n * Chord qualities the boundary search considers.\n *\n * This lexicon only has to be rich enough to tell \"the harmony changed here\"\n * from \"it did not\": the chord a segment finally reports comes from\n * {@link analyzeWindow} over the settled span, which searches the full quality\n * table. Adding rarer qualities here would slow every slot down without\n * changing where the boundaries land.\n */\nconst SEGMENTATION_QUALITIES: readonly ChordQuality[] = [\n 'maj',\n 'min',\n 'dim',\n 'aug',\n 'dom7',\n 'maj7',\n 'min7',\n 'm7b5',\n 'dim7',\n 'sus4',\n '6',\n 'min6',\n];\n\n/**\n * The 12 x {@link SEGMENTATION_QUALITIES} lexicon, built once and held flat.\n *\n * Candidate `c` has root `LEXICON_ROOTS[c]` and sounds the pitch classes in\n * `LEXICON_TONES` from `LEXICON_TONE_START[c]` up to `LEXICON_TONE_START[c + 1]`.\n * The search scores every candidate against every slot of the piece, so the\n * inner loop reads a flat table rather than chasing one object and one array per\n * candidate.\n */\nconst LEXICON_SIZE = 12 * SEGMENTATION_QUALITIES.length;\nconst LEXICON_ROOTS = new Int32Array(LEXICON_SIZE);\nconst LEXICON_TONE_START = new Int32Array(LEXICON_SIZE + 1);\nconst LEXICON_TONES: Int32Array = (() => {\n const tones: number[] = [];\n let candidate = 0;\n for (let rootPc = 0; rootPc < 12; rootPc += 1) {\n for (const quality of SEGMENTATION_QUALITIES) {\n LEXICON_ROOTS[candidate] = rootPc;\n LEXICON_TONE_START[candidate] = tones.length;\n tones.push(...chordPitchClasses(makeChord(rootPc, quality)));\n candidate += 1;\n }\n }\n LEXICON_TONE_START[candidate] = tones.length;\n return Int32Array.from(tones);\n})();\n\n/** Weight of a slot's non-chord tones, relative to its chord tones. */\nconst OUTSIDE_TONE_PENALTY = 1;\n\n/** Penalty per absent chord tone, in units of the candidate's average tone weight. */\nconst MISSING_TONE_PENALTY = 0.5;\n\n/** Extra credit for the candidate's root carrying weight of its own. */\nconst ROOT_PRESENCE_BONUS = 0.3;\n\n/**\n * Cost of one chord change, as a fraction of the evidence an expected-length\n * chord carries. A change has to improve the fit by at least this much to be\n * worth making, which is what keeps passing tones and appoggiaturas from\n * splitting a segment.\n */\nconst CHANGE_COST = 0.35;\n\n/** How much of the change cost a maximally strong beat waives. */\nconst STRONG_BEAT_DISCOUNT = 0.5;\n\n/** The largest value {@link metricWeight} returns (a downbeat). */\nconst MAX_METRIC_WEIGHT = 3;\n\n/**\n * Score how well one lexicon candidate explains a slot's weights.\n *\n * Chord tones earn their weight, everything else sounding costs it, and each\n * chord tone that never sounds costs a share of what the candidate would have\n * earned had it been complete — so a triad is not rewarded for the two thirds\n * of itself that are missing.\n */\nfunction candidateScore(\n candidate: number,\n weights: readonly number[],\n totalWeight: number,\n): number {\n const first = LEXICON_TONE_START[candidate] ?? 0;\n const last = LEXICON_TONE_START[candidate + 1] ?? first;\n let covered = 0;\n let missing = 0;\n for (let index = first; index < last; index += 1) {\n const weight = weights[LEXICON_TONES[index] ?? 0] ?? 0;\n covered += weight;\n if (weight <= EPS) {\n missing += 1;\n }\n }\n const outside = totalWeight - covered;\n const averageToneWeight = totalWeight / (last - first);\n return (\n covered -\n OUTSIDE_TONE_PENALTY * outside -\n MISSING_TONE_PENALTY * missing * averageToneWeight +\n ROOT_PRESENCE_BONUS * (weights[LEXICON_ROOTS[candidate] ?? 0] ?? 0)\n );\n}\n\n/** A half-open span of slots the boundary search settled on. */\ntype BoundarySpan = { startBeat: number; endBeat: number };\n\n/**\n * The grid the span is examined in is the shared {@link SlotGrid}: equal slots\n * of `slotBeats`, starting at `origin`. The origin is 0 for a piece that starts\n * on the downbeat and negative for one that starts with a pickup;\n * {@link gridOriginOf} places it, and the key search cuts its own slots on a\n * grid of the same shape.\n */\n\n/** First beat of slot `index`. */\nfunction beatOfSlot(grid: SlotGrid, index: number): number {\n return grid.origin + index * grid.slotBeats;\n}\n\n/** Index of the slot containing `beat`, unclamped. */\nfunction slotOfBeat(grid: SlotGrid, beat: number): number {\n return (beat - grid.origin) / grid.slotBeats;\n}\n\n/**\n * The tables the boundary search runs on, sized once for the whole piece.\n *\n * Every field is indexed by absolute slot, so a run of slots is searched by\n * bounds rather than by being copied out — and a cell of the recurrence is a\n * pair of numbers in a typed array rather than an object. The score table is\n * also the only part of the search that depends on what the notes are, which is\n * what lets an unchanged slot's row be carried over from a previous analysis.\n */\nexport type BoundaryTables = {\n /** Fit of slot `i` to candidate `c`, at `scores[i * LEXICON_SIZE + c]`. */\n scores: Float64Array;\n /** Chosen predecessor of slot `i`, candidate `c`, at the same index. */\n back: Int32Array;\n /** The two rolling cost rows the recurrence alternates between. */\n costs: Float64Array;\n spare: Float64Array;\n /** The winning candidate of each slot, filled by the backward pass. */\n path: Int32Array;\n};\n\n/** Allocate the boundary-search tables for a piece of `slotCount` slots. */\nexport function createBoundaryTables(slotCount: number): BoundaryTables {\n return {\n scores: new Float64Array(slotCount * LEXICON_SIZE),\n back: new Int32Array(slotCount * LEXICON_SIZE),\n costs: new Float64Array(LEXICON_SIZE),\n spare: new Float64Array(LEXICON_SIZE),\n path: new Int32Array(slotCount),\n };\n}\n\n/**\n * Score one slot against the whole lexicon, into its row of the score table.\n *\n * A slot carrying no weight scores zero everywhere, which is what the row\n * already holds, so silence costs nothing to score.\n */\nexport function scoreSlot(tables: BoundaryTables, slot: WindowWeights, index: number): void {\n const base = index * LEXICON_SIZE;\n if (slot.totalWeight <= EPS) {\n tables.scores.fill(0, base, base + LEXICON_SIZE);\n return;\n }\n for (let c = 0; c < LEXICON_SIZE; c += 1) {\n tables.scores[base + c] = candidateScore(c, slot.weights, slot.totalWeight);\n }\n}\n\n/**\n * Choose chord boundaries over one run of slots by dynamic programming.\n *\n * Every slot is scored against the whole lexicon; the optimal path trades the\n * per-slot fit against a fixed cost per chord change, discounted on strong\n * beats so that a change lands on the bar line rather than a beat either side\n * of it. Because the change cost is the same whichever candidate is left\n * behind, the previous stage collapses to \"stay on this candidate\" versus\n * \"come from the cheapest one\", which keeps the search linear in the lexicon\n * rather than quadratic.\n */\nfunction chooseBoundaries(\n tables: BoundaryTables,\n from: number,\n to: number,\n grid: SlotGrid,\n meters: MeterMap,\n changeCost: number,\n): BoundarySpan[] {\n const width = to - from;\n if (width <= 0) {\n return [];\n }\n const { scores, back, path } = tables;\n // Costs are negated scores, so the search minimises.\n let costs = tables.costs;\n let next = tables.spare;\n for (let c = 0; c < LEXICON_SIZE; c += 1) {\n costs[c] = -(scores[from * LEXICON_SIZE + c] ?? 0);\n }\n for (let i = 1; i < width; i += 1) {\n let cheapest = Number.POSITIVE_INFINITY;\n let cheapestIndex = 0;\n for (let c = 0; c < LEXICON_SIZE; c += 1) {\n const cost = costs[c] ?? Number.POSITIVE_INFINITY;\n if (cost < cheapest) {\n cheapest = cost;\n cheapestIndex = c;\n }\n }\n const slotStart = beatOfSlot(grid, from + i);\n const accent = metricWeight(slotStart, meters);\n const penalty = changeCost * (1 - (STRONG_BEAT_DISCOUNT * accent) / MAX_METRIC_WEIGHT);\n const rowBase = (from + i) * LEXICON_SIZE;\n const change = cheapest + penalty;\n for (let c = 0; c < LEXICON_SIZE; c += 1) {\n const stay = costs[c] ?? Number.POSITIVE_INFINITY;\n // Ties hold the current chord: a change has to be strictly better.\n const carried = stay <= change ? stay : change;\n back[rowBase + c] = stay <= change ? c : cheapestIndex;\n next[c] = carried - (scores[rowBase + c] ?? 0);\n }\n const previous = costs;\n costs = next;\n next = previous;\n }\n\n let best = 0;\n for (let c = 1; c < LEXICON_SIZE; c += 1) {\n if ((costs[c] ?? Number.POSITIVE_INFINITY) < (costs[best] ?? Number.POSITIVE_INFINITY)) {\n best = c;\n }\n }\n let current = best;\n for (let i = width - 1; i >= 0; i -= 1) {\n path[from + i] = current;\n if (i > 0) {\n current = back[(from + i) * LEXICON_SIZE + current] ?? current;\n }\n }\n\n const spans: BoundarySpan[] = [];\n let runStart = 0;\n for (let i = 1; i <= width; i += 1) {\n if (i === width || path[from + i] !== path[from + runStart]) {\n spans.push({\n startBeat: beatOfSlot(grid, from + runStart),\n endBeat: beatOfSlot(grid, from + i),\n });\n runStart = i;\n }\n }\n return spans;\n}\n\n/** Shortest chord the dynamic search reports unless the caller asks for finer. */\nconst DEFAULT_MIN_CHORD_BEATS = 1;\n\n/**\n * The slot length a requested resolution is read at: a whole division of the\n * expected chord length, never coarser than the caller asked for.\n *\n * A change is discounted on strong beats, and the strongest of them are where\n * one expected chord gives way to the next. A grid that steps over those beats\n * never earns the discount, so the search reports no change anywhere and a run\n * of alternating harmony comes back as one segment naming a chord that never\n * sounded. Dividing the expected chord length keeps its own boundaries — and\n * the bar lines with them — on the grid at every resolution, which is what\n * makes the resolution a knob rather than a trap: every setting finds the\n * changes the coarser settings found.\n *\n * @param harmonicRhythm Expected chord length in beats.\n * @param minChordBeats Shortest chord the caller will accept, in beats.\n * @returns The slot length, which divides `harmonicRhythm` exactly.\n */\nfunction slotBeatsWithin(harmonicRhythm: number, minChordBeats: number): number {\n const divisions = Math.max(1, Math.ceil(harmonicRhythm / minChordBeats - EPS));\n return harmonicRhythm / divisions;\n}\n\n/** One span per slot: the fixed grid, unchanged. */\nfunction gridSpans(slotCount: number, grid: SlotGrid): BoundarySpan[] {\n return Array.from({ length: slotCount }, (_, i) => ({\n startBeat: beatOfSlot(grid, i),\n endBeat: beatOfSlot(grid, i + 1),\n }));\n}\n\n/**\n * Place chord boundaries where the notes imply them.\n *\n * Silence long enough to be a chord of its own ends the run: nothing sounds\n * across it, so nothing may be merged across it either, and `at(beat)` answers\n * null there. A shorter rest is absorbed — a chord is not over because the\n * players took a breath.\n */\nfunction dynamicSpans(\n slotWeights: readonly WindowWeights[],\n tables: BoundaryTables,\n grid: SlotGrid,\n harmonicRhythm: number,\n meters: MeterMap,\n): BoundarySpan[] {\n const slotBeats = grid.slotBeats;\n const slotCount = slotWeights.length;\n let soundingSlots = 0;\n let weightSum = 0;\n for (let i = 0; i < slotCount; i += 1) {\n const weight = slotWeights[i]?.totalWeight ?? 0;\n if (weight > EPS) {\n soundingSlots += 1;\n weightSum += weight;\n }\n }\n if (soundingSlots === 0) {\n return [];\n }\n // The change cost is expressed in the same weight units the slot scores are,\n // scaled to one expected chord's worth of evidence: a change must be worth\n // that much of the fit before the search will make it, whatever the absolute\n // loudness or note lengths of the piece happen to be.\n //\n // A slot's weight is proportional to its length, so the mean cancels the\n // slots-per-chord factor and the cost depends on `harmonicRhythm` alone. That\n // is what makes `minChordBeats` a resolution knob rather than a sensitivity\n // one: asking for finer slots finds a boundary in more places, but does not\n // change how much evidence placing one takes.\n const meanSlotWeight = weightSum / soundingSlots;\n const changeCost = CHANGE_COST * meanSlotWeight * (harmonicRhythm / slotBeats);\n\n const silent = slotWeights.map((slot) => slot.totalWeight <= EPS);\n const breaks = new Array<boolean>(slotCount).fill(false);\n for (let i = 0; i < slotCount; ) {\n if (!silent[i]) {\n i += 1;\n continue;\n }\n let end = i;\n while (end < slotCount && silent[end]) {\n end += 1;\n }\n if ((end - i) * slotBeats >= harmonicRhythm - EPS) {\n for (let k = i; k < end; k += 1) {\n breaks[k] = true;\n }\n }\n i = end;\n }\n\n const spans: BoundarySpan[] = [];\n for (let i = 0; i < slotCount; ) {\n if (breaks[i]) {\n i += 1;\n continue;\n }\n let end = i;\n while (end < slotCount && !breaks[end]) {\n end += 1;\n }\n spans.push(...chooseBoundaries(tables, i, end, grid, meters, changeCost));\n i = end;\n }\n return spans;\n}\n\n/** The distinct notes overlapping a span, gathered from the slots it covers. */\nfunction notesOfSpan(\n slotNotes: readonly NoteEvent[][],\n span: BoundarySpan,\n grid: SlotGrid,\n): NoteEvent[] {\n const first = Math.round(slotOfBeat(grid, span.startBeat));\n const lastExclusive = Math.round(slotOfBeat(grid, span.endBeat));\n if (lastExclusive - first === 1) {\n return [...(slotNotes[first] ?? [])];\n }\n // A note held across several slots appears in each of them; the histogram\n // weighs by overlap, so counting it twice would double its influence.\n const seen = new Set<NoteEvent>();\n for (let i = first; i < lastExclusive; i += 1) {\n for (const note of slotNotes[i] ?? []) {\n seen.add(note);\n }\n }\n return [...seen];\n}\n\n/**\n * Infer a chord timeline from raw multi-track notes.\n *\n * Chord boundaries are searched for rather than assumed: the span is examined\n * in `minChordBeats` slots, and the change points that best explain the notes\n * are chosen, trading each slot's harmonic fit against a cost per chord change\n * that a strong beat discounts. A bar holding two chords therefore yields two\n * segments and a bar holding one yields one, without the caller having to know\n * the harmonic rhythm in advance. Pass `segmentation: 'grid'` to cut a segment\n * every `harmonicRhythm` beats instead.\n *\n * Each settled segment's chord is inferred from a pitch-class weight histogram\n * of the notes overlapping it (weight = overlap duration x velocity x\n * metric-accent bonus for onsets in the segment). Adjacent segments carrying\n * the identical chord are merged; a stretch with no notes produces no segment,\n * so `at(beat)` returns null there. Each segment carries a confidence in [0, 1]\n * — the fraction of its weight explained by chord tones, reduced when the match\n * is inexact, and duration-weighted across anything merged into it.\n *\n * Notes with a zero or negative duration never sound, so they are dropped at\n * ingest: they contribute to neither the key inference, the span, nor any\n * window's histogram.\n *\n * Bar lines, downbeats and the metric accents the boundary search discounts by\n * all follow the signature in force at the beat in question, so a piece that\n * changes meter is read in the meter it is actually in. The first downbeat is\n * beat 0, so a pickup is written at negative beats and the search still finds\n * the bar lines where the score has them.\n *\n * @param notes The notes to analyze (any number of tracks, flattened).\n * @param opts Analysis options; see {@link ChordTimelineOptions}.\n * @returns The inferred timeline, the key regions it ran against, the key that\n * prevails across them, and per-segment confidences.\n * @throws If `harmonicRhythm` is not positive.\n * @example\n * ```ts\n * import { chordTimelineFromNotes } from '@libraz/libcantus';\n * const notes = [\n * { pitch: 60, startBeat: 0, durationBeat: 2 }, // C\n * { pitch: 64, startBeat: 0, durationBeat: 2 }, // E\n * { pitch: 67, startBeat: 0, durationBeat: 2 }, // G\n * ];\n * const { timeline, prevailingKey } = chordTimelineFromNotes(notes);\n * timeline.at(0); // the chord inferred over beat 0, or null\n * prevailingKey; // the key held longest across the piece\n * ```\n * @category Arrangement & Analysis\n */\nexport function chordTimelineFromNotes(\n notes: readonly NoteEvent[],\n opts: ChordTimelineOptions = {},\n): ChordTimelineResult {\n return analyzeTimeline(notes, opts).result;\n}\n\n/**\n * The slot-level evidence one chord-timeline analysis drew from the notes.\n *\n * Everything here is a pure function of the notes overlapping one slot, so a\n * slot the edit cannot reach carries over to the next analysis unchanged. The\n * grid itself is recorded alongside, because a carried-over row only means the\n * same thing if the slot it indexes still covers the same beats.\n *\n * Not part of the public surface: it is the private state of an incremental\n * re-analysis, and a caller holding it could pair it with notes it was never\n * derived from.\n */\nexport type TimelineEvidence = {\n /** The grid the slots were cut on. */\n grid: SlotGrid;\n slotCount: number;\n totalBeats: number;\n segmentation: ChordSegmentation;\n harmonicRhythm: number;\n /** Whether the key was given rather than searched for. */\n keyGiven: boolean;\n slotWeights: WindowWeights[];\n tables: BoundaryTables;\n /**\n * The chord inferred over each settled span, keyed by the span's bounds and\n * the key it was read in — the two things besides the span's notes that\n * {@link analyzeWindow} depends on.\n */\n windows: Map<string, WindowChord | null>;\n};\n\n/** A half-open run of slots whose contents an edit may have changed. */\nexport type DirtySlots = { from: number; to: number };\n\n/**\n * How many slots either side of an edit are treated as dirty on top of the\n * slots it actually overlaps.\n *\n * The evidence itself needs none: a slot's histogram sees exactly the notes\n * overlapping it, so an edit confined to `[startBeat, endBeat)` cannot change a\n * slot it does not touch. The extra slot is there so that an onset landing a\n * floating-point hair either side of a slot boundary cannot leave a stale row\n * behind, which would be wrong output rather than merely slow output.\n */\nconst DIRTY_SLOT_MARGIN = 1;\n\n/**\n * The slots an edit over `[startBeat, endBeat)` may have changed.\n *\n * @param evidence Evidence from the analysis being updated, whose grid the\n * slots are numbered on.\n * @param startBeat First beat the edit touched.\n * @param endBeat End of the beats the edit touched, exclusive.\n * @returns The half-open run of slots to recompute.\n */\nexport function dirtySlotsFor(\n evidence: TimelineEvidence,\n startBeat: number,\n endBeat: number,\n): DirtySlots {\n const first = Math.floor(slotOfBeat(evidence.grid, startBeat)) - DIRTY_SLOT_MARGIN;\n const lastExclusive = Math.ceil(slotOfBeat(evidence.grid, endBeat)) + DIRTY_SLOT_MARGIN;\n return {\n from: Math.max(0, Math.min(evidence.slotCount, first)),\n to: Math.max(0, Math.min(evidence.slotCount, lastExclusive)),\n };\n}\n\n/** Whether every slot a span covers lies outside the dirty run. */\nfunction spanIsClean(\n grid: SlotGrid,\n span: BoundarySpan,\n dirtyFrom: number,\n dirtyTo: number,\n): boolean {\n const first = Math.round(slotOfBeat(grid, span.startBeat));\n const lastExclusive = Math.round(slotOfBeat(grid, span.endBeat));\n return lastExclusive <= dirtyFrom || first >= dirtyTo;\n}\n\n/** Identity of a key, for keying the per-span chord cache. */\nfunction keyId(key: KeyScale): string {\n return `${key.rootPc}:${key.modeMask12}`;\n}\n\n/** Cache key of one settled span read in one key. */\nfunction windowId(startBeat: number, endBeat: number, key: KeyScale): string {\n return `${startBeat}|${endBeat}|${keyId(key)}`;\n}\n\n/**\n * Whether previous evidence describes the same grid as this analysis, and so\n * may have rows carried over from it. A different grid means slot `i` no longer\n * covers the beats it did, which would silently pair a row with the wrong slot.\n */\nfunction evidenceFits(\n previous: TimelineEvidence,\n grid: SlotGrid,\n slotCount: number,\n totalBeats: number,\n segmentation: ChordSegmentation,\n harmonicRhythm: number,\n keyGiven: boolean,\n): boolean {\n return (\n previous.grid.origin === grid.origin &&\n previous.grid.slotBeats === grid.slotBeats &&\n previous.slotCount === slotCount &&\n previous.totalBeats === totalBeats &&\n previous.segmentation === segmentation &&\n previous.harmonicRhythm === harmonicRhythm &&\n previous.keyGiven === keyGiven\n );\n}\n\n/**\n * Infer a chord timeline, carrying over every slot the edit cannot reach.\n *\n * The dependency margin is worked out per stage rather than guessed at once:\n *\n * - A slot's histogram sees exactly the notes overlapping it, so the slots\n * whose evidence an edit can change are the slots overlapping the edited\n * beats — margin zero, rounded outward to whole slots.\n * - The boundary search has no finite margin inside a run. It is a Viterbi over\n * the run's slots, so the winning path at any slot depends on every other\n * slot of the run; and its change cost is calibrated on the mean weight of\n * every sounding slot in the piece, which couples the runs to each other.\n * The recurrence is therefore re-run over the whole piece — pure arithmetic\n * over the carried-over score table, with no per-cell allocation.\n * - A settled span's chord depends only on the notes overlapping it, so it is\n * carried over when the span's bounds, its key, and its slots are all\n * unchanged. Bounds may move anywhere within a run, so which spans qualify is\n * read off the freshly chosen boundaries rather than assumed.\n *\n * @param notes The notes to analyze.\n * @param opts Analysis options.\n * @param previous Evidence from the analysis being updated, when there is one.\n * @param dirty The slots the edit may have changed; every slot when omitted.\n * @returns The analysis and the evidence behind it.\n */\nexport function analyzeTimeline(\n notes: readonly NoteEvent[],\n opts: ChordTimelineOptions = {},\n previous?: TimelineEvidence,\n dirty?: DirtySlots,\n): { result: ChordTimelineResult; evidence: TimelineEvidence } {\n const meters = resolveMeters(opts, 'timeline meters');\n const harmonicRhythm = opts.harmonicRhythm ?? beatsPerBarAt(0, meters);\n assertRange(harmonicRhythm, Number.MIN_VALUE, Number.MAX_SAFE_INTEGER, 'harmonic rhythm');\n const segmentation = opts.segmentation ?? 'dynamic';\n const minChordBeats = opts.minChordBeats ?? DEFAULT_MIN_CHORD_BEATS;\n assertRange(minChordBeats, Number.MIN_VALUE, Number.MAX_SAFE_INTEGER, 'minChordBeats');\n const budget = opts.budget;\n const noteOptions: NoteEventAssertOptions = { allowNonPositiveDuration: true, budget };\n if (opts.pickupBeats !== undefined) {\n assertRange(opts.pickupBeats, 0, Number.MAX_SAFE_INTEGER, 'timeline pickupBeats');\n noteOptions.minStartBeat = -opts.pickupBeats;\n }\n assertNoteEvents(notes, 'timeline notes', noteOptions);\n // Zero/negative-length notes never sound; drop them before any inference.\n const soundingIndex = createNoteEventIndex(\n notes.filter((note) => note.durationBeat > 0),\n { name: 'timeline notes', budget },\n );\n const sounding = soundingIndex.notes.map(({ note }) => note);\n const lastNoteEnd = sounding.reduce((end, n) => Math.max(end, n.startBeat + n.durationBeat), 0);\n const totalBeats = opts.totalBeats ?? lastNoteEnd;\n assertRange(totalBeats, 0, Number.MAX_SAFE_INTEGER, 'timeline totalBeats');\n const slotBeats =\n segmentation === 'grid' ? harmonicRhythm : slotBeatsWithin(harmonicRhythm, minChordBeats);\n const firstOnset = sounding.reduce((first, n) => Math.min(first, n.startBeat), 0);\n const { origin, startBeat: musicStart } = gridOriginOf(firstOnset, slotBeats);\n const grid: SlotGrid = { origin, slotBeats };\n // A given key is taken as read across the whole span: the caller has already\n // answered the question, and second-guessing it would make the option mean\n // \"a hint\" when it reads as an instruction. Its one region starts where the\n // music does rather than at beat 0, so a piece opening with a pickup is\n // covered from its first note however the key was arrived at — and the two\n // paths name the same beat, since the search reads its origin from the same\n // place. Otherwise the key is searched for over time, so a piece that\n // modulates is not analysed against the wrong key for every bar after it does.\n const keys: KeyRegion[] =\n opts.key !== undefined\n ? [{ startBeat: musicStart, endBeat: totalBeats, key: opts.key, confidence: 1 }]\n : keyTimelineFromNotes(sounding, { meters, totalBeats, budget });\n const prevailingKey = prevailingKeyOf(keys) ?? majorKey(0);\n const keyAt = keyLookup(keys, prevailingKey);\n\n const segments: ChordSegment[] = [];\n const segmentConfidence: number[] = [];\n const slotCount = Math.max(0, Math.ceil((totalBeats - grid.origin) / slotBeats - EPS));\n assertGenerationBudget(slotCount, 'timeline windows', budget);\n const slotNotes = bucketNotesBySlot(sounding, grid, slotCount, {\n name: 'timeline note-to-window memberships',\n budget,\n });\n\n const carried =\n previous !== undefined &&\n dirty !== undefined &&\n evidenceFits(\n previous,\n grid,\n slotCount,\n totalBeats,\n segmentation,\n harmonicRhythm,\n opts.key !== undefined,\n )\n ? previous\n : undefined;\n const dirtyFrom = carried === undefined ? 0 : Math.max(0, dirty?.from ?? 0);\n const dirtyTo = carried === undefined ? slotCount : Math.min(slotCount, dirty?.to ?? slotCount);\n\n const tables = createBoundaryTables(slotCount);\n const slotWeights: WindowWeights[] = new Array<WindowWeights>(slotCount);\n if (carried !== undefined) {\n tables.scores.set(carried.tables.scores);\n }\n for (let i = 0; i < slotCount; i += 1) {\n const reusable = i < dirtyFrom || i >= dirtyTo ? carried?.slotWeights[i] : undefined;\n if (reusable !== undefined) {\n slotWeights[i] = reusable;\n continue;\n }\n const start = beatOfSlot(grid, i);\n const weights = windowWeights(\n slotNotes[i] ?? [],\n start,\n Math.min(start + slotBeats, totalBeats),\n meters,\n );\n slotWeights[i] = weights;\n scoreSlot(tables, weights, i);\n }\n\n const spans =\n segmentation === 'grid'\n ? gridSpans(slotCount, grid)\n : dynamicSpans(slotWeights, tables, grid, harmonicRhythm, meters);\n\n const windows = new Map<string, WindowChord | null>();\n for (const span of spans) {\n // The slot before the pickup is analyzed — its notes are the pickup's — but\n // the stretch of it that precedes the first note holds nothing, so the\n // segment is reported from where the music starts, as the last one is\n // reported to where it ends.\n const start = Math.max(span.startBeat, musicStart);\n const end = Math.min(span.endBeat, totalBeats);\n const spanKey = keyAt(start);\n const id = windowId(start, end, spanKey);\n // A span whose slots are all clean holds exactly the notes it held before,\n // so its chord and confidence are the same numbers over the same input.\n const cached =\n carried !== undefined && spanIsClean(grid, span, dirtyFrom, dirtyTo)\n ? carried.windows.get(id)\n : undefined;\n const inferred =\n cached !== undefined\n ? cached\n : analyzeWindow(notesOfSpan(slotNotes, span, grid), start, end, meters, spanKey);\n windows.set(id, inferred);\n if (!inferred) {\n continue;\n }\n const last = segments[segments.length - 1];\n if (last && sameChord(last.chord, inferred.chord) && Math.abs(last.endBeat - start) < EPS) {\n // Merge into the previous segment, blending confidence by duration.\n const lastLength = last.endBeat - last.startBeat;\n const length = end - start;\n const lastConfidence = segmentConfidence[segmentConfidence.length - 1] ?? 0;\n segmentConfidence[segmentConfidence.length - 1] =\n (lastConfidence * lastLength + inferred.confidence * length) / (lastLength + length);\n last.endBeat = end;\n } else {\n segments.push({ startBeat: start, endBeat: end, chord: inferred.chord });\n segmentConfidence.push(inferred.confidence);\n }\n }\n\n return {\n result: {\n timeline: { at: segmentLookup(segments), segments },\n keys: attachPivots(keys, segments),\n prevailingKey,\n segmentConfidence,\n },\n evidence: {\n grid,\n slotCount,\n totalBeats,\n segmentation,\n harmonicRhythm,\n keyGiven: opts.key !== undefined,\n slotWeights,\n tables,\n windows,\n },\n };\n}\n\n/**\n * A cadence found between two consecutive timeline segments.\n *\n * @category Arrangement & Analysis\n */\nexport type CadenceHit = {\n /** The beat where the cadence arrives (the second chord's onset). */\n atBeat: number;\n /**\n * The cadence formed by the pair. Its `type` is never null: a pair forming no\n * cadence is not a hit. A timeline names no voicing, so `strength` is null for\n * an authentic cadence whose chords are both in root position.\n */\n cadence: CadenceResult;\n from: Chord;\n to: Chord;\n};\n\n/**\n * Find cadences between consecutive segments of a chord timeline.\n *\n * Each temporally adjacent segment pair is classified with\n * {@link detectCadence}; pairs forming no cadence are skipped. Segments\n * separated by a gap (a rest in the timeline) are not a chord-to-chord\n * progression, so they are never paired.\n *\n * The segment before the pair is passed as the approach, which is what tells a\n * cadential six-four from an inverted tonic: `I I64 V I` is one cadence\n * arriving on the final tonic, described from the six-four the dominant was\n * already sounding under, rather than a half cadence onto the dominant\n * followed by an authentic one. A passing six-four — one whose dominant is not\n * what follows it — is unaffected, and so is a pair whose predecessor is\n * separated from it by a rest.\n *\n * @param timeline The chord timeline to scan.\n * @param key The prevailing key, or the key in force at a given beat.\n * @returns The cadences found, in time order.\n * @example\n * ```ts\n * import { chordTimelineFromNotes, detectCadences } from '@libraz/libcantus';\n * const notes = [\n * { pitch: 67, startBeat: 0, durationBeat: 4 }, // G, a dominant\n * { pitch: 60, startBeat: 4, durationBeat: 4 }, // C, the tonic\n * ];\n * const { timeline, prevailingKey } = chordTimelineFromNotes(notes);\n * const cadences = detectCadences(timeline, prevailingKey);\n * ```\n * @category Arrangement & Analysis\n */\nexport function detectCadences(timeline: ChordTimeline, key: KeyContext): CadenceHit[] {\n // A cadence belongs to the key it arrives in, so a modulating piece is asked\n // for the key at the arrival beat rather than for one key for the whole span.\n const keyAt = typeof key === 'function' ? key : () => key;\n const hits: CadenceHit[] = [];\n for (let i = 1; i < timeline.segments.length; i += 1) {\n const prev = timeline.segments[i - 1];\n const cur = timeline.segments[i];\n if (!prev || !cur) {\n continue;\n }\n if (Math.abs(cur.startBeat - prev.endBeat) > EPS) {\n continue; // A rest separates the chords; no cadential motion across it.\n }\n // The chord before the pair, when it sounded straight into it: a six-four\n // across a rest was not still sounding when the dominant arrived.\n const before = timeline.segments[i - 2];\n const approach =\n before !== undefined && Math.abs(prev.startBeat - before.endBeat) <= EPS\n ? before.chord\n : undefined;\n const cadence = detectCadence(prev.chord, cur.chord, keyAt(cur.startBeat), {\n ...(approach === undefined ? {} : { approach }),\n });\n if (cadence.type !== null) {\n hits.push({ atBeat: cur.startBeat, cadence, from: prev.chord, to: cur.chord });\n }\n }\n return hits;\n}\n","import { pitchClassOf as pitchClass } from '../../core/pitch/index.js';\nimport type { KeyScale, NoteEvent } from '../../core/types.js';\nimport { assertNoteEvents } from '../../core/validation/index.js';\nimport type { Chord, ChordToneRole } from '../../theory/chord/index.js';\nimport { chordToneRole, intervalAboveRoot, isChordMember } from '../../theory/chord/index.js';\nimport {\n createsVerticalDissonance,\n isLeadingToneResolution,\n} from '../../theory/counterpoint/index.js';\nimport type { VoiceSnapshot } from '../../theory/safety/index.js';\n\n/**\n * A suspension figure, named by the interval above the bass and the interval it\n * resolves to.\n *\n * A figure is reported only when the suspended note forms the interval the\n * figure names. A dissonance that resolves by step from anywhere else — a\n * retardation rising a semitone from the seventh to the octave, say — is left\n * unfigured rather than named after an interval it does not form.\n *\n * @category Arrangement & Analysis\n */\nexport type SuspensionFigure = 'sus4-3' | 'sus6-5' | 'sus7-6' | 'sus9-8' | 'sus2-3';\n\n/**\n * A theory annotation attached to a note.\n *\n * @category Arrangement & Analysis\n */\nexport type TheoryLabel =\n | { kind: 'chordTone'; role: ChordToneRole }\n | { kind: 'tension'; degree: 9 | 11 | 13 }\n | { kind: 'avoid' }\n | { kind: 'passing' }\n | { kind: 'neighbor' }\n | { kind: 'suspension'; type: SuspensionFigure; resolveTo: number }\n /**\n * A note leant on: reached by leap and given up by step the other way, with\n * `resolveTo` naming the pitch it gives way to. The word is the one\n * {@link MelodyToneRole} uses for the same figure, so a melody read through\n * this layer and through the harmonizer comes back under one vocabulary\n * rather than under two names for one note.\n */\n | { kind: 'appoggiatura'; resolveTo: number }\n | { kind: 'anticipation' }\n | { kind: 'escape' }\n | { kind: 'needsResolution'; resolveTo: number }\n | { kind: 'leadingTone'; resolveTo: number };\n\n/**\n * A note with its theory labels and a short rationale.\n *\n * The labels describe the note against the chord sounding at its **onset**;\n * they are not re-derived for the later chords a long note sustains across.\n * `analyzeArrangement` does re-check every crossed chord change, and reports\n * what it finds as conflicts, so a note labelled a chord tone here can still\n * appear in `conflicts` at a later beat.\n *\n * The identity fields make an annotation mappable back to the caller's input:\n * `originalIndex` is the note's position in the array that was passed in, and\n * `trackIndex` is set by {@link analyzeArrangement}.\n *\n * @category Arrangement & Analysis\n */\nexport type AnalyzedNote = {\n noteId: number;\n /** The note's index in the caller's own array, when the caller supplied one. */\n originalIndex?: number;\n /** The note's track, set by `analyzeArrangement`. */\n trackIndex?: number;\n pitch: number;\n startBeat: number;\n durationBeat: number;\n labels: TheoryLabel[];\n rationale?: string;\n};\n\n/**\n * A single note in a monophonic voice: a {@link NoteEvent} with a stable id and,\n * optionally, its index in the caller's original array.\n *\n * @category Arrangement & Analysis\n */\nexport type VoiceNote = NoteEvent & { id?: number; originalIndex?: number };\n\n/**\n * A voice note whose id has been assigned, as {@link toVoiceNotes} returns and\n * {@link analyzeArrangement} works in.\n *\n * @category Arrangement & Analysis\n */\nexport type IdentifiedVoiceNote = VoiceNote & { id: number };\n\n/**\n * A key that applies to the whole voice, or the key in force at a given beat.\n *\n * The callback form is what a piece that modulates needs: it is asked for the\n * key at a specific beat, the same way a chord is asked for with `chordAtBeat`.\n *\n * @example\n * ```ts\n * import { majorKey } from '@libraz/libcantus';\n * const wholePiece = majorKey(0); // C major throughout\n * const modulating = (beat: number) => (beat < 8 ? majorKey(5) : majorKey(0));\n * ```\n * @category Arrangement & Analysis\n */\nexport type KeyContext = KeyScale | ((beat: number) => KeyScale);\n\n/**\n * Give plain note events the ids {@link analyzeVoice} reports back.\n *\n * The id is only an identity handle, so the array position serves; this exists\n * so the mapping is a named call rather than a `map` every caller writes.\n *\n * @param events The note events, in time order.\n * @returns The same notes as voice notes, each carrying its index as its id.\n * @category Arrangement & Analysis\n */\nexport function toVoiceNotes(events: readonly NoteEvent[]): IdentifiedVoiceNote[] {\n return events.map((event, index) => ({ ...event, id: index, originalIndex: index }));\n}\n\n/** Maximum gap/overlap treated as adjacent after MIDI humanization. */\nconst HUMANIZE_ADJACENCY = 0.05;\n\n/**\n * Interval class of a pitch above the actual sounding bass.\n *\n * Suspension figures (4-3, 7-6, 9-8) are named for the interval above the bass,\n * not the chord root, so this prefers the lowest pitch among the other sounding\n * voices when one lies below the note. Without a lower sounding voice it falls\n * back to the chord's bass pitch class (or root in root position).\n */\nfunction intervalAboveBass(pitch: number, others: VoiceSnapshot[], chord: Chord): number {\n let bass = Number.POSITIVE_INFINITY;\n for (const ov of others) {\n bass = Math.min(bass, ov.pitch);\n }\n if (Number.isFinite(bass) && bass < pitch) {\n return (((pitch - bass) % 12) + 12) % 12;\n }\n const ref = chord.bassPc ?? chord.rootPc;\n return (((pitchClass(pitch) - pitchClass(ref)) % 12) + 12) % 12;\n}\n\n/**\n * The extension each interval class above the root forms: a flat, natural or\n * raised ninth (1, 2 or 3) to 9, an eleventh (5 or 6) to 11, and a flat or\n * natural thirteenth (8 or 9) to 13.\n *\n * The interval classes left out are the ones a chord states as its own root,\n * third, fifth and seventh, which {@link chordToneRole} names instead.\n */\nconst TENSION_DEGREE_BY_INTERVAL: Readonly<Record<number, 9 | 11 | 13>> = {\n 1: 9,\n 2: 9,\n 3: 9,\n 5: 11,\n 6: 11,\n 8: 13,\n 9: 13,\n};\n\n/**\n * The extension an interval class above the root names, or undefined when it\n * names none.\n *\n * Answering undefined rather than a default is what keeps a raised ninth — the\n * interval class three semitones above the root — from being reported as the\n * thirteenth that a catch-all would name it.\n */\nfunction tensionDegree(ic: number): 9 | 11 | 13 | undefined {\n return TENSION_DEGREE_BY_INTERVAL[ic];\n}\n\nfunction isStep(a: number, b: number): boolean {\n const d = Math.abs(a - b);\n return d === 1 || d === 2;\n}\n\n/** Narrowest interval heard as a leap: anything wider than a step. */\nconst LEAP_SEMITONES = 3;\n\n/**\n * Classify a suspension figure from the interval class above the sounding bass\n * (`ic`) and the resolution direction (`delta`, positive when resolving upward).\n */\nfunction suspensionType(ic: number, delta: number): SuspensionFigure | null {\n if (delta > 0) {\n // Resolving upward is the 2-3 only from a second above the bass. A seventh\n // above it rising to the octave is a retardation, and the figure 2-3 would\n // name it after an interval it does not form.\n return ic === 1 || ic === 2 ? 'sus2-3' : null;\n }\n if (ic === 2) {\n return 'sus9-8';\n }\n if (ic === 10 || ic === 11) {\n return 'sus7-6';\n }\n if (ic === 5 || ic === 6) {\n return 'sus4-3';\n }\n if (ic === 8 || ic === 9) {\n return 'sus6-5';\n }\n // Any other interval above the bass is a downward-resolving dissonance with\n // no standard figure; a catch-all `sus4-3` would name it after an interval it\n // does not form.\n return null;\n}\n\n/** Widest interval, in semitones, a resolution by step may cover. */\nconst STEP_RESOLUTION = 2;\n\n/**\n * How far the fallback search for a resolution target reaches, in semitones.\n * A chord sounds its root, so a member is always found inside an octave.\n */\nconst NEAREST_CHORD_TONE_REACH = 12;\n\n/**\n * The nearest chord member to a pitch, searched outward from it and downward\n * first at equal distance, within `maxDistance` semitones.\n *\n * A dissonance resolves by step wherever a chord tone lies a step away, which\n * is what a `maxDistance` of {@link STEP_RESOLUTION} asks for. The wider search\n * names the closest chord tone there is, so a dissonance with no stepwise exit\n * — a minor third or a major seventh over a bare triad — still reports where it\n * wants to go instead of going unlabelled.\n */\nfunction nearestChordTone(pitch: number, chord: Chord, maxDistance: number): number | undefined {\n for (let delta = 1; delta <= maxDistance; delta += 1) {\n if (isChordMember(pitch - delta, chord)) {\n return pitch - delta;\n }\n if (isChordMember(pitch + delta, chord)) {\n return pitch + delta;\n }\n }\n return undefined;\n}\n\n/**\n * Label every note of a voice with its theory roles.\n *\n * Each note is classified against the chord sounding at its beat. Chord tones\n * get a role label; non-chord tones are matched, in order, as suspensions\n * (prepared by an identical consonant pitch and resolving by step), passing\n * tones, neighbors, anticipations, appoggiaturas (approached by leap and\n * resolving by step the other way), and escape tones, then fall back to\n * tension, avoid, or an unresolved-dissonance label. The figures are named as\n * {@link classifyMelodyTones} names them, so a caller reading a melody through\n * both layers gets one vocabulary; that classifier weighs the metre as well,\n * which this one is not given, so it is the stricter of the two. Leading-tone resolutions are noted\n * additionally, judged against the key in force at the beat the resolution\n * lands on, so a modulation is heard from its new tonic.\n *\n * The voice is expected to be monophonic (one note at a time). Notes sharing an\n * onset are treated as simultaneous cluster members, not melodic neighbors, so\n * they receive no melodic labels (suspension, passing, neighbor, anticipation,\n * escape) — only harmonic ones. Callers with truly polyphonic material should\n * split it into monophonic sub-voices first, as `analyzeArrangement` does.\n *\n * @param voice The monophonic voice, in time order. A note without an `id` is\n * identified by its position, so plain note events can be passed straight in.\n * @param chordAtBeat Chord sounding at a given beat, or null.\n * @param key Key context for leading-tone detection: a single {@link KeyScale}\n * covering the whole voice, or a callback giving the key in force at a given\n * beat, for music that modulates.\n * @param otherVoicesAtBeat Other sounding voices at a given beat; defaults to\n * none, which is the whole story for a solo line.\n * @returns One annotation per input note.\n * @example\n * ```ts\n * import { analyzeVoice, makeChord, majorKey } from '@libraz/libcantus';\n * const voice = [\n * { pitch: 60, startBeat: 0, durationBeat: 1 },\n * { pitch: 62, startBeat: 1, durationBeat: 1 },\n * { pitch: 64, startBeat: 2, durationBeat: 1 },\n * ];\n * const cMajor = makeChord(0, 'maj');\n * const labels = analyzeVoice(voice, () => cMajor, majorKey(0));\n * labels; // one AnalyzedNote per input note, in the same order\n * // A voice that modulates to C major at beat 2:\n * analyzeVoice(voice, () => cMajor, (beat) => (beat < 2 ? majorKey(5) : majorKey(0)));\n * ```\n * @category Arrangement & Analysis\n */\nexport function analyzeVoice(\n voice: readonly VoiceNote[],\n chordAtBeat: (beat: number) => Chord | null,\n key: KeyContext,\n otherVoicesAtBeat: (beat: number) => VoiceSnapshot[] = () => [],\n): AnalyzedNote[] {\n assertNoteEvents(voice, 'voice notes', { allowNonPositiveDuration: true });\n // A KeyScale is a plain object, so a callable value can only be the per-beat\n // form; normalising here keeps the classification below beat-oriented.\n const keyAt: (beat: number) => KeyScale = typeof key === 'function' ? key : () => key;\n const result: AnalyzedNote[] = [];\n\n for (let i = 0; i < voice.length; i += 1) {\n const note = voice[i];\n if (!note) {\n continue;\n }\n const prevNote = i > 0 ? voice[i - 1] : undefined;\n const nextNote = i + 1 < voice.length ? voice[i + 1] : undefined;\n // A neighboring note sharing this note's onset is a simultaneous cluster\n // member, not a melodic predecessor/successor; treating it as one would\n // fabricate passing/suspension figures, so it is dropped here.\n const prevEnd =\n prevNote === undefined\n ? Number.NEGATIVE_INFINITY\n : prevNote.startBeat + prevNote.durationBeat;\n const noteEnd = note.startBeat + note.durationBeat;\n const prev =\n prevNote !== undefined && Math.abs(prevEnd - note.startBeat) <= HUMANIZE_ADJACENCY\n ? prevNote\n : undefined;\n const next =\n nextNote !== undefined && Math.abs(noteEnd - nextNote.startBeat) <= HUMANIZE_ADJACENCY\n ? nextNote\n : undefined;\n const chord = chordAtBeat(note.startBeat);\n const labels: TheoryLabel[] = [];\n const member = isChordMember(note.pitch, chord);\n let handled = false;\n\n if (chord && member) {\n const role = chordToneRole(note.pitch, chord);\n const degree =\n role === null ? tensionDegree(intervalAboveRoot(note.pitch, chord)) : undefined;\n if (role) {\n labels.push({ kind: 'chordTone', role });\n handled = true;\n } else if (degree !== undefined) {\n labels.push({ kind: 'tension', degree });\n handled = true;\n }\n }\n\n if (!handled && chord && prev && next) {\n const prevChord = chordAtBeat(prev.startBeat);\n const prepared = prev.pitch === note.pitch && isChordMember(prev.pitch, prevChord);\n const resolves = isStep(next.pitch, note.pitch);\n const others = otherVoicesAtBeat(note.startBeat);\n const verticallyDissonant = others.some((ov) =>\n createsVerticalDissonance(note.pitch, ov.pitch, true),\n );\n if (prepared && verticallyDissonant && resolves) {\n const ic = intervalAboveBass(note.pitch, others, chord);\n const type = suspensionType(ic, next.pitch - note.pitch);\n if (type !== null) {\n labels.push({ kind: 'suspension', type, resolveTo: next.pitch });\n handled = true;\n }\n }\n }\n\n if (!handled && chord && prev && next) {\n const prevMember = isChordMember(prev.pitch, chordAtBeat(prev.startBeat));\n const nextMember = isChordMember(next.pitch, chordAtBeat(next.startBeat));\n const up = note.pitch - prev.pitch > 0;\n const contInto = next.pitch - note.pitch > 0;\n const sameDir = note.pitch !== prev.pitch && next.pitch !== note.pitch && up === contInto;\n if (\n prevMember &&\n nextMember &&\n !member &&\n isStep(note.pitch, prev.pitch) &&\n isStep(next.pitch, note.pitch) &&\n sameDir\n ) {\n labels.push({ kind: 'passing' });\n handled = true;\n }\n }\n\n if (\n !handled &&\n chord &&\n prev &&\n next &&\n !member &&\n prev.pitch === next.pitch &&\n isChordMember(prev.pitch, chordAtBeat(prev.startBeat)) &&\n isStep(note.pitch, prev.pitch)\n ) {\n labels.push({ kind: 'neighbor' });\n handled = true;\n }\n\n if (!handled && chord && next && !member && note.pitch === next.pitch) {\n const nextChord = chordAtBeat(next.startBeat);\n if (isChordMember(next.pitch, nextChord)) {\n labels.push({ kind: 'anticipation' });\n handled = true;\n }\n }\n\n // The appoggiatura and the escape tone are each other's mirror: one leans in\n // by leap and gives way by step, the other steps out of the harmony and\n // leaves by leap. Both turn back on themselves, which is what separates them\n // from a passing tone travelling the same way throughout.\n if (!handled && chord && prev && next && !member) {\n const prevMember = isChordMember(prev.pitch, chordAtBeat(prev.startBeat));\n const nextMember = isChordMember(next.pitch, chordAtBeat(next.startBeat));\n const leapFromPrev = Math.abs(note.pitch - prev.pitch) >= LEAP_SEMITONES;\n const stepToNext = isStep(next.pitch, note.pitch);\n const opposite = note.pitch - prev.pitch > 0 !== next.pitch - note.pitch > 0;\n if (prevMember && nextMember && leapFromPrev && stepToNext && opposite) {\n labels.push({ kind: 'appoggiatura', resolveTo: next.pitch });\n handled = true;\n }\n }\n\n if (!handled && chord && prev && next && !member) {\n const prevMember = isChordMember(prev.pitch, chordAtBeat(prev.startBeat));\n const nextMember = isChordMember(next.pitch, chordAtBeat(next.startBeat));\n const stepFromPrev = isStep(note.pitch, prev.pitch);\n const leapToNext = Math.abs(next.pitch - note.pitch) >= LEAP_SEMITONES;\n const opposite = note.pitch - prev.pitch > 0 !== next.pitch - note.pitch > 0;\n if (prevMember && nextMember && stepFromPrev && leapToNext && opposite) {\n labels.push({ kind: 'escape' });\n handled = true;\n }\n }\n\n if (!handled && chord && !member) {\n const ic = intervalAboveRoot(note.pitch, chord);\n const degree = tensionDegree(ic);\n const isTension = ic === 2 || ic === 5 || ic === 9;\n const avoid =\n (ic === 5 && chord.intervals.includes(4) && !chord.intervals.includes(5)) ||\n (ic === 11 && chord.intervals.includes(4) && chord.intervals.includes(10));\n if (avoid) {\n labels.push({ kind: 'avoid' });\n const resolveTo = nearestChordTone(note.pitch, chord, STEP_RESOLUTION);\n if (resolveTo !== undefined) {\n labels.push({ kind: 'needsResolution', resolveTo });\n }\n } else if (isTension && degree !== undefined) {\n labels.push({ kind: 'tension', degree });\n } else {\n // Every note sounding against a chord is classified, so this branch\n // labels the dissonance whether or not it has a stepwise exit: an empty\n // label list would be indistinguishable from a consonance.\n const resolveTo = nearestChordTone(note.pitch, chord, NEAREST_CHORD_TONE_REACH);\n labels.push(\n resolveTo === undefined ? { kind: 'avoid' } : { kind: 'needsResolution', resolveTo },\n );\n }\n }\n\n // A leading tone is only one because of the tonic it arrives on, so the\n // key is read at the beat of that arrival rather than at this note's own\n // beat: a modulation places its key change on the new tonic, and the\n // leading tone that carries the voice into it still sits in the old key.\n if (next && isLeadingToneResolution(note.pitch, next.pitch, keyAt(next.startBeat))) {\n labels.push({ kind: 'leadingTone', resolveTo: next.pitch });\n }\n\n const analyzed: AnalyzedNote = {\n noteId: note.id ?? i,\n pitch: note.pitch,\n startBeat: note.startBeat,\n durationBeat: note.durationBeat,\n labels,\n rationale: describe(labels),\n };\n if (note.originalIndex !== undefined) {\n analyzed.originalIndex = note.originalIndex;\n }\n result.push(analyzed);\n }\n\n return result;\n}\n\n/** Build a short rationale from a note's primary label. */\nfunction describe(labels: TheoryLabel[]): string {\n const primary = labels[0];\n if (!primary) {\n return 'Unclassified note';\n }\n switch (primary.kind) {\n case 'chordTone':\n return `Chord ${primary.role}`;\n case 'tension':\n return `${primary.degree}th tension`;\n case 'avoid':\n return 'Avoid note';\n case 'passing':\n return 'Passing tone between chord tones';\n case 'neighbor':\n return 'Neighbor tone';\n case 'suspension':\n return `Suspension (${primary.type})`;\n case 'appoggiatura':\n return 'Appoggiatura leaning on the harmony, resolving by step';\n case 'anticipation':\n return 'Anticipation of the next chord';\n case 'escape':\n return 'Escape tone';\n case 'needsResolution':\n return 'Unresolved dissonance';\n case 'leadingTone':\n return 'Leading tone resolving to the tonic';\n }\n}\n","/**\n * Shared helpers for the arrangement-analysis modules: pooling every track's\n * sounding notes and partitioning a track into monophonic sub-voices, plus the\n * small geometric primitives both the track-role and tension passes rely on.\n */\n\nimport { InvalidInputError } from '../../core/errors/index.js';\nimport type { NoteEvent } from '../../core/types.js';\nimport type { NoteEventAssertOptions } from '../../core/validation/index.js';\nimport { assertNoteEvents, assertOneOf } from '../../core/validation/index.js';\nimport { PROFILE_WEIGHTS, type SafetyProfile } from '../../theory/safety/index.js';\nimport type { IdentifiedVoiceNote } from '../voice/index.js';\nimport type { ArrangementTrack, TrackRole } from './tracks.js';\n\n/** Float tolerance for beat boundary comparisons. */\nexport const EPS = 1e-9;\n\n/**\n * Largest interval, in semitones, a sub-voice will span between consecutive\n * notes. Beyond two octaves the connection is not a melodic step or leap but an\n * artefact of lane packing, so a new sub-voice is opened instead.\n */\nconst MAX_LANE_LEAP = 24;\n\n/** A note with the pitch that immediately preceded it in the same sub-voice. */\nexport type PreparedNote = {\n pitch: number;\n prevPitch?: number;\n startBeat: number;\n endBeat: number;\n};\n\n/** One monophonic sub-voice of a track: ordered notes plus sounding spans. */\nexport type PreparedVoice = {\n /** The sub-voice's notes in time order, each with a stable id. */\n voice: IdentifiedVoiceNote[];\n /** The same notes as sounding spans, each carrying its predecessor's pitch. */\n sounding: PreparedNote[];\n /**\n * The span sounding at a beat, or undefined. A sub-voice is monophonic by\n * construction, so at most one span covers any beat and the lookup is a\n * single binary search rather than a scan.\n */\n at: (beat: number) => PreparedNote | undefined;\n};\n\n/** A track prepared for analysis: resolved metadata plus monophonic sub-voices. */\nexport type PreparedTrack = {\n name: string;\n role: TrackRole;\n /** The track's index in the caller's input array. */\n trackIndex: number;\n /**\n * The track's notes partitioned into monophonic sub-voices. Voice analysis\n * assumes one note at a time, so a polyphonic track (e.g. a 'harmony' track\n * playing block chords) is split here; a monophonic track yields exactly one\n * sub-voice.\n */\n voices: PreparedVoice[];\n};\n\n/** Whether a sounding span `[start, end)` covers a beat. */\nexport function covers(note: PreparedNote, beat: number): boolean {\n return beat >= note.startBeat - EPS && beat < note.endBeat - EPS;\n}\n\n/** Whether a track's role means it carries no harmonic content. */\nexport function isPercussion(role: TrackRole | undefined): boolean {\n return role === 'drums';\n}\n\n/** The safety profile an arrangement is judged under when the caller names none. */\nconst DEFAULT_PROFILE: SafetyProfile = 'pop';\n\n/** The profiles that exist, read off the table rather than listed again here. */\nconst SAFETY_PROFILES = Object.keys(PROFILE_WEIGHTS) as SafetyProfile[];\n\n/**\n * The safety profile an arrangement is judged under, checked against the\n * profiles that exist.\n *\n * A name outside the table is an input error rather than a quiet fall back to\n * `pop`, which would read as a verdict about the music instead of as the\n * misspelling it is. Both entry points resolve it here so they cannot disagree\n * about which names are taken.\n *\n * @param profile The caller's profile, if any.\n * @returns The profile to judge under.\n * @throws If the profile names no known profile.\n */\nexport function arrangementProfile(profile: SafetyProfile | undefined): SafetyProfile {\n return assertOneOf(profile ?? DEFAULT_PROFILE, SAFETY_PROFILES, 'arrangement profile');\n}\n\n/**\n * Validate the note array of every track, naming the track that is wrong.\n *\n * A missing or non-array `notes` is rejected here rather than becoming a raw\n * `TypeError` further down, where the message no longer says which track. Every\n * entry point that takes arrangement tracks checks them this way, so the same\n * malformed input is refused with the same message whichever one is called.\n *\n * @param tracks The tracks as the caller passed them.\n * @param options The note-event assertion options this entry point works under.\n * @returns How many notes the tracks hold in total.\n * @throws If a track's `notes` is not an array, or holds a malformed event.\n */\nexport function assertTrackNotes(\n tracks: readonly ArrangementTrack[],\n options: NoteEventAssertOptions,\n): number {\n let noteCount = 0;\n for (let index = 0; index < tracks.length; index += 1) {\n const notes = tracks[index]?.notes;\n if (!Array.isArray(notes)) {\n throw new InvalidInputError(\n `tracks[${index}].notes must be an array; received ${typeof notes}`,\n );\n }\n assertNoteEvents(notes, `tracks[${index}].notes`, options);\n noteCount += notes.length;\n }\n return noteCount;\n}\n\n/**\n * The tracks the harmony is inferred from, as a set of indices checked against\n * the tracks that exist.\n *\n * An index naming no track cannot be honoured, and matching nothing quietly\n * turns a stale index — the ordinary result of deleting a track — into an\n * analysis with no harmony at all, reported as though the arrangement were at\n * fault. The session's `trackIndex` is checked the same way.\n *\n * @param harmonyTracks The caller's indices, if any.\n * @param trackCount How many tracks were passed in.\n * @param name What the indices belong to, for the error message.\n * @returns The indices as a set, or undefined when the caller named none.\n * @throws If an index is not finite or names no track.\n */\nexport function harmonyTrackSet(\n harmonyTracks: readonly number[] | undefined,\n trackCount: number,\n name = 'harmonyTracks',\n): ReadonlySet<number> | undefined {\n if (harmonyTracks === undefined) {\n return undefined;\n }\n const only = new Set<number>();\n for (let index = 0; index < harmonyTracks.length; index += 1) {\n const value = harmonyTracks[index];\n if (value === undefined || !Number.isFinite(value)) {\n throw new InvalidInputError(`${name}[${index}] must be a track index; received ${value}`);\n }\n const track = Math.trunc(value);\n if (track < 0 || track >= trackCount) {\n throw new InvalidInputError(\n `${name}[${index}] ${track} is outside the arrangement's ${trackCount} tracks`,\n );\n }\n only.add(track);\n }\n return only;\n}\n\n/**\n * Every sounding note of every pitched track pooled into one flat list. Zero-\n * and negative-length notes never sound, so they are dropped here (matching the\n * chord-inference ingest in the timeline module), and percussion tracks are\n * skipped entirely: their pitches are instrument selections, not harmony.\n */\nexport function poolNotes(tracks: ArrangementTrack[], only?: ReadonlySet<number>): NoteEvent[] {\n const notes: NoteEvent[] = [];\n for (let index = 0; index < tracks.length; index += 1) {\n const track = tracks[index];\n if (track === undefined || isPercussion(track.role)) {\n continue;\n }\n if (only !== undefined && !only.has(index)) {\n continue;\n }\n for (const note of track.notes) {\n if (note.durationBeat > 0) {\n notes.push(note);\n }\n }\n }\n return notes;\n}\n\n/**\n * Split a track's ordered notes into monophonic sub-voices.\n *\n * Each note joins the free sub-voice — one whose last note has ended — whose\n * last pitch is nearest its own, so a track whose voices cross keeps each voice\n * on its own lane instead of handing a low note to whichever lane happened to\n * be created first. Ties go to the lane that ended latest, which is the one the\n * note actually follows. A connection wider than {@link MAX_LANE_LEAP} is not a\n * melodic one, so it opens a new sub-voice instead.\n *\n * The predecessor pitch this produces feeds leap and parallel detection, so a\n * mis-assigned lane invents voice-leading faults that are not in the music.\n */\nfunction splitIntoSubVoices(ordered: IdentifiedVoiceNote[]): IdentifiedVoiceNote[][] {\n const lanes: IdentifiedVoiceNote[][] = [];\n const attachNearest = (note: IdentifiedVoiceNote, excluded = new Set<number>()): boolean => {\n let best = -1;\n let bestDistance = Number.POSITIVE_INFINITY;\n let bestEnd = Number.NEGATIVE_INFINITY;\n for (let lane = 0; lane < lanes.length; lane += 1) {\n if (excluded.has(lane)) {\n continue;\n }\n const last = lanes[lane]?.[(lanes[lane]?.length ?? 0) - 1];\n if (last === undefined) {\n continue;\n }\n const end = last.startBeat + last.durationBeat;\n if (end > note.startBeat + EPS) {\n continue; // still sounding, so the lane is not free\n }\n const distance = Math.abs(last.pitch - note.pitch);\n if (distance > MAX_LANE_LEAP) {\n continue;\n }\n if (distance < bestDistance || (distance === bestDistance && end > bestEnd)) {\n best = lane;\n bestDistance = distance;\n bestEnd = end;\n }\n }\n if (best >= 0) {\n lanes[best]?.push(note);\n return true;\n }\n return false;\n };\n\n for (let start = 0; start < ordered.length; ) {\n const onset = ordered[start]?.startBeat ?? 0;\n let end = start + 1;\n while (end < ordered.length && Math.abs((ordered[end]?.startBeat ?? 0) - onset) <= EPS) {\n end += 1;\n }\n const block = ordered.slice(start, end);\n // Notes struck together form a vertical slice, not a sequence of greedy\n // nearest-neighbour decisions. Pair their low-to-high order with the\n // already-free lanes' low-to-high order so parallel block chords cannot\n // swap lanes and manufacture a crossing on the following beat.\n if (block.length > 1) {\n const freeLanes = lanes\n .map((lane, index) => ({ index, last: lane[lane.length - 1] }))\n .filter(\n (entry): entry is { index: number; last: IdentifiedVoiceNote } =>\n entry.last !== undefined &&\n entry.last.startBeat + entry.last.durationBeat <= onset + EPS,\n )\n .sort((a, b) => a.last.pitch - b.last.pitch);\n const paired = new Set<number>();\n for (let index = 0; index < block.length; index += 1) {\n const note = block[index];\n const lane = freeLanes[index];\n if (\n note !== undefined &&\n lane !== undefined &&\n Math.abs(lane.last.pitch - note.pitch) <= MAX_LANE_LEAP\n ) {\n lanes[lane.index]?.push(note);\n paired.add(lane.index);\n } else if (note !== undefined && !attachNearest(note, paired)) {\n lanes.push([note]);\n }\n }\n } else {\n const note = block[0];\n if (note !== undefined && !attachNearest(note)) {\n lanes.push([note]);\n }\n }\n start = end;\n }\n return lanes;\n}\n\n/** Index of the last span starting at or before a beat, or -1. */\nfunction lastStartingAtOrBefore(sounding: PreparedNote[], beat: number): number {\n let low = 0;\n let high = sounding.length;\n while (low < high) {\n const middle = Math.floor((low + high) / 2);\n if ((sounding[middle]?.startBeat ?? Number.POSITIVE_INFINITY) <= beat + EPS) {\n low = middle + 1;\n } else {\n high = middle;\n }\n }\n return low - 1;\n}\n\n/**\n * Resolve each track's metadata and derive its monophonic sub-voices.\n *\n * Notes are sorted by onset, then pitch, so ordering is deterministic, and ids\n * are assigned in that order; each note keeps the index it had in the caller's\n * array so annotations can be mapped back. Zero- and negative-length notes never\n * sound and are dropped at ingest. Each track is then partitioned into\n * monophonic sub-voices (see {@link splitIntoSubVoices}); the predecessor pitch\n * used for suspension and parallel detection is the previous note of the same\n * sub-voice, never a simultaneous chord member.\n */\nexport function prepareTracks(tracks: ArrangementTrack[]): PreparedTrack[] {\n let nextId = 0;\n return tracks.map((track, index) => {\n const ordered: IdentifiedVoiceNote[] = track.notes\n .map((note, originalIndex) => ({ note, originalIndex }))\n .filter(({ note }) => note.durationBeat > 0)\n .sort((a, b) => a.note.startBeat - b.note.startBeat || a.note.pitch - b.note.pitch)\n .map(({ note, originalIndex }) => ({ ...note, id: nextId++, originalIndex }));\n const voices: PreparedVoice[] = splitIntoSubVoices(ordered).map((voice) => {\n const sounding: PreparedNote[] = voice.map((note, i) => {\n const prev = i > 0 ? voice[i - 1] : undefined;\n const prevEnd = prev === undefined ? undefined : prev.startBeat + prev.durationBeat;\n const contiguous = prevEnd !== undefined && Math.abs(prevEnd - note.startBeat) <= EPS;\n return {\n pitch: note.pitch,\n prevPitch: contiguous ? prev?.pitch : undefined,\n startBeat: note.startBeat,\n endBeat: note.startBeat + note.durationBeat,\n };\n });\n return {\n voice,\n sounding,\n at: (beat: number) => {\n const candidate = sounding[lastStartingAtOrBefore(sounding, beat)];\n return candidate !== undefined && covers(candidate, beat) ? candidate : undefined;\n },\n };\n });\n return {\n name: track.name ?? `track ${index + 1}`,\n role: track.role ?? 'other',\n trackIndex: index,\n voices,\n };\n });\n}\n","/**\n * Arrangement track-role analysis: a single harmony is inferred from every\n * track's notes pooled together, each track is then analysed against that shared\n * harmony, and notes that clash with the sounding chord are collected as\n * conflicts.\n */\n\nimport type { MeterMap, TimeSignature } from '../../core/meter/index.js';\nimport { isStrongBeat, resolveMeters } from '../../core/meter/index.js';\nimport type { KeyScale, NoteEvent } from '../../core/types.js';\nimport type { NoteEventAssertOptions } from '../../core/validation/index.js';\nimport { assertGenerationBudget, assertRange } from '../../core/validation/index.js';\nimport {\n evaluateSafety,\n NoteSafety,\n type SafetyProfile,\n type VoiceSnapshot,\n} from '../../theory/safety/index.js';\nimport { majorKey } from '../../theory/scale/index.js';\nimport {\n attachPivots,\n type KeyRegion,\n keyLookup,\n keyTimelineFromNotes,\n prevailingKeyOf,\n} from '../keys/index.js';\nimport {\n analyzeTimeline,\n type CadenceHit,\n type ChordTimeline,\n type ChordTimelineResult,\n type DirtySlots,\n detectCadences,\n type TimelineEvidence,\n} from '../timeline/index.js';\nimport {\n type AnalyzedNote,\n analyzeVoice,\n type TheoryLabel,\n type VoiceNote,\n} from '../voice/index.js';\nimport {\n arrangementProfile,\n assertTrackNotes,\n EPS,\n harmonyTrackSet,\n isPercussion,\n type PreparedTrack,\n poolNotes,\n prepareTracks,\n} from './internal.js';\n\n/**\n * The musical role a track plays in the arrangement.\n *\n * Only `drums` changes the analysis: its pitches select instruments rather than\n * naming harmony, so such a track is excluded from chord and key inference and\n * from every voice-leading comparison, and is reported back with no\n * annotations. `harmony` is meaningful through\n * {@link ArrangementOptions.harmonyTracks}, which names the tracks the chords\n * are inferred from. The rest are labels carried through to\n * {@link TrackAnalysis.role} for the caller's own use; nothing is inferred, so\n * a track left unlabelled reports `'other'` rather than a guess.\n *\n * @category Arrangement & Analysis\n */\nexport type TrackRole = 'melody' | 'harmony' | 'bass' | 'drums' | 'other';\n\n/**\n * One input track: its notes plus optional name and role.\n *\n * @category Arrangement & Analysis\n */\nexport type ArrangementTrack = {\n name?: string;\n role?: TrackRole;\n notes: readonly NoteEvent[];\n};\n\n/**\n * A track after analysis: its resolved name, role, and per-note annotations.\n *\n * @category Arrangement & Analysis\n */\nexport type TrackAnalysis = {\n name: string;\n role: TrackRole;\n /** One {@link AnalyzedNote} per note, labelled against the inferred chords. */\n notes: AnalyzedNote[];\n};\n\n/**\n * A note that clashes with the harmony sounding beneath it.\n *\n * @category Arrangement & Analysis\n */\nexport type Conflict = {\n beat: number;\n trackName: string;\n /** The clashing track's index in the caller's input array. */\n trackIndex: number;\n /** The note's index in that track's own note array. */\n originalIndex?: number;\n /** The id the note carries in {@link TrackAnalysis.notes}. */\n noteId: number;\n pitch: number;\n /**\n * The note's theory labels at its onset, so a caller can tell a genuine clash\n * from a passing tone, a neighbour, or a prepared suspension — all of which\n * are ordinary non-chord tones that still evaluate as unsafe.\n */\n labels: TheoryLabel[];\n /** The clash severity: {@link NoteSafety.Warning} or {@link NoteSafety.Dissonant}. */\n safety: NoteSafety;\n /** {@link SafetyResult.reasons} bitmask. */\n reasons: number;\n /** Preferred stepwise resolution, when the safety evaluator provides one. */\n resolveTo?: number;\n /** Nearby fully safe replacement pitches, nearest first. */\n suggestions?: number[];\n rationale?: string;\n};\n\n/**\n * Override an inferred result's key regions with ones the caller already had.\n *\n * The chords stay as inferred: they were found against the caller's regions\n * only if the caller also passed them down, and quietly re-deriving them here\n * would make the same options produce different chords depending on which pass\n * ran first.\n */\nfunction withGivenKeys(\n result: ChordTimelineResult,\n given: KeyRegion[] | undefined,\n): ChordTimelineResult {\n if (given === undefined) {\n return result;\n }\n return { ...result, keys: given, prevailingKey: prevailingKeyOf(given) ?? result.prevailingKey };\n}\n\n/**\n * Build the same result shape from a timeline the caller already had.\n *\n * The timeline was not inferred from these notes, so it carries no measured\n * confidence — reporting 1 would turn every confidence gate into an\n * unconditional pass. The key regions still come from the notes, because the\n * caller supplied chords, not an answer about the key; they are read against the\n * whole meter map and annotated with their pivots, exactly as the inferring path\n * reads and annotates its own.\n */\nfunction callerTimeline(\n timeline: ChordTimeline,\n key: KeyScale | undefined,\n given: KeyRegion[] | undefined,\n pooled: NoteEvent[],\n meters: MeterMap,\n budget: number | undefined,\n): {\n timeline: ChordTimeline;\n keys: KeyRegion[];\n prevailingKey: KeyScale;\n segmentConfidence: number[];\n} {\n const totalBeats = timeline.segments.reduce((end, segment) => Math.max(end, segment.endBeat), 0);\n // Caller-supplied regions are the caller's answer and are passed through as\n // they are; the ones derived here are annotated like any other, since the\n // chords a pivot is read from are in hand either way.\n const keys =\n given ??\n attachPivots(\n key !== undefined\n ? [{ startBeat: 0, endBeat: totalBeats, key, confidence: 1 }]\n : keyTimelineFromNotes(pooled, { meters, totalBeats, budget }),\n timeline.segments,\n );\n return {\n timeline,\n keys,\n prevailingKey: prevailingKeyOf(keys) ?? majorKey(0),\n segmentConfidence: timeline.segments.map(() => 0),\n };\n}\n\n/**\n * The full result of {@link analyzeArrangement}.\n *\n * @category Arrangement & Analysis\n */\nexport type ArrangementAnalysis = {\n /**\n * The key regions the analysis ran against, in time order. Roman numerals,\n * harmonic function and note safety are all judged against the region\n * covering the beat, so a piece that modulates is not read against the key it\n * started in for every bar after it leaves.\n */\n keys: KeyRegion[];\n /** The key held longest across {@link ArrangementAnalysis.keys}. */\n prevailingKey: KeyScale;\n timeline: ChordTimeline;\n segmentConfidence: number[];\n cadences: CadenceHit[];\n tracks: TrackAnalysis[];\n /** Notes clashing with the sounding harmony, worst severity first. */\n conflicts: Conflict[];\n};\n\n/**\n * Options controlling {@link analyzeArrangement} and {@link tensionCurve}.\n *\n * @category Arrangement & Analysis\n */\nexport type ArrangementOptions = {\n /**\n * A single key held across the whole arrangement. Omit it, and `keys` with\n * it, to have the key searched for over time so a modulating piece is read\n * against the key actually in force.\n */\n key?: KeyScale;\n /**\n * Key regions to judge against, when a previous pass already worked them out.\n * Takes precedence over `key`; supplying both is answering the same question\n * twice, and the more specific answer wins.\n */\n keys?: KeyRegion[];\n /**\n * A chord timeline to analyse against, instead of inferring one from the\n * notes. Supply it to analyse a hand-written progression, or to reuse the\n * timeline of an earlier call rather than re-running chord inference.\n */\n timeline?: ChordTimeline;\n /**\n * Indices of the tracks the harmony is inferred from. Defaults to every\n * pitched track. Percussion tracks are excluded either way. An index naming\n * no track is an input error rather than one that matches nothing.\n */\n harmonyTracks?: number[];\n /**\n * Lowest severity reported in `conflicts`.\n *\n * @defaultValue {@link NoteSafety.Warning}\n */\n minSeverity?: NoteSafety;\n /**\n * A single time signature held across the whole arrangement, as sugar for a\n * one-element `meters`; defaults to 4/4. Giving both is an input error.\n *\n * @defaultValue `4/4`\n */\n ts?: TimeSignature;\n /**\n * The meter as it changes over the arrangement. Bar lines, downbeats and\n * metric weight all follow the signature in force at the beat in question.\n *\n * @defaultValue 4/4 throughout\n */\n meters?: MeterMap;\n /**\n * Length of the pickup in beats, when the piece starts with one.\n *\n * The first downbeat is beat 0, so an upbeat is written at negative beats.\n * Declaring its length rejects a note that starts before the pickup does;\n * leave it unset to accept any finite onset.\n *\n * @defaultValue no declared pickup\n */\n pickupBeats?: number;\n /**\n * Chord-slot length in beats; defaults to the length of the opening bar.\n *\n * @defaultValue the length of the opening bar\n */\n harmonicRhythm?: number;\n /**\n * Safety profile used for conflict detection; defaults to `pop`.\n *\n * @defaultValue `'pop'`\n */\n profile?: SafetyProfile;\n /**\n * Upper bound on the work this call may do — note counts, windows, and\n * candidate counts are each checked against it before anything is allocated.\n *\n * Raise it to analyse a piece larger than the default allows; the default is\n * {@link DEFAULT_GENERATION_BUDGET}, chosen so a runaway input fails fast\n * rather than blocking the thread.\n *\n * @defaultValue {@link DEFAULT_GENERATION_BUDGET}\n */\n budget?: number;\n};\n\n/**\n * Sounding pitches of every sub-voice at a beat, excluding one sub-voice.\n *\n * Only the sub-voice under analysis is excluded — sibling sub-voices of the\n * same track are included, so dissonant clusters inside a single polyphonic\n * track are still detected.\n */\ntype SoundingVoice = {\n track: number;\n voice: number;\n snapshot: VoiceSnapshot;\n};\n\n/** Build one complete arrangement snapshot for an evaluation beat. */\nfunction soundingVoicesAt(prepared: PreparedTrack[], beat: number): SoundingVoice[] {\n const out: SoundingVoice[] = [];\n for (let t = 0; t < prepared.length; t += 1) {\n const track = prepared[t];\n if (!track || isPercussion(track.role)) {\n continue;\n }\n for (let v = 0; v < track.voices.length; v += 1) {\n // Each sub-voice is monophonic, so this is one binary search per voice\n // rather than a scan of every note in the arrangement.\n const note = track.voices[v]?.at(beat);\n if (note === undefined) {\n continue;\n }\n const snap: VoiceSnapshot = { pitch: note.pitch };\n // Motion reasons compare one real transition shared by both voices.\n // A voice attacking exactly here contributes its adjacent predecessor;\n // a sustained voice contributes the same pitch (oblique motion).\n const previous = Math.abs(note.startBeat - beat) <= EPS ? note.prevPitch : note.pitch;\n if (previous !== undefined) {\n snap.prevPitch = previous;\n }\n out.push({ track: t, voice: v, snapshot: snap });\n }\n }\n return out;\n}\n\n/** Get every simultaneous voice except the one being evaluated. */\nfunction otherVoicesSounding(\n prepared: PreparedTrack[],\n excludeTrack: number,\n excludeVoice: number,\n beat: number,\n cache: Map<number, SoundingVoice[]>,\n): VoiceSnapshot[] {\n const sounding = cache.get(beat) ?? soundingVoicesAt(prepared, beat);\n cache.set(beat, sounding);\n return sounding\n .filter(({ track, voice }) => track !== excludeTrack || voice !== excludeVoice)\n .map(({ snapshot }) => snapshot);\n}\n\n/** Index of the first segment starting strictly after a beat. */\nfunction firstSegmentAfter(timeline: ChordTimeline, beat: number): number {\n let low = 0;\n let high = timeline.segments.length;\n while (low < high) {\n const middle = Math.floor((low + high) / 2);\n if ((timeline.segments[middle]?.startBeat ?? Number.POSITIVE_INFINITY) <= beat + EPS) {\n low = middle + 1;\n } else {\n high = middle;\n }\n }\n return low;\n}\n\n/**\n * The beats at which a note must be checked against the harmony: its onset,\n * plus the onset of every later chord segment the note sustains across. Each\n * crossed segment is visited exactly once (at its own start), so a boundary\n * beat is never evaluated twice for the same note.\n *\n * Segments are in beat order, so the crossed ones are a contiguous run found by\n * binary search rather than by scanning the whole timeline for every note.\n */\nfunction evaluationBeats(note: VoiceNote, timeline: ChordTimeline): number[] {\n const noteEnd = note.startBeat + note.durationBeat;\n const beats = [note.startBeat];\n for (let i = firstSegmentAfter(timeline, note.startBeat); i < timeline.segments.length; i += 1) {\n const start = timeline.segments[i]?.startBeat;\n if (start === undefined || start >= noteEnd - EPS) {\n break;\n }\n beats.push(start);\n }\n return beats;\n}\n\n/**\n * Analyse a whole arrangement against the harmony inferred from it.\n *\n * The chord timeline and the key regions it is read against are inferred from\n * the pooled notes of every pitched track (see\n * {@link chordTimelineFromNotes}); pooling all voices is\n * robust even when roles are absent or a track doubles the harmony, so it is\n * preferred over deriving the harmony from a subset. A track marked\n * `role: 'drums'` is excluded — its pitches name instruments, not harmony, and\n * pooling them would corrupt the key, the timeline and every reading built on\n * them. Pass `harmonyTracks` to restrict inference further, or `timeline` to\n * analyse against a progression you already have. Zero- and negative-length\n * notes never sound, so they are dropped at ingest and appear in neither the\n * annotations nor the conflicts.\n *\n * Voice analysis assumes one monophonic voice at a time, so each track is\n * first partitioned into monophonic sub-voices (a polyphonic block-chord track\n * fans out into one sub-voice per simultaneous note; a monophonic track is\n * unaffected). Each sub-voice is labelled note-by-note against the harmony\n * with {@link analyzeVoice}, seeing every other sub-voice — including siblings\n * within the same track — as its accompaniment. Every note is then re-checked\n * with {@link evaluateSafety} at its onset and again at each chord change it\n * sustains across, so a held note that clashes with a later chord is caught:\n * evaluations that are not {@link NoteSafety.Safe} become conflicts, one per\n * clashing beat, sorted worst severity first and then by beat.\n *\n * Every ordinary non-chord tone — a passing note, a neighbour, a prepared\n * suspension — is by definition unsafe against the chord under it, so the\n * conflict list is a report, not a fault list. Each conflict carries the note's\n * `labels` and its `trackIndex`/`originalIndex`, so a caller can tell those\n * apart and map a conflict back to the note it passed in; `minSeverity` narrows\n * the list to outright dissonance.\n *\n * @param tracks The tracks to analyse.\n * @param opts Analysis options; see {@link ArrangementOptions}.\n * @returns The inferred harmony, per-track annotations, cadences, and conflicts.\n * @example\n * ```ts\n * import { analyzeArrangement } from '@libraz/libcantus';\n * const melody = [\n * { pitch: 60, startBeat: 0, durationBeat: 2 },\n * { pitch: 67, startBeat: 2, durationBeat: 2 },\n * ];\n * const { prevailingKey, conflicts } = analyzeArrangement([{ role: 'melody', notes: melody }]);\n * prevailingKey.rootPc; // the key held longest across the analysis\n * conflicts; // notes clashing with the inferred harmony, worst severity first\n * ```\n * @category Arrangement & Analysis\n */\nexport function analyzeArrangement(\n tracks: ArrangementTrack[],\n opts: ArrangementOptions = {},\n): ArrangementAnalysis {\n return analyzeArrangementWith(tracks, opts).analysis;\n}\n\n/**\n * Analyse an arrangement, carrying over the harmonic evidence an edit cannot\n * reach and handing back the evidence this pass leaves behind.\n *\n * Only the harmony is carried over. The per-note annotation pass is re-run in\n * full because neither half of it is local to the edit: sub-voice lane\n * assignment is a greedy left-to-right pass over the whole track, so an inserted\n * note can re-lane every note after it, and every note is evaluated against the\n * other voices sounding beneath it, so an edit in one track changes what every\n * other track hears at those beats.\n *\n * Not part of the public surface: the evidence is the private state of an\n * incremental re-analysis, and a caller holding it could pair it with notes it\n * was never derived from.\n *\n * @param tracks The tracks to analyse.\n * @param opts Analysis options.\n * @param previous Evidence from the analysis being updated, when there is one.\n * @param dirty The slots the edit may have changed.\n * @returns The analysis, and the evidence behind it when the harmony was\n * inferred rather than supplied.\n */\nexport function analyzeArrangementWith(\n tracks: ArrangementTrack[],\n opts: ArrangementOptions = {},\n previous?: TimelineEvidence,\n dirty?: DirtySlots,\n): { analysis: ArrangementAnalysis; evidence?: TimelineEvidence } {\n const meters = resolveMeters(opts, 'arrangement meters');\n const budget = opts.budget;\n const noteOptions: NoteEventAssertOptions = { allowNonPositiveDuration: true, budget };\n if (opts.pickupBeats !== undefined) {\n assertRange(opts.pickupBeats, 0, Number.MAX_SAFE_INTEGER, 'arrangement pickupBeats');\n noteOptions.minStartBeat = -opts.pickupBeats;\n }\n assertGenerationBudget(tracks.length, 'arrangement tracks', budget);\n const noteCount = assertTrackNotes(tracks, noteOptions);\n // The pooled total is what the downstream timeline analysis actually sizes\n // its work by, so it is checked here, under this layer's own name.\n assertGenerationBudget(noteCount, 'arrangement notes', budget);\n const profile = arrangementProfile(opts.profile);\n const minSeverity = opts.minSeverity ?? NoteSafety.Warning;\n const harmonyTracks = harmonyTrackSet(opts.harmonyTracks, tracks.length);\n const prepared = prepareTracks(tracks);\n // Every note is evaluated against the other sub-voices sounding beneath it, so\n // the work is the product of the two — not either dimension alone, which is\n // all the scalar checks above measure. The sub-voices have to be derived to be\n // counted, but that is the same O(n log n) pass the note checks already are,\n // and the check still lands before the harmony is pooled and inferred.\n const voiceCount = prepared.reduce((sum, track) => sum + track.voices.length, 0);\n assertGenerationBudget(noteCount * voiceCount, 'arrangement note-voice comparisons', budget);\n const pooled = poolNotes(tracks, harmonyTracks);\n\n let evidence: TimelineEvidence | undefined;\n let inferred: ChordTimelineResult;\n if (opts.timeline === undefined) {\n const run = analyzeTimeline(\n pooled,\n {\n key: opts.key,\n meters,\n pickupBeats: opts.pickupBeats,\n harmonicRhythm: opts.harmonicRhythm,\n budget,\n },\n previous,\n dirty,\n );\n evidence = run.evidence;\n inferred = withGivenKeys(run.result, opts.keys);\n } else {\n inferred = callerTimeline(opts.timeline, opts.key, opts.keys, pooled, meters, budget);\n }\n const { timeline, keys, prevailingKey, segmentConfidence } = inferred;\n const keyAt = keyLookup(keys, prevailingKey);\n // A cadence is heard in the key it arrives in, which after a modulation is\n // not the key the piece opened in.\n const cadences = detectCadences(timeline, keyAt);\n const soundingCache = new Map<number, SoundingVoice[]>();\n\n const trackAnalyses: TrackAnalysis[] = [];\n const conflicts: Conflict[] = [];\n\n for (let t = 0; t < prepared.length; t += 1) {\n const track = prepared[t];\n if (!track) {\n continue;\n }\n if (isPercussion(track.role)) {\n // A drum hit has no harmonic reading, so labelling it against the chord\n // would produce noise rather than analysis.\n trackAnalyses.push({ name: track.name, role: track.role, notes: [] });\n continue;\n }\n const notes: AnalyzedNote[] = [];\n for (let v = 0; v < track.voices.length; v += 1) {\n const subVoice = track.voices[v];\n if (!subVoice) {\n continue;\n }\n const analyzed = analyzeVoice(subVoice.voice, timeline.at, keyAt, (beat) =>\n otherVoicesSounding(prepared, t, v, beat, soundingCache),\n );\n // Appended one at a time: a spread of a long sub-voice passes every note\n // as an argument, which overflows the call stack on a track the budget\n // above accepts.\n for (const note of analyzed) {\n notes.push({ ...note, trackIndex: track.trackIndex });\n }\n const labelsById = new Map(analyzed.map((note) => [note.noteId, note.labels]));\n\n for (let noteIndex = 0; noteIndex < subVoice.voice.length; noteIndex += 1) {\n const note = subVoice.voice[noteIndex];\n const preparedNote = subVoice.sounding[noteIndex];\n if (note === undefined || preparedNote === undefined) {\n continue;\n }\n for (const beat of evaluationBeats(note, timeline)) {\n const atOnset = Math.abs(beat - note.startBeat) <= EPS;\n const safetyQuery = {\n profile,\n candidatePitch: note.pitch,\n prevPitch: atOnset ? preparedNote.prevPitch : note.pitch,\n chord: timeline.at(beat),\n key: keyAt(beat),\n otherVoices: otherVoicesSounding(prepared, t, v, beat, soundingCache),\n strongBeat: isStrongBeat(beat, meters),\n };\n // Most evaluations become no reportable conflict. Avoid their\n // replacement-pitch scan (up to 24 recursive safety checks), then\n // collect suggestions only for the conflicts we will retain.\n let result = evaluateSafety(safetyQuery, { suggestions: false });\n if (result.safety !== NoteSafety.Safe && result.safety >= minSeverity) {\n result = evaluateSafety(safetyQuery);\n const conflict: Conflict = {\n beat,\n trackName: track.name,\n trackIndex: track.trackIndex,\n noteId: note.id,\n pitch: note.pitch,\n labels: labelsById.get(note.id) ?? [],\n safety: result.safety,\n reasons: result.reasons,\n };\n if (note.originalIndex !== undefined) {\n conflict.originalIndex = note.originalIndex;\n }\n if (result.rationale !== undefined) {\n conflict.rationale = result.rationale;\n }\n if (result.resolveTo !== undefined) {\n conflict.resolveTo = result.resolveTo;\n }\n if (result.suggestions !== undefined) {\n conflict.suggestions = [...result.suggestions];\n }\n conflicts.push(conflict);\n }\n }\n }\n }\n // Sub-voice results interleave; restore the track's onset-then-pitch order.\n notes.sort((a, b) => a.noteId - b.noteId);\n trackAnalyses.push({ name: track.name, role: track.role, notes });\n }\n\n conflicts.sort((a, b) => b.safety - a.safety || a.beat - b.beat);\n\n const analysis: ArrangementAnalysis = {\n keys,\n prevailingKey,\n timeline,\n segmentConfidence,\n cadences,\n tracks: trackAnalyses,\n conflicts,\n };\n return evidence === undefined ? { analysis } : { analysis, evidence };\n}\n","/**\n * Incremental arrangement analysis: an analysis held open across edits, so a\n * host that re-analyses on every keystroke pays for the beats that changed\n * rather than for the whole piece.\n */\n\nimport { InvalidInputError } from '../../core/errors/index.js';\nimport type { NoteEvent } from '../../core/types.js';\nimport { assertGenerationBudget, assertNoteEvents } from '../../core/validation/index.js';\nimport type { DirtySlots, TimelineEvidence } from '../timeline/index.js';\nimport { dirtySlotsFor } from '../timeline/index.js';\nimport type { ArrangementAnalysis, ArrangementOptions, ArrangementTrack } from './tracks.js';\nimport { analyzeArrangementWith } from './tracks.js';\n\n/**\n * One track's notes as they stand after an edit.\n *\n * The whole note array is given rather than a description of what moved: a host\n * always has the track's current contents, and working out what actually\n * changed is this module's job, not the caller's.\n *\n * @category Arrangement & Analysis\n */\nexport type TrackEdit = {\n /** Index of the track in the session's track array. */\n trackIndex: number;\n /** The track's notes after the edit. */\n notes: readonly NoteEvent[];\n};\n\n/**\n * An arrangement analysis held open for editing.\n *\n * @category Arrangement & Analysis\n */\nexport type ArrangementSession = {\n /** The tracks the current analysis was made from. */\n readonly tracks: readonly ArrangementTrack[];\n /** The analysis of those tracks. */\n readonly analysis: ArrangementAnalysis;\n /**\n * Re-analyse after replacing the notes of one or more tracks, reusing\n * everything the edit provably cannot reach.\n *\n * The result equals {@link analyzeArrangement} over the edited tracks — the\n * same segments, confidences, key regions, cadences, annotations and\n * conflicts — so a host may hold a session for as long as it likes without\n * the analysis drifting from what a fresh pass would say.\n */\n update: (edits: readonly TrackEdit[]) => ArrangementSession;\n};\n\n/** Identity of one note, for telling an edited note array from an unedited one. */\nfunction noteId(note: NoteEvent): string {\n return `${note.pitch}|${note.startBeat}|${note.durationBeat}|${note.velocity ?? ''}`;\n}\n\n/** The beats an edit touched, or null when the two note arrays agree. */\ntype EditedSpan = { startBeat: number; endBeat: number };\n\n/**\n * Whether two note arrays hold the same notes in the same positions.\n *\n * A re-ordering changes no beat of the music, but `originalIndex` reports a\n * note's position in the caller's array, so the annotations still have to be\n * remade against the new order.\n */\nfunction sameOrder(before: readonly NoteEvent[], after: readonly NoteEvent[]): boolean {\n if (before.length !== after.length) {\n return false;\n }\n for (let index = 0; index < before.length; index += 1) {\n const previous = before[index];\n const current = after[index];\n if (previous === undefined || current === undefined || noteId(previous) !== noteId(current)) {\n return false;\n }\n }\n return true;\n}\n\n/**\n * The beats covered by the notes that `before` and `after` disagree on.\n *\n * Notes are compared as values and by multiplicity, so re-ordering a track's\n * array — which no beat of the harmony is sensitive to — covers no beats at\n * all, while adding a second copy of a note covers the one it sounds on.\n */\nfunction editedSpan(before: readonly NoteEvent[], after: readonly NoteEvent[]): EditedSpan | null {\n const counts = new Map<string, { count: number; startBeat: number; endBeat: number }>();\n const tally = (notes: readonly NoteEvent[], sign: number): void => {\n for (const note of notes) {\n const id = noteId(note);\n const entry = counts.get(id);\n if (entry === undefined) {\n counts.set(id, {\n count: sign,\n startBeat: note.startBeat,\n endBeat: note.startBeat + note.durationBeat,\n });\n } else {\n entry.count += sign;\n }\n }\n };\n tally(before, 1);\n tally(after, -1);\n let startBeat = Number.POSITIVE_INFINITY;\n let endBeat = Number.NEGATIVE_INFINITY;\n for (const entry of counts.values()) {\n if (entry.count === 0) {\n continue;\n }\n startBeat = Math.min(startBeat, entry.startBeat);\n // A note of zero or negative length never sounds, but its onset still marks\n // where the caller edited, so the span is never empty for a real change.\n endBeat = Math.max(endBeat, entry.endBeat, entry.startBeat);\n }\n return startBeat <= endBeat ? { startBeat, endBeat } : null;\n}\n\n/** Merge two edited spans into the one covering both. */\nfunction unionSpan(a: EditedSpan | null, b: EditedSpan | null): EditedSpan | null {\n if (a === null) {\n return b;\n }\n if (b === null) {\n return a;\n }\n return {\n startBeat: Math.min(a.startBeat, b.startBeat),\n endBeat: Math.max(a.endBeat, b.endBeat),\n };\n}\n\n/** Build a session around one completed analysis. */\nfunction sessionOf(\n tracks: readonly ArrangementTrack[],\n opts: ArrangementOptions,\n analysis: ArrangementAnalysis,\n evidence: TimelineEvidence | undefined,\n): ArrangementSession {\n return {\n tracks,\n analysis,\n update(edits) {\n assertGenerationBudget(edits.length, 'arrangement edits', opts.budget);\n const next = [...tracks];\n let edited: EditedSpan | null = null;\n let reordered = false;\n for (let index = 0; index < edits.length; index += 1) {\n const edit = edits[index];\n if (edit === undefined) {\n continue;\n }\n const target = next[edit.trackIndex];\n if (target === undefined) {\n throw new InvalidInputError(\n `edits[${index}].trackIndex ${edit.trackIndex} is outside the session's ${tracks.length} tracks`,\n );\n }\n if (!Array.isArray(edit.notes)) {\n throw new InvalidInputError(\n `edits[${index}].notes must be an array; received ${typeof edit.notes}`,\n );\n }\n assertNoteEvents(edit.notes, `edits[${index}].notes`, {\n allowNonPositiveDuration: true,\n budget: opts.budget,\n });\n edited = unionSpan(edited, editedSpan(target.notes, edit.notes));\n reordered = reordered || !sameOrder(target.notes, edit.notes);\n next[edit.trackIndex] = { ...target, notes: edit.notes };\n }\n // Nothing the analysis reads actually changed, so the analysis has not.\n if (edited === null && !reordered) {\n return sessionOf(next, opts, analysis, evidence);\n }\n // A pure re-ordering moves no note, so every slot of the harmony is clean\n // and the whole timeline is carried over; what it does move is the array\n // position each annotation reports, so the annotation pass is re-run.\n const dirty: DirtySlots | undefined =\n evidence === undefined\n ? undefined\n : edited === null\n ? { from: 0, to: 0 }\n : dirtySlotsFor(evidence, edited.startBeat, edited.endBeat);\n const run = analyzeArrangementWith(next, opts, evidence, dirty);\n return sessionOf(next, opts, run.analysis, run.evidence);\n },\n };\n}\n\n/**\n * Open an arrangement analysis that can be updated a few beats at a time.\n *\n * The first pass is an ordinary {@link analyzeArrangement}; what the session\n * adds is the slot-level evidence behind it, so a later {@link\n * ArrangementSession.update} can work out which beats an edit could possibly\n * have changed and leave the rest of the harmony alone. An update returns a new\n * session, so the analysis before an edit stays valid and a host can keep it\n * for undo.\n *\n * What an update recomputes is derived rather than guessed at:\n *\n * - The edited beats are the beats covered by the notes the old and new track\n * arrays disagree on, which is narrower than the track and usually far\n * narrower than the piece.\n * - A slot's evidence sees exactly the notes overlapping it, so the slots to\n * re-derive are the slots overlapping those beats — plus one either side, so\n * that an onset sitting a floating-point hair off a slot boundary cannot\n * leave a stale slot behind.\n * - The boundary search itself has no such margin. It is a shortest-path search\n * over a whole run of slots, so a changed slot can move a boundary anywhere in\n * the run, and its change cost is calibrated on the mean weight of every\n * sounding slot in the piece, which couples the runs to one another. The\n * search is therefore re-run over the whole piece — arithmetic over the\n * carried-over evidence, with nothing allocated per slot — rather than being\n * spliced at a margin that does not exist.\n * - Each settled segment's chord is carried over when its bounds, its key and\n * its slots are all unchanged, since those are the only things chord\n * inference reads.\n *\n * Key regions and the per-note annotations are recomputed in full. Both are\n * whole-piece passes — the key search is a shortest-path search with no reset\n * points, sub-voice lane assignment is greedy from the first note, and every\n * note is judged against the other voices sounding beneath it — so neither has\n * a sound local answer, and reporting one would mean an update that quietly\n * disagreed with a fresh analysis.\n *\n * An update falls back to a full analysis whenever the edit moves the ground the\n * evidence stands on: a changed piece length, a changed first onset (both of\n * which move the slot grid), or an edit to a session whose harmony was supplied\n * through {@link ArrangementOptions.timeline}. The answer is the same either\n * way; only the work differs.\n *\n * @param tracks The tracks to analyse.\n * @param opts Analysis options; see {@link ArrangementOptions}.\n * @returns A session holding the analysis and the evidence behind it.\n * @example\n * ```ts\n * import { createArrangementSession } from '@libraz/libcantus';\n * const melody = [{ pitch: 60, startBeat: 0, durationBeat: 4 }];\n * const session = createArrangementSession([{ role: 'melody', notes: melody }]);\n * const moved = [{ pitch: 62, startBeat: 0, durationBeat: 4 }];\n * const next = session.update([{ trackIndex: 0, notes: moved }]);\n * next.analysis.timeline.segments; // as if analyzed from scratch\n * ```\n * @category Arrangement & Analysis\n */\nexport function createArrangementSession(\n tracks: ArrangementTrack[],\n opts: ArrangementOptions = {},\n): ArrangementSession {\n const run = analyzeArrangementWith(tracks, opts);\n return sessionOf([...tracks], opts, run.analysis, run.evidence);\n}\n","/**\n * Arrangement tension analysis: a coarse harmonic-tension curve sampled at\n * regular beats across a whole multi-track piece, combining the sounding\n * chord's harmonic function, the dissonance of the sounding notes, and their\n * registral span into a single normalized reading.\n */\n\nimport type { MeterMap } from '../../core/meter/index.js';\nimport { isStrongBeat, resolveMeters } from '../../core/meter/index.js';\nimport { pitchClassOf as pitchClass } from '../../core/pitch/index.js';\nimport type { KeyScale } from '../../core/types.js';\nimport type { NoteEventAssertOptions } from '../../core/validation/index.js';\nimport { assertGenerationBudget, assertRange } from '../../core/validation/index.js';\nimport { chordPitchClasses } from '../../theory/chord/index.js';\nimport { evaluateSafety, NoteSafety, type SafetyProfile } from '../../theory/safety/index.js';\nimport { majorKey } from '../../theory/scale/index.js';\nimport { functionOf } from '../functional/index.js';\nimport { keyLookup, keyTimelineFromNotes, prevailingKeyOf } from '../keys/index.js';\nimport { type ChordTimeline, chordTimelineFromNotes } from '../timeline/index.js';\nimport {\n arrangementProfile,\n assertTrackNotes,\n EPS,\n harmonyTrackSet,\n isPercussion,\n type PreparedTrack,\n poolNotes,\n prepareTracks,\n} from './internal.js';\nimport type { ArrangementAnalysis, ArrangementOptions, ArrangementTrack } from './tracks.js';\n\n/**\n * A tension reading sampled at a beat.\n *\n * @category Arrangement & Analysis\n */\nexport type TensionPoint = {\n beat: number;\n /** Combined tension in [0, 1]. */\n tension: number;\n};\n\n/** Tension contributed by the sounding chord's harmonic function. */\nconst FUNCTION_TENSION: Record<'tonic' | 'subdominant' | 'dominant', number> = {\n tonic: 0,\n subdominant: 0.5,\n dominant: 1,\n};\n\n/** Weight of the harmonic-function term in the combined tension score. */\nconst FUNCTION_WEIGHT = 0.5;\n/** Weight of the non-chord-tone / dissonance term. */\nconst DISSONANCE_WEIGHT = 0.35;\n/** Weight of the registral-span term. */\nconst SPAN_WEIGHT = 0.15;\n/** Pitch span, in semitones, that saturates the span term. */\nconst SPAN_SATURATION = 24;\n\n/** Clamp a value into [0, 1]. */\nfunction clamp01(value: number): number {\n return Math.min(1, Math.max(0, value));\n}\n\n/**\n * Sample the harmonic tension of an arrangement at regular beats.\n *\n * At each sample beat the tension combines three normalized terms:\n * - the sounding chord's harmonic function (dominant `1`, subdominant `0.5`,\n * tonic or no chord `0`), weighted `FUNCTION_WEIGHT`;\n * - the dissonance of the sounding notes — the larger of the share of sounding\n * pitches that are not chord tones and the share that {@link evaluateSafety}\n * rates {@link NoteSafety.Dissonant} — weighted `DISSONANCE_WEIGHT`;\n * - the registral span of the sounding notes, saturating at\n * `SPAN_SATURATION` semitones, weighted `SPAN_WEIGHT`.\n *\n * The weighted sum is clamped to [0, 1]. The harmony is inferred from all tracks\n * pooled together, so the result is deterministic and self-contained.\n *\n * @param tracks The tracks to sample.\n * @param opts Analysis options plus an optional `step` (default one beat).\n * @returns One {@link TensionPoint} per sampled beat, in beat order.\n * @throws If `step` is not positive.\n * @example\n * ```ts\n * import { tensionCurve } from '@libraz/libcantus';\n * const notes = [{ pitch: 60, startBeat: 0, durationBeat: 4 }];\n * const curve = tensionCurve([{ notes }], { step: 1 });\n * curve; // one { beat, tension } sample per beat, in beat order\n * ```\n * @category Arrangement & Analysis\n */\nexport function tensionCurve(\n tracks: ArrangementTrack[],\n opts: ArrangementOptions & { step?: number } = {},\n): TensionPoint[] {\n const meters = resolveMeters(opts, 'arrangement meters');\n const budget = opts.budget;\n const noteOptions: NoteEventAssertOptions = { allowNonPositiveDuration: true, budget };\n if (opts.pickupBeats !== undefined) {\n assertRange(opts.pickupBeats, 0, Number.MAX_SAFE_INTEGER, 'arrangement pickupBeats');\n noteOptions.minStartBeat = -opts.pickupBeats;\n }\n assertGenerationBudget(tracks.length, 'arrangement tracks', budget);\n const noteCount = assertTrackNotes(tracks, noteOptions);\n // The pooled total is what the sampling and the inferred timeline size their\n // work by, so it is checked here, under this layer's own name.\n assertGenerationBudget(noteCount, 'arrangement notes', budget);\n const profile = arrangementProfile(opts.profile);\n const step = opts.step ?? 1;\n assertRange(step, Number.MIN_VALUE, Number.MAX_SAFE_INTEGER, 'tension sampling step');\n\n const harmonyTracks = harmonyTrackSet(opts.harmonyTracks, tracks.length);\n const pooled = poolNotes(tracks, harmonyTracks);\n const all = poolNotes(tracks);\n const totalBeats = all.reduce((end, n) => Math.max(end, n.startBeat + n.durationBeat), 0);\n // A pickup sounds before beat 0, so the curve starts there — a whole number\n // of steps before it, so the samples still land on the beats they would have\n // without one.\n const firstOnset = all.reduce((first, n) => Math.min(first, n.startBeat), 0);\n const sampleStart = Math.min(0, Math.floor(firstOnset / step + EPS) * step);\n // A caller-supplied timeline still needs key regions to be read against: the\n // ones the caller gave, the single key they named, or the ones the notes\n // themselves give, since supplying chords answers nothing about the key.\n const suppliedKeys =\n opts.timeline === undefined\n ? undefined\n : (opts.keys ??\n (opts.key !== undefined\n ? [{ startBeat: 0, endBeat: totalBeats, key: opts.key, confidence: 1 }]\n : keyTimelineFromNotes(pooled, { meters, totalBeats, budget })));\n const { timeline, keys, prevailingKey } =\n opts.timeline === undefined || suppliedKeys === undefined\n ? chordTimelineFromNotes(pooled, {\n key: opts.key,\n meters,\n pickupBeats: opts.pickupBeats,\n harmonicRhythm: opts.harmonicRhythm,\n budget,\n })\n : {\n timeline: opts.timeline,\n keys: suppliedKeys,\n prevailingKey: prevailingKeyOf(suppliedKeys) ?? majorKey(0),\n };\n // Harmonic tension is judged against the key in force at the sample, not\n // against one key for the piece: a chord is only tense relative to a tonic.\n const regions = opts.keys ?? keys;\n const keyAt = keyLookup(regions, prevailingKeyOf(regions) ?? prevailingKey);\n\n const prepared = prepareTracks(tracks);\n const points: TensionPoint[] = [];\n const sampleCount = Math.max(0, Math.ceil((totalBeats - sampleStart) / step - EPS));\n assertGenerationBudget(sampleCount, 'tension samples', budget);\n // The per-sample cost is one lookup per sub-voice, so it is their product —\n // not either dimension alone — that has to stay inside the budget.\n const voiceCount = prepared.reduce((sum, track) => sum + track.voices.length, 0);\n assertGenerationBudget(\n sampleCount * Math.min(128, voiceCount) * voiceCount,\n 'tension sample-voice comparisons',\n budget,\n );\n for (let i = 0; i < sampleCount; i += 1) {\n const beat = sampleStart + i * step;\n points.push({\n beat,\n tension: sampleTension(prepared, timeline, keyAt(beat), meters, profile, beat),\n });\n }\n return points;\n}\n\n/**\n * Sample the tension of an arrangement already analysed by\n * {@link analyzeArrangement}, reusing its harmony instead of inferring it again.\n *\n * `analyzeArrangement` followed by `tensionCurve` runs chord and key inference\n * twice over the same notes. This takes the analysis it already produced, so\n * the curve and the annotations describe the same harmony by construction.\n *\n * @param tracks The same tracks that were analysed.\n * @param analysis The result of {@link analyzeArrangement} for those tracks.\n * @param opts Analysis options plus an optional `step` (default one beat).\n * @returns One {@link TensionPoint} per sampled beat, in beat order.\n * @example\n * ```ts\n * import { analyzeArrangement, tensionCurveFrom } from '@libraz/libcantus';\n * const tracks = [{ notes: [{ pitch: 60, startBeat: 0, durationBeat: 4 }] }];\n * const analysis = analyzeArrangement(tracks);\n * tensionCurveFrom(tracks, analysis, { step: 1 });\n * ```\n * @category Arrangement & Analysis\n */\nexport function tensionCurveFrom(\n tracks: ArrangementTrack[],\n analysis: ArrangementAnalysis,\n opts: ArrangementOptions & { step?: number } = {},\n): TensionPoint[] {\n return tensionCurve(tracks, {\n ...opts,\n timeline: analysis.timeline,\n keys: analysis.keys,\n });\n}\n\n/**\n * All sounding pitched notes across every track at a beat, tagged with their\n * track. Percussion carries no harmony, so it is skipped; each sub-voice is\n * monophonic, so its contribution is one binary search rather than a scan.\n */\nfunction allSounding(prepared: PreparedTrack[], beat: number): { pitch: number; track: number }[] {\n const out: { pitch: number; track: number }[] = [];\n for (let t = 0; t < prepared.length; t += 1) {\n const track = prepared[t];\n if (!track || isPercussion(track.role)) {\n continue;\n }\n for (const subVoice of track.voices) {\n const note = subVoice.at(beat);\n if (note !== undefined) {\n out.push({ pitch: note.pitch, track: t });\n }\n }\n }\n return out;\n}\n\n/** Combine the harmonic, dissonance, and span terms at one sample beat. */\nfunction sampleTension(\n prepared: PreparedTrack[],\n timeline: ChordTimeline,\n key: KeyScale,\n meters: MeterMap,\n profile: SafetyProfile,\n beat: number,\n): number {\n const sounding = allSounding(prepared, beat);\n if (sounding.length === 0) {\n return 0;\n }\n const chord = timeline.at(beat);\n const functionScore = chord ? FUNCTION_TENSION[functionOf(chord, key)] : 0;\n\n const chordPcs = chord ? new Set(chordPitchClasses(chord)) : null;\n const strongBeat = isStrongBeat(beat, meters);\n let nonChord = 0;\n let dissonant = 0;\n // A coarse tension sample supplies no individual voice history. Every voice\n // at the same MIDI pitch consequently receives the same safety verdict, so\n // a dense pad needs at most 128 evaluations instead of one per voice.\n const pitches = new Map<number, number>();\n for (const voice of sounding) {\n pitches.set(voice.pitch, (pitches.get(voice.pitch) ?? 0) + 1);\n }\n for (const [pitch, count] of pitches) {\n // Non-chord-tone share only applies when a chord is sounding; at a timeline\n // gap there is no reference harmony, so vertical dissonance alone drives the\n // dissonance term (the chord function term is already 0 there).\n if (chordPcs && !chordPcs.has(pitchClass(pitch))) {\n nonChord += count;\n }\n const others = [...pitches].flatMap(([otherPitch, otherCount]) =>\n Array.from({ length: otherCount - (otherPitch === pitch ? 1 : 0) }, () => ({\n pitch: otherPitch,\n })),\n );\n // Only the verdict is read, so the replacement-pitch search is skipped.\n const result = evaluateSafety(\n {\n profile,\n candidatePitch: pitch,\n chord,\n key,\n otherVoices: others,\n strongBeat,\n },\n { suggestions: false },\n );\n if (result.safety === NoteSafety.Dissonant) {\n dissonant += count;\n }\n }\n const dissonanceScore = Math.max(nonChord, dissonant) / sounding.length;\n\n let low = Number.POSITIVE_INFINITY;\n let high = Number.NEGATIVE_INFINITY;\n for (const voice of sounding) {\n low = Math.min(low, voice.pitch);\n high = Math.max(high, voice.pitch);\n }\n const spanScore = Math.min(1, (high - low) / SPAN_SATURATION);\n\n return clamp01(\n FUNCTION_WEIGHT * functionScore + DISSONANCE_WEIGHT * dissonanceScore + SPAN_WEIGHT * spanScore,\n );\n}\n","/**\n * Bar-level evidence shared by the form analyses.\n *\n * Phrases, hypermeter and sections all ask the same two questions of a bar —\n * what sounds in it, and how far that is from what sounded in the bar before —\n * so they read the answer from one place. Everything here works in absolute\n * quarter-note beats and 0-based bar indices, so a pickup is bar -1 rather than\n * a special case.\n */\n\nimport type { MeterLike } from '../../core/meter/index.js';\nimport { barIndexAt, barPositionToBeat } from '../../core/meter/index.js';\nimport type { NoteEvent } from '../../core/types.js';\nimport { assertGenerationBudget } from '../../core/validation/index.js';\nimport { windowWeights } from '../histogram.js';\n\n/** Tolerance for beat comparisons reached by float arithmetic. */\nexport const EPS = 1e-9;\n\n/** Hold a score inside the [0, 1] range every confidence in this module reports. */\nexport function clamp01(value: number): number {\n return Math.min(1, Math.max(0, value));\n}\n\n/**\n * Remainder carrying the sign of the divisor.\n *\n * Bar indices run negative through a pickup, so the phase of a hypermetric\n * group has to be asked with a modulo that answers 3 for bar -1 of a four-bar\n * group rather than -1.\n */\nexport function floorMod(value: number, modulus: number): number {\n return ((value % modulus) + modulus) % modulus;\n}\n\n/**\n * Where the analysed span starts: the earliest beat that sounds.\n *\n * Seeding this with 0 instead would anchor every analysis to bar 0 — an excerpt\n * lifted from bar 9 would be reported as starting at beat 0 and as holding\n * eight bars of silence it never had. The span runs back past beat 0 only for a\n * genuine pickup, which sounds before the first downbeat.\n *\n * @param notes The sounding notes.\n * @param fallback The beat to report when nothing sounds.\n * @returns The earliest onset, or `fallback` for an empty span.\n */\nexport function firstSoundingBeat(notes: readonly NoteEvent[], fallback = 0): number {\n let first = Number.POSITIVE_INFINITY;\n for (const note of notes) {\n first = Math.min(first, note.startBeat);\n }\n return Number.isFinite(first) ? first : fallback;\n}\n\n/**\n * Index of the last bar a span occupies.\n *\n * A span ending exactly on a bar line ends in the bar before it: the bar its\n * end beat falls in has not begun. Asking for the bar of `spanEnd` minus an\n * epsilon does not answer this — bar arithmetic carries a tolerance of its own,\n * measured in bars rather than beats, and rounds such a beat back up — so the\n * empty trailing bar is dropped explicitly.\n *\n * @param meter A single signature, or the piece's meter map.\n * @param spanStart First beat of the span.\n * @param spanEnd End of the span, exclusive.\n * @returns The last bar index, never before the span's first bar.\n */\nexport function lastBarOf(meter: MeterLike, spanStart: number, spanEnd: number): number {\n const firstBar = barIndexAt(spanStart, meter);\n const bar = barIndexAt(Math.max(spanStart, spanEnd), meter);\n if (bar > firstBar && barPositionToBeat({ bar, beat: 0 }, meter) >= spanEnd - EPS) {\n return bar - 1;\n }\n return bar;\n}\n\n/** One bar of the analysed span, with what sounds in it. */\nexport type BarSlice = {\n /** 0-based bar index; negative inside a pickup. */\n index: number;\n /** First beat of the bar. */\n startBeat: number;\n /** End of the bar, exclusive. */\n endBeat: number;\n /** Every note overlapping the bar, in input order. */\n notes: NoteEvent[];\n /** Pitch-class weights over the bar, indexed 0-11. */\n weights: number[];\n /** Sum of those weights; zero for a silent bar. */\n totalWeight: number;\n};\n\n/**\n * Cut a span into its bars and weigh what sounds in each.\n *\n * Notes are bucketed into the bars they overlap before any histogram is built,\n * so the cost is one pass over the notes plus one per bar-membership rather\n * than a scan of the whole piece per bar.\n *\n * @param notes The sounding notes; those outside the span are ignored.\n * @param meter A single signature, or the piece's meter map.\n * @param spanStart First beat of the span, negative inside a pickup.\n * @param spanEnd End of the span, exclusive.\n * @param budget Upper bound on the work this may do.\n * @returns One slice per bar, in bar order.\n */\nexport function sliceBars(\n notes: readonly NoteEvent[],\n meter: MeterLike,\n spanStart: number,\n spanEnd: number,\n budget?: number,\n): BarSlice[] {\n const firstBar = barIndexAt(spanStart, meter);\n const lastBar = lastBarOf(meter, spanStart, spanEnd);\n const barCount = lastBar - firstBar + 1;\n assertGenerationBudget(barCount, 'form bars', budget);\n\n const buckets: NoteEvent[][] = Array.from({ length: barCount }, () => []);\n let memberships = 0;\n for (const note of notes) {\n const noteEnd = note.startBeat + note.durationBeat;\n const from = Math.max(firstBar, barIndexAt(note.startBeat, meter));\n const to = Math.min(lastBar, barIndexAt(Math.max(note.startBeat, noteEnd - EPS), meter));\n memberships += Math.max(0, to - from + 1);\n assertGenerationBudget(memberships, 'form note-to-bar memberships', budget);\n for (let bar = from; bar <= to; bar += 1) {\n buckets[bar - firstBar]?.push(note);\n }\n }\n\n const slices: BarSlice[] = [];\n for (let bar = firstBar; bar <= lastBar; bar += 1) {\n const startBeat = barPositionToBeat({ bar, beat: 0 }, meter);\n const endBeat = barPositionToBeat({ bar: bar + 1, beat: 0 }, meter);\n const barNotes = buckets[bar - firstBar] ?? [];\n const { weights, totalWeight } = windowWeights(barNotes, startBeat, endBeat, meter);\n slices.push({ index: bar, startBeat, endBeat, notes: barNotes, weights, totalWeight });\n }\n return slices;\n}\n\n/**\n * Cosine similarity of two non-negative weight vectors, in [0, 1].\n *\n * Cosine rather than a difference of weights, because two statements of the\n * same harmony are the same harmony whether they are played loud or soft, long\n * or short. Two silent spans are alike; a silent one and a sounding one are not.\n */\nexport function weightSimilarity(a: readonly number[], b: readonly number[]): number {\n let dot = 0;\n let normA = 0;\n let normB = 0;\n for (let i = 0; i < a.length; i += 1) {\n const x = a[i] ?? 0;\n const y = b[i] ?? 0;\n dot += x * y;\n normA += x * x;\n normB += y * y;\n }\n if (normA <= EPS && normB <= EPS) {\n return 1;\n }\n if (normA <= EPS || normB <= EPS) {\n return 0;\n }\n return clamp01(dot / Math.sqrt(normA * normB));\n}\n\n/**\n * How much the harmony changes at the start of slice `index`, in [0, 1].\n *\n * The opening of the music counts as a change outright: nothing precedes it to\n * be the same as. A pickup bar does not — it is an upbeat into the first bar,\n * and treating it as an opening would pull the hypermetric phase onto it.\n */\nexport function harmonicNovelty(slices: readonly BarSlice[], index: number): number {\n const current = slices[index];\n if (current === undefined) {\n return 0;\n }\n const previous = slices[index - 1];\n if (previous === undefined) {\n return current.index === 0 ? 1 : 0;\n }\n return 1 - weightSimilarity(previous.weights, current.weights);\n}\n\n/**\n * Sum independent evidence into one strength in [0, 1].\n *\n * Two signals pointing at the same boundary reinforce each other without ever\n * summing past certainty, and neither can drag the other down — which a plain\n * sum or a mean would both get wrong.\n */\nexport function combineEvidence(strengths: Iterable<number>): number {\n let remaining = 1;\n for (const strength of strengths) {\n remaining *= 1 - clamp01(strength);\n }\n return clamp01(1 - remaining);\n}\n","/**\n * Hypermeter: the grouping of bars into hyperbars.\n *\n * Bars are felt in groups the way beats are felt in bars — four bars at a time\n * in most tonal and popular music, but two, three, six or eight elsewhere. The\n * grouping is not written in the score, so it has to be inferred from where the\n * music actually restarts: harmony changes on a hypermetric downbeat, and\n * cadences arrive at the end of a hyperbar.\n */\n\nimport type { MeterLike } from '../../core/meter/index.js';\nimport { barIndexAt, beatsPerBarAt, resolveMeters } from '../../core/meter/index.js';\nimport type { NoteEvent } from '../../core/types.js';\nimport {\n assertGenerationBudget,\n assertNoteEvents,\n assertRange,\n} from '../../core/validation/index.js';\nimport { gridOriginOf } from '../grid.js';\nimport type { BarSlice } from './internal.js';\nimport {\n clamp01,\n EPS,\n firstSoundingBeat,\n floorMod,\n harmonicNovelty,\n sliceBars,\n} from './internal.js';\n\n/**\n * The bar groupings the search considers.\n *\n * Four is the norm, two and eight its halving and doubling, three and six the\n * triple-hypermetre alternatives. Anything else is better described as an\n * irregular succession of these than as a grouping of its own.\n */\nconst GROUP_CANDIDATES: readonly number[] = [2, 3, 4, 6, 8];\n\n/**\n * How readily each grouping is believed before the music is consulted.\n *\n * The prior only settles ties: a piece that is plainly in three does not become\n * a piece in four because four is commoner. It is what decides a genuinely\n * ambiguous case, where two-bar and four-bar readings explain the accents\n * equally well.\n */\nconst GROUP_PRIOR: ReadonlyMap<number, number> = new Map([\n [2, 0.9],\n [3, 0.75],\n [4, 1],\n [6, 0.7],\n [8, 0.85],\n]);\n\n/** Share of the evidence carried by harmonic change landing on group heads. */\nconst CONTRAST_SHARE = 0.65;\n\n/** Share of the evidence carried by cadences landing at group ends. */\nconst CADENCE_SHARE = 0.35;\n\n/** How much of a cadence's fit a group-final arrival earns. */\nconst GROUP_FINAL_FIT = 1;\n\n/**\n * How much of a cadence's fit an arrival on the next group's head earns.\n *\n * An elided cadence lands on the downbeat that starts the next hyperbar, so the\n * reading is real; it is weaker evidence than a group-final arrival only\n * because a chord on a hypermetric downbeat is unremarkable on its own.\n */\nconst ELIDED_FIT = 0.5;\n\n/**\n * How many hyperbars the span must hold before a grouping is considered.\n *\n * One statement of a grouping is not evidence of a grouping: any span is\n * trivially one group of its own length.\n */\nconst MIN_GROUPS = 2;\n\n/** Share of the confidence that survives a rival grouping scoring just as well. */\nconst MARGIN_FLOOR = 0.7;\n\n/**\n * A bar grouping inferred from the music.\n *\n * @category Arrangement & Analysis\n */\nexport type Hypermeter = {\n /**\n * Bars per hyperbar. Falls back to the span's own bar count when the span is\n * too short to hold two of any candidate grouping.\n */\n groupBars: number;\n /**\n * Absolute beat of each hypermetric downbeat inside the analysed span, in\n * time order. A pickup bar is not a group head, so the first entry is the\n * first full bar that starts a group.\n */\n downbeats: number[];\n /**\n * How much the music supports the grouping, in [0, 1]: how strongly harmonic\n * change and cadences prefer these bars over the others, discounted when the\n * runner-up grouping explains the music nearly as well.\n */\n confidence: number;\n /** Why this grouping was chosen. */\n rationale: string;\n};\n\n/**\n * Options for {@link hypermeter}.\n *\n * @category Arrangement & Analysis\n */\nexport type HypermeterOptions = {\n /**\n * End of the analysed span in beats; defaults to the end of the last note.\n *\n * @defaultValue the end of the last note\n */\n totalBeats?: number;\n /**\n * Beats at which cadences arrive, as {@link CadenceHit.atBeat} reports them.\n *\n * A cadence is the clearest hypermetric evidence there is — it arrives at the\n * end of a hyperbar — but finding one needs a chord timeline and a key, which\n * this function is not given. Callers that already hold cadences should pass\n * them; {@link phrasesFromTimeline} does.\n *\n * @defaultValue none\n */\n cadenceBeats?: readonly number[];\n /**\n * Upper bound on the work this call may do.\n *\n * @defaultValue {@link DEFAULT_GENERATION_BUDGET}\n */\n budget?: number;\n};\n\n/** One grouping-and-phase reading, with the evidence behind it. */\ntype Reading = {\n groupBars: number;\n phase: number;\n contrast: number;\n cadenceFit: number;\n evidence: number;\n score: number;\n};\n\n/**\n * How much more the group heads are accented than the bars between them.\n *\n * The difference of the two means rather than the head mean alone: a piece\n * whose every bar changes harmony accents its group heads no more than anything\n * else, and a reading that cannot tell the two apart has found nothing.\n */\nfunction accentContrast(\n novelty: readonly number[],\n slices: readonly BarSlice[],\n groupBars: number,\n phase: number,\n): number {\n let headSum = 0;\n let headCount = 0;\n let otherSum = 0;\n let otherCount = 0;\n for (let i = 0; i < slices.length; i += 1) {\n const slice = slices[i];\n if (slice === undefined) {\n continue;\n }\n const value = novelty[i] ?? 0;\n if (floorMod(slice.index - phase, groupBars) === 0) {\n headSum += value;\n headCount += 1;\n } else {\n otherSum += value;\n otherCount += 1;\n }\n }\n if (headCount === 0 || otherCount === 0) {\n return 0;\n }\n return headSum / headCount - otherSum / otherCount;\n}\n\n/** Share of the cadences arriving where the grouping says a hyperbar ends. */\nfunction cadenceFitOf(cadenceBars: readonly number[], groupBars: number, phase: number): number {\n if (cadenceBars.length === 0) {\n return 0;\n }\n let fit = 0;\n for (const bar of cadenceBars) {\n const position = floorMod(bar - phase, groupBars);\n if (position === groupBars - 1) {\n fit += GROUP_FINAL_FIT;\n } else if (position === 0) {\n fit += ELIDED_FIT;\n }\n }\n return clamp01(fit / cadenceBars.length);\n}\n\n/** The reading a span too short to hold two of any candidate grouping gets. */\nfunction undecidedReading(slices: readonly BarSlice[]): Hypermeter {\n const groupBars = Math.max(1, slices.length);\n const first = slices[0];\n return {\n groupBars,\n downbeats: first === undefined ? [] : [first.startBeat],\n confidence: 0,\n rationale: `Span of ${slices.length} bar(s) is too short to establish a grouping; read as one group`,\n };\n}\n\n/**\n * Infer how a piece's bars group into hyperbars.\n *\n * Groupings of 2, 3, 4, 6, and 8 bars are tried at every phase, and each\n * reading is scored on two counts: how much more harmonic change lands on the\n * bars it calls group heads than on the bars between them, and how many of the\n * cadences it is given arrive where it says a hyperbar ends. The commonest\n * groupings are believed a little more readily than the rare ones, which\n * settles ties without letting a prior overrule the music.\n *\n * The meter is read at the beat in question rather than at the start, so a\n * piece that changes metre keeps being cut at its own bar lines. Bar 0 is the\n * first full bar however much pickup precedes it, so an upbeat is bar -1 and is\n * never taken for a hypermetric downbeat. Whether there is an upbeat at all is\n * decided the way the rest of the analysis decides it: a note a millibeat early\n * is playing the downbeat, not anticipating it.\n *\n * @param notes The notes to read, in any order. Notes that never sound are\n * dropped.\n * @param meter A single time signature, or the piece's meter map; defaults to\n * 4/4 throughout.\n * @param opts Analysis options; see {@link HypermeterOptions}.\n * @returns The grouping, its downbeats, a confidence, and a rationale.\n * @example\n * ```ts\n * import { hypermeter } from '@libraz/libcantus';\n * const notes = [\n * { pitch: 60, startBeat: 0, durationBeat: 16 },\n * { pitch: 65, startBeat: 16, durationBeat: 16 },\n * ];\n * hypermeter(notes).groupBars; // 4\n * ```\n * @category Arrangement & Analysis\n */\nexport function hypermeter(\n notes: readonly NoteEvent[],\n meter?: MeterLike,\n opts: HypermeterOptions = {},\n): Hypermeter {\n const meters = meter ?? resolveMeters({});\n assertNoteEvents(notes, 'hypermeter notes', {\n allowNonPositiveDuration: true,\n budget: opts.budget,\n });\n const sounding = notes.filter((note) => note.durationBeat > 0);\n const firstOnset = firstSoundingBeat(sounding);\n const lastEnd = sounding.reduce((end, n) => Math.max(end, n.startBeat + n.durationBeat), 0);\n const spanEnd = opts.totalBeats ?? lastEnd;\n assertRange(spanEnd, 0, Number.MAX_SAFE_INTEGER, 'hypermeter totalBeats');\n // Whether the music starts before the downbeat is the analysis-wide pickup\n // question, and the slot grids of chord and key inference answer it here: a\n // note a millibeat early is playing beat 0, and only one a whole grid unit\n // early is an upbeat. Reading a hair-early onset as a pickup would hand the\n // phase search a silent bar of its own to group against. An excerpt that\n // begins later keeps the beat it begins on.\n const spanStart =\n firstOnset < 0\n ? gridOriginOf(firstOnset, beatsPerBarAt(firstOnset, meters)).startBeat\n : firstOnset;\n const slices = sliceBars(sounding, meters, spanStart, spanEnd, opts.budget);\n\n const candidates = GROUP_CANDIDATES.filter(\n (groupBars) => slices.length >= groupBars * MIN_GROUPS,\n );\n if (candidates.length === 0) {\n return undecidedReading(slices);\n }\n assertGenerationBudget(\n candidates.reduce((sum, groupBars) => sum + groupBars * slices.length, 0),\n 'hypermeter readings',\n opts.budget,\n );\n\n const novelty = slices.map((_, index) => harmonicNovelty(slices, index));\n const cadenceBars = (opts.cadenceBeats ?? []).map((beat) => barIndexAt(beat, meters));\n\n const readings: Reading[] = [];\n for (const groupBars of candidates) {\n for (let phase = 0; phase < groupBars; phase += 1) {\n const contrast = accentContrast(novelty, slices, groupBars, phase);\n const cadenceFit = cadenceFitOf(cadenceBars, groupBars, phase);\n const evidence = clamp01(\n cadenceBars.length === 0\n ? contrast\n : CONTRAST_SHARE * contrast + CADENCE_SHARE * cadenceFit,\n );\n readings.push({\n groupBars,\n phase,\n contrast,\n cadenceFit,\n evidence,\n score: evidence * (GROUP_PRIOR.get(groupBars) ?? 0),\n });\n }\n }\n // Ties keep the commoner grouping, then the shorter one, then the earlier\n // phase, so the answer never depends on enumeration order.\n readings.sort(\n (a, b) =>\n b.score - a.score ||\n (GROUP_PRIOR.get(b.groupBars) ?? 0) - (GROUP_PRIOR.get(a.groupBars) ?? 0) ||\n a.groupBars - b.groupBars ||\n a.phase - b.phase,\n );\n const best = readings[0];\n if (best === undefined) {\n return undecidedReading(slices);\n }\n const runnerUp = readings.find((reading) => reading.groupBars !== best.groupBars);\n const margin =\n runnerUp === undefined || best.score <= EPS\n ? 1\n : clamp01((best.score - runnerUp.score) / best.score);\n\n // A pickup bar is bar -1, and a phase can put it on a group head — but an\n // upbeat is what leads into the first hyperbar, not what starts one, so it\n // never becomes a hypermetric downbeat however the phase falls.\n const downbeats = slices\n .filter((slice) => slice.index >= 0 && floorMod(slice.index - best.phase, best.groupBars) === 0)\n .map((slice) => slice.startBeat);\n const cadencePart =\n cadenceBars.length === 0\n ? 'no cadences given'\n : `${Math.round(best.cadenceFit * 100)}% of cadences arriving at a group end`;\n\n return {\n groupBars: best.groupBars,\n downbeats,\n // Mostly the evidence, discounted when a rival grouping explains the\n // accents nearly as well. The discount is the smaller term because the\n // runner-up is nearly always a multiple or a divisor of the winner — four\n // bars nest inside eight — and a reading of the same accents at another\n // level is weaker counter-evidence than an unrelated rival would be.\n confidence: clamp01(best.evidence * (MARGIN_FLOOR + (1 - MARGIN_FLOOR) * margin)),\n rationale:\n `${best.groupBars}-bar groups from bar ${best.phase}: harmonic change ` +\n `${Math.round(best.contrast * 100)}% stronger on group heads, ${cadencePart}`,\n };\n}\n","/**\n * Melody as a shape, rather than as a run of notes.\n *\n * A motif is held here as an interval contour plus a rhythmic profile, which is\n * what makes a restatement at another pitch level the same motif; the absolute\n * pitches only ever describe one statement of it. On that footing two statements\n * can be compared and the transformation between them named — repetition,\n * transposition, inversion, retrograde, augmentation — and phrases that stand in\n * no such relation can still be scored for similarity.\n */\n\nimport { InvalidInputError } from '../../core/errors/index.js';\nimport type { SpelledInterval } from '../../core/pitch/index.js';\nimport { midiToNote, pitchClassOf as pitchClass, spelledInterval } from '../../core/pitch/index.js';\nimport type { KeyScale, NoteEvent } from '../../core/types.js';\nimport {\n assertGenerationBudget,\n assertInteger,\n assertNoteEvents,\n} from '../../core/validation/index.js';\nimport {\n keySignatureFifths,\n scaleTonesInDegreeOrder,\n spelledKeyOf,\n} from '../../theory/scale/index.js';\n\n/** Tolerance for beat, ratio, and interval comparisons. */\nconst EPS = 1e-9;\n\n/**\n * Grid the rhythmic profile is compared on, as a divisor of the cell's first\n * onset gap.\n *\n * Two statements of a motif have to agree on their rhythm exactly, and floating\n * beat arithmetic does not: 1/3 of a beat written two different ways differs in\n * the last bits. Rounding the ratios onto a grid this fine keeps a dotted figure\n * distinct from an even one while absorbing that error. Onsets are still\n * expected to be quantised — a humanized MIDI take should be quantised before it\n * is searched for motifs.\n */\nconst RHYTHM_GRID = 64;\n\n/**\n * Shortest cell the search reports, in notes.\n *\n * Two notes carry a single interval, and a lone rising fourth recurs in almost\n * any melody without naming anything. Three notes are the shortest cell with a\n * shape: two intervals, so it can turn.\n */\nconst MIN_CELL_NOTES = 3;\n\n/**\n * Longest cell the search reports, in notes.\n *\n * Eight notes span two bars of eighths, or four bars of quarters. Recurring\n * material longer than that is a phrase rather than a motif and wants a\n * phrase-level reading; the bound also keeps the number of windows examined\n * linear in the length of the melody.\n */\nconst MAX_CELL_NOTES = 8;\n\n/**\n * How many statements make a cell a motif.\n *\n * A motif is by definition restated, so two is the floor. Raising it is how a\n * caller asks for only the material the piece is actually built from.\n */\nconst MIN_OCCURRENCES = 2;\n\n/**\n * Largest gap, in beats, between one statement ending and the next beginning for\n * the pair to be heard as a sequence.\n *\n * A sequence follows its model immediately; anything further off is a\n * restatement somewhere else in the piece. The tolerance is the same order as\n * the timing slack a MIDI take carries, so a statement that starts a hair early\n * still counts.\n */\nconst SEQUENCE_GAP = 0.05;\n\n/**\n * Interval difference, in semitones, at which two steps count as wholly\n * different in {@link melodicSimilarity}.\n *\n * A third answered by a fourth is nearly the same gesture; a third answered by a\n * minor seventh is not. The tritone is where the graded cost saturates, so\n * anything wider than half an octave apart costs the same as an outright\n * mismatch.\n */\nconst INTERVAL_TOLERANCE = 6;\n\n/** Share of {@link melodicSimilarity} carried by the interval sequence. */\nconst PITCH_WEIGHT = 0.75;\n\n/** Share of {@link melodicSimilarity} carried by the rhythmic profile. */\nconst RHYTHM_WEIGHT = 0.25;\n\n/**\n * Share of a line's total motion that has to run one way before the line is\n * called ascending, descending, or an arch.\n *\n * A melody that climbs an octave with one dip on the way is heard as ascending,\n * so the classification is a dominance test rather than a strict-monotonicity\n * one. Below this share the line is reported as a wave, which is the honest\n * answer for something that changes direction repeatedly.\n */\nconst DIRECTIONAL_SHARE = 0.75;\n\n/**\n * Step direction between two consecutive notes.\n *\n * @category Arrangement & Analysis\n */\nexport type ContourDirection = 'up' | 'down' | 'same';\n\n/**\n * The overall shape of a line.\n *\n * The first four names are the vocabulary the motif generator's contours are\n * written in, and mean the same here. `'static'` is the shape a generator never\n * asks for and an analysis regularly meets: a line that does not move. A line\n * that dips and comes back — an arch upside down — reads as `'wave'`, since the\n * shared vocabulary has no name of its own for it.\n *\n * A wave has to turn more than once to be heard as one, so a cell the generator\n * writes for `'wave'` reads back as `'wave'` only from three bars up; the one-\n * and two-bar cells turn once and read as `'arch'`. The other three shapes come\n * back under their own name at every length.\n *\n * @category Arrangement & Analysis\n */\nexport type MelodicContourShape = 'arch' | 'ascending' | 'descending' | 'wave' | 'static';\n\n/**\n * The abstract shape of a line: its step directions and what they add up to.\n *\n * @category Arrangement & Analysis\n */\nexport type MelodicContour = {\n /** One direction per adjacent pair of notes, in time order. */\n directions: ContourDirection[];\n /** What the directions add up to. */\n shape: MelodicContourShape;\n /** Index of the highest note; ties keep the earliest. */\n peakIndex: number;\n /** Index of the lowest note; ties keep the earliest. */\n troughIndex: number;\n /** Semitone distance from the lowest note to the highest. */\n range: number;\n /** Why the line was given that shape. */\n rationale: string;\n};\n\n/**\n * One statement of a motif, addressed in the melody it was found in.\n *\n * The notes themselves are not repeated here: a statement is the motif's notes\n * moved by `transpose` and stretched by `timeRatio`, and `noteIndex` addresses\n * the original array for a caller that wants them exactly.\n *\n * @category Arrangement & Analysis\n */\nexport type MotifOccurrence = {\n /**\n * Index of the statement's first note among the sounding notes of the melody,\n * in time order. Notes that never sound are dropped before the search, so this\n * is not an index into the caller's array unless the caller's array has none.\n */\n noteIndex: number;\n /** Onset of the statement's first note. */\n startBeat: number;\n /** End of the statement's last note, exclusive. */\n endBeat: number;\n /** Semitones this statement stands above the motif's first statement. */\n transpose: number;\n /**\n * How much this statement is stretched against the motif's first: 2 for a\n * statement in doubled note values, 0.5 for one in halved values, 1 for one in\n * the same values.\n */\n timeRatio: number;\n};\n\n/**\n * A recurring melodic cell.\n *\n * Identity is the interval sequence plus the rhythmic profile, so every\n * statement of the motif carries the same `intervals` and `rhythm` however far\n * it has been transposed or however wide its note values are written. `notes`\n * holds one statement — the first — exactly as it sounds.\n *\n * @category Arrangement & Analysis\n */\nexport type MotifData = {\n /** The first statement, exactly as it sounds in the melody. */\n notes: NoteEvent[];\n /**\n * Semitones between consecutive notes. This is the transposition-invariant\n * identity of the cell: two statements a fifth apart share it.\n */\n intervals: number[];\n /**\n * Onset-to-onset gaps as multiples of the first gap, so an even cell reads\n * `[1, 1, 1]` whatever the tempo or note values it is written in.\n */\n rhythm: number[];\n /** Every statement found, in time order; the first is `notes`. */\n occurrences: MotifOccurrence[];\n /** Why this counts as a motif. */\n rationale: string;\n};\n\n/**\n * Options controlling {@link extractMotifs}.\n *\n * @category Arrangement & Analysis\n */\nexport type ExtractMotifsOptions = {\n /**\n * Shortest cell to report, in notes.\n *\n * @defaultValue 3\n */\n minNotes?: number;\n /**\n * Longest cell to report, in notes.\n *\n * @defaultValue 8\n */\n maxNotes?: number;\n /**\n * How many statements a cell needs before it is reported.\n *\n * @defaultValue 2\n */\n minOccurrences?: number;\n /**\n * Upper bound on the work this call may do.\n *\n * @defaultValue {@link DEFAULT_GENERATION_BUDGET}\n */\n budget?: number;\n};\n\n/**\n * How one statement of a motif stands to another.\n *\n * `'transposition'` is the literal one — every interval preserved, which is what\n * makes a sequence built on it a *real* sequence. `'tonalTransposition'` shifts\n * by scale degrees instead and lets the key resize the intervals, which is what\n * makes a sequence built on it a *tonal* sequence; the two are different devices\n * and are never reported as one. `'augmentation'` and `'diminution'` name the\n * relation only when the pitches are untouched, so an inversion in doubled note\n * values reads as `'inversion'` with a `timeRatio` of 2 rather than losing the\n * inversion to the stretch.\n *\n * @category Arrangement & Analysis\n */\nexport type MotifRelationKind =\n | 'repetition'\n | 'transposition'\n | 'tonalTransposition'\n | 'inversion'\n | 'retrograde'\n | 'retrogradeInversion'\n | 'augmentation'\n | 'diminution';\n\n/**\n * The transformation that turns one motif into another.\n *\n * @category Arrangement & Analysis\n */\nexport type MotifRelation = {\n /** The named transformation. */\n kind: MotifRelationKind;\n /**\n * Whether the second statement begins where the first ends, which is what\n * separates a sequence from a restatement elsewhere in the piece: a\n * `'transposition'` that is one is a real sequence, a `'tonalTransposition'`\n * that is one is a tonal sequence.\n */\n sequence: boolean;\n /** Semitones from the first statement's first note to the second's. */\n semitones: number;\n /** The same distance named as a spelled interval. */\n interval: SpelledInterval;\n /** Scale degrees the second statement stands above the first, when tonal. */\n degrees?: number;\n /**\n * How much the second statement is stretched against the first: 2 for doubled\n * note values, 0.5 for halved, 1 for unchanged.\n */\n timeRatio: number;\n /** Why the pair was given that name. */\n rationale: string;\n};\n\n/**\n * Anything that can be read as a melodic line: a motif, or plain note events.\n *\n * @category Arrangement & Analysis\n */\nexport type MelodicPhrase = MotifData | readonly NoteEvent[];\n\n/**\n * How alike two lines are, and how the answer was reached.\n *\n * @category Arrangement & Analysis\n */\nexport type MelodicComparison = {\n /** Overall likeness in [0, 1]; 1 for the same line, transposition included. */\n similarity: number;\n /** Likeness of the interval sequences alone, in [0, 1]. */\n pitchSimilarity: number;\n /** Likeness of the rhythmic profiles alone, in [0, 1]. */\n rhythmSimilarity: number;\n /** Why the lines scored what they did. */\n rationale: string;\n};\n\n/** The notes of anything that can be read as a line. */\nfunction phraseNotes(phrase: MelodicPhrase): readonly NoteEvent[] {\n return 'notes' in phrase ? phrase.notes : phrase;\n}\n\n/**\n * The sounding notes of a melody in time order.\n *\n * The line is expected to be monophonic, as {@link analyzeVoice} expects one;\n * notes sharing an onset are read low to high so the ordering is at least\n * deterministic.\n */\nfunction orderedNotes(\n notes: readonly NoteEvent[],\n name: string,\n budget?: number,\n): readonly NoteEvent[] {\n assertNoteEvents(notes, name, { allowNonPositiveDuration: true, budget });\n return notes\n .filter((note) => note.durationBeat > 0)\n .sort((a, b) => a.startBeat - b.startBeat || a.pitch - b.pitch);\n}\n\n/** Semitones between consecutive notes. */\nfunction intervalsOf(notes: readonly NoteEvent[]): number[] {\n const out: number[] = [];\n for (let i = 1; i < notes.length; i += 1) {\n out.push((notes[i]?.pitch ?? 0) - (notes[i - 1]?.pitch ?? 0));\n }\n return out;\n}\n\n/** Onset-to-onset gaps between consecutive notes. */\nfunction onsetGaps(notes: readonly NoteEvent[]): number[] {\n const out: number[] = [];\n for (let i = 1; i < notes.length; i += 1) {\n out.push((notes[i]?.startBeat ?? 0) - (notes[i - 1]?.startBeat ?? 0));\n }\n return out;\n}\n\n/**\n * Onset gaps as multiples of the first gap, rounded onto the comparison grid.\n *\n * Gaps rather than durations, because how long a note is held is a matter of\n * articulation — the same figure played staccato and legato is the same figure —\n * while where the next note falls is the rhythm itself. Normalising by the first\n * gap is what lets a statement in doubled note values match the motif it doubles;\n * the stretch is reported separately as a time ratio.\n *\n * A cell whose first gap is not positive has no profile to normalise by, and\n * answers null.\n */\nfunction rhythmProfile(gaps: readonly number[]): number[] | null {\n const unit = gaps[0] ?? 0;\n if (!(unit > EPS)) {\n return gaps.length === 0 ? [] : null;\n }\n return gaps.map((gap) => Math.round((gap / unit) * RHYTHM_GRID) / RHYTHM_GRID);\n}\n\n/** Compare two numeric sequences elementwise. */\nfunction sameNumbers(a: readonly number[], b: readonly number[]): boolean {\n if (a.length !== b.length) {\n return false;\n }\n for (let i = 0; i < a.length; i += 1) {\n if (Math.abs((a[i] ?? 0) - (b[i] ?? 0)) > EPS) {\n return false;\n }\n }\n return true;\n}\n\n/** Round for display, so a rationale never carries floating-point noise. */\nfunction roundTo(value: number, digits: number): number {\n const scale = 10 ** digits;\n return Math.round(value * scale) / scale;\n}\n\n/** Beat of the last note's end. */\nfunction endBeatOf(notes: readonly NoteEvent[]): number {\n let end = 0;\n for (const note of notes) {\n end = Math.max(end, note.startBeat + note.durationBeat);\n }\n return end;\n}\n\n/** Distance from the first onset to the last, which is what a stretch scales. */\nfunction onsetSpan(notes: readonly NoteEvent[]): number {\n if (notes.length < 2) {\n return 0;\n }\n return (notes[notes.length - 1]?.startBeat ?? 0) - (notes[0]?.startBeat ?? 0);\n}\n\n/** Whether a key writes its accidentals as flats. */\nfunction spellingOf(key?: KeyScale): 'sharp' | 'flat' {\n if (key === undefined) {\n return 'sharp';\n }\n const spelled = spelledKeyOf(key);\n return keySignatureFifths(spelled.tonic, spelled.key) < 0 ? 'flat' : 'sharp';\n}\n\n/** Name the distance between two pitches as a spelled interval. */\nfunction intervalBetween(from: number, to: number, key?: KeyScale): SpelledInterval {\n const spelling = spellingOf(key);\n return spelledInterval(midiToNote(from, spelling), midiToNote(to, spelling));\n}\n\n/** An interval name in the grammar `parseInterval` reads, such as `-m3`. */\nfunction intervalName(interval: SpelledInterval): string {\n return `${interval.descending && interval.semitones !== 0 ? '-' : ''}${interval.quality}${interval.number}`;\n}\n\n/**\n * Position of every note on the key's diatonic ladder, counted in scale degrees\n * from the root and continuing across octaves.\n *\n * This is what a tonal transposition moves by, and it only exists for a line\n * that stays in the key: one chromatic note and the answer is null, because a\n * degree cannot be named for a pitch the scale does not contain.\n */\nfunction ladderIndices(notes: readonly NoteEvent[], key: KeyScale): number[] | null {\n const root = pitchClass(key.rootPc);\n const offsets = scaleTonesInDegreeOrder(key).map((pc) => pitchClass(pc - root));\n if (offsets.length === 0) {\n return null;\n }\n const out: number[] = [];\n for (const note of notes) {\n const semitones = note.pitch - root;\n const octave = Math.floor(semitones / 12);\n const degree = offsets.indexOf(semitones - octave * 12);\n if (degree < 0) {\n return null;\n }\n out.push(octave * offsets.length + degree);\n }\n return out;\n}\n\n/** Differences between consecutive ladder positions. */\nfunction ladderSteps(indices: readonly number[]): number[] {\n const out: number[] = [];\n for (let i = 1; i < indices.length; i += 1) {\n out.push((indices[i] ?? 0) - (indices[i - 1] ?? 0));\n }\n return out;\n}\n\n/**\n * The identity of one window: its intervals and its rhythmic profile.\n *\n * Two windows sharing this string are the same cell, wherever they sit and\n * whatever pitch level they sit at. A window with no usable rhythmic profile has\n * no identity and answers null.\n */\nfunction windowSignature(notes: readonly NoteEvent[]): string | null {\n const profile = rhythmProfile(onsetGaps(notes));\n if (profile === null) {\n return null;\n }\n return `${notes.length}|${intervalsOf(notes).join(',')}|${profile.join(',')}`;\n}\n\n/** A group of identical windows, before it is decided whether it is a motif. */\ntype WindowGroup = {\n signature: string;\n length: number;\n /** Index of the first note of each window, ascending. */\n starts: number[];\n};\n\n/**\n * Keep the statements that do not overlap, earliest first.\n *\n * A cell of even notes matches itself one note along, so the raw matches of a\n * repetitive figure overlap heavily; counting those as separate statements would\n * report a motif as recurring far more often than it is heard to.\n */\nfunction nonOverlapping(starts: readonly number[], length: number): number[] {\n const picked: number[] = [];\n let lastEnd = Number.NEGATIVE_INFINITY;\n for (const start of starts) {\n if (start > lastEnd) {\n picked.push(start);\n lastEnd = start + length - 1;\n }\n }\n return picked;\n}\n\n/** Whether every statement of `inner` sits inside one statement of `outer`. */\nfunction subsumes(\n outer: { starts: number[]; length: number },\n inner: { starts: number[]; length: number },\n): boolean {\n return inner.starts.every((start) =>\n outer.starts.some((from) => start >= from && start + inner.length <= from + outer.length),\n );\n}\n\n/** List the statement onsets for a rationale without letting it run away. */\nfunction listBeats(occurrences: readonly MotifOccurrence[]): string {\n const shown = occurrences.slice(0, 5).map((occurrence) => roundTo(occurrence.startBeat, 3));\n return occurrences.length > shown.length\n ? `${shown.join(', ')}, and ${occurrences.length - shown.length} more`\n : shown.join(', ');\n}\n\n/** Build the motif a group of identical windows stands for. */\nfunction motifFromGroup(group: WindowGroup, notes: readonly NoteEvent[]): MotifData {\n const first = group.starts[0] ?? 0;\n const prime = notes.slice(first, first + group.length).map((note) => ({ ...note }));\n const primePitch = prime[0]?.pitch ?? 0;\n const primeSpan = onsetSpan(prime);\n const occurrences: MotifOccurrence[] = group.starts.map((start) => {\n const statement = notes.slice(start, start + group.length);\n const span = onsetSpan(statement);\n return {\n noteIndex: start,\n startBeat: statement[0]?.startBeat ?? 0,\n endBeat: endBeatOf(statement),\n transpose: (statement[0]?.pitch ?? 0) - primePitch,\n timeRatio: primeSpan > EPS ? span / primeSpan : 1,\n };\n });\n const shape = melodicContour(prime).shape;\n const transposed = occurrences.some((occurrence) => occurrence.transpose !== 0);\n const stretched = occurrences.some((occurrence) => Math.abs(occurrence.timeRatio - 1) > EPS);\n const detail = [\n transposed ? 'at more than one pitch level' : 'at the same pitch level',\n stretched ? 'and in more than one set of note values' : '',\n ]\n .filter((part) => part !== '')\n .join(' ');\n return {\n notes: prime,\n intervals: intervalsOf(prime),\n rhythm: rhythmProfile(onsetGaps(prime)) ?? [],\n occurrences,\n rationale:\n `${group.length}-note ${shape} cell stated ${occurrences.length} ` +\n `${occurrences.length === 1 ? 'time' : 'times'} (beats ${listBeats(occurrences)}), ${detail}`,\n };\n}\n\n/**\n * Find the cells a melody keeps coming back to.\n *\n * Every window of `minNotes` to `maxNotes` consecutive notes is reduced to its\n * interval sequence and its rhythmic profile, and windows sharing both are\n * statements of one motif — so a restatement a fifth higher, or one written in\n * doubled note values, is found as the same cell rather than as a new one.\n * Overlapping statements of a repetitive figure are counted once, and a cell\n * whose statements all sit inside the statements of a longer cell that recurs at\n * least as often is dropped, so the answer names the longest thing that recurs\n * rather than every fragment of it.\n *\n * The melody is expected to be monophonic, as {@link analyzeVoice} expects a\n * voice to be; split polyphonic material into lines first. Onsets are expected to\n * be quantised, since two statements must agree on their rhythm to be recognised\n * as one motif.\n *\n * @param notes The melody, in time order. Notes that never sound are dropped.\n * @param opts Cell-length bounds and the recurrence threshold; see\n * {@link ExtractMotifsOptions}.\n * @returns The motifs found, longest first, then by how often they recur, then\n * by where they first appear. Empty when nothing recurs.\n * @example\n * ```ts\n * import { extractMotifs } from '@libraz/libcantus';\n * const notes = [\n * { pitch: 60, startBeat: 0, durationBeat: 1 },\n * { pitch: 62, startBeat: 1, durationBeat: 1 },\n * { pitch: 64, startBeat: 2, durationBeat: 1 },\n * { pitch: 67, startBeat: 4, durationBeat: 1 },\n * { pitch: 69, startBeat: 5, durationBeat: 1 },\n * { pitch: 71, startBeat: 6, durationBeat: 1 },\n * ];\n * const motifs = extractMotifs(notes);\n * motifs[0]?.occurrences.map((o) => o.startBeat); // [0, 4]\n * ```\n * @category Arrangement & Analysis\n */\nexport function extractMotifs(\n notes: readonly NoteEvent[],\n opts: ExtractMotifsOptions = {},\n): MotifData[] {\n const minNotes = assertInteger(opts.minNotes ?? MIN_CELL_NOTES, 'motif minNotes', 2, 64);\n const maxNotes = assertInteger(opts.maxNotes ?? MAX_CELL_NOTES, 'motif maxNotes', 2, 64);\n if (maxNotes < minNotes) {\n throw new InvalidInputError(\n `motif maxNotes must be at least minNotes ${minNotes}; received ${maxNotes}`,\n );\n }\n const minOccurrences = assertInteger(\n opts.minOccurrences ?? MIN_OCCURRENCES,\n 'motif minOccurrences',\n 2,\n );\n const sounding = orderedNotes(notes, 'melody notes', opts.budget);\n if (sounding.length < minNotes) {\n return [];\n }\n assertGenerationBudget(sounding.length * (maxNotes - minNotes + 1), 'motif windows', opts.budget);\n\n const groups = new Map<string, WindowGroup>();\n for (let length = minNotes; length <= maxNotes; length += 1) {\n for (let start = 0; start + length <= sounding.length; start += 1) {\n const signature = windowSignature(sounding.slice(start, start + length));\n if (signature === null) {\n continue;\n }\n const group = groups.get(signature);\n if (group === undefined) {\n groups.set(signature, { signature, length, starts: [start] });\n } else {\n group.starts.push(start);\n }\n }\n }\n\n const candidates = [...groups.values()]\n .map((group) => ({ ...group, starts: nonOverlapping(group.starts, group.length) }))\n .filter((group) => group.starts.length >= minOccurrences)\n // Longest first, then most often stated, then earliest; the signature settles\n // any remaining tie so the order never depends on the map's insertion order.\n .sort(\n (a, b) =>\n b.length - a.length ||\n b.starts.length - a.starts.length ||\n (a.starts[0] ?? 0) - (b.starts[0] ?? 0) ||\n (a.signature < b.signature ? -1 : a.signature > b.signature ? 1 : 0),\n );\n\n const kept: WindowGroup[] = [];\n for (const candidate of candidates) {\n const covered = kept.some(\n (outer) =>\n outer.length > candidate.length &&\n outer.starts.length >= candidate.starts.length &&\n subsumes(outer, candidate),\n );\n if (!covered) {\n kept.push(candidate);\n }\n }\n return kept.map((group) => motifFromGroup(group, sounding));\n}\n\n/**\n * Read a run of notes as a single-statement motif.\n *\n * {@link extractMotifs} answers with motifs it found recurring; this is how a\n * caller names one it already has — a subject, an answer, a phrase lifted out of\n * a score — so that it can be handed to {@link relateMotifs}.\n *\n * @param notes The cell, in time order. Notes that never sound are dropped.\n * @returns The motif, carrying exactly one occurrence.\n * @example\n * ```ts\n * import { motifFromNotes, relateMotifs } from '@libraz/libcantus';\n * const subject = motifFromNotes([\n * { pitch: 60, startBeat: 0, durationBeat: 1 },\n * { pitch: 62, startBeat: 1, durationBeat: 1 },\n * { pitch: 64, startBeat: 2, durationBeat: 1 },\n * ]);\n * const answer = motifFromNotes([\n * { pitch: 67, startBeat: 3, durationBeat: 1 },\n * { pitch: 69, startBeat: 4, durationBeat: 1 },\n * { pitch: 71, startBeat: 5, durationBeat: 1 },\n * ]);\n * relateMotifs(subject, answer)?.kind; // 'transposition'\n * ```\n * @category Arrangement & Analysis\n */\nexport function motifFromNotes(notes: readonly NoteEvent[]): MotifData {\n const sounding = orderedNotes(notes, 'motif notes').map((note) => ({ ...note }));\n return motifFromGroup(\n { signature: windowSignature(sounding) ?? '', length: sounding.length, starts: [0] },\n sounding,\n );\n}\n\n/** The pitch relations one motif can stand in to another, before timing. */\ntype PitchRelation = 'exact' | 'inversion' | 'retrograde' | 'retrogradeInversion' | null;\n\n/**\n * Which pitch transformation carries `a`'s intervals onto `b`'s.\n *\n * Checked simplest first, so a cell symmetrical enough to answer to more than\n * one name — the retrograde of an evenly rising line is also its inversion —\n * takes the plainest of them.\n */\nfunction pitchRelation(a: readonly number[], b: readonly number[]): PitchRelation {\n if (sameNumbers(a, b)) {\n return 'exact';\n }\n if (\n sameNumbers(\n b,\n a.map((step) => -step),\n )\n ) {\n return 'inversion';\n }\n const reversed = [...a].reverse();\n if (\n sameNumbers(\n b,\n reversed.map((step) => -step),\n )\n ) {\n return 'retrograde';\n }\n if (sameNumbers(b, reversed)) {\n return 'retrogradeInversion';\n }\n return null;\n}\n\n/** Sentence naming a stretch, for the rationales that mention one. */\nfunction stretchPhrase(ratio: number): string {\n return `note values ${roundTo(ratio, 3)}x the model's`;\n}\n\n/**\n * Name how two statements of a motif relate.\n *\n * The two are compared as shapes, not as pitches: the interval sequences decide\n * the pitch transformation and the onset gaps decide the timing, so a statement\n * a fourth higher in doubled note values is recognised for what it is. A pair\n * whose rhythms do not correspond — the same way round for a repetition or a\n * transposition, back to front for a retrograde — is not a transformation of the\n * motif but a different figure, and answers null; {@link melodicSimilarity} is\n * what scores those.\n *\n * With a `key` in hand the search also asks whether the second statement is the\n * first moved by scale degrees rather than by semitones. That is the tonal\n * answer to the real one, and the two are kept apart: a real transposition keeps\n * every interval literally, a tonal one lets the key resize them, so a subject\n * answered a step higher reads as `'transposition'` when its major second stays\n * major and as `'tonalTransposition'` when the scale turns it minor.\n *\n * @param a The model statement.\n * @param b The statement to name against it.\n * @param key Key context for the tonal reading; without it, a statement that\n * only holds diatonically is not recognised.\n * @returns The relation, or null when the two stand in none.\n * @example\n * ```ts\n * import { majorKey, motifFromNotes, relateMotifs } from '@libraz/libcantus';\n * const model = motifFromNotes([\n * { pitch: 60, startBeat: 0, durationBeat: 1 },\n * { pitch: 62, startBeat: 1, durationBeat: 1 },\n * { pitch: 64, startBeat: 2, durationBeat: 1 },\n * ]);\n * const answer = motifFromNotes([\n * { pitch: 62, startBeat: 3, durationBeat: 1 },\n * { pitch: 64, startBeat: 4, durationBeat: 1 },\n * { pitch: 65, startBeat: 5, durationBeat: 1 },\n * ]);\n * relateMotifs(model, answer, majorKey(0))?.kind; // 'tonalTransposition'\n * ```\n * @category Arrangement & Analysis\n */\nexport function relateMotifs(a: MotifData, b: MotifData, key?: KeyScale): MotifRelation | null {\n const model = a.notes;\n const answer = b.notes;\n if (model.length === 0 || model.length !== answer.length) {\n return null;\n }\n const modelGaps = onsetGaps(model);\n const answerGaps = onsetGaps(answer);\n const modelProfile = rhythmProfile(modelGaps);\n const answerProfile = rhythmProfile(answerGaps);\n const retroProfile = rhythmProfile([...answerGaps].reverse());\n if (modelProfile === null || answerProfile === null) {\n return null;\n }\n const forward = sameNumbers(modelProfile, answerProfile);\n const backward = retroProfile !== null && sameNumbers(modelProfile, retroProfile);\n const modelSpan = onsetSpan(model);\n const timeRatio = modelSpan > EPS ? onsetSpan(answer) / modelSpan : 1;\n const semitones = (answer[0]?.pitch ?? 0) - (model[0]?.pitch ?? 0);\n const interval = intervalBetween(model[0]?.pitch ?? 0, answer[0]?.pitch ?? 0, key);\n const gap = (answer[0]?.startBeat ?? 0) - endBeatOf(model);\n const sequence = gap >= -SEQUENCE_GAP && gap <= SEQUENCE_GAP;\n const stretched = Math.abs(timeRatio - 1) > EPS;\n const relation = pitchRelation(intervalsOf(model), intervalsOf(answer));\n const base = { sequence, semitones, interval, timeRatio };\n\n if (forward && relation === 'exact') {\n if (semitones === 0) {\n if (!stretched) {\n return {\n ...base,\n kind: 'repetition',\n rationale: 'Exact repetition: the same pitches in the same rhythm',\n };\n }\n const augmented = timeRatio > 1;\n return {\n ...base,\n kind: augmented ? 'augmentation' : 'diminution',\n rationale: `${augmented ? 'Augmentation' : 'Diminution'}: the same pitches in ${stretchPhrase(timeRatio)}`,\n };\n }\n const name = intervalName(interval);\n return {\n ...base,\n kind: 'transposition',\n rationale: sequence\n ? `Real sequence: the cell restated ${name} away with every interval kept literally` +\n (stretched ? `, in ${stretchPhrase(timeRatio)}` : '')\n : `Transposition by ${name}: every interval kept literally` +\n (stretched ? `, in ${stretchPhrase(timeRatio)}` : ''),\n };\n }\n\n if (forward && key !== undefined) {\n const modelLadder = ladderIndices(model, key);\n const answerLadder = ladderIndices(answer, key);\n if (modelLadder !== null && answerLadder !== null) {\n const degrees = (answerLadder[0] ?? 0) - (modelLadder[0] ?? 0);\n if (degrees !== 0 && sameNumbers(ladderSteps(modelLadder), ladderSteps(answerLadder))) {\n const direction = degrees > 0 ? 'higher' : 'lower';\n const count = Math.abs(degrees);\n return {\n ...base,\n kind: 'tonalTransposition',\n degrees,\n rationale:\n `${sequence ? 'Tonal sequence' : 'Tonal transposition'}: the cell restated ` +\n `${count} scale ${count === 1 ? 'degree' : 'degrees'} ${direction} inside the key, ` +\n `so the key resizes its intervals — a real one would have kept them literal`,\n };\n }\n }\n }\n\n if (forward && relation === 'inversion') {\n return {\n ...base,\n kind: 'inversion',\n rationale:\n 'Inversion: every interval turned upside down about the first note' +\n (stretched ? `, in ${stretchPhrase(timeRatio)}` : ''),\n };\n }\n if (backward && relation === 'retrograde') {\n return {\n ...base,\n kind: 'retrograde',\n rationale: 'Retrograde: the cell read back to front, rhythm included',\n };\n }\n if (backward && relation === 'retrogradeInversion') {\n return {\n ...base,\n kind: 'retrogradeInversion',\n rationale: 'Retrograde inversion: the cell read back to front and turned upside down',\n };\n }\n return null;\n}\n\n/**\n * Edit distance between two sequences under a graded substitution cost.\n *\n * A flat cost would make a third answered by a fourth as wrong as a third\n * answered by a ninth; `cost` grades that, while an insertion or a deletion — a\n * note added or dropped — always costs one whole step.\n */\nfunction gradedEditDistance(\n a: readonly number[],\n b: readonly number[],\n cost: (x: number, y: number) => number,\n): number {\n let previous = Array.from({ length: b.length + 1 }, (_, i) => i);\n for (let i = 1; i <= a.length; i += 1) {\n const row = new Array<number>(b.length + 1);\n row[0] = i;\n for (let j = 1; j <= b.length; j += 1) {\n const substitution = (previous[j - 1] ?? 0) + cost(a[i - 1] ?? 0, b[j - 1] ?? 0);\n const deletion = (previous[j] ?? 0) + 1;\n const insertion = (row[j - 1] ?? 0) + 1;\n row[j] = Math.min(substitution, deletion, insertion);\n }\n previous = row;\n }\n return previous[b.length] ?? 0;\n}\n\n/** Turn an edit distance into a likeness in [0, 1]. */\nfunction distanceToSimilarity(distance: number, a: number, b: number): number {\n const longest = Math.max(a, b);\n if (longest === 0) {\n return 1;\n }\n return Math.min(1, Math.max(0, 1 - distance / longest));\n}\n\n/** Cost of hearing one interval where another was expected. */\nfunction intervalCost(x: number, y: number): number {\n return Math.min(1, Math.abs(x - y) / INTERVAL_TOLERANCE);\n}\n\n/**\n * Cost of hearing one onset gap where another was expected, measured in octaves\n * of tempo: a gap twice as long as expected costs a whole step, and the measure\n * is the same whichever of the two is the longer.\n */\nfunction gapCost(x: number, y: number): number {\n if (!(x > EPS) || !(y > EPS)) {\n return Math.abs(x - y) > EPS ? 1 : 0;\n }\n return Math.min(1, Math.abs(Math.log2(x / y)));\n}\n\n/**\n * Compare two lines and say why they scored what they did.\n *\n * The metric is a normalised edit distance over the interval sequence, weighted\n * against the same over the rhythmic profile. Intervals rather than pitches,\n * because the same tune in another key is the same tune; edit distance rather\n * than a step-by-step correlation, because a variant that adds a passing note or\n * drops one still has to line up with its model, and only an alignment-based\n * measure can do that. Substituting one interval for another costs by how far\n * apart they are rather than a flat step, so an answer that widens a third to a\n * fourth stays close to its model while one that replaces it with a leap does\n * not. The rhythm carries the smaller share: a melody is recognised mostly by\n * its pitch shape, but a figure in a wholly different rhythm is a different\n * figure.\n *\n * @param a The first line: a motif, or plain note events.\n * @param b The second line.\n * @returns The overall likeness, the two terms behind it, and a rationale.\n * @example\n * ```ts\n * import { compareMelodies } from '@libraz/libcantus';\n * const line = [\n * { pitch: 60, startBeat: 0, durationBeat: 1 },\n * { pitch: 62, startBeat: 1, durationBeat: 1 },\n * { pitch: 64, startBeat: 2, durationBeat: 1 },\n * ];\n * compareMelodies(line, line).similarity; // 1\n * ```\n * @category Arrangement & Analysis\n */\nexport function compareMelodies(a: MelodicPhrase, b: MelodicPhrase): MelodicComparison {\n const left = orderedNotes(phraseNotes(a), 'melody a');\n const right = orderedNotes(phraseNotes(b), 'melody b');\n const leftIntervals = intervalsOf(left);\n const rightIntervals = intervalsOf(right);\n const pitchSimilarity = distanceToSimilarity(\n gradedEditDistance(leftIntervals, rightIntervals, intervalCost),\n leftIntervals.length,\n rightIntervals.length,\n );\n const leftGaps = rhythmProfile(onsetGaps(left)) ?? onsetGaps(left);\n const rightGaps = rhythmProfile(onsetGaps(right)) ?? onsetGaps(right);\n const rhythmSimilarity = distanceToSimilarity(\n gradedEditDistance(leftGaps, rightGaps, gapCost),\n leftGaps.length,\n rightGaps.length,\n );\n const similarity = PITCH_WEIGHT * pitchSimilarity + RHYTHM_WEIGHT * rhythmSimilarity;\n return {\n similarity,\n pitchSimilarity,\n rhythmSimilarity,\n rationale:\n `Interval shapes ${Math.round(pitchSimilarity * 100)}% alike, ` +\n `rhythms ${Math.round(rhythmSimilarity * 100)}% alike, ` +\n `weighted ${PITCH_WEIGHT}/${RHYTHM_WEIGHT} into ${roundTo(similarity, 3)}`,\n };\n}\n\n/**\n * How alike two lines are, in [0, 1].\n *\n * The number {@link compareMelodies} reaches, for callers that only want to rank\n * phrases: 1 for the same line — a transposition of it included, since the\n * measure is built on intervals — and lower the further two lines are from being\n * each other's variant. Symmetric in its arguments, and deterministic.\n *\n * @param a The first line: a motif, or plain note events.\n * @param b The second line.\n * @returns The likeness in [0, 1].\n * @example\n * ```ts\n * import { melodicSimilarity } from '@libraz/libcantus';\n * const line = [\n * { pitch: 60, startBeat: 0, durationBeat: 1 },\n * { pitch: 62, startBeat: 1, durationBeat: 1 },\n * { pitch: 64, startBeat: 2, durationBeat: 1 },\n * ];\n * melodicSimilarity(line, line); // 1\n * ```\n * @category Arrangement & Analysis\n */\nexport function melodicSimilarity(a: MelodicPhrase, b: MelodicPhrase): number {\n return compareMelodies(a, b).similarity;\n}\n\n/** Where the highest and lowest notes of a line sit; ties keep the earliest. */\nfunction extremes(notes: readonly NoteEvent[]): { peak: number; trough: number } {\n let peak = 0;\n let trough = 0;\n for (let i = 1; i < notes.length; i += 1) {\n if ((notes[i]?.pitch ?? 0) > (notes[peak]?.pitch ?? 0)) {\n peak = i;\n }\n if ((notes[i]?.pitch ?? 0) < (notes[trough]?.pitch ?? 0)) {\n trough = i;\n }\n }\n return { peak, trough };\n}\n\n/**\n * Read the shape of a line.\n *\n * The step directions are the literal answer; the shape is what they add up to.\n * A line is called ascending or descending when most of its motion runs one way,\n * not only when every step does — a climb with one dip in it is still heard as a\n * climb — and an arch when it rises to an interior peak and comes back down.\n * Anything that keeps changing direction is a wave, and a line that does not move\n * is static.\n *\n * @param notes The line: a motif, or plain note events. Notes that never sound\n * are dropped.\n * @returns The directions, the shape, the peak and trough, and a rationale.\n * @example\n * ```ts\n * import { melodicContour } from '@libraz/libcantus';\n * const notes = [\n * { pitch: 60, startBeat: 0, durationBeat: 1 },\n * { pitch: 64, startBeat: 1, durationBeat: 1 },\n * { pitch: 60, startBeat: 2, durationBeat: 1 },\n * ];\n * melodicContour(notes).shape; // 'arch'\n * ```\n * @category Arrangement & Analysis\n */\nexport function melodicContour(notes: MelodicPhrase): MelodicContour {\n const line = orderedNotes(phraseNotes(notes), 'contour notes');\n const intervals = intervalsOf(line);\n const directions: ContourDirection[] = intervals.map((step) =>\n step > 0 ? 'up' : step < 0 ? 'down' : 'same',\n );\n const { peak, trough } = extremes(line);\n const range = (line[peak]?.pitch ?? 0) - (line[trough]?.pitch ?? 0);\n const motion = intervals.reduce((sum, step) => sum + Math.abs(step), 0);\n if (motion === 0) {\n return {\n directions,\n shape: 'static',\n peakIndex: peak,\n troughIndex: trough,\n range: 0,\n rationale:\n line.length < 2\n ? 'Too short to have a direction, so the line is static'\n : 'Every note at the same pitch, so the line is static',\n };\n }\n\n const first = line[0]?.pitch ?? 0;\n const last = line[line.length - 1]?.pitch ?? 0;\n const net = last - first;\n const directness = Math.abs(net) / motion;\n const share = Math.round(directness * 100);\n if (directness >= DIRECTIONAL_SHARE) {\n const shape = net > 0 ? 'ascending' : 'descending';\n return {\n directions,\n shape,\n peakIndex: peak,\n troughIndex: trough,\n range,\n rationale: `${share}% of the motion runs ${net > 0 ? 'up' : 'down'}, so the line is ${shape}`,\n };\n }\n\n const peakPitch = line[peak]?.pitch ?? 0;\n const archMotion = peakPitch - first + (peakPitch - last);\n if (peak > 0 && peak < line.length - 1 && archMotion >= DIRECTIONAL_SHARE * motion) {\n return {\n directions,\n shape: 'arch',\n peakIndex: peak,\n troughIndex: trough,\n range,\n rationale: `Rises to its peak at note ${peak + 1} of ${line.length} and falls back, so the line is an arch`,\n };\n }\n return {\n directions,\n shape: 'wave',\n peakIndex: peak,\n troughIndex: trough,\n range,\n rationale: `Changes direction without settling either way (${share}% of the motion nets out), so the line is a wave`,\n };\n}\n","/**\n * Phrases: the unit of music one bar larger than the chord.\n *\n * Every other analysis in this library is local — a chord against the next\n * chord, a note against the chord under it — and that is the ceiling on what it\n * can say. A cadence is a cadence because it falls at the end of a phrase, so\n * two chords read in isolation cannot settle how much weight one carries. This\n * module draws the boundaries, then ranks the cadences that sit on them.\n *\n * No single signal draws a phrase boundary reliably, so three are combined: the\n * cadences the harmony forms, the rests and long notes the melody breathes at,\n * and the points where the music starts saying something it has said before. A\n * boundary is reported with the evidence behind it and a confidence, because a\n * phrase reading is an interpretation and pretending otherwise would be worse\n * than useless to a caller deciding whether to trust it.\n */\n\nimport type { MeterMap, TimeSignature } from '../../core/meter/index.js';\nimport { barPositionToBeat, beatsPerBarAt, resolveMeters } from '../../core/meter/index.js';\nimport type { NoteEvent } from '../../core/types.js';\nimport {\n assertGenerationBudget,\n assertNoteEvents,\n assertRange,\n} from '../../core/validation/index.js';\nimport { majorKey } from '../../theory/scale/index.js';\nimport type { CadenceResult } from '../functional/index.js';\nimport { keyLookup, keyTimelineFromNotes, prevailingKeyOf } from '../keys/index.js';\nimport { melodicSimilarity } from '../melody/index.js';\nimport type { CadenceHit, ChordTimeline } from '../timeline/index.js';\nimport { detectCadences } from '../timeline/index.js';\nimport type { KeyContext } from '../voice/index.js';\nimport type { Hypermeter } from './hypermeter.js';\nimport { hypermeter } from './hypermeter.js';\nimport { clamp01, combineEvidence, EPS, sliceBars } from './internal.js';\n\n/**\n * A reason a phrase boundary was placed where it was.\n *\n * @category Arrangement & Analysis\n */\nexport type PhraseSignal =\n /** A cadence closed on the beat the boundary falls at. */\n | 'cadence'\n /** The melody stopped sounding long enough to have taken a breath. */\n | 'rest'\n /** A note was held long enough to read as an arrival. */\n | 'longNote'\n /** The music began repeating material heard immediately before. */\n | 'repetition'\n /** The boundary falls on a hypermetric downbeat. */\n | 'hypermeter'\n /** The music ends here, which closes whatever phrase was open. */\n | 'pieceEnd';\n\n/**\n * One phrase: a span of music closed by a boundary, and the evidence for it.\n *\n * @category Arrangement & Analysis\n */\nexport type Phrase = {\n /** First beat of the phrase; negative when the phrase begins in a pickup. */\n startBeat: number;\n /** End of the phrase, exclusive. */\n endBeat: number;\n /**\n * The cadence closing the phrase, or null when the boundary rests on the\n * melody and the metre alone.\n */\n cadence: CadenceHit | null;\n /** Why the closing boundary was placed here, strongest evidence first. */\n signals: PhraseSignal[];\n /**\n * How well evidenced the phrase is, in [0, 1]: how strong its closing\n * boundary is, and how close its length is to the length expected of a\n * phrase in this piece.\n */\n confidence: number;\n /**\n * How much structural weight the closing cadence carries, in [0, 1]; 0 for a\n * phrase that no cadence closes.\n *\n * This is the number that answers \"which cadence matters most\": it grades the\n * cadence by how conclusive its type is, then by where the phrase it closes\n * sits — a cadence ending the piece or landing on a hypermetric boundary\n * closes more than one in the middle of a hyperbar.\n * {@link structuralCadences} ranks by it.\n */\n structuralWeight: number;\n /** A short reading of the phrase in words. */\n rationale: string;\n};\n\n/**\n * Options for {@link phrasesFromTimeline}.\n *\n * @category Arrangement & Analysis\n */\nexport type PhraseOptions = {\n /**\n * A single time signature held across the whole span, as sugar for a\n * one-element `meters`; defaults to 4/4. Giving both is an input error.\n *\n * @defaultValue `4/4`\n */\n ts?: TimeSignature;\n /**\n * The meter as it changes over the span. Bar lines, hypermetric downbeats and\n * the default phrase length all follow the signature in force at the beat in\n * question, so a piece that changes metre is not read in the one it opened in.\n *\n * @defaultValue 4/4 throughout\n */\n meters?: MeterMap;\n /**\n * Key context for cadence detection: one key for the whole span, or the key\n * in force at a given beat. Omit it to have the key searched for over time,\n * so a piece that modulates has each cadence read in the key it arrives in.\n *\n * @defaultValue the key inferred from the notes over time\n */\n key?: KeyContext;\n /**\n * End of the analysed span in beats; defaults to the end of the last note or\n * timeline segment.\n *\n * @defaultValue the end of the input\n */\n totalBeats?: number;\n /**\n * Length a phrase is expected to run, in beats; defaults to one hyperbar.\n *\n * This is a prior, not a rule: a boundary is placed where the evidence puts\n * it, and this decides how much evidence an irregular length has to carry\n * before it is preferred to a regular one. Set it to two hyperbars for music\n * whose phrases are periods.\n *\n * @defaultValue one hyperbar of the opening meter\n */\n expectedPhraseBeats?: number;\n /**\n * Shortest phrase that may be reported, in beats; defaults to one bar of the\n * opening meter.\n *\n * @defaultValue one bar of the opening meter\n */\n minPhraseBeats?: number;\n /**\n * Silence at least this long divides phrases; defaults to half a bar of the\n * opening meter.\n *\n * @defaultValue half a bar of the opening meter\n */\n restBeats?: number;\n /**\n * A note held at least this long reads as an arrival and closes a phrase;\n * defaults to one bar of the opening meter.\n *\n * @defaultValue one bar of the opening meter\n */\n longNoteBeats?: number;\n /**\n * A hypermeter already computed for this piece, to be used instead of\n * inferring one. Pass it when the caller has already asked for the grouping,\n * or to impose a grouping the search would not have chosen.\n *\n * @defaultValue inferred from the notes and the cadences found\n */\n hypermeter?: Hypermeter;\n /**\n * Upper bound on the work this call may do.\n *\n * @defaultValue {@link DEFAULT_GENERATION_BUDGET}\n */\n budget?: number;\n};\n\n/**\n * A cadence ranked by how much of the structure it closes.\n *\n * @category Arrangement & Analysis\n */\nexport type StructuralCadence = {\n /** Index of the phrase this cadence closes, in the array it came from. */\n phraseIndex: number;\n /** The beat the cadence arrives on. */\n atBeat: number;\n /** The beat the phrase it closes ends at. */\n endBeat: number;\n /** The cadence itself. */\n cadence: CadenceResult;\n /** Structural weight in [0, 1]; {@link Phrase.structuralWeight}. */\n weight: number;\n /** Why it weighs what it does. */\n rationale: string;\n};\n\n/**\n * How strongly each cadence type marks a boundary.\n *\n * This grades the cadence as a punctuation mark, not as a close: a half cadence\n * ends an antecedent as audibly as an authentic one ends a consequent, which is\n * why it scores near it here and far below it in {@link CADENCE_CLOSURE}. A\n * deceptive cadence scores lowest of all because its whole effect is to\n * withhold the ending it prepared, so the phrase usually runs on past it.\n */\nconst CADENCE_BOUNDARY_STRENGTH: Record<NonNullable<CadenceResult['type']>, number> = {\n authentic: 0.9,\n modal: 0.75,\n plagal: 0.7,\n phrygian: 0.65,\n half: 0.6,\n deceptive: 0.45,\n};\n\n/**\n * How much of the structure each cadence type closes.\n *\n * The perfect authentic cadence is the only one that ends anything outright;\n * everything else leaves the music open to some degree, and the half cadence\n * most of all — it ends a phrase by pointing at the next one.\n */\nconst CADENCE_CLOSURE: Record<NonNullable<CadenceResult['type']>, number> = {\n authentic: 0.85,\n modal: 0.6,\n plagal: 0.55,\n phrygian: 0.4,\n half: 0.35,\n deceptive: 0.3,\n};\n\n/** Weight of a full-bar rest as a boundary signal. */\nconst REST_STRENGTH = 0.6;\n\n/** Weight of a held note as a boundary signal. */\nconst LONG_NOTE_STRENGTH = 0.4;\n\n/** Weight of immediate repetition as a boundary signal. */\nconst REPETITION_STRENGTH = 0.5;\n\n/** How alike two adjacent spans must be before the second is heard as a restart. */\nconst REPETITION_THRESHOLD = 0.75;\n\n/** Weight of a hypermetric downbeat as a boundary signal. */\nconst HYPERMETER_STRENGTH = 0.25;\n\n/** Weight of the end of the music as a boundary signal. */\nconst PIECE_END_STRENGTH = 0.5;\n\n/**\n * What a phrase boundary has to be worth before one is placed.\n *\n * The search adds up boundary evidence and length fit across a whole reading,\n * so without a cost per cut it would cut wherever cutting was not actively\n * wrong. Set just above the best length fit alone, so that a boundary needs\n * some evidence of its own: metre may confirm a phrase ending, but it may not\n * declare one. A boundary worth exactly this much is break-even, and a\n * break-even boundary is not taken — see {@link choosePhrasePath}.\n *\n * Not part of the public surface; exported so the search's break-even point can\n * be stated exactly rather than approximated.\n */\nexport const PHRASE_CUT_COST = 1.3;\n\n/** Share of a phrase's confidence carried by its closing boundary. */\nconst CLOSING_SHARE = 0.65;\n\n/** Bonus for a cadence that closes the last phrase of the piece. */\nconst FINAL_PHRASE_BONUS = 0.15;\n\n/** Bonus for a cadence whose phrase ends on a hypermetric boundary. */\nconst HYPERMETRIC_CLOSE_BONUS = 0.1;\n\n/** How much of a cadence's weight survives resolving onto an inverted tonic. */\nconst EVADED_FACTOR = 0.6;\n\n/** Order the boundary signals are reported in when they are equally strong. */\nconst SIGNAL_ORDER: readonly PhraseSignal[] = [\n 'cadence',\n 'rest',\n 'repetition',\n 'longNote',\n 'pieceEnd',\n 'hypermeter',\n];\n\n/** A candidate boundary and everything that argues for it. */\ntype Boundary = {\n beat: number;\n strengths: Map<PhraseSignal, number>;\n cadence: CadenceHit | null;\n};\n\n/** Record a signal at a beat, keeping the strongest claim made for it. */\nfunction addSignal(\n boundaries: Map<number, Boundary>,\n beat: number,\n signal: PhraseSignal,\n strength: number,\n cadence: CadenceHit | null = null,\n): void {\n // Beats arrive from bar arithmetic and from note ends, which land an epsilon\n // apart on the same boundary; keying on a rounded beat merges them.\n const key = Math.round(beat / EPS);\n const existing = boundaries.get(key);\n const boundary = existing ?? { beat, strengths: new Map(), cadence: null };\n boundary.strengths.set(signal, Math.max(boundary.strengths.get(signal) ?? 0, strength));\n if (cadence !== null && boundary.cadence === null) {\n boundary.cadence = cadence;\n }\n boundaries.set(key, boundary);\n}\n\n/** Total strength of a boundary, and its signals strongest first. */\nfunction readBoundary(boundary: Boundary): { strength: number; signals: PhraseSignal[] } {\n const signals = [...boundary.strengths.entries()].sort(\n (a, b) => b[1] - a[1] || SIGNAL_ORDER.indexOf(a[0]) - SIGNAL_ORDER.indexOf(b[0]),\n );\n return {\n strength: combineEvidence(signals.map(([, strength]) => strength)),\n signals: signals.map(([signal]) => signal),\n };\n}\n\n/**\n * How well a length matches what a phrase is expected to run.\n *\n * 1 at the expected length, 0 at twice or none of it, and negative beyond —\n * a phrase three times the expected length is not merely unsurprising, it is\n * evidence that a boundary was missed.\n */\nfunction lengthFit(length: number, expected: number): number {\n if (!(expected > 0)) {\n return 0;\n }\n return Math.max(-1, 1 - Math.abs(length - expected) / expected);\n}\n\n/**\n * Choose which of the candidate stops become phrase boundaries.\n *\n * A reading is worth the evidence at every boundary it takes, plus how well\n * each phrase's length matches what a phrase is expected to run, less a cost\n * per cut. The best reading is found by dynamic programming over the stops,\n * since a boundary's worth depends on where the previous one fell.\n *\n * Predecessors are considered in time order, and only a strictly better reading\n * displaces the one already found. Two readings that score the same therefore\n * settle on the earlier predecessor — the longer phrase, with one boundary\n * fewer — because a search may not invent a boundary that the evidence does not\n * prefer to having none. The rule is a property of the comparison, not of the\n * order the loop happens to run in.\n *\n * Not part of the public surface: it takes the stops and strengths some other\n * pass already derived, so exposing it would invite callers to pair the two by\n * hand.\n *\n * @param beats Beat of each stop, ascending; index 0 is the span's start and\n * the last is its end.\n * @param strengths Boundary evidence at each stop, in the same order.\n * @param minPhraseBeats Shortest phrase the search may report.\n * @param expected Length a phrase is expected to run, in beats.\n * @returns The chosen stop indices after the start, in time order.\n */\nexport function choosePhrasePath(\n beats: readonly number[],\n strengths: readonly number[],\n minPhraseBeats: number,\n expected: number,\n): number[] {\n const value = new Array<number>(beats.length).fill(Number.NEGATIVE_INFINITY);\n const cameFrom = new Array<number>(beats.length).fill(-1);\n value[0] = 0;\n for (let j = 1; j < beats.length; j += 1) {\n const endBeat = beats[j] ?? 0;\n for (let i = 0; i < j; i += 1) {\n if (!Number.isFinite(value[i] ?? Number.NEGATIVE_INFINITY)) {\n continue;\n }\n const length = endBeat - (beats[i] ?? 0);\n if (length < minPhraseBeats - EPS) {\n continue;\n }\n const gain =\n (value[i] ?? 0) + (strengths[j] ?? 0) + lengthFit(length, expected) - PHRASE_CUT_COST;\n if (gain > (value[j] ?? Number.NEGATIVE_INFINITY)) {\n value[j] = gain;\n cameFrom[j] = i;\n }\n }\n }\n\n const last = beats.length - 1;\n const path: number[] = [];\n if (last > 0 && Number.isFinite(value[last] ?? Number.NEGATIVE_INFINITY)) {\n for (let i = last; i > 0; i = cameFrom[i] ?? 0) {\n path.push(i);\n }\n path.reverse();\n } else if (last > 0) {\n // Nothing reached the end — the span is shorter than one phrase — so the\n // whole of it is the phrase.\n path.push(last);\n }\n return path;\n}\n\n/** The beat at which the chord arriving at `atBeat` gives way to the next. */\nfunction arrivalEnd(timeline: ChordTimeline, atBeat: number): number {\n for (const segment of timeline.segments) {\n if (Math.abs(segment.startBeat - atBeat) < EPS) {\n return segment.endBeat;\n }\n }\n return atBeat;\n}\n\n/** Resolve the key context cadence detection is to be read against. */\nfunction resolveKey(\n opts: PhraseOptions,\n notes: NoteEvent[],\n meters: MeterMap,\n totalBeats: number,\n): KeyContext {\n if (opts.key !== undefined) {\n return opts.key;\n }\n const regions = keyTimelineFromNotes(notes, {\n meters,\n totalBeats,\n budget: opts.budget,\n });\n return keyLookup(regions, prevailingKeyOf(regions) ?? majorKey(0));\n}\n\n/** Boundaries argued for by silence in the melody. */\nfunction restBoundaries(\n boundaries: Map<number, Boundary>,\n notes: readonly NoteEvent[],\n meters: MeterMap,\n restBeats: number,\n): void {\n const ordered = [...notes].sort((a, b) => a.startBeat - b.startBeat || a.pitch - b.pitch);\n let covered = Number.NEGATIVE_INFINITY;\n for (const note of ordered) {\n const gap = note.startBeat - covered;\n if (Number.isFinite(covered) && gap >= restBeats - EPS) {\n const bar = beatsPerBarAt(note.startBeat, meters);\n addSignal(\n boundaries,\n note.startBeat,\n 'rest',\n REST_STRENGTH * clamp01(bar > 0 ? gap / bar : 1),\n );\n }\n covered = Math.max(covered, note.startBeat + note.durationBeat);\n }\n}\n\n/** Boundaries argued for by immediate repetition of the preceding span. */\nfunction repetitionBoundaries(\n boundaries: Map<number, Boundary>,\n notes: readonly NoteEvent[],\n meters: MeterMap,\n spanStart: number,\n spanEnd: number,\n windowBeats: number,\n budget: number | undefined,\n): void {\n if (!(windowBeats > 0)) {\n return;\n }\n const slices = sliceBars(notes, meters, spanStart, spanEnd, budget);\n assertGenerationBudget(slices.length * notes.length, 'form repetition comparisons', budget);\n const onsetsIn = (from: number, to: number): NoteEvent[] =>\n notes.filter((note) => note.startBeat >= from - EPS && note.startBeat < to - EPS);\n for (const slice of slices) {\n const beat = slice.startBeat;\n if (beat - windowBeats < spanStart - EPS || beat + windowBeats > spanEnd + EPS) {\n continue;\n }\n const before = onsetsIn(beat - windowBeats, beat);\n const after = onsetsIn(beat, beat + windowBeats);\n // One note is a pitch, not a figure; two identical single notes would score\n // a perfect likeness and mark a boundary at every held tone.\n if (before.length < 2 || after.length < 2) {\n continue;\n }\n const similarity = melodicSimilarity(before, after);\n if (similarity >= REPETITION_THRESHOLD) {\n addSignal(boundaries, beat, 'repetition', REPETITION_STRENGTH * similarity);\n }\n }\n}\n\n/** Grade a cadence by how much of the structure it closes. */\nfunction structuralWeightOf(\n cadence: CadenceHit | null,\n confidence: number,\n aligned: boolean,\n isLast: boolean,\n): number {\n if (cadence === null || cadence.cadence.type === null) {\n return 0;\n }\n const result = cadence.cadence;\n let base = CADENCE_CLOSURE[result.type as NonNullable<CadenceResult['type']>];\n if (result.type === 'authentic') {\n if (result.strength === 'perfect') {\n base = 1;\n } else if (result.strength === 'imperfect') {\n base *= 0.8;\n }\n }\n if (result.evaded) {\n base *= EVADED_FACTOR;\n }\n let weight = base * confidence;\n if (isLast) {\n weight += FINAL_PHRASE_BONUS;\n }\n if (aligned || isLast) {\n weight += HYPERMETRIC_CLOSE_BONUS;\n }\n return clamp01(weight);\n}\n\n/** Name a cadence the way a rationale reads it. */\nfunction describeCadence(cadence: CadenceHit | null): string {\n if (cadence === null || cadence.cadence.type === null) {\n return 'no cadence';\n }\n const { type, strength } = cadence.cadence;\n const graded = strength === null ? type : `${strength} ${type}`;\n return `a ${graded} cadence at beat ${cadence.atBeat}`;\n}\n\n/**\n * Split a piece into phrases.\n *\n * Three kinds of evidence are gathered independently and then reconciled. The\n * harmony contributes its cadences, each closing at the end of the chord it\n * arrives on. The melody contributes the rests it breathes at and the notes it\n * holds long enough to read as arrivals. Repetition contributes the beats where\n * the music restarts material it has just played. The metre contributes its\n * hypermetric downbeats, which confirm a boundary but never declare one on\n * their own.\n *\n * The boundaries are then chosen together rather than one at a time: the\n * reading that best trades each boundary's evidence against how regular the\n * phrase lengths come out is selected by dynamic programming, at a fixed cost\n * per cut. That is what stops a phrase being declared at every plausible beat,\n * and what lets a well-evidenced irregular phrase survive next to regular ones.\n *\n * The result is contiguous: phrases abut, and together they cover the span from\n * the first note to the end of the music. A cadence found in the middle of a\n * phrase is not lost — it is still reachable through {@link detectCadences} —\n * but only the cadences the reading puts at phrase ends are ranked by\n * {@link structuralCadences}.\n *\n * @param timeline The chord timeline the cadences are read from.\n * @param notes The melody, for its rests, held notes and repetitions. Notes\n * that never sound are dropped.\n * @param opts Analysis options; see {@link PhraseOptions}.\n * @returns The phrases, in time order; one phrase covering the whole span when\n * the span is too short to divide.\n * @example\n * ```ts\n * import { chordTimelineFromNotes, phrasesFromTimeline } from '@libraz/libcantus';\n * const notes = [\n * { pitch: 60, startBeat: 0, durationBeat: 4 },\n * { pitch: 67, startBeat: 4, durationBeat: 4 },\n * { pitch: 60, startBeat: 8, durationBeat: 8 },\n * ];\n * const { timeline } = chordTimelineFromNotes(notes);\n * const phrases = phrasesFromTimeline(timeline, notes);\n * phrases[phrases.length - 1]?.cadence?.cadence.type; // 'authentic', when one closes it\n * ```\n * @category Arrangement & Analysis\n */\nexport function phrasesFromTimeline(\n timeline: ChordTimeline,\n notes: readonly NoteEvent[],\n opts: PhraseOptions = {},\n): Phrase[] {\n const meters = resolveMeters(opts, 'phrase meters');\n assertNoteEvents(notes, 'phrase notes', {\n allowNonPositiveDuration: true,\n budget: opts.budget,\n });\n const sounding = notes.filter((note) => note.durationBeat > 0);\n const segments = timeline.segments;\n const firstSegment = segments[0];\n const lastSegment = segments[segments.length - 1];\n const spanStart = sounding.reduce(\n (first, n) => Math.min(first, n.startBeat),\n firstSegment?.startBeat ?? 0,\n );\n const notesEnd = sounding.reduce(\n (end, n) => Math.max(end, n.startBeat + n.durationBeat),\n lastSegment?.endBeat ?? 0,\n );\n const spanEnd = Math.max(spanStart, opts.totalBeats ?? notesEnd);\n assertRange(spanEnd, 0, Number.MAX_SAFE_INTEGER, 'phrase totalBeats');\n\n const barBeats = beatsPerBarAt(Math.max(0, spanStart), meters);\n const restBeats = opts.restBeats ?? barBeats / 2;\n assertRange(restBeats, Number.MIN_VALUE, Number.MAX_SAFE_INTEGER, 'phrase restBeats');\n const longNoteBeats = opts.longNoteBeats ?? barBeats;\n assertRange(longNoteBeats, Number.MIN_VALUE, Number.MAX_SAFE_INTEGER, 'phrase longNoteBeats');\n const minPhraseBeats = opts.minPhraseBeats ?? barBeats;\n assertRange(minPhraseBeats, Number.MIN_VALUE, Number.MAX_SAFE_INTEGER, 'phrase minPhraseBeats');\n\n const key = resolveKey(opts, sounding, meters, spanEnd);\n const cadences = detectCadences(timeline, key);\n const grouping =\n opts.hypermeter ??\n hypermeter(sounding, meters, {\n totalBeats: spanEnd,\n cadenceBeats: cadences.map((hit) => hit.atBeat),\n budget: opts.budget,\n });\n const hyperBeats = Math.max(\n barBeats,\n barPositionToBeat({ bar: grouping.groupBars, beat: 0 }, meters) -\n barPositionToBeat({ bar: 0, beat: 0 }, meters),\n );\n const expected = opts.expectedPhraseBeats ?? hyperBeats;\n assertRange(expected, Number.MIN_VALUE, Number.MAX_SAFE_INTEGER, 'phrase expectedPhraseBeats');\n\n const boundaries = new Map<number, Boundary>();\n for (const hit of cadences) {\n const type = hit.cadence.type;\n if (type === null) {\n continue;\n }\n addSignal(\n boundaries,\n arrivalEnd(timeline, hit.atBeat),\n 'cadence',\n CADENCE_BOUNDARY_STRENGTH[type],\n hit,\n );\n }\n restBoundaries(boundaries, sounding, meters, restBeats);\n for (const note of sounding) {\n if (note.durationBeat >= longNoteBeats - EPS) {\n addSignal(boundaries, note.startBeat + note.durationBeat, 'longNote', LONG_NOTE_STRENGTH);\n }\n }\n repetitionBoundaries(boundaries, sounding, meters, spanStart, spanEnd, hyperBeats, opts.budget);\n for (const downbeat of grouping.downbeats) {\n addSignal(boundaries, downbeat, 'hypermeter', HYPERMETER_STRENGTH * grouping.confidence);\n }\n addSignal(boundaries, spanEnd, 'pieceEnd', PIECE_END_STRENGTH);\n\n // The span's own ends are boundaries whether or not anything argues for them,\n // so the phrases cover the music rather than only its well-marked middle.\n const candidates = [...boundaries.values()]\n .filter((boundary) => boundary.beat > spanStart + EPS && boundary.beat < spanEnd - EPS)\n .sort((a, b) => a.beat - b.beat);\n const endBoundary = boundaries.get(Math.round(spanEnd / EPS)) ?? {\n beat: spanEnd,\n strengths: new Map<PhraseSignal, number>(),\n cadence: null,\n };\n const stops: Boundary[] = [\n { beat: spanStart, strengths: new Map(), cadence: null },\n ...candidates,\n endBoundary,\n ];\n assertGenerationBudget(stops.length * stops.length, 'form phrase boundary pairs', opts.budget);\n\n const readings = stops.map(readBoundary);\n const path = choosePhrasePath(\n stops.map((stop) => stop.beat),\n readings.map((reading) => reading.strength),\n minPhraseBeats,\n expected,\n );\n\n const downbeatKeys = new Set(grouping.downbeats.map((downbeat) => Math.round(downbeat / EPS)));\n const phrases: Phrase[] = [];\n let from = 0;\n for (let n = 0; n < path.length; n += 1) {\n const to = path[n] ?? stops.length - 1;\n const startBeat = stops[from]?.beat ?? spanStart;\n const stop = stops[to];\n const endBeat = stop?.beat ?? spanEnd;\n const reading = readings[to] ?? { strength: 0, signals: [] as PhraseSignal[] };\n const length = endBeat - startBeat;\n const confidence = clamp01(\n CLOSING_SHARE * reading.strength +\n (1 - CLOSING_SHARE) * Math.max(0, lengthFit(length, expected)),\n );\n const isLast = n === path.length - 1;\n const cadence = stop?.cadence ?? null;\n const structuralWeight = structuralWeightOf(\n cadence,\n confidence,\n downbeatKeys.has(Math.round(endBeat / EPS)),\n isLast,\n );\n const bars = barBeats > 0 ? Math.round((length / barBeats) * 100) / 100 : 0;\n phrases.push({\n startBeat,\n endBeat,\n cadence,\n signals: reading.signals,\n confidence,\n structuralWeight,\n rationale:\n `Phrase of ${bars} bar(s) closing on ${describeCadence(cadence)}; ` +\n `boundary from ${reading.signals.length === 0 ? 'the end of the span' : reading.signals.join(', ')}`,\n });\n from = to;\n }\n return phrases;\n}\n\n/**\n * Rank the cadences that close phrases by how much structure they close.\n *\n * This is the question the phrase layer exists to answer. Two chords alone\n * cannot say whether a V-to-I is the end of the piece or a passing confirmation\n * halfway through a hyperbar; a phrase reading can, because it knows what the\n * cadence closes and where that sits. Phrases no cadence closes are left out.\n *\n * @param phrases The phrases, as {@link phrasesFromTimeline} returns them.\n * @returns The cadences, heaviest first; ties keep the earlier one.\n * @example\n * ```ts\n * import { chordTimelineFromNotes, phrasesFromTimeline, structuralCadences } from '@libraz/libcantus';\n * const notes = [\n * { pitch: 60, startBeat: 0, durationBeat: 4 },\n * { pitch: 67, startBeat: 4, durationBeat: 4 },\n * { pitch: 60, startBeat: 8, durationBeat: 8 },\n * ];\n * const { timeline } = chordTimelineFromNotes(notes);\n * const ranked = structuralCadences(phrasesFromTimeline(timeline, notes));\n * ranked[0]; // the cadence carrying the most structural weight, if any\n * ```\n * @category Arrangement & Analysis\n */\nexport function structuralCadences(phrases: readonly Phrase[]): StructuralCadence[] {\n const ranked: StructuralCadence[] = [];\n for (let index = 0; index < phrases.length; index += 1) {\n const phrase = phrases[index];\n if (phrase === undefined || phrase.cadence === null || phrase.cadence.cadence.type === null) {\n continue;\n }\n const hit = phrase.cadence;\n ranked.push({\n phraseIndex: index,\n atBeat: hit.atBeat,\n endBeat: phrase.endBeat,\n cadence: hit.cadence,\n weight: phrase.structuralWeight,\n rationale: `Closes ${describeCadence(hit)} on a phrase of confidence ${\n Math.round(phrase.confidence * 100) / 100\n }`,\n });\n }\n return ranked.sort((a, b) => b.weight - a.weight || a.atBeat - b.atBeat);\n}\n","/**\n * Sections: the largest unit this library reads, recovered from repetition.\n *\n * A section is found, not named. What the analysis can see is that a span of\n * music is a restatement of an earlier one, so the spans are labelled by order\n * of first appearance — A, B, C — and nothing more is claimed. Calling a\n * repeated span a chorus would be a guess dressed as a finding: repetition\n * alone cannot tell a chorus from a second verse, and the label a listener\n * would give depends on lyrics, arrangement and loudness that the notes do not\n * carry.\n */\n\nimport type { MeterMap, TimeSignature } from '../../core/meter/index.js';\nimport { barIndexAt, barPositionToBeat, resolveMeters } from '../../core/meter/index.js';\nimport type { NoteEvent } from '../../core/types.js';\nimport {\n assertGenerationBudget,\n assertNoteEvents,\n assertPositiveInt,\n assertRange,\n} from '../../core/validation/index.js';\nimport { windowWeights } from '../histogram.js';\nimport { melodicSimilarity } from '../melody/index.js';\nimport { hypermeter } from './hypermeter.js';\nimport { clamp01, EPS, firstSoundingBeat, lastBarOf, weightSimilarity } from './internal.js';\n\n/**\n * A span of music labelled by which earlier span it restates.\n *\n * @category Arrangement & Analysis\n */\nexport type FormSection = {\n /**\n * The section's letter, assigned in order of first appearance: the opening\n * material is A, the first material unlike it is B, and a later restatement\n * of A is A again. Past Z the letters double (AA, AB), the way spreadsheet\n * columns do. No functional name — verse, chorus, bridge — is ever inferred.\n */\n label: string;\n /** First beat of the section; negative when it opens with a pickup. */\n startBeat: number;\n /** End of the section, exclusive. */\n endBeat: number;\n /** The section's length in bars. */\n bars: number;\n /**\n * Index, in the returned array, of the first section carrying this label; the\n * section's own index when it is that first appearance.\n */\n firstOccurrence: number;\n /**\n * How alike this section and its first occurrence are, in [0, 1]; 1 on the\n * first occurrence itself. The mean over the units the section was merged\n * from, each scored against the unit it matched.\n */\n similarity: number;\n /** How the section came to carry its label. */\n rationale: string;\n};\n\n/**\n * Options for {@link sectionsFromNotes}.\n *\n * @category Arrangement & Analysis\n */\nexport type FormSectionOptions = {\n /**\n * A single time signature held across the whole span, as sugar for a\n * one-element `meters`; defaults to 4/4. Giving both is an input error.\n *\n * @defaultValue `4/4`\n */\n ts?: TimeSignature;\n /**\n * The meter as it changes over the span, so a piece that changes metre is cut\n * at its own bar lines.\n *\n * @defaultValue 4/4 throughout\n */\n meters?: MeterMap;\n /**\n * Length of the unit spans are compared in, in bars; defaults to one hyperbar.\n *\n * Sections are found by comparing equal units and merging the neighbours that\n * match, so this is the resolution of the answer rather than the length of a\n * section: a section is any whole number of units long.\n *\n * @defaultValue the inferred hyperbar, in bars\n */\n unitBars?: number;\n /**\n * How alike two units must be before the later one is called a restatement of\n * the earlier, in [0, 1].\n *\n * @defaultValue `0.8`\n */\n threshold?: number;\n /**\n * End of the analysed span in beats; defaults to the end of the last note.\n *\n * @defaultValue the end of the last note\n */\n totalBeats?: number;\n /**\n * Upper bound on the work this call may do.\n *\n * @defaultValue {@link DEFAULT_GENERATION_BUDGET}\n */\n budget?: number;\n};\n\n/** Share of a unit's likeness carried by its melodic line. */\nconst MELODY_SHARE = 0.6;\n\n/** Share of a unit's likeness carried by its harmonic content. */\nconst HARMONY_SHARE = 0.4;\n\n/** How alike two units must be before one is called a restatement of the other. */\nconst DEFAULT_THRESHOLD = 0.8;\n\n/** One unit of comparison: a fixed run of bars and what sounds in it. */\ntype Unit = {\n startBeat: number;\n endBeat: number;\n /** The notes whose onsets fall inside the unit, in time order. */\n notes: NoteEvent[];\n /** Pitch-class weights over the unit. */\n weights: number[];\n};\n\n/**\n * The nth label in the sequence A, B, ... Z, AA, AB, ...\n *\n * Doubling the letters past Z rather than numbering them (A1, A2) keeps the\n * label free of digits, so nothing reads as \"the second A\" when it is in fact\n * the twenty-seventh distinct span.\n */\nfunction labelAt(index: number): string {\n let n = index;\n let label = '';\n do {\n label = String.fromCharCode(65 + (n % 26)) + label;\n n = Math.floor(n / 26) - 1;\n } while (n >= 0);\n return label;\n}\n\n/**\n * How alike two units are, in [0, 1].\n *\n * Melody and harmony are both asked, because either alone is fooled: the same\n * tune over new chords is a variation and not a restatement, and the same\n * chords under a new tune is a different section of the same song. A unit with\n * too little melody to compare falls back to its harmony alone.\n */\nfunction unitSimilarity(a: Unit, b: Unit): number {\n const harmony = weightSimilarity(a.weights, b.weights);\n if (a.notes.length < 2 || b.notes.length < 2) {\n return a.notes.length === b.notes.length ? harmony : 0;\n }\n const melody = melodicSimilarity(a.notes, b.notes);\n return clamp01(MELODY_SHARE * melody + HARMONY_SHARE * harmony);\n}\n\n/**\n * Recover a piece's sections from what it repeats.\n *\n * The span is cut into equal units of `unitBars` bars, each unit is compared\n * with every unit before it, and a unit alike enough to an earlier one takes\n * that unit's label. Neighbouring units carrying the same label are then merged,\n * so an eight-bar section built of two like four-bar units is reported as one\n * section rather than two.\n *\n * Likeness is measured on the melodic line and on the harmonic content\n * together — the same tune under different harmony is a variation, not a\n * restatement — and the labels are letters in order of first appearance.\n * Nothing beyond \"this is a restatement of that\" is asserted: see\n * {@link FormSection.label}.\n *\n * A pickup belongs to the section it leads into, so material before the first\n * bar is folded into the opening unit rather than made a section of its own.\n *\n * @param notes The piece's notes, in any order. Notes that never sound are\n * dropped.\n * @param opts Analysis options; see {@link FormSectionOptions}.\n * @returns The sections, in time order.\n * @example\n * ```ts\n * import { sectionsFromNotes } from '@libraz/libcantus';\n * const bar = (pitch: number, at: number) => ({ pitch, startBeat: at, durationBeat: 4 });\n * const notes = [bar(60, 0), bar(64, 4), bar(67, 8), bar(60, 12)];\n * sectionsFromNotes(notes, { unitBars: 2 }).map((section) => section.label);\n * ```\n * @category Arrangement & Analysis\n */\nexport function sectionsFromNotes(\n notes: readonly NoteEvent[],\n opts: FormSectionOptions = {},\n): FormSection[] {\n const meters = resolveMeters(opts, 'section meters');\n assertNoteEvents(notes, 'section notes', {\n allowNonPositiveDuration: true,\n budget: opts.budget,\n });\n const sounding = notes.filter((note) => note.durationBeat > 0);\n if (sounding.length === 0) {\n // Nothing sounds, so there is no material to hear a form in. A section over\n // an empty span would name a stretch of silence as a statement.\n return [];\n }\n const firstOnset = firstSoundingBeat(sounding);\n const lastEnd = sounding.reduce((end, n) => Math.max(end, n.startBeat + n.durationBeat), 0);\n const spanEnd = Math.max(firstOnset, opts.totalBeats ?? lastEnd);\n assertRange(spanEnd, 0, Number.MAX_SAFE_INTEGER, 'section totalBeats');\n const threshold = opts.threshold ?? DEFAULT_THRESHOLD;\n assertRange(threshold, 0, 1, 'section threshold');\n const unitBars =\n opts.unitBars ??\n hypermeter(sounding, meters, { totalBeats: spanEnd, budget: opts.budget }).groupBars;\n assertPositiveInt(unitBars, 'section unitBars');\n\n const firstBar = barIndexAt(firstOnset, meters);\n const lastBar = lastBarOf(meters, firstOnset, spanEnd);\n // The units are counted from the bar the music starts in, not from bar 0: an\n // excerpt lifted from bar 9 has no bars 0 to 8 to divide. A pickup leads into\n // the first full bar rather than starting a unit of its own, so it is folded\n // into the opening one.\n const startBar = Math.max(0, firstBar);\n const unitCount = Math.max(1, Math.ceil((lastBar - startBar + 1) / unitBars));\n assertGenerationBudget(unitCount * unitCount, 'form section comparisons', opts.budget);\n assertGenerationBudget(unitCount * sounding.length, 'form section unit notes', opts.budget);\n\n const units: Unit[] = [];\n for (let index = 0; index < unitCount; index += 1) {\n const barStart = startBar + index * unitBars;\n const startBeat =\n index === 0 ? firstOnset : barPositionToBeat({ bar: barStart, beat: 0 }, meters);\n const endBeat =\n index === unitCount - 1\n ? spanEnd\n : barPositionToBeat({ bar: barStart + unitBars, beat: 0 }, meters);\n const unitNotes = sounding\n .filter((note) => note.startBeat >= startBeat - EPS && note.startBeat < endBeat - EPS)\n .sort((a, b) => a.startBeat - b.startBeat || a.pitch - b.pitch);\n units.push({\n startBeat,\n endBeat,\n notes: unitNotes,\n weights: windowWeights(unitNotes, startBeat, endBeat, meters).weights,\n });\n }\n\n // Label every unit, then merge the neighbours that agree.\n const labels: string[] = [];\n const scores: number[] = [];\n let distinct = 0;\n for (let index = 0; index < units.length; index += 1) {\n const unit = units[index];\n if (unit === undefined) {\n continue;\n }\n let bestScore = -1;\n let bestIndex = -1;\n for (let earlier = 0; earlier < index; earlier += 1) {\n const other = units[earlier];\n if (other === undefined) {\n continue;\n }\n const score = unitSimilarity(unit, other);\n // Ties keep the earlier match, which is the statement being restated.\n if (score > bestScore) {\n bestScore = score;\n bestIndex = earlier;\n }\n }\n if (bestIndex >= 0 && bestScore >= threshold) {\n labels.push(labels[bestIndex] ?? labelAt(distinct));\n scores.push(bestScore);\n } else {\n labels.push(labelAt(distinct));\n scores.push(1);\n distinct += 1;\n }\n }\n\n const sections: FormSection[] = [];\n const firstByLabel = new Map<string, number>();\n for (let index = 0; index < units.length; ) {\n const label = labels[index] ?? labelAt(0);\n let end = index;\n let scoreSum = 0;\n while (end < units.length && labels[end] === label) {\n scoreSum += scores[end] ?? 1;\n end += 1;\n }\n const startBeat = units[index]?.startBeat ?? 0;\n const endBeat = units[end - 1]?.endBeat ?? spanEnd;\n const sectionIndex = sections.length;\n const first = firstByLabel.get(label);\n if (first === undefined) {\n firstByLabel.set(label, sectionIndex);\n }\n const similarity = first === undefined ? 1 : clamp01(scoreSum / (end - index));\n sections.push({\n label,\n startBeat,\n endBeat,\n bars: lastBarOf(meters, startBeat, endBeat) - barIndexAt(startBeat, meters) + 1,\n firstOccurrence: first ?? sectionIndex,\n similarity,\n rationale:\n first === undefined\n ? `Section ${label}: material first heard at beat ${startBeat}`\n : `Section ${label}: ${Math.round(similarity * 100)}% alike the ${label} at beat ${\n sections[first]?.startBeat ?? 0\n }`,\n });\n index = end;\n }\n return sections;\n}\n","/**\n * Harmonic reduction: which chords carry the progression and which decorate it.\n *\n * `analyzeVoice` can say that a note is a passing tone; a timeline is flat, so\n * nothing could say the same of a chord. This is that answer one level up — the\n * middle chord of `Cmaj7 - C#dim7 - Dm7` is an embellishment, and the frame\n * under `I - IV - #ivo7 - V - I` is `I - IV - V - I`.\n *\n * **The position taken here.** What counts as structural is a theoretical\n * choice, not a fact, and this module makes the functional one: *a chord is\n * structural unless it is demonstrably an embellishment of its neighbours*. The\n * frame is what carries harmonic function in the prevailing key — the tonic, the\n * dominant, and the chords that cadence — so those are never reduced away; a\n * chord that tonicizes something other than the tonic is prolonging, not\n * framing, however dominant its sonority. Everything else keeps its place unless\n * its root traces one of two figures against the chords around it, the same two\n * figures {@link analyzeVoice} matches at the note level: a step through\n * (passing) and a step away and back (neighbor). Defaulting to structural, and\n * demoting only on evidence, is what keeps a reduction from quietly deleting\n * harmony it merely failed to recognize.\n *\n * The salience reading — that the frame is whatever lasts longest, regardless of\n * function — is the main alternative, and it is a live one for repertoire where\n * function is weak. {@link ReduceProgressionOptions.basis} switches to it: the\n * figures stay the same, only what protects a chord from them changes.\n *\n * Both readings are deterministic: the same timeline and key always reduce the\n * same way.\n */\n\nimport { pitchClassOf as mod12 } from '../../core/pitch/index.js';\nimport type { KeyScale } from '../../core/types.js';\nimport { assertOneOf } from '../../core/validation/index.js';\nimport type { Chord, ChordSegment } from '../../theory/chord/index.js';\nimport { isCadentialSixFour } from '../functional/cadence.js';\nimport type { CadenceResult } from '../functional/index.js';\nimport { detectCadence } from '../functional/index.js';\nimport type { ChordTimeline } from '../timeline/index.js';\nimport type { KeyContext } from '../voice/index.js';\n\n/**\n * Tolerance for treating two segments as touching in time.\n *\n * Segment boundaries are computed, so the end of one and the start of the next\n * meet to within rounding rather than exactly; a real gap is a rest, and chords\n * either side of a rest embellish nothing.\n */\nconst ADJACENCY_EPS = 1e-9;\n\n/**\n * The place a chord holds in a progression.\n *\n * `'structural'` is the frame; `'passing'` and `'neighbor'` are the two\n * embellishing figures, spelled exactly as {@link TheoryLabel} spells them for\n * a note, so one legend covers both levels of analysis.\n *\n * @category Arrangement & Analysis\n */\nexport type ReductionLevel = 'structural' | 'passing' | 'neighbor';\n\n/**\n * A chord with the place it holds, the beats it holds it for, and a short\n * rationale.\n *\n * One entry per timeline segment, in the same order. The entry carries the\n * segment's own beat range, so the chords that survive a filter still say when\n * they sound: holding the structural chords fixed is what a reharmonizer needs,\n * and it cannot hold a chord at a place the filter threw away.\n *\n * @category Arrangement & Analysis\n */\nexport type ReducedChord = {\n chord: Chord;\n startBeat: number;\n endBeat: number;\n level: ReductionLevel;\n rationale: string;\n};\n\n/**\n * What a reduction treats as the frame.\n *\n * `'function'` is the functional reading: the tonic, the dominant and the\n * chords that cadence are the frame. `'duration'` is the salience reading: the\n * chords that hold the harmony at least as long as the median chord are the\n * frame, whatever function they carry.\n *\n * @category Arrangement & Analysis\n */\nexport type ReductionBasis = 'function' | 'duration';\n\n/**\n * Options for {@link reduceProgression}.\n *\n * @category Arrangement & Analysis\n */\nexport type ReduceProgressionOptions = {\n /**\n * Which reading decides what the frame is; see {@link ReductionBasis}.\n *\n * @defaultValue `'function'`\n */\n basis?: ReductionBasis;\n};\n\n/** Cadences whose arrival is the tonic or the substitute standing in for it. */\nconst TONIC_ARRIVING: ReadonlySet<string> = new Set(['authentic', 'plagal', 'modal', 'deceptive']);\n\n/** Why a chord was kept in the frame, or which figure demoted it. */\ntype ReductionReason =\n | { kind: 'tonic' }\n | { kind: 'dominant' }\n | { kind: 'cadentialSixFour' }\n | { kind: 'cadenceArrival'; cadence: NonNullable<CadenceResult['type']> }\n | { kind: 'cadenceAgent'; cadence: NonNullable<CadenceResult['type']> }\n | { kind: 'salient' }\n | { kind: 'first' }\n | { kind: 'last' }\n | { kind: 'unfigured' }\n | { kind: 'passing' }\n | { kind: 'neighbor' };\n\n/** Signed semitone distance between two pitch classes, in [-6, 5]. */\nfunction signedStep(fromPc: number, toPc: number): number {\n return ((mod12(toPc) - mod12(fromPc) + 18) % 12) - 6;\n}\n\n/** Whether a root motion is a step: a semitone or a whole tone, either way. */\nfunction isStep(distance: number): boolean {\n const size = Math.abs(distance);\n return size === 1 || size === 2;\n}\n\n/** Whether two segments touch, so that the motion between them is heard. */\nfunction touches(before: ChordSegment, after: ChordSegment): boolean {\n return Math.abs(after.startBeat - before.endBeat) <= ADJACENCY_EPS;\n}\n\n/** Median of a list of durations, averaging the middle pair on an even count. */\nfunction median(values: readonly number[]): number {\n if (values.length === 0) {\n return 0;\n }\n const sorted = [...values].sort((a, b) => a - b);\n const middle = sorted.length >> 1;\n if (sorted.length % 2 === 1) {\n return sorted[middle] ?? 0;\n }\n return ((sorted[middle - 1] ?? 0) + (sorted[middle] ?? 0)) / 2;\n}\n\n/** Whether a chord stands on the key's tonic. */\nfunction isTonicChord(chord: Chord, key: KeyScale): boolean {\n return mod12(chord.rootPc - key.rootPc) === 0;\n}\n\n/**\n * Whether a chord is the key's own dominant.\n *\n * The major third is what makes it one: the minor v of a natural-minor key\n * carries no leading tone and frames nothing the tonic is approached from.\n */\nfunction isDominantChord(chord: Chord, key: KeyScale): boolean {\n return mod12(chord.rootPc - key.rootPc) === 7 && chord.intervals.some((iv) => mod12(iv) === 4);\n}\n\n/**\n * Why a chord belongs to the frame under the functional reading, or null when\n * nothing keeps it there.\n *\n * A cadence protects both of its chords, but asymmetrically: the arrival of any\n * cadence is a goal, while the chord driving one only holds the frame when the\n * cadence reaches the tonic. That asymmetry is what lets a chromatic chord slip\n * into a half cadence — `IV - #ivo7 - V` — and still be heard as passing,\n * while the dominant it arrives on is not.\n */\nfunction functionalReason(\n segment: ChordSegment,\n before: ChordSegment | undefined,\n after: ChordSegment | undefined,\n keyAt: (beat: number) => KeyScale,\n): ReductionReason | null {\n const key = keyAt(segment.startBeat);\n // Asked before the tonic, because the cadential six-four is a tonic triad and\n // would otherwise be kept as one: it stands on the bass of the dominant that\n // follows and is that dominant, not the tonic framing the progression. A\n // six-four the bass leaves — the passing `IV-I64-IV` — answers false here and\n // is read as the inverted tonic it is.\n if (after !== undefined && isCadentialSixFour(segment.chord, after.chord, key)) {\n return { kind: 'cadentialSixFour' };\n }\n if (isTonicChord(segment.chord, key)) {\n return { kind: 'tonic' };\n }\n if (isDominantChord(segment.chord, key)) {\n return { kind: 'dominant' };\n }\n if (before !== undefined) {\n const arrival = detectCadence(before.chord, segment.chord, key).type;\n if (arrival !== null) {\n return { kind: 'cadenceArrival', cadence: arrival };\n }\n }\n if (after !== undefined) {\n const agent = detectCadence(segment.chord, after.chord, keyAt(after.startBeat)).type;\n if (agent !== null && TONIC_ARRIVING.has(agent)) {\n return { kind: 'cadenceAgent', cadence: agent };\n }\n }\n return null;\n}\n\n/** The figure a chord's root traces against its neighbours, or null for none. */\nfunction figureReason(\n segment: ChordSegment,\n before: ChordSegment | undefined,\n after: ChordSegment | undefined,\n): ReductionReason | null {\n if (before === undefined || after === undefined) {\n return null;\n }\n const incoming = signedStep(before.chord.rootPc, segment.chord.rootPc);\n const outgoing = signedStep(segment.chord.rootPc, after.chord.rootPc);\n if (!isStep(incoming) || !isStep(outgoing)) {\n return null;\n }\n // A step in and a step on in the same direction passes through; a step away\n // from a harmony that comes back is a neighbour to it.\n if (incoming > 0 === outgoing > 0) {\n return { kind: 'passing' };\n }\n return mod12(before.chord.rootPc) === mod12(after.chord.rootPc) ? { kind: 'neighbor' } : null;\n}\n\n/** The rationale for a reduction level, in the phrasing a note label uses. */\nfunction describe(reason: ReductionReason): string {\n switch (reason.kind) {\n case 'tonic':\n return 'Structural: the tonic of the key, which the progression is heard against';\n case 'dominant':\n return 'Structural: the dominant of the key, the chord its tonic is approached from';\n case 'cadentialSixFour':\n return 'Structural: a tonic six-four on the bass of the dominant that follows, which prolongs that dominant rather than framing the tonic';\n case 'cadenceArrival':\n return `Structural: the arrival of a ${reason.cadence} cadence`;\n case 'cadenceAgent':\n return `Structural: the chord that drives a ${reason.cadence} cadence`;\n case 'salient':\n return 'Structural: it holds the harmony at least as long as the median chord';\n case 'first':\n return 'Structural: the first chord of the progression frames it';\n case 'last':\n return 'Structural: the last chord of the progression frames it';\n case 'unfigured':\n return 'Structural: its root neither steps through to the next chord nor returns to the last, so it embellishes nothing';\n case 'passing':\n return 'Passing: its root steps in from the previous chord and on to the next in the same direction';\n case 'neighbor':\n return 'Neighbor: its root steps away from the surrounding harmony and returns to it';\n }\n}\n\n/** What a reading is given to decide whether a chord holds the frame. */\ntype FrameContext = {\n segment: ChordSegment;\n before: ChordSegment | undefined;\n after: ChordSegment | undefined;\n keyAt: (beat: number) => KeyScale;\n salienceFloor: number;\n};\n\n/**\n * What keeps a chord in the frame, one entry per reading of what a frame is.\n *\n * {@link ReductionBasis} describes the two readings; the entrance check reads\n * their names out of this table, so a reading cannot be added without becoming\n * accepted input in the same edit.\n */\nconst BASIS_READINGS: Record<ReductionBasis, (context: FrameContext) => ReductionReason | null> = {\n function: ({ segment, before, after, keyAt }) => functionalReason(segment, before, after, keyAt),\n duration: ({ segment, salienceFloor }) =>\n segment.endBeat - segment.startBeat >= salienceFloor ? { kind: 'salient' } : null,\n};\n\n/** Every reading's name, read from the table so the two cannot drift apart. */\nconst REDUCTION_BASES = Object.keys(BASIS_READINGS) as ReductionBasis[];\n\n/** The level a reason places a chord at. */\nfunction levelOf(reason: ReductionReason): ReductionLevel {\n if (reason.kind === 'passing') {\n return 'passing';\n }\n return reason.kind === 'neighbor' ? 'neighbor' : 'structural';\n}\n\n/**\n * Reduce a chord progression to its frame, labelling every chord.\n *\n * One entry comes back per timeline segment, in the same order, so the two read\n * in step. Filtering the result to `'structural'` is the skeleton view — the\n * `I - IV - V - I` under a progression that spells far more chords than that —\n * and holding those chords fixed is what a reharmonizer needs in order to\n * rewrite the surface without moving the harmony. Each entry carries its own\n * `startBeat` and `endBeat`, so a filtered chord still knows where it sounds\n * once the segments it was read against are gone.\n *\n * A chord is structural unless one of two figures demotes it: its root stepping\n * in from the previous chord and on to the next in the same direction (passing),\n * or stepping away from a harmony that returns (neighbor). Chords carrying the\n * frame are never demoted, and what carries the frame is the module's stated\n * theoretical position — see {@link ReductionBasis} for the reading this takes\n * by default and the one it can be switched to.\n *\n * Chords separated by a rest form no figure across it, the way\n * {@link detectCadences} pairs no chords across one.\n *\n * @param timeline The chord timeline to reduce.\n * @param key The prevailing key, or the key in force at a given beat, for music\n * that modulates.\n * @param opts Which reading decides the frame; see\n * {@link ReduceProgressionOptions}.\n * @returns One labelled chord per segment, in time order.\n * @throws If `opts.basis` is not one of the readings {@link ReductionBasis}\n * names.\n * @example\n * ```ts\n * import { chordTimelineFromChords, majorKey, reduceProgression } from '@libraz/libcantus';\n * const timeline = chordTimelineFromChords(\n * [\n * { rootPc: 0, quality: 'maj7', startBeat: 0 },\n * { rootPc: 1, quality: 'dim7', startBeat: 4 },\n * { rootPc: 2, quality: 'min7', startBeat: 8 },\n * ],\n * 12,\n * );\n * reduceProgression(timeline, majorKey(0)).map((entry) => entry.level);\n * // ['structural', 'passing', 'structural']\n * ```\n * @category Arrangement & Analysis\n */\nexport function reduceProgression(\n timeline: ChordTimeline,\n key: KeyContext,\n opts: ReduceProgressionOptions = {},\n): ReducedChord[] {\n // A KeyScale is a plain object, so a callable value can only be the per-beat\n // form; normalising here keeps the reading below segment-oriented.\n const keyAt: (beat: number) => KeyScale = typeof key === 'function' ? key : () => key;\n const basis = assertOneOf(opts.basis ?? 'function', REDUCTION_BASES, 'reduction basis');\n const reading = BASIS_READINGS[basis];\n const segments = timeline.segments;\n const salienceFloor =\n basis === 'duration'\n ? median(segments.map((segment) => segment.endBeat - segment.startBeat))\n : 0;\n const result: ReducedChord[] = [];\n\n for (let index = 0; index < segments.length; index += 1) {\n const segment = segments[index];\n if (segment === undefined) {\n continue;\n }\n const previous = segments[index - 1];\n const following = segments[index + 1];\n const before = previous !== undefined && touches(previous, segment) ? previous : undefined;\n const after = following !== undefined && touches(segment, following) ? following : undefined;\n\n let reason = reading({ segment, before, after, keyAt, salienceFloor });\n if (reason === null && index === 0) {\n reason = { kind: 'first' };\n }\n if (reason === null && index === segments.length - 1) {\n reason = { kind: 'last' };\n }\n reason ??= figureReason(segment, before, after) ?? { kind: 'unfigured' };\n\n result.push({\n chord: segment.chord,\n startBeat: segment.startBeat,\n endBeat: segment.endBeat,\n level: levelOf(reason),\n rationale: describe(reason),\n });\n }\n\n return result;\n}\n"],"mappings":"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","names":["EPS","EPS","best","left","right","EPS","STRONG_BEAT_DISCOUNT","MAX_METRIC_WEIGHT","EPS","EPS","EPS","EPS","clamp01","clamp01","EPS","EPS","EPS","clamp01","clamp01","EPS","isStep","describe"]}
|