/** * The seam between the analysis half and the sound half. Everything above * this interface deals in NoteEvents (a time, a voice, a strength); * everything below turns them into audio. A voice's binding names either a * placeholder synth preset or an instrawave instrument with tuning * overrides; the mixer in main.ts sends each event down the right path and * touches the GPU only when at least one bound instrument is instrawave. */ export interface NoteEvent { t: number; // seconds from the start of the take voiceId: string; gain: number; // linear, 0..1, from the hit's measured loudness } export type SynthPreset = 'kick' | 'snare' | 'hat' | 'tom'; export type Binding = | { kind: 'synth'; preset: SynthPreset } | { kind: 'iw'; instrument: string; set: Record }; /** Map a hit's peak level to a note strength. Levels are taken relative to * the loudest hit in the take and compressed (0.5 exponent over a 30 dB * window), so soft hits stay audible while the ordering is preserved. */ export function gainFromPeakDb(peakDb: number, refDb: number): number { const rel = Math.max(-30, Math.min(0, peakDb - refDb)); return Math.pow(10 ** (rel / 20), 0.5); }