<!-- SS · part 106 -->
40;
      for (var k = a; k < b && k < n; k++) m = Math.max(m, v[k]);
      var hh = Math.max(0, (m + 100) / 100) * (h - 12);
      g.fillStyle = m > -20 ? '#ff6070' : '#e3122c'; g.fillRect(i * (w / bars), h - 12 - hh, Math.max(1, w / bars - 1), hh);
    }
  }
  function idleSpectrum() { var cv = specCv, w = cv.clientWidth, h = cv.clientHeight; if (!w) return; var dpr = window.devicePixelRatio || 1; cv.width = w * dpr; cv.height = h * dpr; var g = cv.getContext('2d'); g.setTransform(dpr, 0, 0, dpr, 0, 0); g.fillStyle = '#050505'; g.fillRect(0, 0, w, h); g.fillStyle = '#666'; g.font = '10px sans-serif'; g.fillText('Press ▶ — live spectrum of the master bus', 10, h / 2 + 3); }

  // ------------------------------------------------------------------ vocal designer
  var EMO = { calm: { speed: 0.92, pitch: -1, aggr: 0, drama: 45, vuln: 45, pass: 35 }, proud: { speed: 1.0, pitch: 0, aggr: 30, drama: 35, vuln: 20, pass: 70 },
    aggressive: { speed: 1.1, pitch: -2, aggr: 80, drama: 15, vuln: 0, pass: 90 }, melancholy: { speed: 0.9, pitch: -2, aggr: 0, drama: 75, vuln: 70, pass: 40 }, hype: { speed: 1.15, pitch: 1, aggr: 55, drama: 25, vuln: 5, pass: 95 } };
  function syncVoiceUI() {
    var V = S.voice; $('#es-voice').value = V.name; if ($('#es-voice').value !== V.name) { V.name = 'am_onyx'; $('#es-voice').value = V.name; }
    $('#es-speed').value = V.speed; $('#es-speed-v').textContent = (+V.speed).toFixed(2); $('#es-pitch').value = V.pitch; $('#es-pitch-v').textContent = (V.pitch > 0 ? '+' : '') + V.pitch + ' st';
    $$('[data-dyn]').forEach(function (r) { var k = r.getAttribute('data-dyn'); r.value = V[k]; $('[data-v="' + k + '"]').textContent = V[k] + '%'; });
    $$('#es-emo button').forEach(function (b) { b.classList.toggle('is-on', b.getAttribute('data-emo') === V.emo); });
    var o = $('#es-voice').selectedOptions[0];
    $('#es-av-name').textContent = 'VOCAL AVATAR: ' + (V.name.split('_')[1] || V.name).toUpperCase(); $('#es-av-sub').textContent = 'KOKORO · ' + (V.name[0] === 'b' ? 'EN-GB' : 'EN-US') + (o ? ' · ' + o.textContent.split('·')[0].trim().toUpperCase() : '');
  }
  $('#es-voice').addEventListener('change', function () { S.voice.name = this.value; syncVoiceUI(); save(); });
  $('#es-speed').addEventListener('input', function () { S.voice.speed = +this.value; syncVoiceUI(); save(); });
  $('#es-pitch').addEventListener('input', function () { S.voice.pitch = +this.value; syncVoiceUI(); save(); });
  $$('[data-dyn]').forEach(function (r) { r.addEventListener('input', function () { S.voice[r.getAttribute('data-dyn')] = +r.value; syncVoiceUI(); applyVocalFx(E); save(); }); });
  $('#es-emo').addEventListener('click', function (e) { var b = e.target.closest('[data-emo]'); if (!b) return; var k = b.getAttribute('data-emo'); Object.assign(S.voice, EMO[k], { emo: k }); syncVoiceUI(); applyVocalFx(E); save(); toast(cap(k) + ': effects apply now; speed and pitch on the next render.'); });
  function drawCurve() {
    var cv = $('#es-curve'), w = cv.clientWidth, h = cv.clientHeight; if (!w) return; var dpr = window.devicePixelRatio || 1; cv.width = w * dpr; cv.height = h * dpr;
    var g = cv.getContext('2d'); g.setTransform(dpr, 0, 0, dpr, 0, 0); g.fillStyle = '#050505'; g.fillRect(0, 0, w, h);
    g.strokeStyle = '#1d1d1d'; for (var i = 1; i < 3; i++) { g.beginPath(); g.moveTo(0, h * i / 3); g.lineTo(w, h * i / 3); g.stroke(); }
    g.fillStyle = '#555'; g.font = '9px sans-serif'; g.fillText('HIGH', 3, 10); g.fillText('LOW', 3, h - 3);
    var tot = totalSec() || 1, pts = [];
    vocalPlan().forEach(function (v) { var c = VOX.clips[v.text]; if (!c) return; c.rms.forEach(function (r, j) { pts.push([(v.t + j / c.rms.length * c.dur) / tot * w, r]); }); });
    if (!pts.length) { g.fillStyle = '#777'; g.fillText('Render vocals to see the expression curve', 34, h / 2 + 3); return; }
    var bias = S.voice.pitch / 12; g.strokeStyle = '#ff3048'; g.lineWidth = 1.6; g.beginPath();
    pts.forEach(function (p, i) { var y = h - 6 - Math.max(0, Math.min(1, p[1] * 3.2 + 0.15 + bias * 0.3)) * (h - 12); if (i) g.lineTo(p[0], y); else g.moveTo(p[0], y); }); g.stroke();
  }
  function b64ToBuf(b64) { var bin = atob(b64), u = new Uint8Array(bin.length); for (var i = 0; i < bin.length; i++) u[i] = bin.charCodeAt(i); return u.buffer; }
  function analyse(buf) { var d = buf.getChannelData(0), N = 160, peaks = [], rms = [], step = Math.max(1, Math.floor(d.length / N)); for (var i = 0; i < N; i++) { var mx = 0, s = 0; for (var j = i * step; j < (i + 1) * step && j < d.length; j++) { var a = Math.abs(d[j]); if (a > mx) mx = a; s += d[j] * d[j]; } peaks.push(Math.min(1, mx * 1.4)); rms.push(Math.sqrt(s / step)); } return { peaks: peaks, rms: rms }; }
  function renderVocals(btn) {
    var lines = uniqueLines(); if (!lines.length) { toast('No lyrics placed in the song yet — generate or write lyrics first.'); return Promise.resolve(false); }
    if (lines.length > 40) { lines = lines.slice(0, 40); toas