EQ 预设 10 个(平直/流行/摇滚/爵士/古典/人声/低音/高音/深夜/柔化): 用控制点在对数频率轴插值成 32 段; CDP 点击实测低音[7,7,7,7,6.5,6]+后段-1、柔化前段-2后段-4.5 均写入 DSP
This commit is contained in:
@@ -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}
|
transform:translateX(-50%);transition:left .12s linear}
|
||||||
.viz .num{width:84px;flex:none;text-align:right;color:var(--fg);font-variant-numeric:tabular-nums}
|
.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 ---- */
|
||||||
.eq{display:flex;align-items:flex-end;gap:3px;height:190px;padding-top:6px}
|
.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%}
|
.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)}
|
|||||||
|
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h2>32 段 EQ(1/3 倍频程 · ±15 dB)</h2>
|
<h2>32 段 EQ(1/3 倍频程 · ±15 dB)</h2>
|
||||||
|
<div class="presets" id="presets"></div>
|
||||||
<div class="eq" id="eq"></div>
|
<div class="eq" id="eq"></div>
|
||||||
<div class="btns">
|
<div class="btns">
|
||||||
<button id="flat">全部归零</button>
|
<button id="flat">全部归零</button>
|
||||||
@@ -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 = [
|
const METERS = [
|
||||||
["inL", "in_rms", 0], ["outL", "out_rms", 0],
|
["inL", "in_rms", 0], ["outL", "out_rms", 0],
|
||||||
@@ -280,6 +340,7 @@ es.onerror = () => say("面板与 DSP 失联, 重连中");
|
|||||||
|
|
||||||
function applyState(d) {
|
function applyState(d) {
|
||||||
if (state === null) {
|
if (state === null) {
|
||||||
|
buildPresets();
|
||||||
buildEq(d.freqs);
|
buildEq(d.freqs);
|
||||||
gains = d.gains.slice();
|
gains = d.gains.slice();
|
||||||
eqBox.querySelectorAll("input").forEach((inp) => {
|
eqBox.querySelectorAll("input").forEach((inp) => {
|
||||||
|
|||||||
@@ -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()
|
||||||
Reference in New Issue
Block a user