diff --git a/pytheory/play.py b/pytheory/play.py index e563bbf..5a78203 100644 --- a/pytheory/play.py +++ b/pytheory/play.py @@ -1989,6 +1989,13 @@ def render_score(score): panned = _pan_to_stereo(mono_hit, pan) drum_stereo[start:start + hit_len] += panned + # Apply drum bus effects (reverb, delay, etc.) to the stereo drum mix + if score.drum_effects: + fx = score.drum_effects + # Apply to each stereo channel using the same effects engine + for ch in range(2): + drum_stereo[:, ch] = _apply_effects_with_params(drum_stereo[:, ch], fx) + # Apply sidechain compression to parts that request it for part, part_buf in _pending_sidechain: part_buf = _apply_sidechain( diff --git a/pytheory/rhythm.py b/pytheory/rhythm.py index 8a4c467..fccf7ca 100644 --- a/pytheory/rhythm.py +++ b/pytheory/rhythm.py @@ -1784,6 +1784,7 @@ class Score: self.bpm = bpm self.swing = swing self._drum_humanize = drum_humanize + self.drum_effects: dict = {} self.notes: list[Note] = [] self.parts: dict[str, Part] = {} self._drum_hits: list[_Hit] = [] @@ -1904,6 +1905,29 @@ class Score: fill_pattern = Pattern.fill(name) return self.add_pattern(fill_pattern, repeats=1) + def set_drum_effects(self, **kwargs) -> "Score": + """Set effects on the drum bus. + + Uses the same parameters as Part effects — reverb, delay, + lowpass, distortion, chorus. Applied to the entire drum mix + before stereo panning. + + Example:: + + score.set_drum_effects(reverb=0.2, reverb_type="plate", + lowpass=8000, distortion=0.1) + + Returns: + Self for chaining. + """ + # Map shorthand names + param_map = {"reverb": "reverb_mix", "delay": "delay_mix", + "distortion": "distortion_mix", "chorus": "chorus_mix"} + for k, v in kwargs.items(): + key = param_map.get(k, k) + self.drum_effects[key] = v + return self + def drums(self, preset: str, repeats: int = 4, fill: str = None, fill_every: int = None) -> "Score": """Add a drum pattern by preset name, with optional auto-fills.