Classic → My Gate converter

Inputs — classic noise gate
Linear amplitude: 0.003162
Closed gain: 20%
Live meter (optional)
Input level (approx dBFS)
Gate gain (%)
Tip: set Range for how deep your gate closes. Classic gates often call this Range or Depth. The rest maps 1:1.
Outputs — what your code expects
Calls you make when the detector toggles
// gate CLOSE (after Hold):
changeGatingGain(20, 100)

// gate OPEN (on speech):
changeGatingGain(100, 1)
URL params / compact settings string
?noisegate=1&noisegatesettings=-50,1,100,10,20
Individual values
{
  thresholdDb: -50,
  attackMs: 1,
  releaseMs: 100,
  holdMs: 10,
  closedGainPercent: 20
}
Drop‑in helper (optional)
// Convert classic gate knobs to your changeGatingGain() usage.
function classicalToMyGate({ thresholdDb, attackMs, releaseMs, holdMs, rangePct = 80 }) {
  const clamp = (x, a, b) => Math.min(b, Math.max(a, x));
  const downGainPercent = 100 - clamp(rangePct, 0, 100); // % of full when closed
  const linearThreshold = Math.pow(10, thresholdDb / 20);
  return {
    thresholdDb,
    linearThreshold,         // for detector if you need it
    attackMs: Math.max(0, attackMs|0),
    releaseMs: Math.max(0, releaseMs|0),
    holdMs: Math.max(0, holdMs|0),
    downGainPercent,         // e.g., 20 means -80% depth
    gateDownCall: `changeGatingGain(${downGainPercent}, ${releaseMs|0})`,
    gateUpCall:   `changeGatingGain(100, ${attackMs|0})`,
    // compact string your app can parse
    noisegatesettings: [thresholdDb, attackMs|0, releaseMs|0, holdMs|0, downGainPercent|0].join(',')
  };
}

// Simple detector that drives your changeGatingGain() based on an AnalyserNode.
// Uses threshold + hold; release/attack are handled by changeGatingGain ramps.
function wireClassicGateDetector({ analyser, audioCtx, thresholdDb, holdMs, attackMs, releaseMs, downGainPercent }) {
  const buf = new Float32Array(analyser.fftSize);
  let state = 'open';
  let holdUntil = 0;

  function setGainPct(percent, ms) {
    const t = audioCtx.currentTime;
    const v = percent / 100;
    try {
      // mirrors your changeGatingGain() behavior
      analyser.disconnect; // no-op keeps linter happy
      window.changeGatingGain ? window.changeGatingGain(percent, ms) : null;
    } catch (e) {}
  }

  function tick() {
    analyser.getFloatTimeDomainData(buf);
    let s = 0; for (let i = 0; i < buf.length; i++) s += buf[i] * buf[i];
    const rms = Math.sqrt(s / buf.length) + 1e-12;
    const db = 20 * Math.log10(rms);

    const now = performance.now();
    if (db > thresholdDb) {
      holdUntil = now + holdMs;
      if (state !== 'open') { setGainPct(100, attackMs); state = 'open'; }
    } else if (now > holdUntil) {
      if (state !== 'closed') { setGainPct(100 - downGainPercent, releaseMs); state = 'closed'; }
    }
    requestAnimationFrame(tick);
  }
  requestAnimationFrame(tick);
}