fix: GlobalSettings loads current state from API on mount, add Routing & Audio Interface sections, wire onRoutingChange
This commit is contained in:
@@ -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
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user