fix: GlobalSettings loads current state from API on mount, add Routing & Audio Interface sections, wire onRoutingChange

This commit is contained in:
2026-06-14 12:39:06 -04:00
parent b24cfab15f
commit 277625f90c
2 changed files with 117 additions and 24 deletions
+4
View File
@@ -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(()=>{})}
/>
)}
+113 -24
View File
@@ -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 (
<div style={{
@@ -348,6 +436,7 @@ export default function GlobalSettings({ onClose, onMasterVolume, onAudioProfile
</button>
<div>
<div style={{ fontSize: 15, fontWeight: 600 }}>Global Settings</div>
{!loaded && <span style={{ fontSize: 10, color: T.textDim }}>Loading current values</span>}
</div>
</div>
</div>