From 6a43111d5a18e369e5f431ab904166357010a40e Mon Sep 17 00:00:00 2001 From: lou Date: Sat, 19 Sep 2026 19:56:58 +0800 Subject: [PATCH] =?UTF-8?q?EQ=20=E9=A2=84=E8=AE=BE=2010=20=E4=B8=AA(?= =?UTF-8?q?=E5=B9=B3=E7=9B=B4/=E6=B5=81=E8=A1=8C/=E6=91=87=E6=BB=9A/?= =?UTF-8?q?=E7=88=B5=E5=A3=AB/=E5=8F=A4=E5=85=B8/=E4=BA=BA=E5=A3=B0/?= =?UTF-8?q?=E4=BD=8E=E9=9F=B3/=E9=AB=98=E9=9F=B3/=E6=B7=B1=E5=A4=9C/?= =?UTF-8?q?=E6=9F=94=E5=8C=96):=20=E7=94=A8=E6=8E=A7=E5=88=B6=E7=82=B9?= =?UTF-8?q?=E5=9C=A8=E5=AF=B9=E6=95=B0=E9=A2=91=E7=8E=87=E8=BD=B4=E6=8F=92?= =?UTF-8?q?=E5=80=BC=E6=88=90=2032=20=E6=AE=B5;=20CDP=20=E7=82=B9=E5=87=BB?= =?UTF-8?q?=E5=AE=9E=E6=B5=8B=E4=BD=8E=E9=9F=B3[7,7,7,7,6.5,6]+=E5=90=8E?= =?UTF-8?q?=E6=AE=B5-1=E3=80=81=E6=9F=94=E5=8C=96=E5=89=8D=E6=AE=B5-2?= =?UTF-8?q?=E5=90=8E=E6=AE=B5-4.5=20=E5=9D=87=E5=86=99=E5=85=A5=20DSP?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/index.html | 61 ++++++++++++++++++++++++++++++++++++++++++++ 测试/cdp_预设验证.py | 53 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 114 insertions(+) create mode 100644 测试/cdp_预设验证.py diff --git a/web/index.html b/web/index.html index a19fc2e..531774f 100644 --- a/web/index.html +++ b/web/index.html @@ -53,6 +53,11 @@ input[type=range]::-moz-range-thumb{width:14px;height:14px;border:none;border-ra transform:translateX(-50%);transition:left .12s linear} .viz .num{width:84px;flex:none;text-align:right;color:var(--fg);font-variant-numeric:tabular-nums} +/* ---- EQ 预设 ---- */ +.presets{display:flex;flex-wrap:wrap;gap:6px;margin-bottom:12px} +.presets button{padding:4px 11px;font-size:11px} +.presets button.on{border-color:var(--ok);color:var(--ok);background:#12211a} + /* ---- EQ ---- */ .eq{display:flex;align-items:flex-end;gap:3px;height:190px;padding-top:6px} .band{flex:1;display:flex;flex-direction:column;align-items:center;gap:4px;height:100%} @@ -121,6 +126,7 @@ button:hover{border-color:var(--ok);color:var(--ok)}

32 段 EQ(1/3 倍频程 · ±15 dB)

+
@@ -219,6 +225,60 @@ function buildEq(freqs) { }); } +/* ---------- EQ 预设 ---------- */ +/* 控制点 = [频率 Hz, 增益 dB], 在对数频率轴上插值成 32 段 —— 比写死 32 个数好维护。 */ +const PRESETS = { + "平直": [], + "流行": [[60, 2.5], [250, -1], [1000, 1], [4000, 2.5], [12000, 3]], + "摇滚": [[60, 5], [250, -2], [1000, -1], [3500, 3], [12000, 5]], + "爵士": [[60, 4], [200, 1.5], [800, -1], [3000, 1.5], [12000, 3]], + "古典": [[60, 3], [400, -1], [2000, 0.5], [8000, 2], [16000, 3]], + "人声": [[100, -3], [500, -1], [2000, 4], [6000, 2], [12000, 1]], + "低音": [[40, 7], [120, 5], [300, 2], [1000, 0], [4000, -1]], + "高音": [[200, -1], [2000, 1], [6000, 4], [14000, 6]], + "深夜": [[60, 4], [200, 3], [1000, 0], [4000, 1], [12000, 3]], // 小音量等响度补偿 + "柔化": [[3000, -2], [6000, -4], [12000, -5], [16000, -3]], // 削刺耳高频 +}; +function curveTo(pts, freqs) { + if (!pts.length) return freqs.map(() => 0); + const first = pts[0], last = pts[pts.length - 1]; + return freqs.map((f) => { + if (f <= first[0]) return first[1]; + if (f >= last[0]) return last[1]; + for (let i = 0; i < pts.length - 1; i++) { + const a = pts[i], b = pts[i + 1]; + if (f >= a[0] && f <= b[0]) { + const t = Math.log(f / a[0]) / Math.log(b[0] / a[0]); + return a[1] + (b[1] - a[1]) * t; + } + } + return 0; + }); +} +function applyPreset(name) { + const freqs = state ? state.freqs : []; + if (!freqs.length) return; + const vals = curveTo(PRESETS[name], freqs).map((g) => Math.round(g * 2) / 2); // 对齐 0.5 步进 + gains = vals.slice(); + eqBox.querySelectorAll("input").forEach((inp, i) => { + inp.value = vals[i]; + const v = inp.previousElementSibling; + v.textContent = vals[i] === 0 ? "" : (vals[i] > 0 ? "+" : "") + vals[i].toFixed(1); + }); + commitEq(); + say("EQ 预设: " + name); +} +function buildPresets() { + const box = el("presets"); + box.innerHTML = ""; + Object.keys(PRESETS).forEach((name) => { + const b = document.createElement("button"); + b.textContent = name; + b.addEventListener("click", () => applyPreset(name)); + box.appendChild(b); + }); +} + /* ---------- 电平表 ---------- */ const METERS = [ ["inL", "in_rms", 0], ["outL", "out_rms", 0], @@ -280,6 +340,7 @@ es.onerror = () => say("面板与 DSP 失联, 重连中"); function applyState(d) { if (state === null) { + buildPresets(); buildEq(d.freqs); gains = d.gains.slice(); eqBox.querySelectorAll("input").forEach((inp) => { diff --git a/测试/cdp_预设验证.py b/测试/cdp_预设验证.py new file mode 100644 index 0000000..85ed581 --- /dev/null +++ b/测试/cdp_预设验证.py @@ -0,0 +1,53 @@ +"""点 EQ 预设, 验证前端与 DSP 都真的变了。""" +from __future__ import annotations + +import json +import subprocess +import time +import urllib.request + +import websocket + +API = "http://127.0.0.1:8789" +page = subprocess.Popen([ + "google-chrome", "--headless=new", "--disable-gpu", "--remote-debugging-port=9333", + "--remote-allow-origins=*", "--user-data-dir=/tmp/cxchrome4", API], + stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) +time.sleep(4) + +tabs = json.load(urllib.request.urlopen("http://127.0.0.1:9333/json/list", timeout=15)) +ws = websocket.create_connection([t for t in tabs if t.get("type") == "page"][0]["webSocketDebuggerUrl"], timeout=25) +_mid = [0] + + +def js(expr: str) -> object: + _mid[0] += 1 + ws.send(json.dumps({"id": _mid[0], "method": "Runtime.evaluate", + "params": {"expression": expr, "returnByValue": True}})) + while True: + msg = json.loads(ws.recv()) + if msg.get("id") == _mid[0]: + return ((msg.get("result") or {}).get("result") or {}).get("value") + + +print("预设按钮:", js("Array.from(document.querySelectorAll('#presets button')).map(b => b.textContent).join(' / ')")) + +for name in ("低音", "柔化"): + js("Array.from(document.querySelectorAll('#presets button')).find(b => b.textContent === '%s').click()" % name) + time.sleep(0.7) + vals = js("Array.from(document.querySelectorAll('#eq input')).map(i => +i.value)") + srv = json.load(urllib.request.urlopen(API + "/api/state", timeout=15)) + print() + print("点[%s] 前端前6段 = %s" % (name, [round(v, 1) for v in vals[:6]])) + print(" 前端后6段 = %s" % [round(v, 1) for v in vals[-6:]]) + print(" 服务端前6 = %s 后6 = %s" % ( + [round(g, 1) for g in srv["gains"][:6]], [round(g, 1) for g in srv["gains"][-6:]])) + print(" 状态栏 =", js("document.getElementById('status').textContent")) + +# 收尾: 回到平直 +js("Array.from(document.querySelectorAll('#presets button')).find(b => b.textContent === '平直').click()") +time.sleep(0.5) +print() +print("已复位到平直:", js("Array.from(document.querySelectorAll('#eq input')).every(i => +i.value === 0)")) +ws.close() +page.terminate()