fix: narrow SHM cleanup in profile handler to client semaphore only; add DC-blocking HPF to pipeline input (60Hz hum fix)
CI / test (push) Has been cancelled
CI / test (push) Has been cancelled
This commit is contained in:
@@ -352,6 +352,15 @@ class AudioPipeline:
|
|||||||
# load_preset() holds it only for the atomic swap (not during model I/O).
|
# load_preset() holds it only for the atomic swap (not during model I/O).
|
||||||
self._lock = threading.Lock()
|
self._lock = threading.Lock()
|
||||||
|
|
||||||
|
# ── DC-blocking filter state ───────────────────────────────────────
|
||||||
|
# Single-pole high-pass at ~15Hz to remove DC offset and subsonic
|
||||||
|
# noise that can amplify into audible hum through NAM models.
|
||||||
|
# Filter: y[n] = alpha * (y[n-1] + x[n] - x[n-1])
|
||||||
|
# where alpha = 1 / (1 + 2*pi*fc/sr)
|
||||||
|
self._dc_prev_x: float = 0.0
|
||||||
|
self._dc_prev_y: float = 0.0
|
||||||
|
self._dc_alpha: float = 1.0 / (1.0 + 2.0 * np.pi * 15.0 / 48000.0)
|
||||||
|
|
||||||
logger.info("Audio pipeline initialized (block=%d, sr=%d)",
|
logger.info("Audio pipeline initialized (block=%d, sr=%d)",
|
||||||
self._block_size, self._sample_rate)
|
self._block_size, self._sample_rate)
|
||||||
|
|
||||||
@@ -646,6 +655,21 @@ class AudioPipeline:
|
|||||||
)
|
)
|
||||||
|
|
||||||
buf = audio_in.copy()
|
buf = audio_in.copy()
|
||||||
|
# ── DC-blocking high-pass filter ────────────────────────────
|
||||||
|
# Removes DC offset and subsonic noise (~15Hz corner) that can
|
||||||
|
# amplify into audible hum through high-gain NAM models.
|
||||||
|
# First-order HPF: y[n] = alpha * (y[n-1] + x[n] - x[n-1])
|
||||||
|
alpha = self._dc_alpha
|
||||||
|
px = self._dc_prev_x
|
||||||
|
py = self._dc_prev_y
|
||||||
|
for i in range(len(buf)):
|
||||||
|
y = alpha * (py + buf[i] - px)
|
||||||
|
px = buf[i]
|
||||||
|
py = y
|
||||||
|
buf[i] = y
|
||||||
|
self._dc_prev_x = px
|
||||||
|
self._dc_prev_y = py
|
||||||
|
|
||||||
for idx, entry in enumerate(chain):
|
for idx, entry in enumerate(chain):
|
||||||
if entry["bypass"] or not entry["enabled"]:
|
if entry["bypass"] or not entry["enabled"]:
|
||||||
continue
|
continue
|
||||||
@@ -683,6 +707,23 @@ class AudioPipeline:
|
|||||||
ch0 = audio_in[0, :].copy()
|
ch0 = audio_in[0, :].copy()
|
||||||
ch1 = audio_in[1, :].copy()
|
ch1 = audio_in[1, :].copy()
|
||||||
|
|
||||||
|
# ── DC-blocking high-pass filter on both channels ───────────
|
||||||
|
alpha = self._dc_alpha
|
||||||
|
px = self._dc_prev_x
|
||||||
|
py = self._dc_prev_y
|
||||||
|
for i in range(len(ch0)):
|
||||||
|
y = alpha * (py + ch0[i] - px)
|
||||||
|
px = ch0[i]
|
||||||
|
py = y
|
||||||
|
ch0[i] = y
|
||||||
|
for i in range(len(ch1)):
|
||||||
|
y = alpha * (py + ch1[i] - px)
|
||||||
|
px = ch1[i]
|
||||||
|
py = y
|
||||||
|
ch1[i] = y
|
||||||
|
self._dc_prev_x = px
|
||||||
|
self._dc_prev_y = py
|
||||||
|
|
||||||
# Update input VU level from the guitar input channel (ch0)
|
# Update input VU level from the guitar input channel (ch0)
|
||||||
in_rms = np.sqrt(np.mean(ch0 ** 2) + _EPS)
|
in_rms = np.sqrt(np.mean(ch0 ** 2) + _EPS)
|
||||||
self._input_level = (
|
self._input_level = (
|
||||||
@@ -3102,6 +3143,7 @@ class AudioPipeline:
|
|||||||
self._block_size = block_size
|
self._block_size = block_size
|
||||||
self._sample_rate = sample_rate
|
self._sample_rate = sample_rate
|
||||||
self._vu_alpha = np.exp(-block_size / (0.05 * sample_rate))
|
self._vu_alpha = np.exp(-block_size / (0.05 * sample_rate))
|
||||||
|
self._dc_alpha = 1.0 / (1.0 + 2.0 * np.pi * 15.0 / sample_rate)
|
||||||
# Clear DSP state — effects will reinit with new block/sample rate
|
# Clear DSP state — effects will reinit with new block/sample rate
|
||||||
self._state.clear()
|
self._state.clear()
|
||||||
self._coeffs.clear()
|
self._coeffs.clear()
|
||||||
|
|||||||
+7
-6
@@ -1768,14 +1768,15 @@ class WebServer:
|
|||||||
# Clean stale client SHM before creating new JACK client.
|
# Clean stale client SHM before creating new JACK client.
|
||||||
# The old process's jack.Client __del__ can recreate the
|
# The old process's jack.Client __del__ can recreate the
|
||||||
# pi-multifx semaphore after stop_jack() cleaned it.
|
# pi-multifx semaphore after stop_jack() cleaned it.
|
||||||
|
# IMPORTANT: Only target our client's semaphore, NOT the
|
||||||
|
# running JACK server's files (jack_sem.*_system, jack-shm-registry,
|
||||||
|
# jack_db-0/) — deleting those between start_jack() and
|
||||||
|
# jack_client.start() kills the JACK server socket and makes
|
||||||
|
# the client connection fail silently.
|
||||||
try:
|
try:
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import shutil
|
for p in Path("/dev/shm").glob("jack_sem.*.default_pi-multifx*"):
|
||||||
for p in Path("/dev/shm").glob("jack*"):
|
p.unlink(missing_ok=True)
|
||||||
if p.is_dir():
|
|
||||||
shutil.rmtree(p, ignore_errors=True)
|
|
||||||
else:
|
|
||||||
p.unlink(missing_ok=True)
|
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user