fix: multiple bug fixes found during comprehensive test plan sweep
CI / test (push) Has been cancelled
CI / test (push) Has been cancelled
- Fix #5: Preset state lost on service restart — call restore_state() in PresetManager.__init__() so last active preset is tracked after boot - Fix #14: Block bypass/toggle WS sync — add WebSocket connection in SPA with block_toggled handler that calls loadState() - Fix #15: IR file naming — add display_name property to IRFile dataclass that strips common IR prefixes/suffixes - Fix: block_id regeneration on every preset load — pass block_id from stored JSON data to FXBlock constructor in _preset_from_dict (both preset chain and snapshot chain)
This commit is contained in:
@@ -43,6 +43,54 @@ class IRFile:
|
|||||||
length_ms: float
|
length_ms: float
|
||||||
channels: int = 1
|
channels: int = 1
|
||||||
|
|
||||||
|
@property
|
||||||
|
def display_name(self) -> str:
|
||||||
|
"""Return a clean human-readable name by stripping common IR prefixes/suffixes.
|
||||||
|
|
||||||
|
Strips leading/trailing markers commonly found in IR filenames
|
||||||
|
(e.g. ``IR_Marshall_1960A.wav`` → ``Marshall 1960A``,
|
||||||
|
``cab_Vox_AC30_48k.wav`` → ``Vox AC30``).
|
||||||
|
"""
|
||||||
|
name = self.name
|
||||||
|
|
||||||
|
# Common IR prefixes (case-insensitive)
|
||||||
|
_prefixes = [
|
||||||
|
"IR_", "ir_", "IR-", "ir-",
|
||||||
|
"Impulse_Response_", "impulse_response_",
|
||||||
|
"Impulse-Response-", "impulse-response-",
|
||||||
|
"Cab_", "cab_", "Cab-", "cab-",
|
||||||
|
]
|
||||||
|
|
||||||
|
# Common IR suffixes (case-insensitive)
|
||||||
|
_suffixes = [
|
||||||
|
"_IR", "_ir", "-IR", "-ir",
|
||||||
|
"_Impulse", "_impulse", "-Impulse", "-impulse",
|
||||||
|
"_Cab", "_cab", "-Cab", "-cab",
|
||||||
|
"_Stereo", "_stereo", "-Stereo", "-stereo",
|
||||||
|
"_Mono", "_mono", "-Mono", "-mono",
|
||||||
|
"_48k", "_48K", "-48k", "-48K",
|
||||||
|
"_44100", "-44100",
|
||||||
|
"_48kHz", "-48kHz",
|
||||||
|
"_16bit", "_24bit", "-16bit", "-24bit",
|
||||||
|
"_LD", "_SD", "-LD", "-SD",
|
||||||
|
]
|
||||||
|
|
||||||
|
for prefix in _prefixes:
|
||||||
|
if name.lower().startswith(prefix.lower()):
|
||||||
|
name = name[len(prefix):]
|
||||||
|
break
|
||||||
|
|
||||||
|
for suffix in _suffixes:
|
||||||
|
if name.lower().endswith(suffix.lower()):
|
||||||
|
name = name[:-len(suffix)]
|
||||||
|
break
|
||||||
|
|
||||||
|
# Replace separators with spaces and clean up
|
||||||
|
name = name.replace("_", " ").replace("-", " ")
|
||||||
|
name = " ".join(name.split()).strip()
|
||||||
|
|
||||||
|
return name if name else self.name
|
||||||
|
|
||||||
|
|
||||||
def _next_pow2(n: int) -> int:
|
def _next_pow2(n: int) -> int:
|
||||||
"""Return the smallest power of 2 >= n."""
|
"""Return the smallest power of 2 >= n."""
|
||||||
|
|||||||
@@ -104,6 +104,7 @@ def _preset_from_dict(data: dict) -> Preset:
|
|||||||
params=dict(block_data.get("params", {})),
|
params=dict(block_data.get("params", {})),
|
||||||
nam_model_path=block_data.get("nam_model_path", ""),
|
nam_model_path=block_data.get("nam_model_path", ""),
|
||||||
ir_file_path=block_data.get("ir_file_path", ""),
|
ir_file_path=block_data.get("ir_file_path", ""),
|
||||||
|
block_id=block_data.get("block_id", ""),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -128,6 +129,7 @@ def _preset_from_dict(data: dict) -> Preset:
|
|||||||
params=dict(bd.get("params", {})),
|
params=dict(bd.get("params", {})),
|
||||||
nam_model_path=bd.get("nam_model_path", ""),
|
nam_model_path=bd.get("nam_model_path", ""),
|
||||||
ir_file_path=bd.get("ir_file_path", ""),
|
ir_file_path=bd.get("ir_file_path", ""),
|
||||||
|
block_id=bd.get("block_id", ""),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
snapshots[int(slot_str)] = Snapshot(
|
snapshots[int(slot_str)] = Snapshot(
|
||||||
@@ -191,6 +193,10 @@ class PresetManager:
|
|||||||
# Migrate legacy flat presets → gtr channel on first boot
|
# Migrate legacy flat presets → gtr channel on first boot
|
||||||
self._migrate_legacy_presets()
|
self._migrate_legacy_presets()
|
||||||
|
|
||||||
|
# Restore last active preset after boot so the pedal comes back
|
||||||
|
# to the same (channel, bank, program) it was on before shutdown.
|
||||||
|
self.restore_state()
|
||||||
|
|
||||||
logger.info("PresetManager root: %s (channel=%s)", self._dir, self._current_channel.value)
|
logger.info("PresetManager root: %s (channel=%s)", self._dir, self._current_channel.value)
|
||||||
|
|
||||||
# ── Public navigation API ───────────────────────────────────────────────
|
# ── Public navigation API ───────────────────────────────────────────────
|
||||||
|
|||||||
@@ -945,6 +945,22 @@ async function pollTuner(){
|
|||||||
function startPolling(){stopPolling();loadState();pollTimer=setInterval(loadState,2000);}
|
function startPolling(){stopPolling();loadState();pollTimer=setInterval(loadState,2000);}
|
||||||
function stopPolling(){if(pollTimer){clearInterval(pollTimer);pollTimer=null;}}
|
function stopPolling(){if(pollTimer){clearInterval(pollTimer);pollTimer=null;}}
|
||||||
|
|
||||||
|
// ═══ WebSocket — real-time updates ═══
|
||||||
|
let ws=null;
|
||||||
|
function setupWebSocket(){
|
||||||
|
if(ws)try{ws.close()}catch(e){}
|
||||||
|
const proto=location.protocol==='https:'?'wss:':'ws:';
|
||||||
|
ws=new WebSocket(proto+'//'+location.host+'/ws');
|
||||||
|
ws.onmessage=(ev)=>{
|
||||||
|
try{
|
||||||
|
const msg=JSON.parse(ev.data);
|
||||||
|
if(msg.type==='block_toggled'){loadState();}
|
||||||
|
}catch(e){}
|
||||||
|
};
|
||||||
|
ws.onclose=()=>{setTimeout(setupWebSocket,3000);};
|
||||||
|
ws.onerror=()=>{};
|
||||||
|
}
|
||||||
|
|
||||||
// ═══ Init! ═══
|
// ═══ Init! ═══
|
||||||
function init(){
|
function init(){
|
||||||
// Restore saved channel
|
// Restore saved channel
|
||||||
@@ -1243,8 +1259,9 @@ function init(){
|
|||||||
document.getElementById('dlSearchBtn').onclick=doDlSearch;
|
document.getElementById('dlSearchBtn').onclick=doDlSearch;
|
||||||
document.getElementById('dlSearch').onkeydown=(e)=>{if(e.key==='Enter')doDlSearch();};
|
document.getElementById('dlSearch').onkeydown=(e)=>{if(e.key==='Enter')doDlSearch();};
|
||||||
|
|
||||||
// Start polling
|
// Start polling + WebSocket
|
||||||
startPolling();
|
startPolling();
|
||||||
|
setupWebSocket();
|
||||||
loadPresets();
|
loadPresets();
|
||||||
loadSnapshots();
|
loadSnapshots();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user