<!-- SS · part 73 -->
ency: 120, octaves: 3 } });
      else if (kind === 'FM') s = new Tone.FMSynth({ harmonicity: 1, modulationIndex: 6, envelope: { attack: 0.005, decay: 0.3, sustain: 0.4, release: 0.2 } });
      else s = new Tone.Synth({ oscillator: { type: 'sine' }, envelope: { attack: 0.005, decay: 0.4, sustain: 0.6, release: 0.3 }, volume: 4 });
    } else if (track === 'chords') {
      if (kind === 'Keys') s = new Tone.PolySynth(Tone.FMSynth, { harmonicity: 3, modulationIndex: 2, envelope: { attack: 0.005, decay: 0.8, sustain: 0.2, release: 0.8 } });
      else if (kind === 'Pluck') s = new Tone.PolySynth(Tone.Synth, { oscillator: { type: 'triangle' }, envelope: { attack: 0.002, decay: 0.25, sustain: 0, release: 0.2 } });
      else s = new Tone.PolySynth(Tone.Synth, { oscillator: { type: 'fatsawtooth', count: 3, spread: 20 }, envelope: { attack: 0.3, decay: 0.4, sustain: 0.7, release: 1.2 }, volume: -6 });
      s.maxPolyphony = 16;
    } else {
      if (kind === 'Square') s = new Tone.Synth({ oscillator: { type: 'square' }, envelope: { attack: 0.005, decay: 0.15, sustain: 0.3, release: 0.15 }, volume: -6 });
      else if (kind === 'Bell') s = new Tone.FMSynth({ harmonicity: 3.01, modulationIndex: 12, envelope: { attack: 0.001, decay: 1.2, sustain: 0, release: 1 }, modulationEnvelope: { attack: 0.001, decay: 0.6, sustain: 0, release: 0.5 } });
      else s = new Tone.MonoSynth({ oscillator: { type: 'sawtooth' }, filter: { Q: 1, type: 'lowpass' }, envelope: { attack: 0.01, decay: 0.2, sustain: 0.4, release: 0.25 }, filterEnvelope: { attack: 0.01, decay: 0.2, sustain: 0.4, baseFrequency: 600, octaves: 2.5 }, volume: -4 });
    }
    return s.connect(out);
  }
  // One complete signal graph in the CURRENT Tone context (realtime, or inside Tone.Offline).
  function makeGraph(withMeters) {
    var G = { ch: {}, inst: {}, nodes: [] };
    function k(n) { G.nodes.push(n); return n; }
    G.out = k(new Tone.Gain(1));
    G.eq = k(new Tone.EQ3(0, 0, 0)); G.drive = k(new Tone.Distortion({ distortion: 0, wet: 0 }));
    G.comp = k(new Tone.Compressor({ threshold: -12, ratio: 1.5, attack: 0.01, release: 0.2 }));
    G.width = k(new Tone.StereoWidener(0.5)); G.limiter = k(new Tone.Limiter(-1)); G.mvol = k(new Tone.Gain(0.9));
    // insert points (EQ plugin rewires in->out): master and one per lane
    G.mIn = k(new Tone.Gain(1)); G.mOut = k(new Tone.Gain(1)); G.out.connect(G.mIn); G.mIn.connect(G.mOut);
    G.mOut.chain(G.eq, G.drive, G.comp, G.width, G.limiter, G.mvol, Tone.getDestination());
    G.lIn = {}; G.lOut = {};
    G.verb = k(new Tone.Reverb({ decay: 2.6, wet: 1 })); G.verbSend = k(new Tone.Gain(0.2)); G.verbSend.connect(G.verb); G.verb.connect(G.out);
    LANES.forEach(function (t) { G.lIn[t] = k(new Tone.Gain(1)); G.lOut[t] = k(new Tone.Gain(1)); G.lIn[t].connect(G.lOut[t]); G.lOut[t].connect(G.out); G.ch[t] = k(new Tone.Channel({ volume: S.vols[t] || 0 }).connect(G.lIn[t])); });
    var cs = k(new Tone.Gain(0.8)); G.ch.chords.connect(cs); cs.connect(G.verbSend);
    var ls = k(new Tone.Gain(0.6)); G.ch.lead.connect(ls); ls.connect(G.verbSend);
    // vocal chain: highpass -> lowpass (vulnerability) -> EQ -> drive (aggression) -> comp (passion) -> channel; reverb send (melodrama)
    G.vin = k(new Tone.Gain(1)); G.vFilter = k(new Tone.Filter(16000, 'lowpass')); G.vHp = k(new Tone.Filter(90, 'highpass'));
    G.vEq = k(new Tone.EQ3(0, 0, 2)); G.vDrive = k(new Tone.Distortion({ distortion: 0, wet: 0 }));
    G.vComp = k(new Tone.Compressor({ threshold: -20, ratio: 3, attack: 0.005, release: 0.12 })); G.vMake = k(new Tone.Gain(1));
    G.vin.chain(G.vHp, G.vFilter, G.vEq, G.vDrive, G.vComp, G.vMake, G.ch.vocals);
    G.vSend = k(new Tone.Gain(0.3)); G.vMake.connect(G.vSend); G.vSend.connect(G.verbSend);
    ['drums', 'bass', 'chords', 'lead'].forEach(function (t) { setSound(G, t, S.sounds[t]); });
    if (withMeters) { G.fft = k(new Tone.FFT(1024)); G.mvol.connect(G.fft); }
    applyMix(G); applyMaster(G); applyVocalFx(G);
    (window.ES_HOOKS || []).forEach(function (h) { if (h.graph) try { h.graph(G, !!withMeters); } catch (e) { console.error('hook graph', e); } });
    G.dispose = function () { ['drums', 'bass', 'chords', 'lead'].forEach(function (t) { try { G.inst[t].dispose(); } catch (e) {} }); G.nodes.forEach(function (n) { try { n.dispose(); } catch (e) {} }); };
    return G;
  }
  function setSound(G, t, kind) {
    if (!G) return;
    if (G.inst[t]) { try { G.inst[t].releaseAll && G.inst[t].releaseAll(); } catch (e) {} try { G.inst[t].dispose(); } catch (e) {} }
    G.inst[t] = t === 'drums' ? makeDrums(kind, G.ch.drums) : makeSynth(t, kind, G.ch[t]);
    (window.ES_HOOKS || []).forEach(function (h) { if (h.sound) try { h.sound(G, t); } catch (e) { console.error('hook sound', e); } });
  }
  function applyMix(G) {
    if (!G) return; var anySolo = LANES.some(function (t) { return S.solos[t]; });
    LANES.forEach(function (t) { G.ch[t].volume.value = S.vols[t] || 0; G.ch[t].mute = !