<!-- SS · part 38 -->
revokeObjectURL(u); }, 5000); return; }
    else if (a === 'del') { if (!confirm('Delete "' + s.name + '" from this device?')) return; STEMS = STEMS.filter(function (x) { return x !== s; }); idb('readwrite', function (st) { return st.delete(s.id); }).catch(function () {}); }
    persist(s); renderStems(); changed();
  });
  // upload
  $('#es-r-file').addEventListener('change', function () {
    var f = this.files && this.files[0]; this.value = ''; if (!f) return;
    if (f.size > MAXB) return toast('That file is over 25 MB.');
    if (!/\.(mp3|wav|m4a|aac|ogg)$/i.test(f.name) && !/^audio\//.test(f.type)) return toast('Audio files only (MP3, WAV, M4A).');
    $('#es-r-status').textContent = 'Reading ' + f.name + '…';
    ES.unlock().catch(function () {}).then(function () { return decode(f); }).then(function (buf) {
      addStem(f, buf, { name: f.name.replace(/\.[^.]+$/, '').slice(0, 40), kind: 'file', start: 0 }); $('#es-r-status').textContent = 'Added "' + f.name + '" (' + buf.duration.toFixed(1) + 's) at 0:00. Move it with Start.';
    }).catch(function (e) { $('#es-r-status').textContent = e.message; });
  });
  // ---------- recording
  var stream = null, mr = null, chunks = [], recAt = 0, recOn = false, metro = null, monNode = null, an = null, meterRaf = 0, endT = null, savedOut = null;
  function click(time, accent) { if (!metro) metro = new Tone.MembraneSynth({ pitchDecay: 0.008, octaves: 2, envelope: { attack: 0.001, decay: 0.08, sustain: 0 } }).toDestination(); metro.triggerAttackRelease(accent ? 'C6' : 'G5', 0.03, time, accent ? 0.9 : 0.55); }
  var metroLoop = null, metroOn = false;
  function ensureMetroLoop() { if (metroLoop) return; var T = ES.engine().T; metroLoop = new Tone.Loop(function (t) { if (!metroOn) return; var q = Math.round(T.getTicksAtTime(t) / T.PPQ); click(t, q % 4 === 0); }, '4n').start(0); }
  function meter() { var cv = $('#es-r-meter'), g = cv.getContext('2d'), w = cv.width = cv.clientWidth, h = cv.height; var d = new Float32Array(1024); (function f() { if (!an) { g.clearRect(0, 0, w, h); return; } an.getFloatTimeDomainData(d); var m = 0; for (var i = 0; i < d.length; i++) m = Math.max(m, Math.abs(d[i])); g.fillStyle = '#050505'; g.fillRect(0, 0, w, h); g.fillStyle = m > 0.95 ? '#ff2a2a' : m > 0.6 ? '#e8c36a' : '#2fbf5b'; g.fillRect(0, 0, w * Math.min(1, m), h); meterRaf = requestAnimationFrame(f); })(); }
  function startRec() {
    var btn = $('#es-r-rec');
    if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia || typeof MediaRecorder === 'undefined') return toast('This browser cannot record from the mic.');
    ES.unlock().then(function () {
      return stream ? stream : navigator.mediaDevices.getUserMedia({ audio: { echoCancellation: false, noiseSuppression: false, autoGainControl: false } }).then(function (st) { stream = st; return st; });
    }).then(function (st) {
      var ctx = Tone.getContext().rawContext, src = ctx.createMediaStreamSource(st); an = ctx.createAnalyser(); an.fftSize = 2048; src.connect(an); meter();
      if ($('#es-r-mon').checked) { monNode = src; src.connect(ctx.destination); }
      var lat = $('#es-r-lat'); if (+lat.value === 0 && !lat._set) { lat._set = 1; lat.value = Math.round(((ctx.baseLatency || 0) + (ctx.outputLatency || 0)) * 1000 * 2 + 20); $('#es-r-lat-v').textContent = lat.value + ' ms'; }
      ES.stop(); var pos = ES.curPos(), beat = 60 / (ES.S.bpm || 90), cnt = $('#es-r-count').checked ? 4 : 0, t0 = Tone.now() + 0.1;
      for (var i = 0; i < cnt; i++) click(t0 + i * beat, i === 0);
      $('#es-r-status').textContent = cnt ? 'Count-in…' : 'Recording…'; btn.textContent = '■ Stop'; btn.classList.add('is-on'); recOn = true;
      setTimeout(function () {
        if (!recOn) return;
        chunks = []; mr = new MediaRecorder(st); mr.ondataavailable = function (e) { if (e.data && e.data.size) chunks.push(e.data); }; mr.onstop = finish;
        ensureMetroLoop(); metroOn = $('#es-r-metro').checked;
        var E = ES.engine(); if (!$('#es-r-beat').checked) { savedOut = E.out.gain.value; E.out.gain.value = 0; }
        mr.start(); recAt = pos; ES.play(pos); $('#es-r-status').textContent = '● Recording from ' + ES.mmss(pos) + '…';
        endT = setTimeout(function () { if (recOn) stopRec(); }, Math.max(10, ES.songEnd() - pos + 2) * 1000);
      }, Math.max(0, (t0 + cnt * beat - Tone.now()) * 1000));
    }).catch(function (e) { toast(e && e.name === 'NotAllowedError' ? 'Mic permission was denied. Allow the microphone in your browser settings.' : 'Could not start the mic: ' + (e && e.message || e)); stopUI(); });
  }
  function stopUI() { recOn = false; var b = $('#es-r-rec'); b.textContent = '● Record take'; b.classList.remove('is-on'); metroOn = false; clearTimeout(endT); var E = ES.engine(); if (E && savedOut != null) { E.out.gain.value = savedOut; savedOut = null; } if (monNode) { try { monNode.disconnect(); } catch (e) {} monNode = null; } cancelAnimationFrame(meterRaf); an = null; }
  fun