compressor.py 2.0 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950
  1. import numpy as np
  2. class Compressor:
  3. """
  4. A fast, block-based dynamic range compressor designed to run in real-time.
  5. Perfect for movie soundtracks to reduce dynamic range (makes quiet dialog louder, clamps loud explosions).
  6. """
  7. def __init__(self):
  8. self.enabled = False
  9. self.threshold_db = -20.0
  10. self.ratio = 4.0
  11. self.makeup_gain_db = 6.0
  12. # Smoothing factors for chunk-based envelope (0.0 to 1.0)
  13. self.attack_factor = 0.8 # Fast attack to catch peaks
  14. self.release_factor = 0.05 # Slower release for smooth pumping
  15. self._current_gain_db = 0.0
  16. def process(self, chunk: np.ndarray) -> np.ndarray:
  17. if not self.enabled or chunk.size == 0:
  18. return chunk
  19. # Calculate RMS of the entire chunk
  20. # Using peak detection or RMS. RMS is better for overall loudness, peak is better for hard limiting.
  21. # Movie dialogue is better managed with RMS.
  22. rms = np.sqrt(np.mean(chunk**2))
  23. rms_db = 20 * np.log10(max(rms, 1e-6))
  24. # Calculate target gain reduction
  25. if rms_db > self.threshold_db:
  26. overshoot = rms_db - self.threshold_db
  27. target_gain_db = -overshoot * (1.0 - 1.0 / self.ratio)
  28. else:
  29. target_gain_db = 0.0
  30. # Smooth the gain reduction to prevent clicks
  31. if target_gain_db < self._current_gain_db:
  32. # Gain is going down (compressing harder) -> Attack
  33. self._current_gain_db += (target_gain_db - self._current_gain_db) * self.attack_factor
  34. else:
  35. # Gain is going up (recovering) -> Release
  36. self._current_gain_db += (target_gain_db - self._current_gain_db) * self.release_factor
  37. # Combine with makeup gain and convert to linear scale
  38. total_gain_db = self._current_gain_db + self.makeup_gain_db
  39. linear_gain = 10 ** (total_gain_db / 20.0)
  40. # Multiply chunk (numpy vectorized)
  41. return chunk * linear_gain