backing tracks: add source selector + bluealsa in device list

- Audio devices endpoint now includes Bluetooth Audio (bluealsa) and
  other software PCM devices from aplay -L
- Added GET/POST /api/backing-source endpoints with JACK port routing
- Backing Track Source dropdown in Audio section (None, Bluetooth
  Audio, or any JACK capture port found)
- Selecting a source routes it into fx_in:input_2 via jack_connect
- Also supports bt_audio:capture_1/2 from existing JACK bridge
This commit is contained in:
2026-06-16 19:20:30 -04:00
parent 334e035cde
commit a37e381c4e
2 changed files with 97 additions and 1 deletions
+69
View File
@@ -2110,10 +2110,79 @@ iface eth0 inet static
if "USB" in line or "usb" in line.lower():
m = re.match(r"\s*(\d+)", line)
if m: usb_id = int(m.group(1))
# Add software PCM devices (bluealsa, etc.) as virtual cards
try:
r3 = subprocess.run(["aplay","-L"], capture_output=True, text=True, timeout=3)
for line in r3.stdout.splitlines():
name = line.strip()
if name.startswith("bluealsa") or name.startswith("bluealsa:"):
if not any(c.get("name","") == "Bluetooth Audio" for c in cards):
cards.append({"card": -1, "name": "Bluetooth Audio",
"device": 0, "desc": "BlueALSA (BT playback)"})
except: pass
return {"devices": cards, "usb_card": usb_id, "suggested": f"hw:{usb_id or 0},0"}
except Exception as e:
return {"error": str(e), "devices": []}
# ── Backing track source ────────────────────────────────────
@app.get("/api/backing-source")
async def get_backing_source():
"""Get current backing track source and available sources."""
import subprocess
audio = self.deps.audio_system
cfg = audio.config.__dict__ if audio and hasattr(audio.config, '__dict__') else {}
current = cfg.get("backing_source", "none")
# Check available JACK ports for possible sources
sources = [{"id": "none", "name": "None"}]
try:
r = subprocess.run(["jack_lsp"], capture_output=True, text=True, timeout=3)
for line in r.stdout.splitlines():
line = line.strip()
if line.endswith(":capture_1") or line.endswith(":capture_2"):
name = line.split(":")[0]
if name != "system" and name != "pi-multifx" and name != "bt_audio":
sources.append({"id": name, "name": f"{name} (capture)"})
# Always offer bluetooth if JACK has bt_audio ports
r2 = subprocess.run(["jack_lsp","bt_audio:capture_1"], capture_output=True, text=True, timeout=2)
if r2.returncode == 0:
sources.append({"id": "bt_audio", "name": "Bluetooth Audio"})
except: pass
return {"current": current, "sources": sources}
@app.post("/api/backing-source")
async def set_backing_source(data: dict):
"""Set backing track source and route it into the FX chain.
Body: { "source": "none" | "bt_audio" | "<jack_source>" }
"""
import subprocess
source = data.get("source", "none")
audio = self.deps.audio_system
# Save to config
if audio and hasattr(audio.config, '__dict__'):
audio.config.__dict__["backing_source"] = source
# Disconnect old backing connections
try:
r = subprocess.run(["jack_lsp","fx_in:input_2"], capture_output=True, text=True, timeout=2)
if r.returncode == 0:
subprocess.run(["jack_disconnect","bt_audio:capture_1","fx_in:input_2"],
capture_output=True, timeout=2)
subprocess.run(["jack_disconnect","bt_audio:capture_2","fx_in:input_2"],
capture_output=True, timeout=2)
except: pass
# Connect new source
if source != "none":
try:
if source == "bt_audio":
subprocess.run(["jack_connect","bt_audio:capture_1","fx_in:input_2"],
capture_output=True, timeout=3)
else:
subprocess.run(["jack_connect",f"{source}:capture_1","fx_in:input_2"],
capture_output=True, timeout=3)
except: pass
return {"ok": True, "source": source}
@app.get("/api/levels")
async def get_signal_levels():
levels = {"channels": {}}