102 lines
3.5 KiB
Python
102 lines
3.5 KiB
Python
from __future__ import annotations
|
|
|
|
import struct
|
|
import unittest
|
|
|
|
from zyrarbot.audio.effects import (
|
|
AudioEffectsPipeline,
|
|
DistortionEffect,
|
|
FourthDensityEffect,
|
|
MuffledEffect,
|
|
RobotEffect,
|
|
ToasterEffect,
|
|
TremoloEffect,
|
|
create_default_effects,
|
|
)
|
|
|
|
|
|
class TestAudioEffects(unittest.TestCase):
|
|
def test_distortion_clips_pcm(self):
|
|
fx = DistortionEffect(multiplier=10.0)
|
|
pcm = struct.pack("<hh", 10000, -10000)
|
|
out = fx.process(pcm)
|
|
left, right = struct.unpack("<hh", out)
|
|
self.assertEqual(left, 32767)
|
|
self.assertEqual(right, -32768)
|
|
|
|
def test_muffled_filters_pcm(self):
|
|
fx = MuffledEffect(alpha=0.1)
|
|
# Step input
|
|
pcm = struct.pack("<hh", 10000, 10000)
|
|
out = fx.process(pcm)
|
|
left, right = struct.unpack("<hh", out)
|
|
# With alpha=0.1, output sample should be smoothed to 1000
|
|
self.assertEqual(left, 1000)
|
|
self.assertEqual(right, 1000)
|
|
|
|
def test_tremolo_modulates_amplitude(self):
|
|
fx = TremoloEffect(freq_hz=4.0, depth=0.6)
|
|
pcm = struct.pack("<hh", 10000, 10000)
|
|
out = fx.process(pcm)
|
|
left, right = struct.unpack("<hh", out)
|
|
# At phase 0, sin(0) = 0 -> lfo = 0.5 -> factor = 1.0 - 0.6*0.5 = 0.7
|
|
self.assertAlmostEqual(left, 7000, delta=100)
|
|
self.assertAlmostEqual(right, 7000, delta=100)
|
|
|
|
def test_fourth_density_processes_without_error(self):
|
|
fx = FourthDensityEffect()
|
|
pcm = (struct.pack("<hh", 1000, -1000)) * 960
|
|
out = fx.process(pcm)
|
|
self.assertEqual(len(out), len(pcm))
|
|
|
|
def test_robot_modulates_ring(self):
|
|
fx = RobotEffect(freq_hz=65.0, mix=0.85)
|
|
pcm = (struct.pack("<hh", 10000, 10000)) * 960
|
|
out = fx.process(pcm)
|
|
self.assertEqual(len(out), len(pcm))
|
|
self.assertNotEqual(out, pcm)
|
|
|
|
def test_toaster_bitcrush_pcm(self):
|
|
fx = ToasterEffect(bit_depth=5, downsample=6)
|
|
pcm = struct.pack("<hh", 12345, -23456)
|
|
out = fx.process(pcm)
|
|
left, right = struct.unpack("<hh", out)
|
|
# Low 11 bits are zeroed due to 5-bit quantization
|
|
self.assertEqual(left & 0x07FF, 0)
|
|
self.assertEqual(right & 0x07FF, 0)
|
|
|
|
def test_pipeline_chains_multiple_effects_and_applies_volume(self):
|
|
pipeline = AudioEffectsPipeline(create_default_effects())
|
|
pcm = (struct.pack("<hh", 1000, -1000)) * 960
|
|
|
|
# When no effects active and volume=1.0, data is unchanged
|
|
out = pipeline.process(pcm, volume=1.0)
|
|
self.assertEqual(out, pcm)
|
|
|
|
# Activate fourth_density and distortion simultaneously
|
|
pipeline.set_active("fourth_density", True)
|
|
pipeline.set_active("distortion", True)
|
|
self.assertTrue(pipeline.is_active("fourth_density"))
|
|
self.assertTrue(pipeline.is_active("distortion"))
|
|
|
|
out_mult = pipeline.process(pcm, volume=0.5)
|
|
self.assertEqual(len(out_mult), len(pcm))
|
|
self.assertNotEqual(out_mult, pcm)
|
|
|
|
# Toggle off
|
|
toggled = pipeline.toggle("distortion")
|
|
self.assertFalse(toggled)
|
|
self.assertFalse(pipeline.is_active("distortion"))
|
|
|
|
def test_effects_metadata_structure(self):
|
|
from zyrarbot.audio.effects.registry import get_available_effects_metadata
|
|
|
|
metadata = get_available_effects_metadata()
|
|
self.assertGreater(len(metadata), 0)
|
|
for entry in metadata:
|
|
self.assertEqual(set(entry.keys()), {"id", "name", "icon"})
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|