// Pure math for the location filter — the game's signature audio trick.
//
// Music runs through one master lowpass. At the door you hear the kick through a
// brick wall; step inside and the filter opens. The sweep must feel like a door
// swinging, not a fader move, so it ramps exponentially in frequency (which is
// how ears hear pitch) rather than linearly in Hz.

export type AudioLocation = 'door' | 'floor';

/** Muffled-through-the-wall. Low enough that hats vanish and only kick survives. */
export const DOOR_CUTOFF_HZ = 250;
/** Effectively open — above this the filter is inaudible. */
export const FLOOR_CUTOFF_HZ = 18_000;

/** Music dips slightly outside; inside it is full. */
export const DOOR_GAIN = 0.62;
export const FLOOR_GAIN = 1;

/** Door opening is a fast reveal; door closing behind you is slower and sadder. */
export const OPEN_SWEEP_MS = 600;
export const CLOSE_SWEEP_MS = 900;

export const cutoffFor = (location: AudioLocation): number =>
  location === 'floor' ? FLOOR_CUTOFF_HZ : DOOR_CUTOFF_HZ;

export const gainFor = (location: AudioLocation): number =>
  location === 'floor' ? FLOOR_GAIN : DOOR_GAIN;

/** Opening (door -> floor) is the fast one. */
export const sweepMsFor = (to: AudioLocation): number =>
  to === 'floor' ? OPEN_SWEEP_MS : CLOSE_SWEEP_MS;

/**
 * Cutoff at normalised progress t (0..1) of a sweep, interpolated exponentially.
 * t is clamped, so callers can hand it raw elapsed/duration.
 *
 * Exponential in frequency == linear in perceived pitch: at t=0.5 of a
 * 250Hz->18kHz sweep you are at ~2.1kHz, not the ~9kHz a linear ramp would give.
 * That late-blooming brightness is what sells "the door just opened".
 */
export function cutoffAt(t: number, fromHz: number, toHz: number): number {
  const clamped = Math.max(0, Math.min(1, t));
  return fromHz * Math.pow(toHz / fromHz, clamped);
}

/** Master gain at progress t — linear is fine, the ear tracks the filter here. */
export function gainAt(t: number, from: number, to: number): number {
  const clamped = Math.max(0, Math.min(1, t));
  return from + (to - from) * clamped;
}

/**
 * Sampled ramp points for `setValueCurveAtTime`. WebAudio's
 * exponentialRampToValueAtTime would do the job, but sampling lets us assert the
 * curve's shape in tests and keeps door/floor behaviour identical everywhere.
 */
export function cutoffCurve(fromHz: number, toHz: number, points = 32): Float32Array {
  // A degenerate count must still yield a usable curve: a NaN reaching
  // setValueCurveAtTime throws, and a NaN assigned to AudioParam.value kills the
  // filter silently, which would read as "the music stopped" with nothing logged.
  const n = Number.isFinite(points) ? Math.max(1, Math.floor(points)) : 1;
  const span = Math.max(1, n - 1);
  const out = new Float32Array(n);
  for (let i = 0; i < n; i++) out[i] = cutoffAt(i / span, fromHz, toHz);
  return out;
}
