Coarse (10 Hz) DTW per stretch between manual anchors, refined at 50 Hz in a band that widens 0.5 -> 1 -> 2 s on unreachable end cells (coarse path as last resort); one auto anchor per bar from median offsets of diagonal path steps. Fit quality from raw-audio onset peaks vs warped scored-role MIDI onsets (+-70 ms, 30 s windows, flagged on rate < 0.5 or median > 60 ms). trackKeys moves to src/align/roles.ts (pure) and is re-exported by manifest. Co-Authored-By: Claude Opus 5.5 <[email protected]>
29 lines
1.1 KiB
TypeScript
29 lines
1.1 KiB
TypeScript
import type { Role } from "../core/types.ts";
|
|
import type { RefTrack } from "./smf.ts";
|
|
|
|
// Best-effort role for a reference MIDI track; null = unmapped (excluded from scoring until the user maps it).
|
|
export function guessRole(t: RefTrack): Role | null {
|
|
if (t.isDrums) return "drums";
|
|
const n = t.name.trim().toLowerCase();
|
|
if (/vocal|voice|vox|sing/.test(n)) return "vocals";
|
|
if (/bass/.test(n)) return "bass";
|
|
if (/guitar|delay/.test(n)) return "guitar";
|
|
if (/piano|key|synth|organ/.test(n)) return "keys";
|
|
const p = t.program;
|
|
if (p >= 32 && p <= 39) return "bass";
|
|
if (p >= 24 && p <= 31) return "guitar";
|
|
if ((p >= 0 && p <= 7) || (p >= 16 && p <= 23) || (p >= 88 && p <= 95)) return "keys";
|
|
return null;
|
|
}
|
|
|
|
// Role-map key for a track: its name, or "track N" when unnamed; disambiguated on collision.
|
|
export function trackKeys(tracks: { index: number; name: string }[]): string[] {
|
|
const used = new Set<string>();
|
|
return tracks.map((t) => {
|
|
let k = t.name || `track ${t.index}`;
|
|
if (used.has(k)) k = `${k} #${t.index}`;
|
|
used.add(k);
|
|
return k;
|
|
});
|
|
}
|