  function build() {
    if (st.ctx) return true;
    if (!Lazy.loaded('feature:eq-core') || !Lazy.loaded('feature:vocal-reducer')) {
      loadSoundFeatures().then(()=>{apply(); Level.apply();}).catch(error=>toast(error.message));
      return false;
    }
    try {
      st.ctx = new AC();
      st.filters = EQ_BANDS.map((b) => {
        const f = st.ctx.createBiquadFilter();
        f.type = b.type;
        f.frequency.value = b.f;
        f.Q.value = 1.0;
        return f;
      });
      for (let i = 0; i < st.filters.length - 1; i++) st.filters[i].connect(st.filters[i + 1]);
      // Loudness levelling: gain toward -14 LUFS, then a limiter so a boosted
      // quiet track can never clip.
      st.level = st.ctx.createGain();
      st.limiter = st.ctx.createDynamicsCompressor();
      st.limiter.threshold.value = -1; st.limiter.knee.value = 0; st.limiter.ratio.value = 20;
      st.limiter.attack.value = 0.003; st.limiter.release.value = 0.25;
      st.vocal = VocalReducer.create(st.ctx, st.filters[st.filters.length - 1]);
      st.widener = EqCore.createWidener(st.ctx, st.vocal.output);
      st.widener.output.connect(st.level);
      st.level.connect(st.limiter);
      st.limiter.connect(st.ctx.destination);
      return true;
    } catch { st.ctx = null; return false; }
  }

