T11: IPC param sync 2 chieu native<->JS (setParamNormalized/setParameter, vst_param_changed event, guard chong loop)
This commit is contained in:
@@ -5,16 +5,97 @@
|
||||
<title>VST GUI</title>
|
||||
<style>
|
||||
body { font-family: system-ui, sans-serif; background: #1a1d24; color: #c8ccd4;
|
||||
display: flex; align-items: center; justify-content: center; height: 100vh; margin: 0; }
|
||||
#msg { text-align: center; max-width: 520px; line-height: 1.6; }
|
||||
height: 100vh; margin: 0; display: flex; }
|
||||
#editor { flex: 1; position: relative; }
|
||||
#panel { width: 260px; border-left: 1px solid #2c313b; padding: 10px; overflow-y: auto;
|
||||
background: #171a20; }
|
||||
#panel h3 { margin: 4px 0 10px; font-size: 13px; color: #9aa3b2; font-weight: 600;
|
||||
text-transform: uppercase; letter-spacing: .04em; }
|
||||
.row { margin-bottom: 10px; }
|
||||
.row label { display: block; font-size: 12px; color: #c8ccd4; margin-bottom: 2px;
|
||||
white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||||
.row .val { float: right; color: #6ea8ff; font-variant-numeric: tabular-nums; }
|
||||
.row input[type=range] { width: 100%; accent-color: #3b82f6; }
|
||||
#msg { padding: 20px; text-align: center; color: #9aa3b2; line-height: 1.6; }
|
||||
code { background: #262b34; padding: 2px 6px; border-radius: 4px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="msg">Cửa sổ nổi VST GUI (T8).<br>Plugin editor sẽ được attach vào HWND cửa sổ này ở T9.</div>
|
||||
<div id="editor"><div id="msg">Plugin editor attach vào HWND (vùng này).<br>Nếu không thấy editor: cửa sổ này mở qua DAW, native DLL phải tìm thấy (SF_NATIVE_HOST_DIR).</div></div>
|
||||
<div id="panel">
|
||||
<h3>Parameters</h3>
|
||||
<div id="params"><div id="msg">Đang tải params…</div></div>
|
||||
</div>
|
||||
<script>
|
||||
// T8: placeholder — chưa có native editor. JS của DAW gọi invoke('open_vst_gui', ...)
|
||||
// để mở/focus cửa sổ này. Thông tin (track, plugin) nằm ở title + label window.
|
||||
// T11: param sync 2 chiều — native đổi param -> event vst_param_changed -> cập nhật UI;
|
||||
// JS kéo slider -> invoke set_vst_param -> setParamNormalized/setParameter.
|
||||
// Guard chống loop: pendingSet chứa param_id JS vừa set; event echo của chính mình bị bỏ qua.
|
||||
const T = window.__TAURI__;
|
||||
if (!T) { document.getElementById('msg').textContent = 'Không có __TAURI__ (chạy ngoài Tauri).'; throw new Error('no tauri'); }
|
||||
const { invoke } = T.core;
|
||||
const { listen } = T.event;
|
||||
|
||||
const qs = new URLSearchParams(location.search);
|
||||
const trackId = qs.get('track') || '';
|
||||
const pluginId = qs.get('plugin') || '';
|
||||
const pendingSet = new Set();
|
||||
const sliders = new Map(); // param_id -> {slider, val}
|
||||
|
||||
function setValue(paramId, value) {
|
||||
const el = sliders.get(paramId);
|
||||
if (!el) return;
|
||||
el.slider.value = value;
|
||||
el.val.textContent = Number(value).toFixed(3);
|
||||
}
|
||||
|
||||
async function refresh() {
|
||||
const box = document.getElementById('params');
|
||||
let list;
|
||||
try {
|
||||
list = await invoke('get_vst_params', { trackId, pluginId });
|
||||
} catch (e) {
|
||||
box.innerHTML = '<div id="msg">Lỗi: ' + String(e) + '</div>';
|
||||
return;
|
||||
}
|
||||
if (!list || list.length === 0) {
|
||||
box.innerHTML = '<div id="msg">Plugin không có param nào.</div>';
|
||||
return;
|
||||
}
|
||||
box.innerHTML = '';
|
||||
for (const p of list) {
|
||||
const row = document.createElement('div');
|
||||
row.className = 'row';
|
||||
const lbl = document.createElement('label');
|
||||
lbl.textContent = p.title;
|
||||
const val = document.createElement('span');
|
||||
val.className = 'val';
|
||||
val.textContent = Number(p.value).toFixed(3);
|
||||
const slider = document.createElement('input');
|
||||
slider.type = 'range'; slider.min = 0; slider.max = 1; slider.step = 0.001;
|
||||
slider.value = p.value;
|
||||
slider.addEventListener('input', () => { val.textContent = parseFloat(slider.value).toFixed(3); });
|
||||
slider.addEventListener('change', () => {
|
||||
const v = parseFloat(slider.value);
|
||||
pendingSet.add(p.param_id);
|
||||
invoke('set_vst_param', { trackId, pluginId, paramId: p.param_id, value: v })
|
||||
.catch(err => { console.error('set_vst_param', err); })
|
||||
.finally(() => setTimeout(() => pendingSet.delete(p.param_id), 500));
|
||||
});
|
||||
row.appendChild(lbl); row.appendChild(val); row.appendChild(slider);
|
||||
box.appendChild(row);
|
||||
sliders.set(p.param_id, { slider, val });
|
||||
}
|
||||
}
|
||||
|
||||
// native đổi param (từ editor/automation) -> cập nhật UI
|
||||
listen('vst_param_changed', (e) => {
|
||||
const d = e.payload || {};
|
||||
if (d.track_id !== trackId || d.plugin_id !== pluginId) return;
|
||||
if (pendingSet.has(d.param_id)) { pendingSet.delete(d.param_id); return; } // echo của chính mình
|
||||
setValue(d.param_id, d.value);
|
||||
}).catch(err => console.error('listen vst_param_changed', err));
|
||||
|
||||
refresh().catch(err => console.error('refresh', err));
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user