102 lines
3.1 KiB
Python
102 lines
3.1 KiB
Python
|
|
"""Test SoundBuffer DSP effects."""
|
||
|
|
import mcrfpy
|
||
|
|
import sys
|
||
|
|
|
||
|
|
# Create a test buffer: 0.5s 440Hz sine
|
||
|
|
src = mcrfpy.SoundBuffer.tone(440, 0.5, "sine")
|
||
|
|
|
||
|
|
# Test 1: pitch_shift
|
||
|
|
higher = src.pitch_shift(2.0)
|
||
|
|
assert higher is not None
|
||
|
|
assert higher.sample_count > 0
|
||
|
|
# Higher pitch = shorter duration
|
||
|
|
assert higher.duration < src.duration, f"pitch_shift(2.0) should be shorter: {higher.duration} vs {src.duration}"
|
||
|
|
print(f"PASS: pitch_shift(2.0) -> {higher.duration:.3f}s (was {src.duration:.3f}s)")
|
||
|
|
|
||
|
|
lower = src.pitch_shift(0.5)
|
||
|
|
assert lower.duration > src.duration, f"pitch_shift(0.5) should be longer: {lower.duration} vs {src.duration}"
|
||
|
|
print(f"PASS: pitch_shift(0.5) -> {lower.duration:.3f}s")
|
||
|
|
|
||
|
|
# Test 2: low_pass
|
||
|
|
lp = src.low_pass(500.0)
|
||
|
|
assert lp is not None
|
||
|
|
assert lp.sample_count == src.sample_count
|
||
|
|
assert lp.duration == src.duration
|
||
|
|
print("PASS: low_pass preserves sample count and duration")
|
||
|
|
|
||
|
|
# Test 3: high_pass
|
||
|
|
hp = src.high_pass(500.0)
|
||
|
|
assert hp is not None
|
||
|
|
assert hp.sample_count == src.sample_count
|
||
|
|
print("PASS: high_pass preserves sample count")
|
||
|
|
|
||
|
|
# Test 4: echo
|
||
|
|
echoed = src.echo(200.0, 0.4, 0.5)
|
||
|
|
assert echoed is not None
|
||
|
|
assert echoed.sample_count == src.sample_count # same length
|
||
|
|
print("PASS: echo works")
|
||
|
|
|
||
|
|
# Test 5: reverb
|
||
|
|
reverbed = src.reverb(0.8, 0.5, 0.3)
|
||
|
|
assert reverbed is not None
|
||
|
|
assert reverbed.sample_count == src.sample_count
|
||
|
|
print("PASS: reverb works")
|
||
|
|
|
||
|
|
# Test 6: distortion
|
||
|
|
dist = src.distortion(2.0)
|
||
|
|
assert dist is not None
|
||
|
|
assert dist.sample_count == src.sample_count
|
||
|
|
print("PASS: distortion works")
|
||
|
|
|
||
|
|
# Test 7: bit_crush
|
||
|
|
crushed = src.bit_crush(8, 4)
|
||
|
|
assert crushed is not None
|
||
|
|
assert crushed.sample_count == src.sample_count
|
||
|
|
print("PASS: bit_crush works")
|
||
|
|
|
||
|
|
# Test 8: normalize
|
||
|
|
normed = src.normalize()
|
||
|
|
assert normed is not None
|
||
|
|
assert normed.sample_count == src.sample_count
|
||
|
|
print("PASS: normalize works")
|
||
|
|
|
||
|
|
# Test 9: reverse
|
||
|
|
rev = src.reverse()
|
||
|
|
assert rev is not None
|
||
|
|
assert rev.sample_count == src.sample_count
|
||
|
|
print("PASS: reverse preserves sample count")
|
||
|
|
|
||
|
|
# Test 10: slice
|
||
|
|
sliced = src.slice(0.1, 0.3)
|
||
|
|
assert sliced is not None
|
||
|
|
expected_duration = 0.2
|
||
|
|
assert abs(sliced.duration - expected_duration) < 0.02, f"Expected ~{expected_duration}s, got {sliced.duration}s"
|
||
|
|
print(f"PASS: slice(0.1, 0.3) -> {sliced.duration:.3f}s")
|
||
|
|
|
||
|
|
# Test 11: slice out of bounds is safe
|
||
|
|
empty = src.slice(0.5, 0.5) # zero-length
|
||
|
|
assert empty.sample_count == 0
|
||
|
|
print("PASS: slice with start==end returns empty")
|
||
|
|
|
||
|
|
# Test 12: Chaining effects (effects return new buffers)
|
||
|
|
chained = src.low_pass(1000).distortion(1.5).normalize()
|
||
|
|
assert chained is not None
|
||
|
|
assert chained.sample_count > 0
|
||
|
|
print("PASS: Chaining effects works")
|
||
|
|
|
||
|
|
# Test 13: Effects don't modify original
|
||
|
|
orig_count = src.sample_count
|
||
|
|
src.pitch_shift(2.0)
|
||
|
|
assert src.sample_count == orig_count, "Original should not be modified"
|
||
|
|
print("PASS: Effects don't modify original buffer")
|
||
|
|
|
||
|
|
# Test 14: pitch_shift with invalid factor raises ValueError
|
||
|
|
try:
|
||
|
|
src.pitch_shift(-1.0)
|
||
|
|
assert False, "Should have raised ValueError"
|
||
|
|
except ValueError:
|
||
|
|
pass
|
||
|
|
print("PASS: pitch_shift with negative factor raises ValueError")
|
||
|
|
|
||
|
|
print("\nAll soundbuffer_effects tests passed!")
|
||
|
|
sys.exit(0)
|