/** * vocal-map.ts — turn a transcript into the VOCAL MAP the music-video planner * needs: the song segmented by timestamp into rap / hook / instrumental / outro. * * {@link buildVocalMap} is a PURE classifier over whisper-style segments * (`{start, end, text}`). The hook is the SUSTAINED, sung passage; the verses are * the dense, fast-delivered rap. The discriminating signal is WORD DENSITY * (words per second): a sung chorus stretches few words over many seconds (low * wps) while rap packs many words into little time (high wps). The classifier * finds the natural split by the largest gap in the sorted densities. ("Repeated * line" is deliberately NOT the signal — verses repeat too, which mislabels them; * density does not have that failure.) `hookKeywords` overrides when the caller * knows the chorus words. Everything else with words is RAP; gaps with no words * are INSTRUMENTAL; the trailing gap is the OUTRO. The result is a contiguous, * gap-free section list covering [0, songEnd] — exactly the shape the planner * consumes (the per-section performer clip is attached later by the assembler, * rap → rapper clip / hook → singer clip). * * The map can also be authored by hand (an explicit artifact) and fed straight to * the planner; {@link validateVocalMap} guards either source. */ import type { VocalSection } from './vocal-sync-plan.js'; export interface WhisperSegment { start: number; end: number; text: string; } export interface BuildVocalMapOptions { /** Song length in seconds (the map covers [0, songEnd]). */ songEnd: number; /** * Phrases that mark the HOOK. If given, a segment is hook when its text * contains any keyword. If omitted, hooks are auto-detected by word density. */ hookKeywords?: string[]; /** A silent stretch ≥ this is its own INSTRUMENTAL section (default 2.0s). */ minInstrumentalSec?: number; /** Vocal spans of the same type within this gap are merged (default 1.2s). */ mergeGapSec?: number; /** * Minimum words-per-second gap to declare a hook/rap split (default 0.4). If * the song's densities don't separate this much (e.g. all-rap or all-sung), * everything is treated as RAP — pass hookKeywords to force a hook. */ hookDensityGapMin?: number; } /** Classify whisper segments into a contiguous vocal map. PURE. */ export declare function buildVocalMap(segments: WhisperSegment[], options: BuildVocalMapOptions): VocalSection[]; /** * Validate a vocal map (auto-built or hand-authored): contiguous, in-bounds, * gap-free coverage of [0, songEnd]. Returns a list of human-readable issues * (empty = valid). */ export declare function validateVocalMap(sections: VocalSection[], songEnd: number): string[]; //# sourceMappingURL=vocal-map.d.ts.map