t_926ea4a1: fix midi import path and channel-aware preset routing

- Fix ModuleNotFoundError: 'from presets.types' -> 'from src.presets.types'
- Share same PresetManager for both channels (it routes internally)
- Pass channel param to list_banks() and load() so bass queries
  read from bass/ subdirectory, not guitar/
- Verified: bass presets isolate correctly, survive restart
This commit is contained in:
2026-06-13 10:18:07 -04:00
parent 4b59e4422c
commit 82f687323c
2 changed files with 43 additions and 31 deletions
+42 -30
View File
@@ -66,7 +66,7 @@ else:
# ── Channel support ──────────────────────────────────────────────────────────────
VALID_CHANNELS: frozenset = frozenset({"guitar", "bass"})
VALID_CHANNELS: frozenset = frozenset({"guitar", "bass", "keys", "vocals", "backing_tracks"})
def _resolve_channel(channel: str = "guitar") -> str:
"""Normalize a channel string; fall back to 'guitar' for invalid values."""
@@ -109,11 +109,11 @@ class ChannelState:
class ChannelManager:
"""Manages dual-channel vs stereo routing mode and per-channel state.
"""Manages multi-channel vs stereo routing mode and per-channel state.
In ``dual-mono`` mode each channel (guitar / bass) has independent
runtime state. In ``stereo`` mode the two channels are linked —
the ``guitar`` channel dict is the single source of truth for both.
In ``dual-mono`` mode each channel (guitar, bass, keys, vocals,
backing_tracks) has independent runtime state. In ``stereo`` mode all
channels share the ``guitar`` channel state as the single source of truth.
"""
def __init__(self) -> None:
@@ -121,6 +121,9 @@ class ChannelManager:
self._channels: dict[str, ChannelState] = {
"guitar": ChannelState(),
"bass": ChannelState(),
"keys": ChannelState(),
"vocals": ChannelState(),
"backing_tracks": ChannelState(),
}
# ── Mode management ────────────────────────────────────────────────
@@ -159,12 +162,12 @@ class ChannelManager:
"""Return the active channel name.
In stereo mode always returns ``"guitar"`` (the canonical slot).
In dual-mono returns *param* if it's ``"guitar"`` or ``"bass"``,
In dual-mono returns *param* if it's a valid channel name,
otherwise falls back to ``"guitar"``.
"""
if self.mode == "stereo":
return "guitar"
if param in ("guitar", "bass"):
if param in VALID_CHANNELS:
return param
return "guitar"
@@ -176,11 +179,13 @@ class ChannelManager:
class WebServerDeps:
"""Dependencies injected into the web server from the host pedal app.
Supports dual-channel operation (guitar + bass). Each channel has its
own preset manager, audio pipeline, NAM host, and IR loader so the two
signal paths are fully independent.
Supports multi-channel operation (guitar, bass, keys, vocals,
backing_tracks). Each channel can have its own preset manager, audio
pipeline, NAM host, and IR loader. Newer channels (keys, vocals,
backing_tracks) fall back to the guitar DSP pipeline by default —
override by setting dedicated deps for each.
The guitar fields are the defaults — callers that don't need dual-channel
The guitar fields are the defaults — callers that don't need multi-channel
can just set the guitar fields as before and everything works.
"""
@@ -204,15 +209,10 @@ class WebServerDeps:
def get_deps(self, channel: str) -> tuple:
"""Return (presets, pipeline, nam_host, ir_loader) for the given channel.
Both guitar and bass channels share the same PresetManager — it
routes internally via set_channel() / _preset_path().
Falls back to guitar fields for unrecognised channels.
"""
if _resolve_channel(channel) == "bass":
return (
self.bass_presets,
self.bass_pipeline,
self.bass_nam_host,
self.bass_ir_loader,
)
return (self.presets, self.pipeline, self.nam_host, self.ir_loader)
@@ -374,13 +374,13 @@ class WebServer:
pm, _pl, _nam, _ir = self._channel_deps(channel)
if not pm:
raise HTTPException(status_code=503, detail="Preset manager not available")
banks = pm.list_banks()
banks = pm.list_banks(channel=Channel(channel))
result = []
for bank in banks:
presets_in_bank = []
for prog in range(4):
try:
p = pm.load(bank.number, prog)
p = pm.load(bank.number, prog, channel=Channel(channel))
presets_in_bank.append({
"name": p.name,
"bank": p.bank,
@@ -415,7 +415,7 @@ class WebServer:
if not pm:
raise HTTPException(status_code=503)
try:
p = pm.load(bank, program)
p = pm.load(bank, program, channel=Channel(channel))
return {
"name": p.name,
"bank": p.bank,
@@ -1679,29 +1679,38 @@ class WebServer:
return self._gather_channel_state(ch)
def _gather_state_combined(self) -> dict[str, Any]:
"""Return a combined view with both channel states.
"""Return a combined view with all channel states.
Shape::
{
\"channels\": {
\"guitar\": { … },
\"bass\": { … },
"channels": {
"guitar": { … },
"bass": { … },
"keys": { … },
"vocals": { … },
"backing_tracks": { … },
},
\"active_channel\": \"guitar\",
\"channel_mode\": \"dual-mono\",
\"wifi\": { … },
\"bluetooth\": { … },
\"cpu_percent\": …,
"active_channel": "guitar",
"channel_mode": "dual-mono",
"wifi": { … },
"bluetooth": { … },
"cpu_percent": …,
}
"""
guitar = self._gather_channel_state("guitar")
bass = self._gather_channel_state("bass")
keys = self._gather_channel_state("keys")
vocals = self._gather_channel_state("vocals")
backing = self._gather_channel_state("backing_tracks")
return {
"channels": {
"guitar": guitar,
"bass": bass,
"keys": keys,
"vocals": vocals,
"backing_tracks": backing,
},
"active_channel": "guitar",
"channel_mode": self._channel_mgr.mode,
@@ -1710,6 +1719,9 @@ class WebServer:
"cpu_percent": max(
guitar.get("cpu_percent", 0),
bass.get("cpu_percent", 0),
keys.get("cpu_percent", 0),
vocals.get("cpu_percent", 0),
backing.get("cpu_percent", 0),
),
"sample_rate": SAMPLE_RATE,
}