import { canonBeat } from "../project/model.ts"; import type { MidiNote } from "./smf.ts"; /** a part as a run of chords: a new one wherever the set of sounding notes changes */ export function chordsOf( notes: readonly MidiNote[], ): { start: number; dur: number; notes: number[] }[] { const spans = notes.map(n => ({ pitch: n.pitch, start: canonBeat(n.start), end: canonBeat(n.start + n.dur), })); const cuts = [...new Set(spans.flatMap(n => [n.start, n.end]))].sort((a, b) => a - b); const runs: { start: number; end: number; notes: number[] }[] = []; cuts.slice(1).forEach((end, i) => { const start = cuts[i]; const sounding = spans.filter(n => n.start <= start && n.end >= end).map(n => n.pitch); if (!sounding.length) return; const pitches = [...new Set(sounding)].sort((a, b) => a - b); const last = runs.at(-1); if (last?.end === start && last.notes.join() === pitches.join()) last.end = end; else runs.push({ start, end, notes: pitches }); }); return runs.map(({ start, end, notes }) => ({ start, dur: canonBeat(end - start), notes })); }