<!-- SS · part 97 -->
, S[k], st[k]); });
        if (typeof st.swing === 'number') S.swing = st.swing; S.chords = st.chords || []; S.suno = st.suno || ''; S.genre = st.genre || ''; S.mood = st.mood || '';
        S.sel = 0; S.ai = clone(S.sections); VOX.clips = {}; seekPos = 0;
        if (E) { ['drums', 'bass', 'chords', 'lead'].forEach(function (t) { setSound(E, t, S.sounds[t]); }); applyMix(E); applyMaster(E); applyVocalFx(E); }
        syncAll(); root.classList.remove('songs-open'); if (window.innerWidth < 1100) setMTab('arrange'); toast('Opened "' + s.title + '". Render vocals to hear the voice.');
      }).catch(function (x) { toast(x.message); });
    }
  });

  // ------------------------------------------------------------------ boot
  function syncAll() { syncTop(); renderChat(); renderBlocks(); renderFaders(); syncKnobs(); syncVoiceUI(); refreshSection(); drawCurve(); idleSpectrum(); }
  syncAll(); checkUnlock();
  window.ES = { S: S, engine: function () { return E; }, unlock: unlock, play: play, stop: stop, isPlaying: function () { return playing; }, curPos: curPos, seekTo: seekTo, totalSec: totalSec, songEnd: songEnd, barSec: barSec, drawTimeline: drawTimeline, toast: toast, api: api, busy: busy, esc: esc, mmss: mmss, wavEncode: wavEncode, renderOffline: renderOffline, setMTab: setMTab, VOX: VOX, refreshSection: refreshSection, selName: selName, save: save, setSound: setSound, syncAll: syncAll, vocalPlan: vocalPlan, b64ToBuf: b64ToBuf, analyse: analyse };
  hook('ready', window.ES);
  window.addEventListener('resize', function () { clearTimeout(window.__esR); window.__esR = setTimeout(function () { drawTimeline(); drawCurve(); if (!playing) idleSpectrum(); }, 150); });
  document.addEventListener('visibilitychange', function () { if (document.hidden && playing && !recording) stop(); });
})();


===== FILE: public/ss/studio.js.bak-20261009-sing =====
/* Epistles Studio (/ss) — arrangement-based AI music studio. Tone.js (self-hosted), Gemini via /api/ss/arrange,
   Kokoro via /api/ss/voice jobs, offline WAV export via Tone.Offline. The classic page lives at /ss?classic=1. */
(function () {
  'use strict';
  var $ = function (s, r) { return (r || document).querySelector(s); };
  var $$ = function (s, r) { return Array.prototype.slice.call((r || document).querySelectorAll(s)); };
  var root = $('#es'); if (!root) return;
  var IS_ADMIN = root.getAttribute('data-admin') === '1';
  var CSRF = (document.querySelector('meta[name="csrf-token"]') || {}).content || '';
  var STEPS = 16, LS_KEY = 'es_state_v1';
  var SECS = ['intro', 'verse', 'chorus', 'bridge', 'outro'];
  var LANES = ['vocals', 'drums', 'bass', 'chords', 'lead'];
  var LANE_C = { vocals: '#ff3048', drums: '#ff5a3c', bass: '#f2b632', chords: '#b46cff', lead: '#2fd6e0' };
  var DRUM_ROWS = ['kick', 'snare', 'hat', 'perc'];
  var SOUNDS = { drums: ['808', 'Lo-fi', 'Chip'], bass: ['Sub', 'Saw', 'FM'], chords: ['Pad', 'Keys', 'Pluck'], lead: ['Saw', 'Square', 'Bell'] };

  // ------------------------------------------------------------------ utils
  function toast(msg, ms) { var t = $('#es-toast'); t.textContent = msg; t.hidden = false; clearTimeout(toast._t); toast._t = setTimeout(function () { t.hidden = true; }, ms || 3400); }
  function esc(s) { return String(s == null ? '' : s).replace(/[&<>"']/g, function (c) { return { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c]; }); }
  function busy(btn, on) { if (!btn) return; btn.disabled = !!on; btn.classList.toggle('es-busy', !!on); }
  function api(method, url, body) {
    var opt = { method: method, credentials: 'same-origin', headers: { 'x-csrf-token': CSRF, 'Accept': 'application/json' } };
    if (body instanceof FormData) opt.body = body;
    else if (body !== undefined) { opt.headers['Content-Type'] = 'application/json'; opt.body = JSON.stringify(body); }
    return fetch(url, opt).then(function (r) {
      return r.json().catch(function () { return {}; }).then(function (j) {
        if (!r.ok) { var e = new Error(j.error || (r.status === 429 ? 'Slow down — try again shortly.' : r.status === 401 ? 'Please log in again.' : r.status === 413 ? 'File too large.' : 'Request failed (' + r.status + ')')); e.status = r.status; throw e; }
        return j;
      });
    });
  }
  function clone(o) { return JSON.parse(JSON.stringify(o)); }
  function mmss(s) { s = Math.max(0, Math.floor(s)); return Math.floor(s / 60) + ':' + ('0' + (s % 60)).slice(-2); }
  function fmtDate(t) { try { return new Date(t * 1000).toLocaleDateString(undefined, { month: 'short', day: 'numeric', year: 'numeric' }); } catch (e) { return ''; } }
  function cap(s) { return s.charAt(0).toUpperCase() + s.slice(1); }

  // ------------------------------------------------------------------ theory
  var NOTES = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B'];
  var FLAT = { Db: 'C#', Eb: 'D#', Gb: 'F#', Ab: 'G#', Bb: 'A#', Cb: 'B', Fb: 'E' };
  var NICE = { 'C#': 'Db', 'D#': 'Eb',