connect solution into app: VSTi live native (vst3 attach/vst2 bridge) with WASM autosample fallback, VST GUI window (Bug 1), native-first track instrument
This commit is contained in:
@@ -0,0 +1,132 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="vi">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>VST GUI</title>
|
||||
<style>
|
||||
:root { color-scheme: dark; }
|
||||
* { box-sizing: border-box; }
|
||||
body { margin: 0; font-family: system-ui, -apple-system, "Segoe UI", sans-serif; background: #16161a; color: #e4e4e7; }
|
||||
header { display: flex; align-items: center; justify-content: space-between; padding: 8px 12px; background: #1e1e24; border-bottom: 1px solid #2d2d35; position: sticky; top: 0; z-index: 10; }
|
||||
header h1 { font-size: 13px; margin: 0; font-weight: 600; color: #7dd3fc; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
header .meta { font-size: 10px; color: #71717a; margin-top: 2px; }
|
||||
#params { padding: 10px 12px; }
|
||||
.param { display: grid; grid-template-columns: minmax(0,1fr) 60px; align-items: center; gap: 8px; padding: 6px 0; border-bottom: 1px solid #26262d; }
|
||||
.param .title { font-size: 11px; color: #d4d4d8; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.param .val { font-size: 10px; font-family: ui-monospace, monospace; color: #7dd3fc; text-align: right; }
|
||||
input[type=range] { width: 100%; accent-color: #0ea5e9; }
|
||||
#status { padding: 8px 12px; font-size: 11px; color: #a1a1aa; }
|
||||
#status.err { color: #f87171; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<div>
|
||||
<h1 id="title">VST GUI</h1>
|
||||
<div class="meta" id="meta"></div>
|
||||
</div>
|
||||
<button id="closeBtn" style="background:#3f3f46;border:1px solid #52525b;color:#f4f4f5;font-size:11px;padding:4px 10px;border-radius:4px;cursor:pointer;">Đóng</button>
|
||||
</header>
|
||||
<div id="params"></div>
|
||||
<div id="status">Đang tải tham số…</div>
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
const qs = new URLSearchParams(window.location.search);
|
||||
const trackId = qs.get('track') || '';
|
||||
const pluginId = qs.get('plugin') || '';
|
||||
const el = (id) => document.getElementById(id);
|
||||
|
||||
el('title').textContent = 'VST GUI - ' + pluginId;
|
||||
el('meta').textContent = 'track=' + trackId;
|
||||
|
||||
const invoke = (cmd, args) => {
|
||||
if (window.__TAURI__ && window.__TAURI__.core) {
|
||||
return window.__TAURI__.core.invoke(cmd, args);
|
||||
}
|
||||
return Promise.reject(new Error('Tauri core unavailable'));
|
||||
};
|
||||
|
||||
let params = [];
|
||||
|
||||
function render() {
|
||||
const box = el('params');
|
||||
box.innerHTML = '';
|
||||
if (!params.length) {
|
||||
el('status').textContent = 'Plugin không có tham số (hoặc chưa nạp).';
|
||||
return;
|
||||
}
|
||||
el('status').textContent = params.length + ' tham số — kéo slider để đổi giá trị.';
|
||||
params.forEach((p) => {
|
||||
const row = document.createElement('div');
|
||||
row.className = 'param';
|
||||
const title = document.createElement('div');
|
||||
title.className = 'title';
|
||||
title.textContent = p.title || ('Param ' + p.param_id);
|
||||
title.title = title.textContent;
|
||||
const val = document.createElement('div');
|
||||
val.className = 'val';
|
||||
const range = document.createElement('input');
|
||||
range.type = 'range';
|
||||
range.min = 0;
|
||||
range.max = 1;
|
||||
range.step = 0.001;
|
||||
range.value = Math.min(1, Math.max(0, p.value || 0));
|
||||
val.textContent = (p.value || 0).toFixed(3);
|
||||
let dragging = false;
|
||||
range.addEventListener('input', () => {
|
||||
val.textContent = Number(range.value).toFixed(3);
|
||||
});
|
||||
range.addEventListener('change', () => {
|
||||
invoke('set_vst_param', { trackId: trackId, pluginId: pluginId, paramId: p.param_id, value: Number(range.value) })
|
||||
.catch((err) => { el('status').className = 'err'; el('status').textContent = 'set_vst_param lỗi: ' + (err && (err.message || err)); });
|
||||
});
|
||||
row.appendChild(title);
|
||||
row.appendChild(val);
|
||||
row.appendChild(range);
|
||||
box.appendChild(row);
|
||||
});
|
||||
}
|
||||
|
||||
// Sync 2 chiều: native editor đổi param → Rust emit vst_param_changed → cập nhật slider.
|
||||
function onParamChanged(e) {
|
||||
const d = e.payload || {};
|
||||
if (d.param_id === undefined) return;
|
||||
const p = params.find((x) => x.param_id === d.param_id);
|
||||
if (p) {
|
||||
p.value = d.value;
|
||||
const ranges = document.querySelectorAll('input[type=range]');
|
||||
const idx = params.indexOf(p);
|
||||
if (ranges[idx]) ranges[idx].value = Math.min(1, Math.max(0, d.value));
|
||||
const vals = document.querySelectorAll('.val');
|
||||
if (vals[idx]) vals[idx].textContent = Number(d.value).toFixed(3);
|
||||
}
|
||||
}
|
||||
|
||||
function init() {
|
||||
invoke('get_vst_params', { trackId: trackId, pluginId: pluginId }).then((list) => {
|
||||
params = list || [];
|
||||
render();
|
||||
}).catch((err) => {
|
||||
el('status').className = 'err';
|
||||
el('status').textContent = 'get_vst_params lỗi: ' + (err && (err.message || err)) + ' — plugin chưa mở được (kiểm tra log Rust / bridge DLL).';
|
||||
});
|
||||
}
|
||||
|
||||
el('closeBtn').addEventListener('click', () => {
|
||||
invoke('close_vst_editor', { trackId: trackId, pluginId: pluginId }).catch(() => {});
|
||||
window.close();
|
||||
});
|
||||
window.addEventListener('beforeunload', () => {
|
||||
invoke('close_vst_editor', { trackId: trackId, pluginId: pluginId }).catch(() => {});
|
||||
});
|
||||
|
||||
if (window.__TAURI__ && window.__TAURI__.event) {
|
||||
window.__TAURI__.event.listen('vst_param_changed', onParamChanged).catch(() => {});
|
||||
}
|
||||
init();
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user