<!-- SS · part 51 -->
('#es-wv-rack button');
    btns.forEach(function (b) { b.disabled = true; }); say('Processing ' + (r[1] - r[0] === W.buf.length ? 'whole wave' : 'selection') + '…');
    process(fx, seg, amt).then(function (out) {
      W.pending = { fx: fx, a: r[0], z: r[1], out: out }; $('#es-wv-pend').hidden = false; $('#es-wv-pend-t').textContent = 'Preview: ' + $('#es-wv-fxn').textContent + ' (' + out.duration.toFixed(2) + 's)';
      say('Listen to the preview, then Apply (undo is always available) or Cancel.'); playBuf(out, 0, out.length, r[0]);
    }).catch(function (e) { say(e.message); }).then(function () { btns.forEach(function (b) { b.disabled = false; }); });
  }
  $('#es-wv-rack').addEventListener('click', function (e) { var b = e.target.closest('[data-fx]'); if (b) preview(b.getAttribute('data-fx')); });
  function splice(a, z, ins) { var b = W.buf, parts = [a > 0 ? slice(b, 0, a) : null, ins, z < b.length ? slice(b, z, b.length) : null]; return concat(parts, b.numberOfChannels, b.sampleRate); }
  function act(w) {
    var b = W.buf;
    if (w === 'stop') return stopPlay();
    if (!b && w !== 'undo') return say('Load a wave first.');
    var r = b ? range() : null, hasSel = b && W.sel[1] > W.sel[0];
    switch (w) {
      case 'play': return playBuf(b, r[0], r[1]);
      case 'playall': return playBuf(b, W.cur < b.length - 100 ? W.cur : 0, b.length);
      case 'zin': var c = W.sel[1] > W.sel[0] ? (W.sel[0] + W.sel[1]) / 2 : W.cur, vl = (W.view[1] - W.view[0]) / 2; W.view = [Math.max(0, Math.round(c - vl / 2)), Math.min(b.length, Math.round(c + vl / 2))]; return draw();
      case 'zout': var vl2 = (W.view[1] - W.view[0]) * 2, c2 = (W.view[0] + W.view[1]) / 2; W.view = [Math.max(0, Math.round(c2 - vl2 / 2)), Math.min(b.length, Math.round(c2 + vl2 / 2))]; return draw();
      case 'fit': W.view = [0, b.length]; return draw();
      case 'all': W.sel = [0, b.length]; return draw();
      case 'copy': if (!hasSel) return say('Select something first.'); W.clip = slice(b, r[0], r[1]); return say('Copied ' + W.clip.duration.toFixed(2) + 's.');
      case 'cut': if (!hasSel) return say('Select something first.'); W.clip = slice(b, r[0], r[1]); W.cur = r[0]; setBuf(splice(r[0], r[1], null)); return say('Cut ' + W.clip.duration.toFixed(2) + 's.');
      case 'del': if (!hasSel) return say('Select something first.'); W.cur = r[0]; setBuf(splice(r[0], r[1], null)); return say('Deleted.');
      case 'crop': if (!hasSel) return say('Select something first.'); setBuf(slice(b, r[0], r[1])); W.cur = 0; return say('Trimmed to the selection.');
      case 'paste': if (!W.clip) return say('Copy or cut something first.'); return toRate(W.clip, b.sampleRate, b.numberOfChannels).then(function (cl) { var a = hasSel ? r[0] : W.cur, z = hasSel ? r[1] : W.cur; setBuf(splice(a, z, cl)); W.sel = [a, a + cl.length]; draw(); say('Pasted.'); });
      case 'mix': if (!W.clip) return say('Copy or cut something first.'); return toRate(W.clip, b.sampleRate, b.numberOfChannels).then(function (cl) {
        var at = hasSel ? r[0] : W.cur, n = Math.max(b.length, at + cl.length), o = mk(b.numberOfChannels, n, b.sampleRate);
        for (var c = 0; c < b.numberOfChannels; c++) { var d = new Float32Array(n); d.set(b.getChannelData(c)); var s = cl.getChannelData(c); for (var i = 0; i < s.length; i++) d[at + i] = Math.max(-1, Math.min(1, d[at + i] + s[i])); o.copyToChannel(d, c); }
        setBuf(o); W.sel = [at, at + cl.length]; draw(); say('Mixed the clipboard in at ' + t2s(at / b.sampleRate) + '.'); });
      case 'silence': var sec = parseFloat(prompt('Insert how many seconds of silence at the cursor?', '1')); if (!(sec > 0) || sec > 60) return; setBuf(splice(W.cur, W.cur, mk(b.numberOfChannels, Math.round(sec * b.sampleRate), b.sampleRate))); return say('Inserted ' + sec + 's of silence.');
      case 'undo': if (!W.undo.length) return say('Nothing to undo.'); W.redo.push(W.buf); W.buf = W.undo.pop(); W.view = [0, W.buf.length]; W.sel = [0, 0]; draw(); return say('Undone.');
      case 'redo': if (!W.redo.length) return say('Nothing to redo.'); W.undo.push(W.buf); W.buf = W.redo.pop(); W.view = [0, W.buf.length]; W.sel = [0, 0]; draw(); return say('Redone.');
      case 'pplay': if (W.pending) playBuf(W.pending.out, 0, W.pending.out.length, W.pending.a); return;
      case 'cancel': W.pending = null; $('#es-wv-pend').hidden = true; stopPlay(); return say('Cancelled. Nothing changed.');
      case 'apply': if (!W.pending) return; var P = W.pending; W.pending = null; $('#es-wv-pend').hidden = true; stopPlay();
        return toRate(P.out, b.sampleRate, b.numberOfChannels).then(function (o) { setBuf(splice(P.a, P.z, o), true); W.sel = P.z - P.a === b.length ? [0, 0] : [P.a, P.a + o.length]; draw(); say('Applied. Undo is available.'); });
      case 'stem': if (!window.ESREC) return; var blob = ES.wavEncode(b, b.sampleRate); ESREC.add(blob, b, { name: (W.name + ' (edit)').slice(0, 40), kind: 'file', start: 0 }); return sa