From 277625f90cb706982ae4201e885cd929746e56b4 Mon Sep 17 00:00:00 2001 From: Shawn Date: Sun, 14 Jun 2026 12:39:06 -0400 Subject: [PATCH] fix: GlobalSettings loads current state from API on mount, add Routing & Audio Interface sections, wire onRoutingChange --- src/App.jsx | 4 ++ src/GlobalSettings.jsx | 137 +++++++++++++++++++++++++++++++++-------- 2 files changed, 117 insertions(+), 24 deletions(-) diff --git a/src/App.jsx b/src/App.jsx index f51a068..e5df120 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -33,6 +33,9 @@ const API = { activatePreset:(bank, program) => api("POST", `/api/presets/${bank}/${program}/activate`), savePreset: (bank, program, name, tags) => api("PUT", "/api/presets", { bank, program, name, tags }), setAudioProfile:(rate, period) => api("POST", "/api/audio/profile", { profile: "stable", rate: parseInt(rate), period: parseInt(period) }), + getAudioProfile:() => api("GET", "/api/audio/profile"), + getRouting: () => api("GET", "/api/routing"), + setRouting: (mode, breakpoint) => api("POST", "/api/routing", { routing_mode: mode, routing_breakpoint: breakpoint }), searchModels: (q) => api("GET", `/api/tonehub/search?q=${encodeURIComponent(q)}`), searchIrs: (q) => api("GET", `/api/tonehub/search/irs?q=${encodeURIComponent(q)}`), installModel: (url, name) => api("POST", "/api/models/tonedownload/install", { download_url: url, name }), @@ -1011,6 +1014,7 @@ export default function App(){ onClose={()=>setSettingsView(null)} onMasterVolume={(v)=>API.masterVolume(v).catch(()=>{})} onAudioProfile={(rate, period)=>API.setAudioProfile(rate, period).catch(()=>{})} + onRoutingChange={(mode, bp)=>API.setRouting(mode, bp).catch(()=>{})} /> )} diff --git a/src/GlobalSettings.jsx b/src/GlobalSettings.jsx index a1fb152..75e326b 100644 --- a/src/GlobalSettings.jsx +++ b/src/GlobalSettings.jsx @@ -6,6 +6,15 @@ const T = { green: "#3AB87A", red: "#C84040", textPrimary: "#F0EDE6", textSec: "#8888A0", textDim: "#444458", }; +// ── API calls (injected via props or fetched internally) ── +async function api(method, path, body) { + const opts = { method, headers: { "Content-Type": "application/json" } }; + if (body) opts.body = JSON.stringify(body); + const res = await fetch(`${path}`, opts); + if (!res.ok) throw new Error(`API ${method} ${path}: ${res.status}`); + return res.json(); +} + // ── Settings definitions ── const SETTINGS = { "Audio": [ @@ -42,6 +51,44 @@ const SETTINGS = { { value: "68k", label: "68 kΩ" }, ], icon: "⚡" }, ], + "Routing": [ + { key: "routing_mode", label: "Routing Mode", type: "select", def: "mono", + options: [ + { value: "mono", label: "Mono" }, + { value: "4cm", label: "4CM (Stereo FX Loop)" }, + ], icon: "🔀" }, + { key: "routing_breakpoint", label: "FX Loop Breakpoint", type: "select", def: "7", + options: [ + { value: "1", label: "After block 1" }, + { value: "2", label: "After block 2" }, + { value: "3", label: "After block 3" }, + { value: "4", label: "After block 4" }, + { value: "5", label: "After block 5" }, + { value: "6", label: "After block 6" }, + { value: "7", label: "After block 7" }, + { value: "8", label: "After block 8" }, + ], icon: "↔" }, + ], + "Audio Interface": [ + { key: "input_device", label: "Input Device", type: "select", def: "hw:0,0", + options: [ + { value: "hw:0,0", label: "Default (hw:0,0)" }, + { value: "hw:1,0", label: "USB Audio (hw:1,0)" }, + { value: "hw:2,0", label: "HDMI Audio (hw:2,0)" }, + ], icon: "🎤" }, + { key: "output_device", label: "Output Device", type: "select", def: "hw:0,0", + options: [ + { value: "hw:0,0", label: "Default (hw:0,0)" }, + { value: "hw:1,0", label: "USB Audio (hw:1,0)" }, + { value: "hw:2,0", label: "HDMI Audio (hw:2,0)" }, + ], icon: "🔈" }, + { key: "channel_mode", label: "Channel Mode", type: "select", def: "mono", + options: [ + { value: "mono", label: "Mono" }, + { value: "dual-mono", label: "Dual Mono" }, + { value: "stereo_4cm", label: "Stereo (4CM)" }, + ], icon: "🔊" }, + ], "MIDI": [ { key: "midi_channel", label: "MIDI Channel", type: "select", def: "omni", options: [ @@ -80,6 +127,17 @@ const SETTINGS = { ], }; +// ── Build defaults from SETTINGS definitions ── +function buildDefaults(overrides = {}) { + const init = {}; + for (const group of Object.values(SETTINGS)) { + for (const s of group) { + init[s.key] = s.def; + } + } + return { ...init, ...overrides }; +} + // ── Slider Widget ── function SettingsSlider({ label, value, min, max, unit, onChange }) { const trackRef = useRef(null); @@ -291,36 +349,66 @@ function BpmWidget({ label, value, onChange }) { } // ── Global Settings ── -export default function GlobalSettings({ onClose, onMasterVolume, onAudioProfile }) { - const [values, setValues] = useState(() => { - const init = {}; - for (const group of Object.values(SETTINGS)) { - for (const s of group) { - init[s.key] = s.def; +export default function GlobalSettings({ onClose, onMasterVolume, onAudioProfile, onRoutingChange }) { + const [values, setValues] = useState(buildDefaults()); + const [loaded, setLoaded] = useState(false); + + // ── Load current settings from API on mount ── + useEffect(() => { + let cancelled = false; + Promise.all([ + api("GET", "/api/state").catch(() => null), + api("GET", "/api/audio/profile").catch(() => null), + api("GET", "/api/routing").catch(() => null), + ]).then(([state, audioProfile, routing]) => { + if (cancelled) return; + const overrides = {}; + // From /api/state + if (state) { + if (state.master_volume != null) overrides.master_volume = Math.round(state.master_volume * 100); + if (state.sample_rate != null) overrides.sample_rate = String(state.sample_rate); + if (state.channel_mode) overrides.channel_mode = state.channel_mode; } - } - return init; - }); + // From /api/audio/profile (more accurate for rate/period) + if (audioProfile) { + if (audioProfile.rate != null) overrides.sample_rate = String(audioProfile.rate); + if (audioProfile.period != null) overrides.buffer_size = String(audioProfile.period); + } + // From /api/routing + if (routing) { + if (routing.routing_mode) overrides.routing_mode = routing.routing_mode; + if (routing.routing_breakpoint != null) overrides.routing_breakpoint = String(routing.routing_breakpoint); + } + setValues(prev => ({ ...prev, ...overrides })); + setLoaded(true); + }); + return () => { cancelled = true; }; + }, []); const groups = Object.entries(SETTINGS); const update = useCallback((key, val) => { - setValues(prev => ({ ...prev, [key]: val })); - // Fire external callback for master volume - if (key === "master_volume") { - onMasterVolume?.(val); - } - // Fire external callback for audio profile changes - if (key === "sample_rate" || key === "buffer_size") { - // Get the latest values — use the new val for the changed key - setValues(prev => { - const rate = key === "sample_rate" ? val : (prev.sample_rate || "48000"); - const period = key === "buffer_size" ? val : (prev.buffer_size || "512"); + setValues(prev => { + const next = { ...prev, [key]: val }; + + // Fire external callbacks + if (key === "master_volume") { + onMasterVolume?.(val); + } + if (key === "sample_rate" || key === "buffer_size") { + const rate = key === "sample_rate" ? val : (next.sample_rate || "48000"); + const period = key === "buffer_size" ? val : (next.buffer_size || "512"); onAudioProfile?.(parseInt(rate), parseInt(period)); - return prev; - }); - } - }, [onMasterVolume, onAudioProfile]); + } + if (key === "routing_mode" || key === "routing_breakpoint") { + const mode = key === "routing_mode" ? val : (next.routing_mode || "mono"); + const bp = key === "routing_breakpoint" ? val : (next.routing_breakpoint || "7"); + onRoutingChange?.(mode, parseInt(bp)); + } + + return next; + }); + }, [onMasterVolume, onAudioProfile, onRoutingChange]); return (
Global Settings
+ {!loaded && Loading current values…}