diff --git a/src/App.jsx b/src/App.jsx index e5df120..930742e 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -36,6 +36,7 @@ const API = { getAudioProfile:() => api("GET", "/api/audio/profile"), getRouting: () => api("GET", "/api/routing"), setRouting: (mode, breakpoint) => api("POST", "/api/routing", { routing_mode: mode, routing_breakpoint: breakpoint }), + saveSettings: (settings) => api("POST", "/api/settings", settings), 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 }), @@ -1015,6 +1016,7 @@ export default function App(){ onMasterVolume={(v)=>API.masterVolume(v).catch(()=>{})} onAudioProfile={(rate, period)=>API.setAudioProfile(rate, period).catch(()=>{})} onRoutingChange={(mode, bp)=>API.setRouting(mode, bp).catch(()=>{})} + onSaveSettings={(s)=>API.saveSettings(s).catch(()=>{})} /> )} diff --git a/src/GlobalSettings.jsx b/src/GlobalSettings.jsx index 75e326b..37b5f9d 100644 --- a/src/GlobalSettings.jsx +++ b/src/GlobalSettings.jsx @@ -6,7 +6,6 @@ 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); @@ -15,10 +14,10 @@ async function api(method, path, body) { return res.json(); } -// ── Settings definitions ── const SETTINGS = { "Audio": [ - { key: "master_volume", label: "Master Volume", type: "slider", min: 0, max: 100, def: 72, unit: "%", icon: "🔊" }, + { key: "master_volume", label: "Master Volume", type: "slider", min: 0, max: 100, def: 72, unit: "%", icon: "🔊", + via: "volume" }, { key: "sample_rate", label: "Sample Rate", type: "select", def: "48000", options: [ { value: "22050", label: "22.05 kHz" }, @@ -26,7 +25,7 @@ const SETTINGS = { { value: "48000", label: "48 kHz" }, { value: "88200", label: "88.2 kHz" }, { value: "96000", label: "96 kHz" }, - ], icon: "📊" }, + ], icon: "📊", via: "audio_profile" }, { key: "buffer_size", label: "Buffer Size", type: "select", def: "512", options: [ { value: "64", label: "64 (1.3ms)" }, @@ -34,14 +33,14 @@ const SETTINGS = { { value: "256", label: "256 (5.3ms)" }, { value: "512", label: "512 (10.6ms)" }, { value: "1024",label: "1024 (21.3ms)" }, - ], icon: "📦" }, + ], icon: "📦", via: "audio_profile" }, { key: "input_pad", label: "Input Pad", type: "select", def: "off", options: [ { value: "off", label: "Off (0 dB)" }, { value: "-6", label: "-6 dB" }, { value: "-12", label: "-12 dB" }, { value: "-18", label: "-18 dB" }, - ], icon: "📥" }, + ], icon: "📥", via: "settings" }, { key: "input_impedance", label: "Input Impedance", type: "select", def: "1m", options: [ { value: "1m", label: "1 MΩ" }, @@ -49,14 +48,14 @@ const SETTINGS = { { value: "250k", label: "250 kΩ" }, { value: "150k", label: "150 kΩ" }, { value: "68k", label: "68 kΩ" }, - ], icon: "⚡" }, + ], icon: "⚡", via: "settings" }, ], "Routing": [ { key: "routing_mode", label: "Routing Mode", type: "select", def: "mono", options: [ { value: "mono", label: "Mono" }, { value: "4cm", label: "4CM (Stereo FX Loop)" }, - ], icon: "🔀" }, + ], icon: "🔀", via: "routing" }, { key: "routing_breakpoint", label: "FX Loop Breakpoint", type: "select", def: "7", options: [ { value: "1", label: "After block 1" }, @@ -67,7 +66,7 @@ const SETTINGS = { { value: "6", label: "After block 6" }, { value: "7", label: "After block 7" }, { value: "8", label: "After block 8" }, - ], icon: "↔" }, + ], icon: "↔", via: "routing" }, ], "Audio Interface": [ { key: "input_device", label: "Input Device", type: "select", def: "hw:0,0", @@ -75,19 +74,19 @@ const SETTINGS = { { 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: "🎤" }, + ], icon: "🎤", via: "settings" }, { 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: "🔈" }, + ], icon: "🔈", via: "settings" }, { 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: "🔊" }, + ], icon: "🔊", via: "settings" }, ], "MIDI": [ { key: "midi_channel", label: "MIDI Channel", type: "select", def: "omni", @@ -96,13 +95,13 @@ const SETTINGS = { ...Array.from({ length: 16 }, (_, i) => ({ value: String(i + 1), label: `Channel ${i + 1}` })), - ], icon: "🎹" }, - { key: "midi_clock", label: "MIDI Clock", type: "toggle", def: false, icon: "⏱" }, - { key: "midi_thru", label: "MIDI Thru", type: "toggle", def: true, icon: "↔" }, + ], icon: "🎹", via: "settings" }, + { key: "midi_clock", label: "MIDI Clock", type: "toggle", def: false, via: "settings", icon: "⏱" }, + { key: "midi_thru", label: "MIDI Thru", type: "toggle", def: true, via: "settings", icon: "↔" }, ], "Display": [ - { key: "brightness", label: "Display Brightness", type: "slider", min: 1, max: 10, def: 8, unit: "", icon: "☀️" }, - { key: "auto_dim", label: "Auto Dim", type: "toggle", def: true, icon: "🌙" }, + { key: "brightness", label: "Display Brightness", type: "slider", min: 1, max: 10, def: 8, unit: "", icon: "☀️", via: "settings" }, + { key: "auto_dim", label: "Auto Dim", type: "toggle", def: true, via: "settings", icon: "🌙" }, { key: "screen_saver", label: "Screen Saver", type: "select", def: "5m", options: [ { value: "off", label: "Off" }, @@ -110,10 +109,10 @@ const SETTINGS = { { value: "5m", label: "5 min" }, { value: "15m", label: "15 min" }, { value: "30m", label: "30 min" }, - ], icon: "💤" }, + ], icon: "💤", via: "settings" }, ], "Tempo": [ - { key: "tap_tempo", label: "Tap Tempo", type: "bpm", def: 120, icon: "🔄" }, + { key: "tap_tempo", label: "Tap Tempo", type: "bpm", def: 120, via: "settings", icon: "🔄" }, { key: "tempo_division", label: "Tempo Division", type: "select", def: "quarter", options: [ { value: "half", label: "½ Note" }, @@ -122,12 +121,11 @@ const SETTINGS = { { value: "triplet", label: "⅛ Triplet" }, { value: "sixteenth", label: "¹⁄₁₆ Note" }, { value: "dotted_eighth", label: "Dotted ⅛" }, - ], icon: "🎶" }, - { key: "tap_tempo_mute", label: "Mute on Tap", type: "toggle", def: false, icon: "🔇" }, + ], icon: "🎶", via: "settings" }, + { key: "tap_tempo_mute", label: "Mute on Tap", type: "toggle", def: false, via: "settings", icon: "🔇" }, ], }; -// ── Build defaults from SETTINGS definitions ── function buildDefaults(overrides = {}) { const init = {}; for (const group of Object.values(SETTINGS)) { @@ -138,7 +136,8 @@ function buildDefaults(overrides = {}) { return { ...init, ...overrides }; } -// ── Slider Widget ── +// ── Widgets ──────────────────────────────────────────────────────────────── + function SettingsSlider({ label, value, min, max, unit, onChange }) { const trackRef = useRef(null); const dragRef = useRef(false); @@ -196,45 +195,22 @@ function SettingsSlider({ label, value, min, max, unit, onChange }) { position: "relative", height: 32, display: "flex", alignItems: "center", cursor: "pointer", touchAction: "none", }}> -
-
+
+
-
+
); } -// ── Select Widget ── -function SettingsSelect({ label, value, options, onChange, icon }) { +function SettingsSelect({ label, value, options, onChange }) { return (
-
+
{label}
-
+
{options.map(opt => ( + >{opt.label} ))}
); } -// ── Toggle Widget ── function SettingsToggle({ label, value, onChange }) { return ( -
+
{label} -
- - {value} BPM - +
+ +
+ + {value} BPM +
); } -// ── Global Settings ── -export default function GlobalSettings({ onClose, onMasterVolume, onAudioProfile, onRoutingChange }) { +// ── Global Settings ───────────────────────────────────────────────────────── + +export default function GlobalSettings({ onClose, onMasterVolume, onAudioProfile, onRoutingChange, onSaveSettings }) { const [values, setValues] = useState(buildDefaults()); const [loaded, setLoaded] = useState(false); - // ── Load current settings from API on mount ── useEffect(() => { let cancelled = false; Promise.all([ @@ -363,18 +303,15 @@ export default function GlobalSettings({ onClose, onMasterVolume, onAudioProfile ]).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; } - // 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); @@ -391,123 +328,55 @@ export default function GlobalSettings({ onClose, onMasterVolume, onAudioProfile setValues(prev => { const next = { ...prev, [key]: val }; - // Fire external callbacks - if (key === "master_volume") { - onMasterVolume?.(val); - } - if (key === "sample_rate" || key === "buffer_size") { + // Route via dedicated endpoints + if (key === "master_volume") onMasterVolume?.(val); + + else 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)); } - if (key === "routing_mode" || key === "routing_breakpoint") { + + else 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)); } + else { + // All other settings → POST /api/settings (generic persistence) + onSaveSettings?.({ [key]: val }); + } + return next; }); - }, [onMasterVolume, onAudioProfile, onRoutingChange]); + }, [onMasterVolume, onAudioProfile, onRoutingChange, onSaveSettings]); return ( -
- {/* Header */} -
+
+
- +
-
Global Settings
- {!loaded && Loading current values…} +
Settings
+ {!loaded && Loading…}
- {/* Body */} -
+
{groups.map(([groupName, settings]) => (
- {/* Group header */} -
- {groupName} -
- - {/* Settings */} -
+
{groupName}
+
{settings.map(s => ( -
- {/* Icon */} -
- {s.icon} -
- {/* Control */} +
+
{s.icon}
- {s.type === "slider" && ( - update(s.key, v)} - /> - )} - {s.type === "select" && ( - update(s.key, v)} - /> - )} - {s.type === "toggle" && ( - update(s.key, v)} - /> - )} - {s.type === "bpm" && ( - update(s.key, v)} - /> - )} + {s.type === "slider" && update(s.key, v)} />} + {s.type === "select" && update(s.key, v)} />} + {s.type === "toggle" && update(s.key, v)} />} + {s.type === "bpm" && update(s.key, v)} />}
))}