1/**
2 * The seam between the analysis half and the sound half. Everything above
3 * this interface deals in NoteEvents (a time, a voice, a strength);
4 * everything below turns them into audio. The synth renderer is the first
5 * implementation; the instrawave renderer replaces a binding's preset with
6 * an instrument file plus parameter values, groups events by bake key so
7 * voices sharing a bake render in one pass, and mixes the traces.
8 */
9export interface NoteEvent {
10 t: number; // seconds from the start of the take
11 voiceId: string;
12 gain: number; // linear, 0..1, from the hit's measured loudness
13}
15export type SynthPreset = 'kick' | 'snare' | 'hat' | 'tom';
17export interface Renderer {
18 render(events: NoteEvent[], preset: (voiceId: string) => SynthPreset, seconds: number): Promise<AudioBuffer>;
19}
21/** Map a hit's peak level to a note strength. Levels are taken relative to
22 * the loudest hit in the take and compressed (0.5 exponent over a 30 dB
23 * window), so soft hits stay audible while the ordering is preserved. */
24export function gainFromPeakDb(peakDb: number, refDb: number): number {
25 const rel = Math.max(-30, Math.min(0, peakDb - refDb));
26 return Math.pow(10 ** (rel / 20), 0.5);
27}