fix: add missing API endpoints for volume POST, bypass/toggle, block params, block toggle; fix JACK 1.9.22 cmd args; fix Scarlett 2i2 channel count

This commit is contained in:
2026-06-13 21:08:58 +00:00
parent cd2ed273e9
commit 9fade4a2d2
61 changed files with 13728 additions and 56 deletions
+70 -12
View File
@@ -12,6 +12,7 @@ Manages ALSA / JACK / I2S setup on RPi 4B:
from __future__ import annotations
import logging
import os
import re
import subprocess
import time
@@ -276,22 +277,28 @@ class AudioSystem:
return True
profile = self.config.latency_profile
jack_env = os.environ.copy()
jack_env["JACK_NO_AUDIO_RESERVATION"] = "1"
# Scarlett 2i2 needs 2 channels even in mono mode
n_in = max(2, self.config.capture_channels)
n_out = max(2, self.config.playback_channels)
cmd = [
"jackd",
f"-P{profile['rt_priority']}",
f"-p{profile['period']}",
f"-n{profile['nperiods']}",
f"-r{profile['rate']}",
"-dalsa",
f"-d{self.config.output_device}",
f"-i{self.config.capture_channels}",
f"-o{self.config.playback_channels}",
"-P", str(profile['rt_priority']),
"-d", "alsa",
"-d", self.config.output_device,
"-r", str(profile['rate']),
"-p", str(profile['period']),
"-n", str(profile['nperiods']),
"-i", str(n_in),
"-o", str(n_out),
]
logger.info("Starting JACK: %s", " ".join(cmd))
try:
proc = subprocess.Popen(
cmd,
env=jack_env,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
)
@@ -759,14 +766,24 @@ WantedBy=multi-user.target
def _jack_is_running() -> bool:
"""Check if JACK is running via jack_wait."""
"""Check if JACK is running via process list or PID file."""
try:
result = subprocess.run(
["jack_wait", "-c"],
capture_output=True, text=True, timeout=5,
["pgrep", "-x", "jackd"],
capture_output=True, timeout=3,
)
return result.returncode == 0
except (FileNotFoundError, subprocess.TimeoutExpired):
except FileNotFoundError:
# pgrep not available — fall back to pidof
try:
result = subprocess.run(
["pidof", "jackd"],
capture_output=True, timeout=3,
)
return result.returncode == 0
except (FileNotFoundError, subprocess.TimeoutExpired):
return False
except subprocess.TimeoutExpired:
return False
@@ -869,6 +886,9 @@ class JackAudioClient:
self._active = True
logger.info("JACK client activated (blocksize=%d)", block_size)
# Explicitly connect ports to system (JACK auto-connect is unreliable)
self._connect_ports()
def stop(self) -> None:
"""Deactivate and close the JACK client."""
if not self._active or self._client is None:
@@ -889,6 +909,44 @@ class JackAudioClient:
self._out_buf = None
logger.info("JACK client stopped")
# ── Port connection helpers ──────────────────────────────────
def _connect_ports(self) -> None:
"""Explicitly connect our ports to the system physical ports.
JACK's auto-connect is unreliable across systemd service restarts.
This ensures audio always flows even when the JACK server was
started before our client.
"""
if self._client is None:
return
# Connect each capture port to the corresponding system capture
for i, port in enumerate(self._inports):
suffix = f"capture_{i + 1}" if self._input_channels > 1 else "capture_1"
sys_port_name = f"system:{suffix}"
try:
sys_ports = self._client.get_ports(sys_port_name)
if sys_ports:
self._client.connect(sys_ports[0], port)
logger.debug("Connected %s -> %s", sys_port_name, port.name)
except Exception as exc:
logger.warning("Failed to connect %s -> %s: %s",
sys_port_name, port.name, exc)
# Connect each playback port to the corresponding system playback
for i, port in enumerate(self._outports):
suffix = f"playback_{i + 1}" if self._output_channels > 1 else "playback_1"
sys_port_name = f"system:{suffix}"
try:
sys_ports = self._client.get_ports(sys_port_name)
if sys_ports:
self._client.connect(port, sys_ports[0])
logger.debug("Connected %s -> %s", port.name, sys_port_name)
except Exception as exc:
logger.warning("Failed to connect %s -> %s: %s",
port.name, sys_port_name, exc)
# ── Runtime reconfiguration ────────────────────────────────────
def set_channel_count(self, input_channels: int, output_channels: int) -> None: