diff --git a/data_only_viz/web/public/control.html b/data_only_viz/web/public/control.html index e0d84ce..6e6a868 100644 --- a/data_only_viz/web/public/control.html +++ b/data_only_viz/web/public/control.html @@ -168,6 +168,19 @@ +

Modes visuels (oF / Metal)

+
+ + + + + + + + + +
+

Retour SC

BPM
Beat
diff --git a/data_only_viz/web/public/js/control.js b/data_only_viz/web/public/js/control.js index 4bc87f4..2acf073 100644 --- a/data_only_viz/web/public/js/control.js +++ b/data_only_viz/web/public/js/control.js @@ -48,15 +48,25 @@ document.querySelectorAll(".slider-row").forEach((row) => { }); // ---------- Scene buttons ---------- -document.querySelectorAll(".scene-btn").forEach((btn) => { +document.querySelectorAll("#scenes .scene-btn").forEach((btn) => { btn.addEventListener("click", () => { - document.querySelectorAll(".scene-btn") + document.querySelectorAll("#scenes .scene-btn") .forEach((b) => b.classList.remove("active")); btn.classList.add("active"); send("/scene/play", [btn.dataset.scene]); }); }); +// ---------- Visual mode buttons ---------- +document.querySelectorAll("#vizmodes .scene-btn").forEach((btn) => { + btn.addEventListener("click", () => { + document.querySelectorAll("#vizmodes .scene-btn") + .forEach((b) => b.classList.remove("active")); + btn.classList.add("active"); + send("/control/vizMode", [parseInt(btn.dataset.viz, 10)]); + }); +}); + // ---------- XY pad ---------- const pad = document.getElementById("xy"); const cursor = document.getElementById("xy-cursor"); diff --git a/data_only_viz/web/public/js/dashboard.js b/data_only_viz/web/public/js/dashboard.js index 71f6c9e..69a2c2d 100644 --- a/data_only_viz/web/public/js/dashboard.js +++ b/data_only_viz/web/public/js/dashboard.js @@ -235,6 +235,107 @@ const handlers = { drawSpark(c); } }, + // ---- Nouveaux feeds (gdelt, wikimedia, tides, atc, pose, mempool, + // github, rte_eco2mix) ------------------------------------- + gdelt: (m) => { + if (m.sub === "batch") { + const [n, countries, tone] = m.args; + const c = ensureCard("gdelt", { title: "GDELT · evenements 15min", + order: 19, wide: true, + alert: tone < -5, warn: tone < -2, green: tone > 2 }); + c.valueEl.textContent = String(n | 0); + c.subEl.textContent = `${countries | 0} pays · tone ${fmt(tone, 2)}`; + pushHistory(c, n); + drawSpark(c); + } + }, + wikimedia: (m) => { + if (m.sub === "rate") { + const c = ensureCard("wiki", { title: "Wikipedia firehose", order: 20 }); + c.valueEl.textContent = `${fmt(m.args[0], 1)} /s`; + pushHistory(c, m.args[0]); + drawSpark(c, "#6fa6ff"); + } else if (m.sub === "edit") { + const c = ensureCard("wiki", { title: "Wikipedia firehose", order: 20 }); + c.subEl.textContent = `${m.args[0]}: ${m.args[1]}`; + } + }, + tides: (m) => { + if (m.sub === "level") { + const [obs, pred, residual] = m.args; + const c = ensureCard("tides", { title: "Marees NOAA", order: 21, wide: true }); + c.valueEl.textContent = `${fmt(obs, 2)} m`; + c.subEl.textContent = `predit ${fmt(pred, 2)} m · residual ${fmt(residual, 2)} m`; + pushHistory(c, obs); + drawSpark(c, "#6fa6ff"); + } else if (m.sub === "moon") { + const [phase, illum] = m.args; + const c = ensureCard("moon", { title: "Phase lunaire", order: 22 }); + const phaseName = phase < 0.05 || phase > 0.95 ? "nouvelle" + : phase < 0.25 ? "premier croissant" + : phase < 0.30 ? "premier quartier" + : phase < 0.45 ? "gibbeuse croissante" + : phase < 0.55 ? "pleine" + : phase < 0.70 ? "gibbeuse decroissante" + : phase < 0.80 ? "dernier quartier" + : "dernier croissant"; + c.valueEl.textContent = `${fmt(illum * 100, 0)}%`; + c.subEl.textContent = phaseName; + } + }, + atc: (m) => { + if (m.sub === "total") { + const [total, hubs] = m.args; + const c = ensureCard("atc", { title: "ATC · auditeurs", order: 23 }); + c.valueEl.textContent = String(total | 0); + c.subEl.textContent = `${hubs | 0} hubs actifs`; + pushHistory(c, total); + drawSpark(c, "#6fe9b3"); + } + }, + pose: (m) => { + if (m.sub === "count") { + const c = ensureCard("pose", { title: "Pose YOLO · personnes", order: 3 }); + c.valueEl.textContent = String(m.args[0] | 0); + pushHistory(c, m.args[0]); + drawSpark(c, "#ff8838"); + } + }, + mempool: (m) => { + if (m.sub === "fee") { + const c = ensureCard("btc", { title: "Bitcoin · fee sat/vB", order: 24 }); + c.valueEl.textContent = String(m.args[0] | 0); + pushHistory(c, m.args[0]); + drawSpark(c, "#ff8838"); + } else if (m.sub === "tx") { + const c = ensureCard("btc", { title: "Bitcoin · fee sat/vB", order: 24 }); + c.subEl.textContent = `${m.args[0] | 0} tx en attente`; + } + }, + github: (m) => { + if (m.sub === "rate") { + const c = ensureCard("gh", { title: "GitHub events", order: 25 }); + c.valueEl.textContent = `${fmt(m.args[0], 1)} /s`; + pushHistory(c, m.args[0]); + drawSpark(c, "#6fa6ff"); + } + }, + rte_eco2mix: (m) => { + if (m.sub === "mix") { + const [nuclear, solar, wind, gas] = m.args; + const c = ensureCard("rte", { title: "RTE eCO2mix · GW", order: 26, wide: true }); + c.valueEl.textContent = `${fmt((nuclear + solar + wind + gas) / 1000, 1)} GW`; + c.subEl.textContent = `nuc ${fmt(nuclear / 1000, 1)} · sol ${fmt(solar / 1000, 1)} · eol ${fmt(wind / 1000, 1)} · gaz ${fmt(gas / 1000, 1)}`; + pushHistory(c, (nuclear + solar + wind + gas) / 1000); + drawSpark(c, "#6fe9b3"); + } else if (m.sub === "co2") { + const c = ensureCard("co2", { title: "Intensite CO2 · g/kWh", order: 27, + alert: m.args[0] > 200, warn: m.args[0] > 100, green: m.args[0] < 50 }); + c.valueEl.textContent = String(m.args[0] | 0); + pushHistory(c, m.args[0]); + drawSpark(c); + } + }, }; function handleMessage(msg) { diff --git a/data_only_viz/web/public/js/map.js b/data_only_viz/web/public/js/map.js index f68a549..b40a835 100644 --- a/data_only_viz/web/public/js/map.js +++ b/data_only_viz/web/public/js/map.js @@ -87,6 +87,13 @@ const handlers = { ephemeralMarker(lat, lon, "volcano", `volcan ${region} (VEI ${vei})`); }, + gdelt: (m) => { + if (m.sub !== "event") return; + const [lat, lon, tone, country] = m.args; + if (!lat && !lon) return; + const tip = `GDELT ${country || "?"} (tone ${(+tone).toFixed(1)})`; + ephemeralMarker(lat, lon, "gdelt", tip); + }, }; function handleMessage(msg) { diff --git a/data_only_viz/web/public/map.html b/data_only_viz/web/public/map.html index aa2dbf9..5184c60 100644 --- a/data_only_viz/web/public/map.html +++ b/data_only_viz/web/public/map.html @@ -27,6 +27,7 @@ .plane { background: #6fe9b3; border-radius: 50%; } .iss { background: #6fa6ff; border-radius: 50%; border: 2px solid #fff; } .volcano { background: #ff8838; border-radius: 50%; } + .gdelt { background: #c878ff; border-radius: 50%; opacity: 0.8; } diff --git a/data_only_viz/web/server.js b/data_only_viz/web/server.js index 13e8523..2def7d2 100644 --- a/data_only_viz/web/server.js +++ b/data_only_viz/web/server.js @@ -25,6 +25,7 @@ const OSC_DATA_IN = parseInt(process.env.OSC_DATA_IN ?? "57124", 10); const OSC_SYNC_IN = parseInt(process.env.OSC_SYNC_IN ?? "57125", 10); const SC_HOST = process.env.SC_HOST ?? "127.0.0.1"; const SC_PORT_OUT = parseInt(process.env.SC_PORT_OUT ?? "57121", 10); +const OF_PORT_OUT = parseInt(process.env.OF_PORT_OUT ?? "57123", 10); const app = express(); app.use(express.static(join(__dirname, "public"))); @@ -69,7 +70,12 @@ wss.on("connection", (ws) => { if (typeof v === "number") return { type: "f", value: v }; return { type: "s", value: String(v) }; }); - udpOut.send({ address: msg.path, args }, SC_HOST, SC_PORT_OUT); + // Routing par path : + // /control/viz* -> oscope-of (:57123) — Metal viz + skeleton renderer + // tout le reste -> SC (:57121) + const port = msg.path.startsWith("/control/viz") + ? OF_PORT_OUT : SC_PORT_OUT; + udpOut.send({ address: msg.path, args }, SC_HOST, port); }); }); diff --git a/launcher/AV-Live-Body/Sources/AVLiveBody/SettingsPanel.swift b/launcher/AV-Live-Body/Sources/AVLiveBody/SettingsPanel.swift index 87a7e95..65176f5 100644 --- a/launcher/AV-Live-Body/Sources/AVLiveBody/SettingsPanel.swift +++ b/launcher/AV-Live-Body/Sources/AVLiveBody/SettingsPanel.swift @@ -64,7 +64,7 @@ struct SettingsPanel: View { label: "Fil de fer", isOn: $settings.showWireframe) layerRow(icon: "figure.stand", - label: "Squelette (articulations)", + label: "Squelette (à venir)", isOn: $settings.showSkeleton) } } diff --git a/launcher/Sources/AVLiveLauncher/ProcessManager.swift b/launcher/Sources/AVLiveLauncher/ProcessManager.swift index 106e75b..afe5c94 100644 --- a/launcher/Sources/AVLiveLauncher/ProcessManager.swift +++ b/launcher/Sources/AVLiveLauncher/ProcessManager.swift @@ -17,7 +17,7 @@ enum LaunchMode: String, CaseIterable, Identifiable { var displayName: String { switch self { case .full: return "Full AV-Live" - case .dataOnly: return "Data-only (SC + oF + feeds, no web)" + case .dataOnly: return "Data-only (SC + Metal viz + feeds + web)" case .bodyMesh: return "Body Mesh (Multi-HMR + RealityKit)" } }