<!-- SS · part 36 -->
codeAudioData(ab, res, function () { rej(new Error('Could not read that audio file.')); }); }); }); }
  function peaksOf(buf, n) { var d = buf.getChannelData(0), st = Math.max(1, Math.floor(d.length / n)), p = [], mx = 0; for (var i = 0; i < n; i++) { var m = 0; for (var j = i * st; j < (i + 1) * st && j < d.length; j += 4) { var a = Math.abs(d[j]); if (a > m) m = a; } p.push(m); if (m > mx) mx = m; } return { p: p, max: mx }; }
  function addStem(blob, buf, o) {
    var pk = peaksOf(buf, 400);
    var s = Object.assign({ id: uid(), name: 'Stem', kind: 'file', start: 0, trimIn: 0, trimOut: 0, fadeIn: 0.01, fadeOut: 0.05, vol: 0, mute: false, fx: { preset: 'dry', verb: 0, echo: 0, comp: 0, level: true }, eq: window.ESEQ ? ESEQ.flat() : null, created: Date.now() }, o || {});
    s.norm = pk.max > 0.001 ? Math.min(8, 0.89 / pk.max) : 1; s.blob = blob; s.buf = buf; s.peaks = pk.p;
    STEMS.push(s); persist(s, true); renderStems(); changed(); return s;
  }
  function len(s) { return Math.max(0.05, (s.buf ? s.buf.duration : 0) - s.trimIn - s.trimOut); }
  function stemEnd(s) { return s.start + len(s); }
  function changed() { if (window.ESEQ) ESEQ.refreshTargets(); if (ES) { ES.drawTimeline(); var E = ES.engine(); if (E && ES.isPlaying()) ES.play(ES.curPos()); } }
  // ---------- audio graph per stem
  function build(G, s, realtime) {
    var n = [];
    function k(x) { n.push(x); return x; }
    var pl = k(new Tone.Player(new Tone.ToneAudioBuffer(s.buf))); pl.fadeIn = Math.max(0.005, s.fadeIn); pl.fadeOut = Math.max(0.005, s.fadeOut);
    var lvl = k(new Tone.Gain((s.fx.level ? s.norm : 1) * Math.pow(10, s.vol / 20))), eqIn = k(new Tone.Gain(1)), eqOut = k(new Tone.Gain(1));
    pl.connect(lvl); lvl.connect(eqIn);
    var filt = window.ESEQ ? ESEQ.wire(eqIn, eqOut, s.eq) : (eqIn.connect(eqOut), []); filt.forEach(function (f) { n.push(f); });
    var post = eqOut;
    if (s.fx.comp > 0) { var c = k(new Tone.Compressor({ threshold: -6 - s.fx.comp * 0.24, ratio: 1.5 + s.fx.comp * 0.05, attack: 0.005, release: 0.15 })); var mk = k(new Tone.Gain(1 + s.fx.comp / 100)); post.connect(c); c.connect(mk); post = mk; }
    post.connect(G.out);
    if (s.fx.verb > 0) { var vs = k(new Tone.Gain(s.fx.verb / 100)); post.connect(vs); vs.connect(G.verbSend); }
    if (s.fx.echo > 0) { var beat = 60 / (ES.S.bpm || 90), d = k(new Tone.FeedbackDelay({ delayTime: beat * 0.75, feedback: 0.25 + s.fx.echo / 300, wet: 1 })), es = k(new Tone.Gain(s.fx.echo / 100)); post.connect(es); es.connect(d); d.connect(G.out); }
    var off = s.trimIn, at = s.start; if (at < 0) { off -= at; at = 0; }
    var dur = len(s) - (off - s.trimIn); if (dur <= 0.02) return n;
    if (realtime) pl.sync().start(at, off, dur); else pl.start(at + 0.02, off, dur);
    s._nodes = realtime ? { eqIn: eqIn, eqOut: eqOut, filt: filt } : s._nodes;
    return n;
  }
  function solos() { return STEMS.some(function (s) { return s.solo; }); }
  window.ES_HOOKS = window.ES_HOOKS || [];
  window.ES_HOOKS.push({
    ready: function (api) { ES = api; boot(); },
    schedule: function (G, realtime) {
      if (realtime) { (G.stemNodes || []).forEach(function (x) { try { x.unsync && x.unsync(); x.dispose(); } catch (e) {} }); G.stemNodes = []; }
      var so = solos();
      STEMS.forEach(function (s) { if (!s.buf || s.mute || (so && !s.solo)) return; var n = build(G, s, realtime); if (realtime) G.stemNodes = G.stemNodes.concat(n); });
    },
    end: function () { return STEMS.reduce(function (m, s) { return s.mute ? m : Math.max(m, stemEnd(s)); }, 0); },
    draw: function (g, w, RU, LH, tot) {
      var lane = 0; STEMS.forEach(function (s, i) {
        if (!s.peaks) return; var x0 = s.start / tot * w, cw = len(s) / tot * w, y = RU + 3 + (i % 2) * (LH / 2 - 2), hh = LH / 2 - 4;
        g.fillStyle = s.mute ? 'rgba(120,120,120,.25)' : 'rgba(232,195,106,.18)'; g.fillRect(x0, y, cw, hh);
        g.fillStyle = s.mute ? '#666' : '#e8c36a'; var tr0 = s.trimIn / s.buf.duration, tr1 = 1 - s.trimOut / s.buf.duration;
        for (var x = 0; x < cw; x++) { var a = s.peaks[Math.floor((tr0 + x / cw * (tr1 - tr0)) * s.peaks.length)] || 0, h2 = Math.max(1, a * s.norm * hh); g.fillRect(x0 + x, y + hh / 2 - h2 / 2, 1, h2); }
        g.fillStyle = '#000'; g.font = '9px sans-serif'; g.fillText(s.name.slice(0, 18), x0 + 3, y + 9); lane++;
      });
    }
  });
  // ---------- UI
  function fmt(v, d) { return (+v).toFixed(d == null ? 2 : d); }
  function row(s, k, lab, min, max, step, unit) { return '<label class="es-slider"><span>' + lab + ' <b>' + fmt(s[k], step < 1 ? 2 : 0) + unit + '</b></span><input type="range" data-k="' + k + '" min="' + min + '" max="' + max + '" step="' + step + '" value="' + s[k] + '"></label>'; }
  function renderStems() {
    var ul = $('#es-stems'); if (!STEMS.length) { ul.innerHTML = '<li class="es-hint">No takes or stems yet.</li>'; return; }
    ul.innerHTML = STEMS.map(function (s) {
      var D = s.buf ? s.buf.duration : 0, tot = Math.ma