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