From a77db557f3ab8ccb072ff34a42d8e8622d943551 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Wed, 25 Mar 2026 21:48:31 -0400 Subject: [PATCH] =?UTF-8?q?Apply=20swing=20to=20drum=20hits=20=E2=80=94=20?= =?UTF-8?q?offbeats=20shift=20with=20the=20groove?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Drum hits on fractional beat positions now get pushed later by the score's swing amount. Everything locks into the same pocket. Co-Authored-By: Claude Opus 4.6 (1M context) --- pytheory/play.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/pytheory/play.py b/pytheory/play.py index 4a00687..ff7c80b 100644 --- a/pytheory/play.py +++ b/pytheory/play.py @@ -1614,12 +1614,20 @@ def render_score(score): # Drum hits — render to separate buffer for sidechain trigger drum_buf = numpy.zeros(total_samples, dtype=numpy.float32) + drum_swing = score.swing for hit in score._drum_hits: + pos = hit.position + # Apply swing: hits on offbeats get pushed later + if drum_swing > 0: + beat_frac = pos % 1.0 + # Offbeat = not on a downbeat (0.0) — shift 8th note upbeats + if 0.1 < beat_frac < 0.9: + pos += drum_swing * 0.15 # subtle swing on drum hits if has_tempo_changes: - start = _beat_to_sample(hit.position, tempo_map) + start = _beat_to_sample(pos, tempo_map) else: - start = int(hit.position * samples_per_beat) - if start >= total_samples: + start = int(pos * samples_per_beat) + if start >= total_samples or start < 0: continue remaining = total_samples - start hit_len = min(int(SAMPLE_RATE * 0.5), remaining)