# Noise Gate Converter — Classic → "My Gate"

Inputs — classic noise gate
Threshold (dBFS)
Linear amplitude: 0.003162
Attack (ms)
Release (ms)
Hold (ms)
Range / Depth (% reduction when closed)
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);
    }