<!-- SS · part 74 -->
!S.mutes[t] || (anySolo && !S.solos[t]); });
  }
  function applyMaster(G) {
    if (!G) return; var M = S.master;
    var drive = M.drive / 100; G.drive.distortion = drive * 0.6; G.drive.wet.value = drive > 0 ? Math.min(1, 0.25 + drive) : 0;
    var c = M.comp / 100; G.comp.threshold.value = -4 - c * 26; G.comp.ratio.value = 1 + c * 5;
    var tilt = M.eq / 100; G.eq.low.value = -tilt * 6 + (M.on ? 1.5 : 0); G.eq.high.value = tilt * 6 + (M.on ? 2 : 0); G.eq.mid.value = M.on ? -1 : 0;
    G.width.width.value = M.width / 100;
    G.verbSend.gain.value = (M.verb / 100) * 0.6;
    G.limiter.threshold.value = M.on ? -0.8 : -1; G.mvol.gain.value = M.on ? 1.0 : 0.85;
  }
  function applyVocalFx(G) {
    if (!G) return; var V = S.voice;
    G.vDrive.distortion = V.aggr / 100 * 0.35; G.vDrive.wet.value = V.aggr > 15 ? 0.15 + V.aggr / 250 : 0;
    G.vSend.gain.value = 0.1 + V.drama / 100 * 0.9;
    G.vFilter.frequency.value = 16000 - V.vuln / 100 * 11000;
    G.vComp.ratio.value = 1.5 + V.pass / 100 * 5; G.vMake.gain.value = 1 + V.pass / 100 * 0.6;
  }
  // shared step trigger (realtime + offline)
  function triggerStep(G, step, time) {
    var bar = Math.floor(step / 16), s = step % 16, it = locate(bar); if (!it) return;
    var p = S.sections[it.name].patterns;
    DRUM_ROWS.forEach(function (r) { if (p.drums[r][s]) G.inst.drums.play(r, time, r === 'hat' ? (s % 4 === 0 ? 0.8 : 0.5) : r === 'perc' ? 0.7 : 0.95); });
    var b = p.bass[s]; if (b) G.inst.bass.triggerAttackRelease(b, '8n', time, 0.9);
    var c = p.chords[s];
    if (c) { var notes = chordNotes(c); if (notes) { var len = 1; for (var k2 = s + 1; k2 < STEPS && !p.chords[k2]; k2++) len++; G.inst.chords.triggerAttackRelease(notes, stepSec() * Math.min(len, 16) * 0.95, time, 0.6); } }
    var l = p.lead[s]; if (l) G.inst.lead.triggerAttackRelease(l, '16n', time, 0.7);
  }
  function swingOffset(s) { return s % 2 === 1 ? S.swing * stepSec() * 0.66 : 0; }

  // ------------------------------------------------------------------ realtime engine + transport
  var E = null, playing = false;
  function engine() {
    if (E) return E;
    if (!window.Tone) { toast('Audio engine failed to load. Reload the page.'); return null; }
    var T = Tone.getTransport();
    E = makeGraph(true); E.T = T; E.players = [];
    E.recorder = typeof MediaRecorder !== 'undefined' ? new Tone.Recorder() : null; if (E.recorder) E.mvol.connect(E.recorder);
    T.bpm.value = S.bpm; T.swing = S.swing; T.swingSubdivision = '16n';
    E.loop = new Tone.Loop(function (time) {
      var ticks = T.getTicksAtTime(time), step = Math.floor((ticks + 8) / (T.PPQ / 4));
      if (step >= PLAN.bars * 16 && !T.loop) return;
      triggerStep(E, step, time);
      Tone.getDraw().schedule(function () { onStepDraw(step); }, time);
    }, '16n').start(0);
    return E;
  }
  function checkUnlock() { $('#es-unlock').hidden = !!(window.Tone && Tone.getContext().state === 'running'); }
  function unlock() { if (!window.Tone) return Promise.reject(new Error('no Tone')); return Tone.start().then(function () { engine(); checkUnlock(); }); }
  $('#es-unlock-btn').addEventListener('click', function () { unlock().then(function () { toast('Sound is on.'); }, function () { toast('Could not start audio.'); }); });
  var loopSection = false;
  var HOOKS = window.ES_HOOKS = window.ES_HOOKS || [];
  function hook(name) { var args = [].slice.call(arguments, 1), out = []; HOOKS.forEach(function (h) { if (h[name]) try { out.push(h[name].apply(null, args)); } catch (e) { console.error('hook', name, e); } }); return out; }
  function songEnd() { return Math.max.apply(null, [totalSec()].concat(hook('end'))); }
  function scheduleVox() {
    (E.players || []).forEach(function (p) { try { p.unsync(); p.dispose(); } catch (e) {} }); E.players = [];
    vocalPlan().forEach(function (v) { var c = VOX.clips[v.text]; if (!c) return; var pl = new Tone.Player(c.buf).connect(E.vin); pl.sync().start(v.t); E.players.push(pl); });
    hook('schedule', E, true);
  }
  var endId = null;
  function play(fromSec) {
    return unlock().then(function () {
      var T = E.T; T.stop(); if (endId != null) { T.clear(endId); endId = null; }
      T.bpm.value = S.bpm; T.swing = S.swing;
      scheduleVox();
      var it = PLAN.items[S.sel];
      if (loopSection && it) { T.loop = true; T.loopStart = it.start * barSec(); T.loopEnd = (it.start + it.bars) * barSec(); fromSec = it.start * barSec(); }
      else { T.loop = false; endId = T.scheduleOnce(function (t) { Tone.getDraw().schedule(function () { if (!recording) { stop(); seekTo(0); } }, t); }, songEnd() + 0.6); }
      T.start('+0.06', fromSec || 0); playing = true; setPlayUI();
    });
  }
  function stop() { if (!E) return; E.T.stop(); playing = false; setPlayUI(); onStepDraw(-1); }
  function setPlayUI() { checkUnlock(); var b = $('#es-play'); b.textContent = playing ? '❚❚' : '▶'; b.classList.toggle('is-on', playing); $('#es-vplay').textContent = playing ? '■ Stop' : 