
import unittest
import numpy as np
from engine.processor import AudioEngine

class TestDeEsser(unittest.TestCase):
    def setUp(self):
        self.engine = AudioEngine()
        self.engine.sample_rate = 44100

    def test_deesser_reduces_sibilance(self):
        """Test that de-esser reduces volume of a 6kHz sine wave (sibilance)."""
        sr = 44100
        duration = 1.0
        t = np.linspace(0, duration, int(sr * duration), endpoint=False)
        
        # Create a loud 6kHz sine wave (sibilance)
        # 0 dBFS peak
        frequency = 6000
        audio = np.sin(2 * np.pi * frequency * t)
        
        # Apply de-esser
        # Threshold -20dB, Ratio 4:1
        processed = self.engine.apply_deesser(
            audio, 
            threshold_db=-20.0, 
            ratio=4.0, 
            frequency_hz=6000.0,
            bandwidth_hz=2000.0
        )
        
        # Calculate RMS of original and processed
        rms_orig = np.sqrt(np.mean(audio**2))
        rms_proc = np.sqrt(np.mean(processed**2))
        
        print(f"Original RMS: {rms_orig:.4f}")
        print(f"Processed RMS: {rms_proc:.4f}")
        
        # Expect processed to be quieter
        self.assertLess(rms_proc, rms_orig)
        
        # Calculate expected reduction roughly
        # Input level is roughly -3dB RMS (sine wave peak 1.0)
        # Threshold is -20dB.
        # It should compress significantly.

    def test_deesser_preserves_lows(self):
        """Test that de-esser does NOT affect a 200Hz sine wave."""
        sr = 44100
        duration = 1.0
        t = np.linspace(0, duration, int(sr * duration), endpoint=False)
        
        # Create a loud 200Hz sine wave (bass)
        frequency = 200
        audio = np.sin(2 * np.pi * frequency * t)
        
        processed = self.engine.apply_deesser(
            audio, 
            threshold_db=-20.0, 
            ratio=4.0, 
            frequency_hz=6000.0,
            bandwidth_hz=2000.0
        )
        
        rms_orig = np.sqrt(np.mean(audio**2))
        rms_proc = np.sqrt(np.mean(processed**2))
        
        print(f"Original Lows RMS: {rms_orig:.4f}")
        print(f"Processed Lows RMS: {rms_proc:.4f}")
        
        # Should be very close (allow small difference due to filter slopes)
        self.assertAlmostEqual(rms_proc, rms_orig, places=2)

    def test_stereo_preservation(self):
        """Test that stereo channels are processed."""
        sr = 44100
        t = np.linspace(0, 0.1, int(sr * 0.1), endpoint=False)
        
        # Left: 6kHz (should compress)
        # Right: 200Hz (should preserve)
        left = np.sin(2 * np.pi * 6000 * t)
        right = np.sin(2 * np.pi * 200 * t)
        
        stereo = np.column_stack((left, right))
        
        processed = self.engine.apply_deesser(
            stereo,
            threshold_db=-20.0
        )
        
        # Check shapes
        self.assertEqual(processed.shape, stereo.shape)
        
        # Check Left compressed
        rms_left_in = np.sqrt(np.mean(left**2))
        rms_left_out = np.sqrt(np.mean(processed[:, 0]**2))
        self.assertLess(rms_left_out, rms_left_in)
        
        # Check Right preserved
        rms_right_in = np.sqrt(np.mean(right**2))
        rms_right_out = np.sqrt(np.mean(processed[:, 1]**2))
        self.assertAlmostEqual(rms_right_out, rms_right_in, places=2)

if __name__ == '__main__':
    unittest.main()
