<!-- SS · part 109 -->
: S.sounds, vols: S.vols, mutes: S.mutes, solos: S.solos, swing: S.swing, chords: S.chords, suno: S.suno, genre: S.genre, mood: S.mood, master: S.master, voice: S.voice }));
    if (audio) fd.append('audio', audio, name);
    return fd;
  }
  function ensureTitle() { if (S.title) return true; var t = prompt('Song title?', 'Untitled'); if (t === null) return false; S.title = t.trim() || 'Untitled'; save(); return true; }
  function showX(msg) { $('#es-exportbox').hidden = false; $('#es-xstatus').textContent = msg; }
  // Offline render of the whole arrangement (+ placed vocals) — returns an AudioBuffer.
  function renderOffline() {
    var dur = songEnd() + 2.5, vp = vocalPlan();
    return Tone.Offline(function () {
      var G = makeGraph(false), steps = PLAN.bars * 16;
      for (var st = 0; st < steps; st++) triggerStep(G, st, st * stepSec() + swingOffset(st % 16) + 0.02);
      vp.forEach(function (v) { var c = VOX.clips[v.text]; if (c) new Tone.Player(c.buf).connect(G.vin).start(v.t + 0.02); });
      hook('schedule', G, false);
      return G.verb.ready;
    }, dur, 2, 44100).then(function (tb) { return tb.get ? tb.get() : tb; });
  }
  window.__esRenderOffline = renderOffline; // used by the headless smoke test only
  $('#es-export').addEventListener('click', function () {
    var btn = this; if (!window.Tone || !ensureTitle()) return;
    stop(); busy(btn, true);
    showX('Rendering ' + mmss(songEnd() + 2.5) + ' offline (full arrangement' + (Object.keys(VOX.clips).length ? ' + vocals' : '') + ')…');
    unlock().catch(function () {}).then(renderOffline).then(function (ab) {
      var rate = 44100; if (ab.length * 4 > 44 * 1048576) rate = 32000; if (ab.length * (rate / ab.sampleRate) * 4 > 44 * 1048576) rate = 22050;
      var wav = wavEncode(ab, rate); var dl = $('#es-dl-wav'); if (dl.href) URL.revokeObjectURL(dl.href); dl.href = URL.createObjectURL(wav); dl.download = (S.title || 'epistles-studio').replace(/[^\w\- ]+/g, '').trim() + '.wav'; dl.hidden = false; showX('Uploading ' + (wav.size / 1048576).toFixed(1) + ' MB WAV → MP3…');
      return api('POST', '/api/ss/save', projectForm(wav, 'epistles-studio.wav'));
    }).then(function (j) { showX('Exported "' + j.song.title + '" to My Songs as MP3.'); var a = $('#es-rec-audio'); a.src = j.song.url; a.hidden = false; $('#es-rec-row').hidden = true; songsLoaded = false; toast('Exported to My Songs.'); })
      .catch(function (e) { showX('Export failed: ' + (e && e.message || e)); })
      .then(function () { busy(btn, false); });
  });
  var recBlob = null, recUrl = null, recording = false;
  $('#es-rec').addEventListener('click', function () {
    var btn = this;
    unlock().then(function () {
      if (!E.recorder) return toast('This browser cannot record audio — use Export instead.');
      if (recording) return finishRec();
      recording = true; btn.classList.add('is-on'); btn.textContent = '■ STOP';
      E.recorder.start(); play(0);
      showX('Recording live… press ■ STOP (or let the song finish).');
      E.T.scheduleOnce(function (t) { Tone.getDraw().schedule(function () { if (recording) finishRec(); }, t); }, totalSec() + 1.5);
    }).catch(function () { toast('Tap again to start audio.'); });
  });
  function finishRec() {
    recording = false; var btn = $('#es-rec'); btn.classList.remove('is-on'); btn.textContent = '● REC'; stop();
    setTimeout(function () { E.recorder.stop().then(function (blob) { recBlob = blob; if (recUrl) URL.revokeObjectURL(recUrl); recUrl = URL.createObjectURL(blob); var a = $('#es-rec-audio'); a.src = recUrl; a.hidden = false; $('#es-rec-row').hidden = false; showX('Recorded. Listen, then save it to My Songs.'); }); }, 700);
  }
  $('#es-rec-discard').addEventListener('click', function () { recBlob = null; $('#es-rec-row').hidden = true; $('#es-rec-audio').hidden = true; $('#es-xstatus').textContent = ''; });
  $('#es-rec-save').addEventListener('click', function () {
    var btn = this; if (!recBlob || !ensureTitle()) return; var type = recBlob.type || 'audio/webm';
    busy(btn, true); api('POST', '/api/ss/save', projectForm(recBlob, 'es-live.' + (/mp4|aac/.test(type) ? 'm4a' : /ogg/.test(type) ? 'ogg' : 'webm')))
      .then(function (j) { toast('Saved "' + j.song.title + '".'); $('#es-rec-row').hidden = true; recBlob = null; songsLoaded = false; }).catch(function (e) { toast(e.message, 5000); }).then(function () { busy(btn, false); });
  });
  $('#es-save-draft').addEventListener('click', function () { var b = this; if (!ensureTitle()) return; busy(b, true); api('POST', '/api/ss/save', projectForm(null)).then(function (j) { toast('Project "' + j.song.title + '" saved to My Songs.'); songsLoaded = false; }).catch(function (e) { toast(e.message); }).then(function () { busy(b, false); }); });

  // ------------------------------------------------------------------ my songs
  var songsLoaded = false;
  if (IS_ADMIN) $('#es-all-wrap').hidden = false;
  $('#es-all').addEventListener('