<!-- SS · part 98 -->
 'F#': 'F#', 'G#': 'Ab', 'A#': 'Bb' };
  function pc(n) { n = FLAT[n] || n; return NOTES.indexOf(n); }
  function nm(i) { var n = NOTES[((i % 12) + 12) % 12]; return NICE[n] || n; }
  var KEYS = []; ['C', 'Db', 'D', 'Eb', 'E', 'F', 'F#', 'G', 'Ab', 'A', 'Bb', 'B'].forEach(function (k) { KEYS.push(k + ' minor'); KEYS.push(k + ' major'); });
  function parseKey(k) {
    var m = /^\s*([A-G])([#b]?)\s*(maj(?:or)?|m(?:in(?:or)?)?)?/i.exec(String(k || ''));
    if (!m) return { root: 0, minor: true, name: 'C minor' };
    var root = pc(m[1].toUpperCase() + m[2]); if (root < 0) root = 0;
    var q = (m[3] || '').toLowerCase(); var minor = q ? q.charAt(0) === 'm' && q.indexOf('maj') !== 0 : /minor/i.test(k);
    return { root: root, minor: minor, name: nm(root) + (minor ? ' minor' : ' major') };
  }
  function scale(k) { var K = parseKey(k), iv = K.minor ? [0, 2, 3, 5, 7, 8, 10] : [0, 2, 4, 5, 7, 9, 11]; return iv.map(function (x) { return (K.root + x) % 12; }); }
  function scaleNotes(k, octs) { var sc = scale(k), K = parseKey(k), out = []; octs.forEach(function (o) { sc.forEach(function (p) { out.push(nm(p) + (o + (p < K.root ? 1 : 0))); }); }); return out; }
  function diatonic(k) { var sc = scale(k), out = []; for (var i = 0; i < 7; i++) { var a = sc[i], b = sc[(i + 2) % 7], c = sc[(i + 4) % 7], t3 = (b - a + 12) % 12, t5 = (c - a + 12) % 12; out.push(nm(a) + (t3 === 3 ? (t5 === 6 ? 'dim' : 'm') : '')); } return out; }
  var QUAL = { '': [0, 4, 7], m: [0, 3, 7], min: [0, 3, 7], maj: [0, 4, 7], maj7: [0, 4, 7, 11], m7: [0, 3, 7, 10], '7': [0, 4, 7, 10], dim: [0, 3, 6], aug: [0, 4, 8], sus2: [0, 2, 7], sus4: [0, 5, 7], add9: [0, 4, 7, 14], '6': [0, 4, 7, 9], m6: [0, 3, 7, 9], '9': [0, 4, 7, 10, 14], m9: [0, 3, 7, 10, 14] };
  function noteMidi(n) { var m = /^([A-G][#b]?)(-?\d)$/.exec(n || ''); if (!m) return null; return 12 * (+m[2] + 1) + pc(m[1]); }
  function midiName(m) { return nm(m) + (Math.floor(m / 12) - 1); }
  function chordMidis(sym) { var m = /^([A-G])([#b]?)(.*)$/.exec(String(sym || '')); if (!m) return null; var r = pc(m[1] + m[2]); var iv = QUAL[m[3]]; if (r < 0 || !iv) return null; var base = 48 + r; if (base > 55) base -= 12; return iv.map(function (x) { return base + x + 12; }); }
  function chordNotes(sym) { var ms = chordMidis(sym); return ms ? ms.map(midiName) : null; }

  // ------------------------------------------------------------------ state
  function z(v) { var a = []; for (var i = 0; i < STEPS; i++) a.push(v); return a; }
  function emptyPat() { return { drums: { kick: z(0), snare: z(0), hat: z(0), perc: z(0) }, bass: z(null), chords: z(null), lead: z(null) }; }
  function normPat(p) {
    p = p || {}; var o = emptyPat(), d = p.drums || {};
    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) { Obj