/** * 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. The synth renderer is the first * implementation; the instrawave renderer replaces a binding's preset with * an instrument file plus parameter values, groups events by bake key so * voices sharing a bake render in one pass, and mixes the traces. */ 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 interface Renderer { render(events: NoteEvent[], preset: (voiceId: string) => SynthPreset, seconds: number): Promise; } /** 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); }