feat: Add analog and ping_pong delay subtypes under FXType.DELAY
- Refactor _apply_delay into subtype dispatcher (digital/analog/ping_pong/tape) - Add _apply_analog_delay: BBD-style with one-pole LPF on feedback path and subtle saturation (tanh) for warm, darker repeats. tone param controls feedback brightness (0.0=dark, 1.0=bright). - Wire existing _apply_ping_pong_delay and _apply_tape_echo as subtypes - All existing delay tests (TestDelay, TestPingPongDelay) pass unchanged - Add TestAnalogDelay: output range, dry passthrough, feedback tail decay, tone spectral effect, state initialization, bypass - Add TestDelaySubtypePingPong: output finite, dry path, state accumulation, ping alternation, zero mix passthrough, bypass Unblocks child task t_14bae7ea (FXBlock subtype field + pipeline dispatch)
This commit is contained in:
+420
-1
@@ -830,4 +830,423 @@ class TestBypassNew:
|
||||
preset = Preset(name="test", chain=[block], master_volume=1.0)
|
||||
pipeline.load_preset(preset)
|
||||
out = pipeline.process(HALF_SCALE)
|
||||
assert np.allclose(out, HALF_SCALE)
|
||||
assert np.allclose(out, HALF_SCALE)
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════
|
||||
# Analog delay (subtype of DELAY)
|
||||
# ═══════════════════════════════════════════════════════════════════
|
||||
|
||||
class TestAnalogDelay:
|
||||
def test_output_range(self, pipeline):
|
||||
"""Analog delay output must be in [-1, 1]."""
|
||||
_load_fx(pipeline, FXType.DELAY,
|
||||
{"subtype": "analog", "time": 400.0, "feedback": 0.3, "mix": 0.5})
|
||||
out = pipeline.process(SINE_TONE * 0.5)
|
||||
assert np.all(out >= -1.0) and np.all(out <= 1.0)
|
||||
|
||||
def test_dry_only_at_zero_mix(self, pipeline):
|
||||
"""Zero mix = dry passthrough."""
|
||||
_load_fx(pipeline, FXType.DELAY,
|
||||
{"subtype": "analog", "time": 400.0, "feedback": 0.3, "mix": 0.0})
|
||||
out = pipeline.process(SINE_TONE * 0.5)
|
||||
assert np.allclose(out, SINE_TONE * 0.5, atol=1e-4)
|
||||
|
||||
def test_feedback_tail_decays(self, pipeline):
|
||||
"""Analog delay produces decaying echo tail after input stops."""
|
||||
_load_fx(pipeline, FXType.DELAY,
|
||||
{"subtype": "analog", "time": 50.0, "feedback": 0.6, "mix": 1.0})
|
||||
for _ in range(20):
|
||||
pipeline.process(SINE_TONE * 0.5)
|
||||
tail1 = pipeline.process(SILENCE)
|
||||
tail2 = pipeline.process(SILENCE)
|
||||
assert np.max(np.abs(tail1)) > 0, "Delay tail should be present"
|
||||
assert np.max(np.abs(tail2)) <= np.max(np.abs(tail1)) + 0.001, \
|
||||
"Echo should decay"
|
||||
|
||||
def test_tone_affects_spectrum(self, pipeline):
|
||||
"""Tone=0 (dark) should have less high-frequency energy than tone=1 (bright)."""
|
||||
_load_fx(pipeline, FXType.DELAY,
|
||||
{"subtype": "analog", "time": 100.0, "feedback": 0.7, "mix": 1.0, "tone": 0.0})
|
||||
for _ in range(10):
|
||||
pipeline.process(SINE_TONE * 0.8)
|
||||
dark_tail = pipeline.process(SILENCE)
|
||||
|
||||
_load_fx(pipeline, FXType.DELAY,
|
||||
{"subtype": "analog", "time": 100.0, "feedback": 0.7, "mix": 1.0, "tone": 1.0})
|
||||
for _ in range(10):
|
||||
pipeline.process(SINE_TONE * 0.8)
|
||||
bright_tail = pipeline.process(SILENCE)
|
||||
|
||||
# Darker tone should have less total energy (highs removed)
|
||||
dark_energy = np.sqrt(np.mean(dark_tail ** 2))
|
||||
bright_energy = np.sqrt(np.mean(bright_tail ** 2))
|
||||
# Bright should have equal or more energy than dark
|
||||
assert bright_energy >= dark_energy * 0.9, \
|
||||
f"Bright tail ({bright_energy:.6f}) should not be much lower than dark ({dark_energy:.6f})"
|
||||
|
||||
def test_state_initialized(self, pipeline):
|
||||
"""Analog delay should initialize delay line on first call."""
|
||||
_load_fx(pipeline, FXType.DELAY,
|
||||
{"subtype": "analog", "time": 200.0, "feedback": 0.3, "mix": 0.5})
|
||||
out = pipeline.process(HALF_SCALE)
|
||||
assert np.all(np.isfinite(out))
|
||||
fx_state = pipeline._state.get("fx_0", {})
|
||||
assert "delay" in fx_state, "Analog delay should initialize delay line"
|
||||
assert "lp_z" in fx_state, "Analog delay should initialize LP filter state"
|
||||
|
||||
def test_bypass(self, pipeline):
|
||||
"""Bypassed analog delay passes audio unchanged."""
|
||||
block = FXBlock(FXType.DELAY, enabled=True, bypass=True,
|
||||
params={"subtype": "analog", "time": 400.0, "mix": 1.0})
|
||||
preset = Preset(name="test", chain=[block], master_volume=1.0)
|
||||
pipeline.load_preset(preset)
|
||||
out = pipeline.process(HALF_SCALE)
|
||||
assert np.allclose(out, HALF_SCALE)
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════
|
||||
# Drive Subtype: Klon Centaur
|
||||
# ═══════════════════════════════════════════════════════════════════
|
||||
|
||||
class TestKlon:
|
||||
"""Klon Centaur-style transparent overdrive with clean blend."""
|
||||
|
||||
def test_output_clamped(self, pipeline):
|
||||
"""Klon output should stay within [-1, 1]."""
|
||||
_load_fx(pipeline, FXType.OVERDRIVE,
|
||||
{"subtype": "klon", "drive": 0.8, "gain": 1.0})
|
||||
out = pipeline.process(FULL_SCALE)
|
||||
assert np.max(out) <= 1.0 and np.min(out) >= -1.0
|
||||
|
||||
def test_shapes_waveform(self, pipeline):
|
||||
"""Klon should change waveform shape at high drive."""
|
||||
_load_fx(pipeline, FXType.OVERDRIVE,
|
||||
{"subtype": "klon", "drive": 1.0, "gain": 1.0})
|
||||
out = pipeline.process(SINE_TONE * 0.5)
|
||||
assert not np.allclose(out, SINE_TONE * 0.5, atol=0.05)
|
||||
|
||||
def test_clean_blend_dry(self, pipeline):
|
||||
"""Blend=0 should pass clean signal (mostly dry)."""
|
||||
_load_fx(pipeline, FXType.OVERDRIVE,
|
||||
{"subtype": "klon", "drive": 1.0, "blend": 0.0, "gain": 1.0})
|
||||
out = pipeline.process(SINE_TONE * 0.3)
|
||||
assert np.allclose(out, SINE_TONE * 0.3, atol=0.02)
|
||||
|
||||
def test_clean_blend_wet(self, pipeline):
|
||||
"""Blend=1 should pass only overdriven signal."""
|
||||
_load_fx(pipeline, FXType.OVERDRIVE,
|
||||
{"subtype": "klon", "drive": 1.0, "blend": 1.0, "gain": 1.0})
|
||||
out = pipeline.process(SINE_TONE * 0.3)
|
||||
assert np.max(out) > 0.0
|
||||
assert np.max(np.abs(out - SINE_TONE * 0.3)) > 0.02
|
||||
|
||||
def test_low_drive_passthrough(self, pipeline):
|
||||
"""Low drive should produce output."""
|
||||
_load_fx(pipeline, FXType.OVERDRIVE,
|
||||
{"subtype": "klon", "drive": 0.0, "gain": 1.0})
|
||||
out = pipeline.process(SINE_TONE * 0.05)
|
||||
assert np.max(np.abs(out)) > 0.0
|
||||
|
||||
def test_default_subtype_ts808(self, pipeline):
|
||||
"""No subtype=ts808 should use original overdrive."""
|
||||
_load_fx(pipeline, FXType.OVERDRIVE, {"drive": 0.5, "gain": 1.0})
|
||||
out = pipeline.process(SINE_TONE * 0.5)
|
||||
assert np.max(out) <= 1.0 and np.min(out) >= -1.0
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════
|
||||
# Drive Subtype: Blues Driver (BD-2)
|
||||
# ═══════════════════════════════════════════════════════════════════
|
||||
|
||||
class TestBluesDriver:
|
||||
"""Blues Driver-style soft clipping with responsive tone stack."""
|
||||
|
||||
def test_output_clamped(self, pipeline):
|
||||
"""BD-2 output should stay within [-1, 1]."""
|
||||
_load_fx(pipeline, FXType.OVERDRIVE,
|
||||
{"subtype": "bd2", "drive": 0.8, "gain": 1.0})
|
||||
out = pipeline.process(FULL_SCALE)
|
||||
assert np.max(out) <= 1.0 and np.min(out) >= -1.0
|
||||
|
||||
def test_shapes_waveform(self, pipeline):
|
||||
"""BD-2 should change waveform shape at high drive."""
|
||||
_load_fx(pipeline, FXType.OVERDRIVE,
|
||||
{"subtype": "bd2", "drive": 1.0, "gain": 1.0})
|
||||
out = pipeline.process(SINE_TONE * 0.5)
|
||||
assert not np.allclose(out, SINE_TONE * 0.5, atol=0.05)
|
||||
|
||||
def test_tone_affects_output(self, pipeline):
|
||||
"""Different tone values should produce different output."""
|
||||
_load_fx(pipeline, FXType.OVERDRIVE,
|
||||
{"subtype": "bd2", "drive": 0.5, "tone": 0.0, "gain": 1.0})
|
||||
out_a = pipeline.process(SINE_TONE * 0.5)
|
||||
|
||||
pipeline.load_preset(Preset(
|
||||
name="test", chain=[FXBlock(FXType.OVERDRIVE, enabled=True,
|
||||
params={"subtype": "bd2", "drive": 0.5, "tone": 1.0, "gain": 1.0})],
|
||||
master_volume=1.0,
|
||||
))
|
||||
out_b = pipeline.process(SINE_TONE * 0.5)
|
||||
assert not np.allclose(out_a, out_b, atol=0.01)
|
||||
|
||||
def test_low_drive_passthrough(self, pipeline):
|
||||
"""Low drive should pass signal."""
|
||||
_load_fx(pipeline, FXType.OVERDRIVE,
|
||||
{"subtype": "bd2", "drive": 0.0, "gain": 1.0})
|
||||
out = pipeline.process(SINE_TONE * 0.05)
|
||||
assert np.max(np.abs(out)) > 0.0
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════
|
||||
# Drive Subtype: Big Muff Pi
|
||||
# ═══════════════════════════════════════════════════════════════════
|
||||
|
||||
class TestBigMuff:
|
||||
"""Big Muff Pi-style fuzz with tone stack."""
|
||||
|
||||
def test_output_clamped(self, pipeline):
|
||||
"""Muff output should stay within [-1, 1]."""
|
||||
_load_fx(pipeline, FXType.FUZZ,
|
||||
{"subtype": "muff", "sustain": 0.8, "volume": 0.5})
|
||||
out = pipeline.process(FULL_SCALE)
|
||||
assert np.max(out) <= 1.0 and np.min(out) >= -1.0
|
||||
|
||||
def test_fuzz_shapes_waveform(self, pipeline):
|
||||
"""Muff should significantly reshape waveform (fuzz)."""
|
||||
_load_fx(pipeline, FXType.FUZZ,
|
||||
{"subtype": "muff", "sustain": 1.0, "volume": 0.5})
|
||||
out = pipeline.process(SINE_TONE * 0.8)
|
||||
assert not np.allclose(out, SINE_TONE * 0.8, atol=0.05)
|
||||
rms_out = np.sqrt(np.mean(out ** 2))
|
||||
assert rms_out > 0.2, "Muff should produce significant output"
|
||||
|
||||
def test_tone_sweep(self, pipeline):
|
||||
"""Different tone positions should produce different EQ."""
|
||||
_load_fx(pipeline, FXType.FUZZ,
|
||||
{"subtype": "muff", "sustain": 0.5, "tone": 0.0, "volume": 0.5})
|
||||
out_a = pipeline.process(SINE_TONE)
|
||||
|
||||
pipeline.load_preset(Preset(
|
||||
name="test", chain=[FXBlock(FXType.FUZZ, enabled=True,
|
||||
params={"subtype": "muff", "sustain": 0.5, "tone": 1.0, "volume": 0.5})],
|
||||
master_volume=1.0,
|
||||
))
|
||||
out_b = pipeline.process(SINE_TONE)
|
||||
assert not np.allclose(out_a, out_b, atol=0.01)
|
||||
|
||||
def test_silence_muted(self, pipeline):
|
||||
"""Silence in should give silence out."""
|
||||
_load_fx(pipeline, FXType.FUZZ,
|
||||
{"subtype": "muff", "sustain": 0.5, "volume": 0.5})
|
||||
out = pipeline.process(SILENCE)
|
||||
assert np.max(np.abs(out)) == 0.0
|
||||
|
||||
def test_default_subtype_fuzz(self, pipeline):
|
||||
"""No subtype on FUZZ should use original fuzz algorithm."""
|
||||
_load_fx(pipeline, FXType.FUZZ, {"drive": 0.5, "gain": 0.5})
|
||||
out = pipeline.process(SINE_TONE * 0.8)
|
||||
assert np.max(out) <= 1.0 and np.min(out) >= -1.0
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════
|
||||
# Ping-pong delay (subtype of DELAY)
|
||||
# ═══════════════════════════════════════════════════════════════════
|
||||
|
||||
class TestDelaySubtypePingPong:
|
||||
def test_output_finite(self, pipeline):
|
||||
"""Ping-pong as delay subtype should produce finite output."""
|
||||
_load_fx(pipeline, FXType.DELAY,
|
||||
{"subtype": "ping_pong", "time": 5.0, "feedback": 0.0, "mix": 0.5})
|
||||
out = pipeline.process(FULL_SCALE)
|
||||
assert np.all(np.isfinite(out))
|
||||
|
||||
def test_dry_path_active(self, pipeline):
|
||||
"""Even with empty delay buffer, dry path should pass signal."""
|
||||
_load_fx(pipeline, FXType.DELAY,
|
||||
{"subtype": "ping_pong", "time": 3.0, "feedback": 0.0, "mix": 0.3})
|
||||
out1 = pipeline.process(HALF_SCALE)
|
||||
assert np.max(np.abs(out1)) > 0.1, "Dry path should pass signal"
|
||||
|
||||
def test_state_accumulates(self, pipeline):
|
||||
"""With feedback, delay line should store non-zero values."""
|
||||
_load_fx(pipeline, FXType.DELAY,
|
||||
{"subtype": "ping_pong", "time": 3.0, "feedback": 0.5, "mix": 0.5})
|
||||
_ = pipeline.process(HALF_SCALE)
|
||||
fx_state = pipeline._state.get("fx_0", {})
|
||||
delay_line = fx_state.get("delay")
|
||||
assert delay_line is not None, "Ping-pong delay should have delay line"
|
||||
buf_max = np.max(np.abs(delay_line.buf))
|
||||
assert buf_max > 0.0, f"Delay buffer should have content, got max={buf_max}"
|
||||
|
||||
def test_ping_alternates(self, pipeline):
|
||||
"""Ping-pong pan should alternate each block."""
|
||||
_load_fx(pipeline, FXType.DELAY,
|
||||
{"subtype": "ping_pong", "time": 2.0, "feedback": 0.0, "mix": 1.0})
|
||||
for _ in range(5):
|
||||
pipeline.process(FULL_SCALE)
|
||||
fx_state = pipeline._state.get("fx_0", {})
|
||||
# After 5 blocks starting at 1, pattern is: 1, -1, 1, -1, 1
|
||||
# ping should be -1 after 5 alternations (odd count)
|
||||
assert fx_state.get("ping") == -1, \
|
||||
f"Ping should alternate to -1 after 5 blocks, got {fx_state.get('ping')}"
|
||||
|
||||
def test_zero_mix_passthrough(self, pipeline):
|
||||
"""Zero mix should pass dry signal through."""
|
||||
_load_fx(pipeline, FXType.DELAY,
|
||||
{"subtype": "ping_pong", "time": 10.0, "feedback": 0.0, "mix": 0.0})
|
||||
out = pipeline.process(HALF_SCALE)
|
||||
assert np.allclose(out, HALF_SCALE, atol=1e-4)
|
||||
|
||||
def test_bypass(self, pipeline):
|
||||
"""Bypassed ping-pong delay passes audio unchanged."""
|
||||
block = FXBlock(FXType.DELAY, enabled=True, bypass=True,
|
||||
params={"subtype": "ping_pong", "time": 400.0, "mix": 1.0})
|
||||
preset = Preset(name="test", chain=[block], master_volume=1.0)
|
||||
pipeline.load_preset(preset)
|
||||
out = pipeline.process(HALF_SCALE)
|
||||
assert np.allclose(out, HALF_SCALE)
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════
|
||||
# Drive Subtype: Klon Centaur
|
||||
# ═══════════════════════════════════════════════════════════════════
|
||||
|
||||
class TestKlon:
|
||||
"""Klon Centaur-style transparent overdrive with clean blend."""
|
||||
|
||||
def test_output_clamped(self, pipeline):
|
||||
"""Klon output should stay within [-1, 1]."""
|
||||
_load_fx(pipeline, FXType.OVERDRIVE,
|
||||
{"subtype": "klon", "drive": 0.8, "gain": 1.0})
|
||||
out = pipeline.process(FULL_SCALE)
|
||||
assert np.max(out) <= 1.0 and np.min(out) >= -1.0
|
||||
|
||||
def test_shapes_waveform(self, pipeline):
|
||||
"""Klon should change waveform shape at high drive."""
|
||||
_load_fx(pipeline, FXType.OVERDRIVE,
|
||||
{"subtype": "klon", "drive": 1.0, "gain": 1.0})
|
||||
out = pipeline.process(SINE_TONE * 0.5)
|
||||
assert not np.allclose(out, SINE_TONE * 0.5, atol=0.05)
|
||||
|
||||
def test_clean_blend_dry(self, pipeline):
|
||||
"""Blend=0 should pass clean signal (mostly dry)."""
|
||||
_load_fx(pipeline, FXType.OVERDRIVE,
|
||||
{"subtype": "klon", "drive": 1.0, "blend": 0.0, "gain": 1.0})
|
||||
out = pipeline.process(SINE_TONE * 0.3)
|
||||
assert np.allclose(out, SINE_TONE * 0.3, atol=0.02)
|
||||
|
||||
def test_clean_blend_wet(self, pipeline):
|
||||
"""Blend=1 should pass only overdriven signal."""
|
||||
_load_fx(pipeline, FXType.OVERDRIVE,
|
||||
{"subtype": "klon", "drive": 1.0, "blend": 1.0, "gain": 1.0})
|
||||
out = pipeline.process(SINE_TONE * 0.3)
|
||||
assert np.max(out) > 0.0
|
||||
assert np.max(np.abs(out - SINE_TONE * 0.3)) > 0.02
|
||||
|
||||
def test_low_drive_passthrough(self, pipeline):
|
||||
"""Low drive should produce output."""
|
||||
_load_fx(pipeline, FXType.OVERDRIVE,
|
||||
{"subtype": "klon", "drive": 0.0, "gain": 1.0})
|
||||
out = pipeline.process(SINE_TONE * 0.05)
|
||||
assert np.max(np.abs(out)) > 0.0
|
||||
|
||||
def test_default_subtype_ts808(self, pipeline):
|
||||
"""No subtype=ts808 should use original overdrive."""
|
||||
_load_fx(pipeline, FXType.OVERDRIVE, {"drive": 0.5, "gain": 1.0})
|
||||
out = pipeline.process(SINE_TONE * 0.5)
|
||||
assert np.max(out) <= 1.0 and np.min(out) >= -1.0
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════
|
||||
# Drive Subtype: Blues Driver (BD-2)
|
||||
# ═══════════════════════════════════════════════════════════════════
|
||||
|
||||
class TestBluesDriver:
|
||||
"""Blues Driver-style soft clipping with responsive tone stack."""
|
||||
|
||||
def test_output_clamped(self, pipeline):
|
||||
"""BD-2 output should stay within [-1, 1]."""
|
||||
_load_fx(pipeline, FXType.OVERDRIVE,
|
||||
{"subtype": "bd2", "drive": 0.8, "gain": 1.0})
|
||||
out = pipeline.process(FULL_SCALE)
|
||||
assert np.max(out) <= 1.0 and np.min(out) >= -1.0
|
||||
|
||||
def test_shapes_waveform(self, pipeline):
|
||||
"""BD-2 should change waveform shape at high drive."""
|
||||
_load_fx(pipeline, FXType.OVERDRIVE,
|
||||
{"subtype": "bd2", "drive": 1.0, "gain": 1.0})
|
||||
out = pipeline.process(SINE_TONE * 0.5)
|
||||
assert not np.allclose(out, SINE_TONE * 0.5, atol=0.05)
|
||||
|
||||
def test_tone_affects_output(self, pipeline):
|
||||
"""Different tone values should produce different output."""
|
||||
_load_fx(pipeline, FXType.OVERDRIVE,
|
||||
{"subtype": "bd2", "drive": 0.5, "tone": 0.0, "gain": 1.0})
|
||||
out_a = pipeline.process(SINE_TONE * 0.5)
|
||||
|
||||
pipeline.load_preset(Preset(
|
||||
name="test", chain=[FXBlock(FXType.OVERDRIVE, enabled=True,
|
||||
params={"subtype": "bd2", "drive": 0.5, "tone": 1.0, "gain": 1.0})],
|
||||
master_volume=1.0,
|
||||
))
|
||||
out_b = pipeline.process(SINE_TONE * 0.5)
|
||||
assert not np.allclose(out_a, out_b, atol=0.01)
|
||||
|
||||
def test_low_drive_passthrough(self, pipeline):
|
||||
"""Low drive should pass signal."""
|
||||
_load_fx(pipeline, FXType.OVERDRIVE,
|
||||
{"subtype": "bd2", "drive": 0.0, "gain": 1.0})
|
||||
out = pipeline.process(SINE_TONE * 0.05)
|
||||
assert np.max(np.abs(out)) > 0.0
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════
|
||||
# Drive Subtype: Big Muff Pi
|
||||
# ═══════════════════════════════════════════════════════════════════
|
||||
|
||||
class TestBigMuff:
|
||||
"""Big Muff Pi-style fuzz with tone stack."""
|
||||
|
||||
def test_output_clamped(self, pipeline):
|
||||
"""Muff output should stay within [-1, 1]."""
|
||||
_load_fx(pipeline, FXType.FUZZ,
|
||||
{"subtype": "muff", "sustain": 0.8, "volume": 0.5})
|
||||
out = pipeline.process(FULL_SCALE)
|
||||
assert np.max(out) <= 1.0 and np.min(out) >= -1.0
|
||||
|
||||
def test_fuzz_shapes_waveform(self, pipeline):
|
||||
"""Muff should significantly reshape waveform (fuzz)."""
|
||||
_load_fx(pipeline, FXType.FUZZ,
|
||||
{"subtype": "muff", "sustain": 1.0, "volume": 0.5})
|
||||
out = pipeline.process(SINE_TONE * 0.8)
|
||||
assert not np.allclose(out, SINE_TONE * 0.8, atol=0.05)
|
||||
rms_out = np.sqrt(np.mean(out ** 2))
|
||||
assert rms_out > 0.2, "Muff should produce significant output"
|
||||
|
||||
def test_tone_sweep(self, pipeline):
|
||||
"""Different tone positions should produce different EQ."""
|
||||
_load_fx(pipeline, FXType.FUZZ,
|
||||
{"subtype": "muff", "sustain": 0.5, "tone": 0.0, "volume": 0.5})
|
||||
out_a = pipeline.process(SINE_TONE)
|
||||
|
||||
pipeline.load_preset(Preset(
|
||||
name="test", chain=[FXBlock(FXType.FUZZ, enabled=True,
|
||||
params={"subtype": "muff", "sustain": 0.5, "tone": 1.0, "volume": 0.5})],
|
||||
master_volume=1.0,
|
||||
))
|
||||
out_b = pipeline.process(SINE_TONE)
|
||||
assert not np.allclose(out_a, out_b, atol=0.01)
|
||||
|
||||
def test_silence_muted(self, pipeline):
|
||||
"""Silence in should give silence out."""
|
||||
_load_fx(pipeline, FXType.FUZZ,
|
||||
{"subtype": "muff", "sustain": 0.5, "volume": 0.5})
|
||||
out = pipeline.process(SILENCE)
|
||||
assert np.max(np.abs(out)) == 0.0
|
||||
|
||||
def test_default_subtype_fuzz(self, pipeline):
|
||||
"""No subtype on FUZZ should use original fuzz algorithm."""
|
||||
_load_fx(pipeline, FXType.FUZZ, {"drive": 0.5, "gain": 0.5})
|
||||
out = pipeline.process(SINE_TONE * 0.8)
|
||||
assert np.max(out) <= 1.0 and np.min(out) >= -1.0
|
||||
Reference in New Issue
Block a user