+
+ AquaSynth lab
+ Patch Playground
+ An AquaSynth .aqua patch is lowered to Faust, compiled to WebAssembly in this page, and played through an AudioWorklet. Nothing here is rendered on a server.
+
+
+
+
+
Controls (from the DSP's own UI)
+
+
+
+
Lowered Faust source
+
+
+
+
+
+
+
+
diff --git a/tools/aqua-playground/web/styles.css b/tools/aqua-playground/web/styles.css
new file mode 100644
index 0000000..3c7dcaf
--- /dev/null
+++ b/tools/aqua-playground/web/styles.css
@@ -0,0 +1,19 @@
+:root { color-scheme: dark; --bg:#0b1418; --fg:#d7e6ea; --dim:#7f9aa3; --acc:#3fc1c9; --line:#1f3640; }
+* { box-sizing: border-box; }
+body { margin:0; background:var(--bg); color:var(--fg); font:15px/1.45 system-ui, sans-serif; }
+main { max-width:1200px; margin:0 auto; padding:24px; }
+.eyebrow { margin:0; color:var(--acc); text-transform:uppercase; letter-spacing:.12em; font-size:12px; }
+h1 { margin:.1em 0 .2em; } h2 { font-size:16px; margin:0 0 8px; } small { color:var(--dim); font-weight:400; }
+.lede { color:var(--dim); max-width:70ch; }
+.bar { display:flex; flex-wrap:wrap; gap:12px 16px; align-items:center; padding:12px 0; border-block:1px solid var(--line); margin-bottom:16px; }
+button, select { background:#10242b; color:var(--fg); border:1px solid var(--line); padding:6px 14px; border-radius:4px; font:inherit; }
+button:hover { border-color:var(--acc); } button:disabled { opacity:.4; }
+#status { color:var(--dim); }
+.cols { display:grid; grid-template-columns:minmax(280px,1fr) minmax(320px,1.4fr); gap:24px; }
+@media (max-width:800px) { .cols { grid-template-columns:1fr; } }
+.controls { display:grid; gap:6px; max-height:70vh; overflow:auto; padding-right:8px; }
+.controls .row { display:grid; grid-template-columns:minmax(0,1fr) 120px 58px; gap:8px; align-items:center; }
+.controls .grp { color:var(--dim); font-size:12px; margin-top:8px; }
+.controls label { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
+.controls output { text-align:right; color:var(--acc); font-variant-numeric:tabular-nums; }
+textarea { width:100%; height:62vh; background:#071013; color:#bcd3d8; border:1px solid var(--line); border-radius:4px; font:12px/1.4 ui-monospace, monospace; padding:8px; resize:vertical; }
diff --git a/tools/aqua-playground/web/ui.js b/tools/aqua-playground/web/ui.js
new file mode 100644
index 0000000..d06102a
--- /dev/null
+++ b/tools/aqua-playground/web/ui.js
@@ -0,0 +1,17 @@
+// Flattens a Faust DSP JSON `ui` tree (factory.json) into the controls a slider panel needs.
+// Shared by the browser app and the node test so both read the DSP's own UI declaration.
+export function controlsOf(json) {
+ const out = [];
+ const walk = (items, group) => {
+ for (const it of items ?? []) {
+ if (it.items) walk(it.items, it.label ? [...group, it.label] : group);
+ else if (/slider|nentry|checkbox|button/.test(it.type))
+ out.push({
+ address: it.address, label: it.label, group: group.join(" / "), type: it.type,
+ init: Number(it.init ?? 0), min: Number(it.min ?? 0), max: Number(it.max ?? 1), step: Number(it.step ?? 0.01),
+ });
+ }
+ };
+ walk(json.ui, []);
+ return out;
+}