<!-- SS · part 72 -->
t.name] || 0; used[it.name] = k + 1;
      var blk = list[k] || (it.name === 'chorus' ? list[list.length - 1] : null); if (!blk) return;
      var n = blk.lines.length; if (!n) return;
      var per = it.bars / n, secEnd = (it.start + it.bars) * barSec(), prevEnd = 0;
      // grid-place each line on a half-bar; once voiced, pack lines so they never overlap (spill is reported, not clipped)
      var need = raw ? 0 : blk.lines.reduce(function (m, l) { var c = VOX.clips[l]; return m + (c && !c.sung ? c.dur + 0.12 : 0); }, 0), tight = need > it.bars * barSec() * 0.92;
      blk.lines.forEach(function (line, j) {
        var bar = it.start + Math.round(j * per * 2) / 2, t = tight ? it.start * barSec() + 0.05 : bar * barSec(), c = raw ? null : VOX.clips[line];
        if (c && c.sung) t = bar * barSec(); else if (j && !raw) t = Math.max(t, prevEnd + 0.12);
        if (c) prevEnd = t + c.dur; else prevEnd = t;
        out.push({ text: line, bar: bar, t: t, slot: Math.max(0.3, (j < n - 1 ? (it.start + Math.round((j + 1) * per * 2) / 2) * barSec() : secEnd) - t), sec: it.i, spill: c ? Math.max(0, t + c.dur - secEnd) : 0 });
      });
    });
    return out;
  }
  function uniqueLines() { var seen = {}, out = []; vocalPlan().forEach(function (p) { if (!seen[p.text]) { seen[p.text] = 1; out.push(p.text); } }); return out; }

  // ------------------------------------------------------------------ mobile tabs / songs drawer
  function setMTab(t) { root.setAttribute('data-mtab', t); $$('.es-mtabs button').forEach(function (b) { b.classList.toggle('is-on', b.getAttribute('data-m') === t); }); if (t === 'songs') loadSongs(); if (t === 'arrange') drawTimeline(); if (t === 'voice') drawCurve(); window.scrollTo(0, 0); }
  $$('.es-mtabs button').forEach(function (b) { b.addEventListener('click', function () { setMTab(b.getAttribute('data-m')); }); });
  $('#es-songs-open').addEventListener('click', function () { if (window.innerWidth >= 1100) { root.classList.toggle('songs-open'); if (root.classList.contains('songs-open')) loadSongs(); } else setMTab('songs'); });
  $('#es-songs-close').addEventListener('click', function () { root.classList.remove('songs-open'); });

  // ------------------------------------------------------------------ AUDIO GRAPH
  function makeDrums(kit, out) {
    var d = {}, nodes = []; function k(n) { nodes.push(n); return n; }
    var hp = k(new Tone.Filter(7000, 'highpass').connect(out));
    var cf = k(new Tone.Filter(1300, 'bandpass').connect(out)); cf.Q.value = 0.8;
    d.perc = k(new Tone.NoiseSynth({ noise: { type: 'white' }, envelope: { attack: 0.002, decay: 0.12, sustain: 0 }, volume: -4 }).connect(cf));
    if (kit === 'Lo-fi') {
      var lp = k(new Tone.Filter(3200, 'lowpass').connect(out));
      d.kick = k(new Tone.MembraneSynth({ pitchDecay: 0.03, octaves: 6, envelope: { attack: 0.001, decay: 0.35, sustain: 0, release: 0.1 } }).connect(lp));
      d.snare = k(new Tone.NoiseSynth({ noise: { type: 'pink' }, envelope: { attack: 0.001, decay: 0.18, sustain: 0 } }).connect(lp));
      d.hat = k(new Tone.NoiseSynth({ noise: { type: 'white' }, envelope: { attack: 0.001, decay: 0.04, sustain: 0 }, volume: -10 }).connect(hp));
      d.kickNote = 'C1';
    } else if (kit === 'Chip') {
      d.kick = k(new Tone.MembraneSynth({ oscillator: { type: 'square' }, pitchDecay: 0.02, octaves: 4, envelope: { attack: 0.001, decay: 0.18, sustain: 0 }, volume: -8 }).connect(out));
      d.snare = k(new Tone.NoiseSynth({ noise: { type: 'brown' }, envelope: { attack: 0.001, decay: 0.09, sustain: 0 }, volume: 2 }).connect(out));
      d.hat = k(new Tone.NoiseSynth({ noise: { type: 'white' }, envelope: { attack: 0.001, decay: 0.02, sustain: 0 }, volume: -6 }).connect(hp));
      d.kickNote = 'C2';
    } else {
      d.kick = k(new Tone.MembraneSynth({ pitchDecay: 0.06, octaves: 8, envelope: { attack: 0.001, decay: 0.6, sustain: 0.01, release: 0.4 } }).connect(out));
      var sf = k(new Tone.Filter(1800, 'bandpass').connect(out));
      d.snare = k(new Tone.NoiseSynth({ noise: { type: 'white' }, envelope: { attack: 0.001, decay: 0.2, sustain: 0 }, volume: 6 }).connect(sf));
      d.hat = k(new Tone.NoiseSynth({ noise: { type: 'white' }, envelope: { attack: 0.001, decay: 0.05, sustain: 0 }, volume: -4 }).connect(hp));
      d.kickNote = 'A0';
    }
    d.play = function (row, time, vel) { if (row === 'kick') d.kick.triggerAttackRelease(d.kickNote, '8n', time, vel); else d[row].triggerAttackRelease(row === 'hat' ? '32n' : '16n', time, vel); };
    d.dispose = function () { nodes.forEach(function (n) { try { n.dispose(); } catch (e) {} }); };
    return d;
  }
  function makeSynth(track, kind, out) {
    var s;
    if (track === 'bass') {
      if (kind === 'Saw') s = new Tone.MonoSynth({ oscillator: { type: 'sawtooth' }, filter: { Q: 2, type: 'lowpass' }, envelope: { attack: 0.005, decay: 0.2, sustain: 0.5, release: 0.2 }, filterEnvelope: { attack: 0.001, decay: 0.15, sustain: 0.2, baseFrequ