fix: critical DSP bugs causing crackles/pops and clipping
1. NAM_AMP: removed double process() call (was processing through C++ engine twice per block, corrupting internal state), removed os.system() debug logging on every audio callback (caused JACK xruns on RPi 4), fixed level*2.0 input clipping to proper 0.0-1.0 gain drive 2. Added np.clip() to all wet/dry mix returns across 9+ effect types (delay, reverb, chorus, flanger, phaser, ring mod, envelope filter, rotary, formant, ping-pong, multi-tap, reverse, shimmer reverb) 3. Clipped all delay feedback write paths (digital, analog, ping-pong, multi-tap, reverse, tape echo) to prevent delay-line runaway 4. Added output clipping to _process_mono and _process_4cm master volume 5. Fixed analog delay tanh(*0.5)*2.0 -> tanh(*0.5) (was re-expanding after soft-clip, reintroducing digital distortion)
This commit is contained in:
+39
-40
@@ -556,7 +556,7 @@ class AudioPipeline:
|
||||
if entry["bypass"] or not entry["enabled"]:
|
||||
continue
|
||||
buf = self._process_single_block(buf, idx, entry)
|
||||
out = buf * self._master_volume
|
||||
out = np.clip(buf * self._master_volume, -1.0, 1.0)
|
||||
|
||||
# Update output VU level
|
||||
out_rms = np.sqrt(np.mean(out ** 2) + _EPS)
|
||||
@@ -606,8 +606,8 @@ class AudioPipeline:
|
||||
ch1 = self._process_single_block(ch1, idx, entry)
|
||||
|
||||
out = np.zeros_like(audio_in)
|
||||
out[0, :] = ch0 * self._master_volume
|
||||
out[1, :] = ch1 * self._master_volume
|
||||
out[0, :] = np.clip(ch0 * self._master_volume, -1.0, 1.0)
|
||||
out[1, :] = np.clip(ch1 * self._master_volume, -1.0, 1.0)
|
||||
|
||||
# Update output VU level from the processed effect return (ch1)
|
||||
out_rms = np.sqrt(np.mean(out ** 2) + _EPS)
|
||||
@@ -695,26 +695,26 @@ class AudioPipeline:
|
||||
case FXType.REVERB:
|
||||
return self._apply_reverb(buf, params, fx_state)
|
||||
case FXType.VOLUME:
|
||||
return self._apply_volume(buf, params, fx_state)
|
||||
return np.clip(self._apply_volume(buf, params, fx_state), -1.0, 1.0)
|
||||
case FXType.NAM_AMP:
|
||||
# Use C++ NeuralAudio engine when a .nam file is loaded.
|
||||
if self.nam.is_loaded:
|
||||
import numpy as np
|
||||
import os as _os
|
||||
_os.system('echo "PIPELINE: NAM_AMP is_loaded=True buf_peak={:.6f}" >> /tmp/pipeline_debug.log'.format(float(np.max(np.abs(buf)))))
|
||||
in_rms = 20 * np.log10(np.sqrt(np.mean(buf**2)) + 1e-10)
|
||||
# Apply input gain as pre-amp drive (level 0.0-1.0 maps to 0-1.0x gain)
|
||||
level = params.get("level", 0.75)
|
||||
drive = np.clip(level, 0.0, 1.0)
|
||||
if drive != 1.0:
|
||||
buf = buf * drive
|
||||
|
||||
# Single process call through the C++ engine
|
||||
processed = self.nam.process(buf)
|
||||
_os.system('echo "PIPELINE: NAM_AMP processed peak={:.6f}" >> /tmp/pipeline_debug.log'.format(float(np.max(np.abs(processed)))))
|
||||
out_rms = 20 * np.log10(np.sqrt(np.mean(processed**2)) + 1e-10)
|
||||
logger.debug("NAM %s: in=%.1fdBFS out=%.1fdBFS gain=%.1fdB",
|
||||
getattr(self.nam, '_loaded_path', '?'),
|
||||
in_rms, out_rms, out_rms - in_rms)
|
||||
|
||||
# Crossfade on preset switch
|
||||
if self.nam._crossfade_buf is not None:
|
||||
processed = self.nam.apply_crossfade(processed)
|
||||
level = params.get("level", 0.75)
|
||||
attenuated = buf * (level * 2.0)
|
||||
processed = self.nam.process(attenuated)
|
||||
return processed
|
||||
_os.system('echo "PIPELINE: NAM_AMP is_loaded=False" >> /tmp/pipeline_debug.log')
|
||||
|
||||
# Clip output to prevent digital distortion
|
||||
return np.clip(processed, -1.0, 1.0)
|
||||
|
||||
logger.debug("NAM_AMP: engine not loaded, passing through")
|
||||
return buf
|
||||
case FXType.IR_CAB:
|
||||
@@ -1219,7 +1219,7 @@ class AudioPipeline:
|
||||
|
||||
delay_line.write_block(buf)
|
||||
|
||||
return buf * (1.0 - mix) + wet * mix
|
||||
return np.clip(buf * (1.0 - mix) + wet * mix, -1.0, 1.0)
|
||||
|
||||
# ── 6. Flanger ──────────────────────────────────────────────────
|
||||
|
||||
@@ -1259,7 +1259,7 @@ class AudioPipeline:
|
||||
# Store feedback for next block
|
||||
state["fb_buf"] = wet * 0.5
|
||||
|
||||
return buf * (1.0 - mix) + wet * mix
|
||||
return np.clip(buf * (1.0 - mix) + wet * mix, -1.0, 1.0)
|
||||
|
||||
# ── 7. Phaser ───────────────────────────────────────────────────
|
||||
|
||||
@@ -1387,13 +1387,12 @@ class AudioPipeline:
|
||||
# Read delayed signal
|
||||
wet = delay_line.read_block(float(delay_samples), len(buf))
|
||||
|
||||
# Write dry + feedback (no self-oscillation guard)
|
||||
# clips feedback automatically
|
||||
# Write dry + clipped feedback to prevent delay-line runaway
|
||||
fb_gain = min(feedback, 0.98)
|
||||
write_sig = buf + wet * fb_gain
|
||||
write_sig = np.clip(buf + wet * fb_gain, -1.0, 1.0)
|
||||
delay_line.write_block(write_sig)
|
||||
|
||||
return buf * (1.0 - mix) + wet * mix
|
||||
return np.clip(buf * (1.0 - mix) + wet * mix, -1.0, 1.0)
|
||||
|
||||
def _apply_analog_delay(self, buf: np.ndarray, params: dict,
|
||||
state: dict) -> np.ndarray:
|
||||
@@ -1441,14 +1440,14 @@ class AudioPipeline:
|
||||
|
||||
# ── BBD-style subtle saturation on feedback path ─────────────
|
||||
# Soft-clip the filtered feedback (BBD companding characteristic)
|
||||
lp_out = np.tanh(lp_out * 0.5) * 2.0
|
||||
lp_out = np.tanh(lp_out * 0.5)
|
||||
|
||||
# Write with filtered feedback
|
||||
# Write with clipped feedback
|
||||
fb_gain = min(feedback, 0.98)
|
||||
write_sig = buf + lp_out * fb_gain
|
||||
write_sig = np.clip(buf + lp_out * fb_gain, -1.0, 1.0)
|
||||
delay_line.write_block(write_sig)
|
||||
|
||||
return buf * (1.0 - mix) + wet * mix
|
||||
return np.clip(buf * (1.0 - mix) + wet * mix, -1.0, 1.0)
|
||||
|
||||
# ── 11. Reverb (subtype dispatch) ───────────────────────────────
|
||||
|
||||
@@ -1477,7 +1476,7 @@ class AudioPipeline:
|
||||
case _:
|
||||
wet = self._reverb_hall(buf, params, state)
|
||||
|
||||
return buf * (1.0 - mix) + wet * mix
|
||||
return np.clip(buf * (1.0 - mix) + wet * mix, -1.0, 1.0)
|
||||
|
||||
def _reverb_hall(self, buf: np.ndarray, params: dict,
|
||||
state: dict) -> np.ndarray:
|
||||
@@ -2046,7 +2045,7 @@ class AudioPipeline:
|
||||
|
||||
wet = delay_line.read_block_varying(mod_delay)
|
||||
delay_line.write_block(buf)
|
||||
return buf * (1.0 - mix) + wet * mix
|
||||
return np.clip(buf * (1.0 - mix) + wet * mix, -1.0, 1.0)
|
||||
|
||||
# ── 19. Ring Modulator ───────────────────────────────────────────
|
||||
|
||||
@@ -2095,7 +2094,7 @@ class AudioPipeline:
|
||||
state["bpf_zi"] = zf
|
||||
|
||||
wet = sig.astype(np.float32)
|
||||
return buf * (1.0 - mix) + wet * mix
|
||||
return np.clip(buf * (1.0 - mix) + wet * mix, -1.0, 1.0)
|
||||
|
||||
# ── 21. Envelope Filter ──────────────────────────────────────────
|
||||
|
||||
@@ -2129,7 +2128,7 @@ class AudioPipeline:
|
||||
state["lpf_zi"] = zf
|
||||
|
||||
wet = sig.astype(np.float32)
|
||||
return buf * (1.0 - mix) + wet * mix
|
||||
return np.clip(buf * (1.0 - mix) + wet * mix, -1.0, 1.0)
|
||||
|
||||
# ── 22. Rotary Speaker ──────────────────────────────────────────
|
||||
|
||||
@@ -2643,7 +2642,7 @@ class AudioPipeline:
|
||||
state[f"form_zi_{stage}"] = zf
|
||||
|
||||
wet = np.clip(sig, -1.0, 1.0).astype(np.float32)
|
||||
return buf * (1.0 - mix) + wet * mix
|
||||
return np.clip(buf * (1.0 - mix) + wet * mix, -1.0, 1.0)
|
||||
|
||||
# ── 39. Ping-pong Delay ─────────────────────────────────────────
|
||||
|
||||
@@ -2711,8 +2710,8 @@ class AudioPipeline:
|
||||
wet += tap_sig * (1.0 / len(taps)) # Normalise
|
||||
|
||||
fb_gain = min(feedback, 0.98)
|
||||
delay_line.write_block(buf + wet * fb_gain)
|
||||
return buf * (1.0 - mix) + wet * mix
|
||||
delay_line.write_block(np.clip(buf + wet * fb_gain, -1.0, 1.0))
|
||||
return np.clip(buf * (1.0 - mix) + wet * mix, -1.0, 1.0)
|
||||
|
||||
# ── 41. Reverse Delay ───────────────────────────────────────────
|
||||
|
||||
@@ -2748,8 +2747,8 @@ class AudioPipeline:
|
||||
state["read_pos"] = rpos % delay_samples
|
||||
|
||||
fb_gain = min(feedback, 0.98)
|
||||
delay_line.write_block(buf + out * fb_gain)
|
||||
return buf * (1.0 - mix) + out * mix
|
||||
delay_line.write_block(np.clip(buf + out * fb_gain, -1.0, 1.0))
|
||||
return np.clip(buf * (1.0 - mix) + out * mix, -1.0, 1.0)
|
||||
|
||||
# ── 42. Tape Echo ───────────────────────────────────────────────
|
||||
|
||||
@@ -2790,8 +2789,8 @@ class AudioPipeline:
|
||||
wet = lp_out.astype(np.float32)
|
||||
|
||||
fb_gain = min(feedback, 0.98)
|
||||
delay_line.write_block(buf + wet * fb_gain)
|
||||
return buf * (1.0 - mix) + wet * mix
|
||||
delay_line.write_block(np.clip(buf + wet * fb_gain, -1.0, 1.0))
|
||||
return np.clip(buf * (1.0 - mix) + wet * mix, -1.0, 1.0)
|
||||
|
||||
# ── 43. Shimmer Reverb ──────────────────────────────────────────
|
||||
# BETA — test on RPi 4B for xruns
|
||||
@@ -2871,7 +2870,7 @@ class AudioPipeline:
|
||||
wet = wet + shifted_out.astype(np.float64) * 0.5 # feedback shimmer
|
||||
|
||||
wet = np.clip(wet, -1.0, 1.0).astype(np.float32)
|
||||
return buf * (1.0 - mix) + wet * mix
|
||||
return np.clip(buf * (1.0 - mix) + wet * mix, -1.0, 1.0)
|
||||
|
||||
# ── 44. Looper ─────────────────────────────────────────────────
|
||||
|
||||
|
||||
Reference in New Issue
Block a user