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:
+1
-1
@@ -21,7 +21,7 @@ from collections import deque
|
|||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
from typing import Any, Callable, Optional
|
from typing import Any, Callable, Optional
|
||||||
|
|
||||||
from presets.types import MIDIMapping
|
from src.presets.types import MIDIMapping
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|||||||
+42
-30
@@ -66,7 +66,7 @@ else:
|
|||||||
|
|
||||||
# ── Channel support ──────────────────────────────────────────────────────────────
|
# ── 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:
|
def _resolve_channel(channel: str = "guitar") -> str:
|
||||||
"""Normalize a channel string; fall back to 'guitar' for invalid values."""
|
"""Normalize a channel string; fall back to 'guitar' for invalid values."""
|
||||||
@@ -109,11 +109,11 @@ class ChannelState:
|
|||||||
|
|
||||||
|
|
||||||
class ChannelManager:
|
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
|
In ``dual-mono`` mode each channel (guitar, bass, keys, vocals,
|
||||||
runtime state. In ``stereo`` mode the two channels are linked —
|
backing_tracks) has independent runtime state. In ``stereo`` mode all
|
||||||
the ``guitar`` channel dict is the single source of truth for both.
|
channels share the ``guitar`` channel state as the single source of truth.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
@@ -121,6 +121,9 @@ class ChannelManager:
|
|||||||
self._channels: dict[str, ChannelState] = {
|
self._channels: dict[str, ChannelState] = {
|
||||||
"guitar": ChannelState(),
|
"guitar": ChannelState(),
|
||||||
"bass": ChannelState(),
|
"bass": ChannelState(),
|
||||||
|
"keys": ChannelState(),
|
||||||
|
"vocals": ChannelState(),
|
||||||
|
"backing_tracks": ChannelState(),
|
||||||
}
|
}
|
||||||
|
|
||||||
# ── Mode management ────────────────────────────────────────────────
|
# ── Mode management ────────────────────────────────────────────────
|
||||||
@@ -159,12 +162,12 @@ class ChannelManager:
|
|||||||
"""Return the active channel name.
|
"""Return the active channel name.
|
||||||
|
|
||||||
In stereo mode always returns ``"guitar"`` (the canonical slot).
|
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"``.
|
otherwise falls back to ``"guitar"``.
|
||||||
"""
|
"""
|
||||||
if self.mode == "stereo":
|
if self.mode == "stereo":
|
||||||
return "guitar"
|
return "guitar"
|
||||||
if param in ("guitar", "bass"):
|
if param in VALID_CHANNELS:
|
||||||
return param
|
return param
|
||||||
return "guitar"
|
return "guitar"
|
||||||
|
|
||||||
@@ -176,11 +179,13 @@ class ChannelManager:
|
|||||||
class WebServerDeps:
|
class WebServerDeps:
|
||||||
"""Dependencies injected into the web server from the host pedal app.
|
"""Dependencies injected into the web server from the host pedal app.
|
||||||
|
|
||||||
Supports dual-channel operation (guitar + bass). Each channel has its
|
Supports multi-channel operation (guitar, bass, keys, vocals,
|
||||||
own preset manager, audio pipeline, NAM host, and IR loader so the two
|
backing_tracks). Each channel can have its own preset manager, audio
|
||||||
signal paths are fully independent.
|
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.
|
can just set the guitar fields as before and everything works.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@@ -204,15 +209,10 @@ class WebServerDeps:
|
|||||||
def get_deps(self, channel: str) -> tuple:
|
def get_deps(self, channel: str) -> tuple:
|
||||||
"""Return (presets, pipeline, nam_host, ir_loader) for the given channel.
|
"""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.
|
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)
|
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)
|
pm, _pl, _nam, _ir = self._channel_deps(channel)
|
||||||
if not pm:
|
if not pm:
|
||||||
raise HTTPException(status_code=503, detail="Preset manager not available")
|
raise HTTPException(status_code=503, detail="Preset manager not available")
|
||||||
banks = pm.list_banks()
|
banks = pm.list_banks(channel=Channel(channel))
|
||||||
result = []
|
result = []
|
||||||
for bank in banks:
|
for bank in banks:
|
||||||
presets_in_bank = []
|
presets_in_bank = []
|
||||||
for prog in range(4):
|
for prog in range(4):
|
||||||
try:
|
try:
|
||||||
p = pm.load(bank.number, prog)
|
p = pm.load(bank.number, prog, channel=Channel(channel))
|
||||||
presets_in_bank.append({
|
presets_in_bank.append({
|
||||||
"name": p.name,
|
"name": p.name,
|
||||||
"bank": p.bank,
|
"bank": p.bank,
|
||||||
@@ -415,7 +415,7 @@ class WebServer:
|
|||||||
if not pm:
|
if not pm:
|
||||||
raise HTTPException(status_code=503)
|
raise HTTPException(status_code=503)
|
||||||
try:
|
try:
|
||||||
p = pm.load(bank, program)
|
p = pm.load(bank, program, channel=Channel(channel))
|
||||||
return {
|
return {
|
||||||
"name": p.name,
|
"name": p.name,
|
||||||
"bank": p.bank,
|
"bank": p.bank,
|
||||||
@@ -1679,29 +1679,38 @@ class WebServer:
|
|||||||
return self._gather_channel_state(ch)
|
return self._gather_channel_state(ch)
|
||||||
|
|
||||||
def _gather_state_combined(self) -> dict[str, Any]:
|
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::
|
Shape::
|
||||||
|
|
||||||
{
|
{
|
||||||
\"channels\": {
|
"channels": {
|
||||||
\"guitar\": { … },
|
"guitar": { … },
|
||||||
\"bass\": { … },
|
"bass": { … },
|
||||||
|
"keys": { … },
|
||||||
|
"vocals": { … },
|
||||||
|
"backing_tracks": { … },
|
||||||
},
|
},
|
||||||
\"active_channel\": \"guitar\",
|
"active_channel": "guitar",
|
||||||
\"channel_mode\": \"dual-mono\",
|
"channel_mode": "dual-mono",
|
||||||
\"wifi\": { … },
|
"wifi": { … },
|
||||||
\"bluetooth\": { … },
|
"bluetooth": { … },
|
||||||
\"cpu_percent\": …,
|
"cpu_percent": …,
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
guitar = self._gather_channel_state("guitar")
|
guitar = self._gather_channel_state("guitar")
|
||||||
bass = self._gather_channel_state("bass")
|
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 {
|
return {
|
||||||
"channels": {
|
"channels": {
|
||||||
"guitar": guitar,
|
"guitar": guitar,
|
||||||
"bass": bass,
|
"bass": bass,
|
||||||
|
"keys": keys,
|
||||||
|
"vocals": vocals,
|
||||||
|
"backing_tracks": backing,
|
||||||
},
|
},
|
||||||
"active_channel": "guitar",
|
"active_channel": "guitar",
|
||||||
"channel_mode": self._channel_mgr.mode,
|
"channel_mode": self._channel_mgr.mode,
|
||||||
@@ -1710,6 +1719,9 @@ class WebServer:
|
|||||||
"cpu_percent": max(
|
"cpu_percent": max(
|
||||||
guitar.get("cpu_percent", 0),
|
guitar.get("cpu_percent", 0),
|
||||||
bass.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,
|
"sample_rate": SAMPLE_RATE,
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user