Add /api/audio/profile endpoints for runtime latency profile switching
- GET /api/audio/profiles — list available profiles with params
- GET /api/audio/profile — current profile + jack_running + xrun_count
- POST /api/audio/profile {profile: 'standard'|'low'} — switch profile,
restart JACK server + JACK audio client atomically
- Adds audio_system + jack_audio refs to WebServerDeps
- Rollback on JACK restart failure
This commit is contained in:
@@ -248,6 +248,8 @@ class PedalApp:
|
|||||||
pipeline=self.pipeline,
|
pipeline=self.pipeline,
|
||||||
nam_host=self.nam_host,
|
nam_host=self.nam_host,
|
||||||
ir_loader=self.ir_loader,
|
ir_loader=self.ir_loader,
|
||||||
|
audio_system=self.audio_system,
|
||||||
|
jack_audio=self.jack_audio,
|
||||||
),
|
),
|
||||||
host="0.0.0.0",
|
host="0.0.0.0",
|
||||||
port=8080,
|
port=8080,
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ from ..system.tonedownload import (
|
|||||||
discover_anon_key,
|
discover_anon_key,
|
||||||
format_size,
|
format_size,
|
||||||
)
|
)
|
||||||
|
from ..system.audio import AudioSystem, JackAudioClient, LATENCY_PROFILES
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -201,6 +202,11 @@ class WebServerDeps:
|
|||||||
bass_nam_host: Optional[NAMHost] = None
|
bass_nam_host: Optional[NAMHost] = None
|
||||||
bass_ir_loader: Optional[IRLoader] = None
|
bass_ir_loader: Optional[IRLoader] = None
|
||||||
|
|
||||||
|
# Audio system (for JACK restart on profile change)
|
||||||
|
audio_system: Optional[AudioSystem] = None
|
||||||
|
# JACK audio client (needs restart when JACK restarts)
|
||||||
|
jack_audio: Optional[JackAudioClient] = None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_connected(self) -> bool:
|
def is_connected(self) -> bool:
|
||||||
"""True if we're wired to a live pedal instance (any channel)."""
|
"""True if we're wired to a live pedal instance (any channel)."""
|
||||||
@@ -1269,6 +1275,106 @@ class WebServer:
|
|||||||
from ..system.bluetooth import bt_midi_connected_devices
|
from ..system.bluetooth import bt_midi_connected_devices
|
||||||
return {"devices": bt_midi_connected_devices()}
|
return {"devices": bt_midi_connected_devices()}
|
||||||
|
|
||||||
|
# ── Audio latency profile endpoints ───────────────────────
|
||||||
|
|
||||||
|
@app.get("/api/audio/profiles")
|
||||||
|
async def list_audio_profiles():
|
||||||
|
"""List all available JACK latency profiles."""
|
||||||
|
return {
|
||||||
|
"profiles": [
|
||||||
|
{
|
||||||
|
"key": k,
|
||||||
|
**v,
|
||||||
|
}
|
||||||
|
for k, v in LATENCY_PROFILES.items()
|
||||||
|
],
|
||||||
|
"current": (
|
||||||
|
self.deps.audio_system.config.profile
|
||||||
|
if self.deps.audio_system
|
||||||
|
else "standard"
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
@app.get("/api/audio/profile")
|
||||||
|
async def get_audio_profile():
|
||||||
|
"""Get the current JACK latency profile and audio stats."""
|
||||||
|
if not self.deps.audio_system:
|
||||||
|
return {
|
||||||
|
"profile": "standard",
|
||||||
|
"period": 256,
|
||||||
|
"nperiods": 2,
|
||||||
|
"rate": 48000,
|
||||||
|
"jack_running": False,
|
||||||
|
"xrun_count": None,
|
||||||
|
}
|
||||||
|
profile_key = self.deps.audio_system.config.profile
|
||||||
|
profile = self.deps.audio_system.config.latency_profile
|
||||||
|
xruns = self.deps.audio_system.read_xrun_count()
|
||||||
|
return {
|
||||||
|
"profile": profile_key,
|
||||||
|
"period": profile["period"],
|
||||||
|
"nperiods": profile["nperiods"],
|
||||||
|
"rate": profile["rate"],
|
||||||
|
"jack_running": True,
|
||||||
|
"xrun_count": xruns,
|
||||||
|
}
|
||||||
|
|
||||||
|
@app.post("/api/audio/profile")
|
||||||
|
async def set_audio_profile(data: dict):
|
||||||
|
"""Switch JACK latency profile (restarts JACK server).
|
||||||
|
|
||||||
|
Body: { "profile": "standard" | "low" }
|
||||||
|
|
||||||
|
The switch takes effect immediately by restarting JACK with the
|
||||||
|
new period / nperiods / priority settings.
|
||||||
|
"""
|
||||||
|
profile_key = data.get("profile", "")
|
||||||
|
if profile_key not in LATENCY_PROFILES:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=400,
|
||||||
|
detail=f"Unknown profile: {profile_key!r}. Options: {list(LATENCY_PROFILES.keys())}",
|
||||||
|
)
|
||||||
|
audio_sys = self.deps.audio_system
|
||||||
|
if not audio_sys:
|
||||||
|
raise HTTPException(status_code=503, detail="Audio system not available")
|
||||||
|
old_key = audio_sys.config.profile
|
||||||
|
if old_key == profile_key:
|
||||||
|
return {"ok": True, "profile": profile_key, "restarted": False}
|
||||||
|
# Apply new profile
|
||||||
|
audio_sys.config.profile = profile_key
|
||||||
|
profile = audio_sys.config.latency_profile
|
||||||
|
logger.info("Switching audio profile: %s → %s (period=%d, nperiods=%d)",
|
||||||
|
old_key, profile_key, profile["period"], profile["nperiods"])
|
||||||
|
# Stop JACK audio client first (holds a live JACK C client)
|
||||||
|
jack_client = self.deps.jack_audio
|
||||||
|
if jack_client:
|
||||||
|
jack_client.stop()
|
||||||
|
# Restart JACK server with new parameters
|
||||||
|
ok = audio_sys.restart_jack(timeout=10)
|
||||||
|
if not ok:
|
||||||
|
# Rollback on failure
|
||||||
|
audio_sys.config.profile = old_key
|
||||||
|
audio_sys.start_jack(timeout=10)
|
||||||
|
if jack_client:
|
||||||
|
jack_client.start()
|
||||||
|
raise HTTPException(status_code=500, detail=f"Failed to start JACK with profile {profile_key!r} — rolled back to {old_key!r}")
|
||||||
|
# Restart JACK audio client
|
||||||
|
if jack_client:
|
||||||
|
jack_client.start()
|
||||||
|
await self._manager.broadcast({
|
||||||
|
"type": "audio_profile_changed",
|
||||||
|
"profile": profile_key,
|
||||||
|
"period": profile["period"],
|
||||||
|
"nperiods": profile["nperiods"],
|
||||||
|
})
|
||||||
|
return {
|
||||||
|
"ok": True,
|
||||||
|
"profile": profile_key,
|
||||||
|
"period": profile["period"],
|
||||||
|
"nperiods": profile["nperiods"],
|
||||||
|
"restarted": True,
|
||||||
|
}
|
||||||
|
|
||||||
# ── FX block param schemas ───────────────────────────────
|
# ── FX block param schemas ───────────────────────────────
|
||||||
|
|
||||||
@app.get("/api/block-params/{fx_type}")
|
@app.get("/api/block-params/{fx_type}")
|
||||||
|
|||||||
Reference in New Issue
Block a user