Files
SonicForgeStudio/src-tauri/ui/vst_gui.html
T

102 lines
4.4 KiB
HTML

<!doctype html>
<html lang="vi">
<head>
<meta charset="utf-8">
<title>VST GUI</title>
<style>
body { font-family: system-ui, sans-serif; background: #1a1d24; color: #c8ccd4;
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="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>
// 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>