| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950 |
- import numpy as np
- class Compressor:
- """
- A fast, block-based dynamic range compressor designed to run in real-time.
- Perfect for movie soundtracks to reduce dynamic range (makes quiet dialog louder, clamps loud explosions).
- """
- def __init__(self):
- self.enabled = False
- self.threshold_db = -20.0
- self.ratio = 4.0
- self.makeup_gain_db = 6.0
-
- # Smoothing factors for chunk-based envelope (0.0 to 1.0)
- self.attack_factor = 0.8 # Fast attack to catch peaks
- self.release_factor = 0.05 # Slower release for smooth pumping
-
- self._current_gain_db = 0.0
-
- def process(self, chunk: np.ndarray) -> np.ndarray:
- if not self.enabled or chunk.size == 0:
- return chunk
-
- # Calculate RMS of the entire chunk
- # Using peak detection or RMS. RMS is better for overall loudness, peak is better for hard limiting.
- # Movie dialogue is better managed with RMS.
- rms = np.sqrt(np.mean(chunk**2))
- rms_db = 20 * np.log10(max(rms, 1e-6))
-
- # Calculate target gain reduction
- if rms_db > self.threshold_db:
- overshoot = rms_db - self.threshold_db
- target_gain_db = -overshoot * (1.0 - 1.0 / self.ratio)
- else:
- target_gain_db = 0.0
-
- # Smooth the gain reduction to prevent clicks
- if target_gain_db < self._current_gain_db:
- # Gain is going down (compressing harder) -> Attack
- self._current_gain_db += (target_gain_db - self._current_gain_db) * self.attack_factor
- else:
- # Gain is going up (recovering) -> Release
- self._current_gain_db += (target_gain_db - self._current_gain_db) * self.release_factor
-
- # Combine with makeup gain and convert to linear scale
- total_gain_db = self._current_gain_db + self.makeup_gain_db
- linear_gain = 10 ** (total_gain_db / 20.0)
-
- # Multiply chunk (numpy vectorized)
- return chunk * linear_gain
|