<!-- SS · part 71 -->
ums || {};
    DRUM_ROWS.forEach(function (r) { for (var i = 0; i < STEPS; i++) o.drums[r][i] = d[r] && d[r][i] ? 1 : 0; });
    ['bass', 'chords', 'lead'].forEach(function (t) { for (var i = 0; i < STEPS; i++) o[t][i] = p[t] && p[t][i] ? p[t][i] : null; });
    return o;
  }
  function starter() {
    var v = emptyPat(); [0, 6, 10].forEach(function (i) { v.drums.kick[i] = 1; }); [4, 12].forEach(function (i) { v.drums.snare[i] = 1; }); for (var i = 0; i < 16; i += 2) v.drums.hat[i] = 1;
    v.bass[0] = 'C2'; v.bass[6] = 'C2'; v.bass[10] = 'Eb2'; v.bass[14] = 'G2'; v.chords[0] = 'Cm'; v.chords[8] = 'Ab';
    var c = clone(v); c.drums.perc[4] = 1; c.drums.perc[12] = 1; c.lead[0] = 'G4'; c.lead[3] = 'Eb4'; c.lead[6] = 'F4'; c.lead[8] = 'G4'; c.lead[12] = 'C5';
    var intro = emptyPat(); intro.chords[0] = 'Cm'; intro.chords[8] = 'Ab'; for (var j = 0; j < 16; j += 4) intro.drums.hat[j] = 1;
    var br = emptyPat(); br.chords[0] = 'Fm'; br.chords[8] = 'G'; br.drums.snare[12] = 1; br.bass[0] = 'F2'; br.bass[8] = 'G2';
    var out = clone(intro); out.chords[0] = 'Ab'; out.chords[8] = 'Cm';
    return { intro: { bars: 2, patterns: intro }, verse: { bars: 4, patterns: v }, chorus: { bars: 4, patterns: c }, bridge: { bars: 2, patterns: br }, outro: { bars: 2, patterns: out } };
  }
  var S = {
    title: '', lyrics: '', suno: '', bpm: 92, key: 'C minor', chords: [], genre: '', mood: '', swing: 0.1, topic: '',
    sections: starter(), arrangement: ['intro', 'verse', 'chorus', 'verse', 'chorus', 'outro'], ai: null,
    sounds: { drums: '808', bass: 'Sub', chords: 'Pad', lead: 'Saw' },
    vols: { vocals: 0, drums: -4, bass: -6, chords: -14, lead: -12 }, mutes: {}, solos: {},
    master: { on: false, drive: 0, comp: 30, eq: 0, width: 50, verb: 25 },
    voice: { name: 'am_onyx', speed: 1, pitch: 0, emo: 'proud', aggr: 30, drama: 35, vuln: 20, pass: 70 },
    chat: [], sel: 1, rollLane: 'bass',
  };
  var VOX = { clips: {} }; // line text -> {buf, dur, peaks, rms}
  function save() { try { var o = {}; Object.keys(S).forEach(function (k) { o[k] = S[k]; }); localStorage.setItem(LS_KEY, JSON.stringify(o)); } catch (e) {} }
  (function load() {
    try {
      var d = JSON.parse(localStorage.getItem(LS_KEY) || 'null');
      if (d && d.sections) { Object.keys(d).forEach(function (k) { S[k] = d[k]; }); }
      else {
        var c = JSON.parse(localStorage.getItem('ss_state_v1') || 'null'); // classic page work carries over
        if (c && c.patterns) { ['title', 'lyrics', 'suno', 'bpm', 'key', 'chords', 'genre', 'mood', 'swing'].forEach(function (k) { if (c[k] != null) S[k] = c[k]; }); SECS.forEach(function (n) { S.sections[n].patterns = normPat(c.patterns); }); }
      }
    } catch (e) {}
    SECS.forEach(function (n) { if (!S.sections[n]) S.sections[n] = { bars: 4, patterns: emptyPat() }; S.sections[n].patterns = normPat(S.sections[n].patterns); });
    S.arrangement = (S.arrangement || []).filter(function (n) { return SECS.indexOf(n) >= 0; });
    if (!S.arrangement.length) S.arrangement = ['verse', 'chorus'];
    if (S.sel >= S.arrangement.length) S.sel = 0;
    S.mutes = S.mutes || {}; S.solos = S.solos || {};
  })();
  function selName() { return S.arrangement[S.sel] || 'verse'; }
  function selPat() { return S.sections[selName()].patterns; }

  // ------------------------------------------------------------------ timing / arrangement
  function stepSec() { return 60 / S.bpm / 4; }
  function barSec() { return stepSec() * 16; }
  function plan() { var out = [], b = 0; S.arrangement.forEach(function (n, i) { var bars = S.sections[n].bars; out.push({ i: i, name: n, start: b, bars: bars }); b += bars; }); return { items: out, bars: b }; }
  var PLAN = plan();
  function locate(bar) { for (var i = 0; i < PLAN.items.length; i++) { var it = PLAN.items[i]; if (bar >= it.start && bar < it.start + it.bars) return it; } return null; }
  function totalSec() { return PLAN.bars * barSec(); }

  // lyric blocks -> per-section vocal placements
  function blocks() {
    var out = [], cur = null;
    String(S.lyrics || '').split(/\r?\n/).forEach(function (l) {
      var t = l.trim(), m = /^\[([^\]]*)\]$/.exec(t);
      if (m) { cur = { label: m[1], type: secType(m[1]), lines: [] }; out.push(cur); return; }
      if (!t) return;
      if (!cur) { cur = { label: 'Verse', type: 'verse', lines: [] }; out.push(cur); }
      cur.lines.push(t);
    });
    return out;
  }
  function secType(label) { var l = String(label).toLowerCase(); if (/chorus|hook|refrain/.test(l)) return 'chorus'; if (/bridge|break/.test(l)) return 'bridge'; if (/intro/.test(l)) return 'intro'; if (/outro|end/.test(l)) return 'outro'; return 'verse'; }
  function vocalPlan(raw) {
    var bl = blocks(), byType = {}, used = {}, out = [];
    bl.forEach(function (b) { (byType[b.type] = byType[b.type] || []).push(b); });
    PLAN.items.forEach(function (it) {
      var list = byType[it.name]; if (!list || !list.length) return;
      var k = used[i