<!-- SS · part 92 -->
ventListener('click', function () {
    S.master.on = !S.master.on;
    if (S.master.on) { S.master.comp = Math.max(S.master.comp, 55); S.master.width = Math.max(S.master.width, 60); toast('Master chain on: glue compressor, EQ lift, limiter at −0.8 dB.'); }
    else toast('Master chain off.');
    applyMaster(E); syncKnobs(); save();
    if (window.innerWidth < 1100) setMTab('mix');
  });
  var specCv = $('#es-spec');
  function drawSpectrum() {
    if (!E || !E.fft) return; var cv = specCv, w = cv.clientWidth, h = cv.clientHeight; if (!w) return;
    var dpr = window.devicePixelRatio || 1; if (cv.width !== w * dpr) { 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);
    var v = E.fft.getValue(), n = v.length, sr = Tone.getContext().sampleRate, bars = Math.min(96, Math.floor(w / 3));
    g.strokeStyle = '#1c1c1c'; g.font = '9px sans-serif';
    [100, 1000, 10000].forEach(function (f) { var x = Math.log(f / 20) / Math.log(1000) * w; g.beginPath(); g.moveTo(x, 0); g.lineTo(x, h); g.stroke(); g.fillStyle = '#555'; g.fillText(f >= 1000 ? f / 1000 + 'k' : f, x + 2, h - 3); });
    for (var i = 0; i < bars; i++) {
      var f0 = 20 * Math.pow(1000, i / bars), f1 = 20 * Math.pow(1000, (i + 1) / bars);
      var a = Math.floor(f0 / (sr / 2) * n), b = Math.max(a + 1, Math.floor(f1 / (sr / 2) * n)), m = -140;
      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 = [