mirror of
https://github.com/kennethreitz/pytheory.git
synced 2026-06-05 14:50:18 +00:00
Marching snare, ensemble, flam/diddle/cheese, resonance buildup
- 3 marching percussion sounds: snare, rimshot, stick click - 4 marching patterns: march, cadence, paradiddle, roll - Part.flam(), Part.diddle(), Part.cheese() rudiment methods - Part ensemble= parameter: duplicate voices with per-player timing tendencies and micro pitch drift (works on any Part) - Sympathetic resonance: marching snare buzz builds up with repeated hits - Song #32: Snare Cadence (16 bars with triplets, 32nds, flams, cheese) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
+221
-1
@@ -2440,6 +2440,225 @@ def acid_tabla():
|
||||
play_song(score, "Acid Tabla — 303 filter automation + tabla (ramp, articulations, Part.hit)")
|
||||
|
||||
|
||||
def snare_cadence():
|
||||
"""Snare Cadence — marching snare with click count-off, flams, diddles, cheese."""
|
||||
score = Score("4/4", bpm=120)
|
||||
p = score.part("snare", synth="sine", volume=0.8, reverb=0.2)
|
||||
|
||||
S = DrumSound.MARCH_SNARE
|
||||
R = DrumSound.MARCH_RIMSHOT
|
||||
C = DrumSound.MARCH_CLICK
|
||||
|
||||
# ── Click count-off ──
|
||||
for _ in range(4):
|
||||
p.hit(C, Duration.QUARTER, velocity=95)
|
||||
|
||||
_trip = 1.0 / 3 # triplet 8th
|
||||
|
||||
# ── Section 1: 16th note groove (4 bars) ──
|
||||
# 16ths are the baseline — accents give it shape
|
||||
for _ in range(2):
|
||||
p.hit(R, Duration.SIXTEENTH, velocity=115)
|
||||
p.hit(S, Duration.SIXTEENTH, velocity=32)
|
||||
p.hit(S, Duration.SIXTEENTH, velocity=35)
|
||||
p.hit(S, Duration.SIXTEENTH, velocity=30)
|
||||
p.hit(R, Duration.SIXTEENTH, velocity=112)
|
||||
p.hit(S, Duration.SIXTEENTH, velocity=30)
|
||||
p.hit(S, Duration.SIXTEENTH, velocity=32)
|
||||
p.hit(S, Duration.SIXTEENTH, velocity=28)
|
||||
p.hit(R, Duration.SIXTEENTH, velocity=115)
|
||||
p.hit(S, Duration.SIXTEENTH, velocity=35)
|
||||
p.hit(S, Duration.SIXTEENTH, velocity=30)
|
||||
p.hit(S, Duration.SIXTEENTH, velocity=32)
|
||||
p.hit(R, Duration.SIXTEENTH, velocity=118)
|
||||
p.hit(S, Duration.SIXTEENTH, velocity=30)
|
||||
p.hit(S, Duration.SIXTEENTH, velocity=28)
|
||||
p.hit(S, Duration.SIXTEENTH, velocity=32)
|
||||
|
||||
# 2 bars with triplets mixed in
|
||||
for _ in range(2):
|
||||
p.hit(R, _trip, velocity=115)
|
||||
p.hit(S, _trip, velocity=32)
|
||||
p.hit(S, _trip, velocity=30)
|
||||
p.hit(R, _trip, velocity=112)
|
||||
p.hit(S, _trip, velocity=28)
|
||||
p.hit(S, _trip, velocity=32)
|
||||
p.hit(R, Duration.SIXTEENTH, velocity=118)
|
||||
p.hit(S, Duration.SIXTEENTH, velocity=30)
|
||||
p.hit(S, Duration.SIXTEENTH, velocity=32)
|
||||
p.hit(S, Duration.SIXTEENTH, velocity=28)
|
||||
p.hit(R, _trip, velocity=115)
|
||||
p.hit(S, _trip, velocity=30)
|
||||
p.hit(S, _trip, velocity=35)
|
||||
|
||||
# ── Section 2: Add flams + triplets (4 bars) ──
|
||||
for _ in range(2):
|
||||
p.flam(S, Duration.QUARTER, velocity=118)
|
||||
p.hit(S, Duration.SIXTEENTH, velocity=30)
|
||||
p.hit(S, Duration.SIXTEENTH, velocity=32)
|
||||
p.hit(R, _trip, velocity=115)
|
||||
p.hit(S, _trip, velocity=28)
|
||||
p.hit(S, _trip, velocity=30)
|
||||
p.flam(S, Duration.QUARTER, velocity=115)
|
||||
|
||||
for _ in range(2):
|
||||
p.hit(S, _trip, velocity=35)
|
||||
p.flam(S, _trip * 2, velocity=118)
|
||||
p.hit(S, Duration.SIXTEENTH, velocity=30)
|
||||
p.hit(S, Duration.SIXTEENTH, velocity=32)
|
||||
p.flam(S, Duration.QUARTER, velocity=115)
|
||||
p.hit(S, _trip, velocity=28)
|
||||
p.hit(R, _trip, velocity=120)
|
||||
p.hit(S, _trip, velocity=35)
|
||||
|
||||
# ── Section 3: Flams + diddles + triplets (4 bars) ──
|
||||
for _ in range(2):
|
||||
p.flam(S, Duration.QUARTER, velocity=118)
|
||||
p.diddle(S, Duration.EIGHTH, velocity=45)
|
||||
p.hit(S, _trip, velocity=30)
|
||||
p.hit(S, _trip, velocity=32)
|
||||
p.hit(S, _trip, velocity=28)
|
||||
p.hit(R, Duration.EIGHTH, velocity=120)
|
||||
p.diddle(S, Duration.EIGHTH, velocity=42)
|
||||
|
||||
for _ in range(2):
|
||||
p.diddle(S, Duration.EIGHTH, velocity=45)
|
||||
p.hit(R, _trip, velocity=118)
|
||||
p.hit(S, _trip, velocity=30)
|
||||
p.hit(S, _trip, velocity=32)
|
||||
p.diddle(S, Duration.EIGHTH, velocity=48)
|
||||
p.hit(R, _trip, velocity=115)
|
||||
p.hit(S, _trip, velocity=28)
|
||||
p.hit(S, _trip, velocity=30)
|
||||
p.flam(S, Duration.EIGHTH, velocity=120)
|
||||
p.hit(S, Duration.EIGHTH, velocity=35)
|
||||
|
||||
# ── Section 4: Syncopation + triplet accents (4 bars) ──
|
||||
for _ in range(2):
|
||||
p.hit(R, Duration.SIXTEENTH, velocity=118)
|
||||
p.hit(S, Duration.SIXTEENTH, velocity=30)
|
||||
p.rest(Duration.SIXTEENTH)
|
||||
p.hit(S, Duration.SIXTEENTH, velocity=32)
|
||||
p.flam(S, Duration.EIGHTH, velocity=118)
|
||||
p.hit(S, _trip, velocity=28)
|
||||
p.hit(R, _trip, velocity=115)
|
||||
p.hit(S, _trip, velocity=30)
|
||||
p.hit(R, Duration.SIXTEENTH, velocity=118)
|
||||
p.hit(S, Duration.SIXTEENTH, velocity=32)
|
||||
p.diddle(S, Duration.EIGHTH, velocity=45)
|
||||
p.hit(R, Duration.EIGHTH, velocity=122)
|
||||
|
||||
# Accent pattern with space
|
||||
p.hit(R, Duration.EIGHTH, velocity=122)
|
||||
p.rest(Duration.EIGHTH)
|
||||
p.hit(R, _trip, velocity=118)
|
||||
p.hit(R, _trip, velocity=115)
|
||||
p.hit(R, _trip, velocity=120)
|
||||
p.rest(Duration.QUARTER)
|
||||
p.hit(R, Duration.EIGHTH, velocity=122)
|
||||
p.rest(Duration.EIGHTH)
|
||||
p.hit(R, Duration.SIXTEENTH, velocity=118)
|
||||
p.hit(R, Duration.SIXTEENTH, velocity=125)
|
||||
|
||||
p.hit(R, Duration.QUARTER, velocity=125)
|
||||
p.rest(Duration.QUARTER)
|
||||
p.hit(R, Duration.QUARTER, velocity=125)
|
||||
p.rest(Duration.EIGHTH)
|
||||
p.diddle(S, Duration.EIGHTH, velocity=52)
|
||||
|
||||
# ── Section 5: Cheese + 32nd bursts (4 bars) ──
|
||||
for _ in range(2):
|
||||
p.cheese(S, Duration.QUARTER, velocity=118)
|
||||
p.hit(S, 0.0625, velocity=30)
|
||||
p.hit(S, 0.0625, velocity=32)
|
||||
p.hit(S, 0.0625, velocity=35)
|
||||
p.hit(S, 0.0625, velocity=30)
|
||||
p.cheese(S, Duration.QUARTER, velocity=115)
|
||||
p.diddle(S, Duration.EIGHTH, velocity=48)
|
||||
p.hit(R, Duration.EIGHTH, velocity=122)
|
||||
|
||||
p.cheese(S, Duration.QUARTER, velocity=120)
|
||||
p.cheese(S, Duration.QUARTER, velocity=118)
|
||||
p.cheese(S, Duration.QUARTER, velocity=122)
|
||||
p.cheese(S, Duration.QUARTER, velocity=125)
|
||||
|
||||
p.flam(S, Duration.EIGHTH, velocity=118)
|
||||
p.diddle(S, Duration.EIGHTH, velocity=50)
|
||||
p.flam(S, Duration.EIGHTH, velocity=120)
|
||||
p.diddle(S, Duration.EIGHTH, velocity=52)
|
||||
p.flam(S, Duration.EIGHTH, velocity=122)
|
||||
p.diddle(S, Duration.EIGHTH, velocity=55)
|
||||
p.hit(R, Duration.EIGHTH, velocity=125)
|
||||
p.hit(S, Duration.EIGHTH, velocity=38)
|
||||
|
||||
# ── Section 6: 16ths with 32nd bursts (4 bars) ──
|
||||
# 16ths with accents, 32nd doubles sprinkled in
|
||||
for _ in range(2):
|
||||
p.hit(R, Duration.SIXTEENTH, velocity=118)
|
||||
p.hit(S, 0.0625, velocity=32) # 32nd
|
||||
p.hit(S, 0.0625, velocity=35) # 32nd
|
||||
p.hit(S, Duration.SIXTEENTH, velocity=30)
|
||||
p.hit(S, Duration.SIXTEENTH, velocity=38)
|
||||
p.hit(R, Duration.SIXTEENTH, velocity=115)
|
||||
p.hit(S, 0.0625, velocity=32)
|
||||
p.hit(S, 0.0625, velocity=30)
|
||||
p.hit(S, 0.0625, velocity=35)
|
||||
p.hit(S, 0.0625, velocity=32)
|
||||
p.hit(R, Duration.SIXTEENTH, velocity=118)
|
||||
p.hit(S, Duration.SIXTEENTH, velocity=35)
|
||||
p.hit(S, 0.0625, velocity=28)
|
||||
p.hit(S, 0.0625, velocity=32)
|
||||
p.hit(S, 0.0625, velocity=35)
|
||||
p.hit(S, 0.0625, velocity=38)
|
||||
p.hit(R, Duration.SIXTEENTH, velocity=112)
|
||||
p.hit(S, Duration.SIXTEENTH, velocity=30)
|
||||
|
||||
# Triplet bars — 12 hits per beat, accent every 3
|
||||
_trip = 1.0 / 3 # triplet 8th
|
||||
for _ in range(2):
|
||||
for beat in range(4):
|
||||
p.hit(R, _trip, velocity=115)
|
||||
p.hit(S, _trip, velocity=35)
|
||||
p.hit(S, _trip, velocity=32)
|
||||
|
||||
# 32nd note run crescendo into rimshot
|
||||
for i in range(32):
|
||||
p.hit(S, 0.0625, velocity=min(22 + i * 3, 92))
|
||||
p.hit(R, Duration.EIGHTH, velocity=122)
|
||||
p.hit(R, Duration.EIGHTH, velocity=125)
|
||||
|
||||
# Triplet 16ths — 6 per beat, insane
|
||||
_trip16 = 1.0 / 6
|
||||
for _ in range(2):
|
||||
for beat in range(4):
|
||||
p.hit(R, _trip16, velocity=112)
|
||||
p.hit(S, _trip16, velocity=30)
|
||||
p.hit(S, _trip16, velocity=32)
|
||||
p.hit(R, _trip16, velocity=108)
|
||||
p.hit(S, _trip16, velocity=28)
|
||||
p.hit(S, _trip16, velocity=30)
|
||||
|
||||
# ── Section 7: Full send (2 bars) ──
|
||||
# 32nd notes building into the tightest buzz roll
|
||||
for i in range(64):
|
||||
p.hit(S, 0.0625, velocity=min(20 + i * 1.5, 100))
|
||||
p.hit(R, Duration.EIGHTH, velocity=125)
|
||||
p.hit(R, Duration.EIGHTH, velocity=127)
|
||||
|
||||
# ── Ending: big unison hits ──
|
||||
p.hit(R, Duration.EIGHTH, velocity=125)
|
||||
p.rest(Duration.QUARTER + Duration.EIGHTH)
|
||||
p.hit(R, Duration.EIGHTH, velocity=125)
|
||||
p.rest(Duration.QUARTER + Duration.EIGHTH)
|
||||
# Flam into final CRACK
|
||||
p.flam(S, Duration.EIGHTH, velocity=127)
|
||||
p.rest(Duration.QUARTER + Duration.EIGHTH)
|
||||
p.hit(R, Duration.QUARTER, velocity=127)
|
||||
p.rest(Duration.HALF)
|
||||
|
||||
play_song(score, "Snare Cadence — marching snare (flams, diddles, cheese, resonance)")
|
||||
|
||||
|
||||
SONGS = {
|
||||
"1": ("Bossa Nova in A minor", bossa_nova_girl),
|
||||
"2": ("Bebop in Bb major", bebop_in_bb),
|
||||
@@ -2472,6 +2691,7 @@ SONGS = {
|
||||
"29": ("Pop Rock (I-V-vi-IV)", pop_rock),
|
||||
"30": ("Sitar Drone (Bhairav, hold() polyphony)", sitar_drone),
|
||||
"31": ("Acid Tabla (303 + tabla, ramp, articulations)", acid_tabla),
|
||||
"32": ("Snare Cadence (marching snare, flams, diddles)", snare_cadence),
|
||||
}
|
||||
|
||||
if __name__ == "__main__":
|
||||
@@ -2485,7 +2705,7 @@ if __name__ == "__main__":
|
||||
print(f" {key:>2}. {name}")
|
||||
|
||||
print()
|
||||
choice = input(" Pick a song (1-31, or 'all'): ").strip()
|
||||
choice = input(" Pick a song (1-32, or 'all'): ").strip()
|
||||
print()
|
||||
|
||||
if choice == "all":
|
||||
|
||||
+184
-29
@@ -2796,6 +2796,83 @@ def _synth_metal_hat(n_samples):
|
||||
return out
|
||||
|
||||
|
||||
def _synth_march_snare(n_samples):
|
||||
"""Marching snare — ultra-tight kevlar head, high and crisp.
|
||||
|
||||
Higher pitched than a kit snare. Very short decay — all attack,
|
||||
no sustain. Tight snare wires give a brief sizzle.
|
||||
"""
|
||||
t = numpy.arange(n_samples, dtype=numpy.float32) / SAMPLE_RATE
|
||||
# Higher-pitched body — tight kevlar pops high
|
||||
body = numpy.sin(2 * numpy.pi * 450 * t) * _exp_decay(n_samples, 60) * 0.4
|
||||
body2 = numpy.sin(2 * numpy.pi * 700 * t) * _exp_decay(n_samples, 75) * 0.2
|
||||
# Sharp stick pop
|
||||
click_len = min(int(SAMPLE_RATE * 0.001), n_samples)
|
||||
click = _noise(click_len) * _exp_decay(click_len, 400) * 1.2
|
||||
# Very tight snare sizzle — higher band, shorter
|
||||
buzz_len = min(int(SAMPLE_RATE * 0.025), n_samples)
|
||||
buzz_raw = _noise(buzz_len)
|
||||
if buzz_len > 20:
|
||||
bl, al = scipy.signal.butter(2, [3500, 8000], btype='band', fs=SAMPLE_RATE)
|
||||
buzz = scipy.signal.lfilter(bl, al, numpy.pad(buzz_raw, (0, max(0, n_samples - buzz_len))))[:buzz_len]
|
||||
else:
|
||||
buzz = buzz_raw
|
||||
buzz *= _exp_decay(buzz_len, 50) * 0.35
|
||||
result = body + body2
|
||||
result[:click_len] += click
|
||||
result[:buzz_len] += buzz
|
||||
return numpy.tanh(result * 2.8)
|
||||
|
||||
|
||||
def _synth_march_rimshot(n_samples):
|
||||
"""Marching rimshot — woody metallic crack.
|
||||
|
||||
The stick catches the rim — you get the full snare hit plus
|
||||
a bright, woody-metallic crack from the aluminum rim. Short
|
||||
ring that dies fast but gives it that cutting edge.
|
||||
"""
|
||||
wave = _synth_march_snare(n_samples)
|
||||
t = numpy.arange(n_samples, dtype=numpy.float32) / SAMPLE_RATE
|
||||
# Rim crack — bright but short, woody-metallic character
|
||||
rim = numpy.sin(2 * numpy.pi * 1100 * t) * _exp_decay(n_samples, 45) * 0.35
|
||||
rim2 = numpy.sin(2 * numpy.pi * 2200 * t) * _exp_decay(n_samples, 55) * 0.2
|
||||
# Hard transient pop
|
||||
pop_len = min(int(SAMPLE_RATE * 0.002), n_samples)
|
||||
pop = _noise(pop_len) * _exp_decay(pop_len, 350) * 1.5
|
||||
# Extra body punch
|
||||
punch = numpy.sin(2 * numpy.pi * 500 * t) * _exp_decay(n_samples, 65) * 0.3
|
||||
result = wave * 1.4 + rim + rim2 + punch
|
||||
result[:pop_len] += pop
|
||||
return numpy.tanh(result * 2.0)
|
||||
|
||||
|
||||
def _synth_march_click(n_samples):
|
||||
"""Stick click — taped hickory sticks clocked together.
|
||||
|
||||
Bright wood-on-wood with a slightly dampened attack from the
|
||||
electrical tape. Not as ringy as a clave — the tape absorbs
|
||||
some of the high overtones — but still bright and snappy.
|
||||
"""
|
||||
t = numpy.arange(n_samples, dtype=numpy.float32) / SAMPLE_RATE
|
||||
# Wood resonance — brighter than before, but tape dampens ring
|
||||
body = numpy.sin(2 * numpy.pi * 1100 * t) * _exp_decay(n_samples, 65) * 0.45
|
||||
body2 = numpy.sin(2 * numpy.pi * 1800 * t) * _exp_decay(n_samples, 80) * 0.25
|
||||
# Woody overtone — gives it that hickory character
|
||||
body3 = numpy.sin(2 * numpy.pi * 2600 * t) * _exp_decay(n_samples, 95) * 0.12
|
||||
# Bright but slightly muffled transient (tape on wood)
|
||||
click_len = min(int(SAMPLE_RATE * 0.001), n_samples)
|
||||
click_raw = _noise(click_len)
|
||||
if click_len > 10:
|
||||
bl, al = scipy.signal.butter(2, [800, 7000], btype='band', fs=SAMPLE_RATE)
|
||||
click = scipy.signal.lfilter(bl, al, numpy.pad(click_raw, (0, max(0, n_samples - click_len))))[:click_len]
|
||||
else:
|
||||
click = click_raw
|
||||
click *= _exp_decay(click_len, 350) * 0.9
|
||||
result = body + body2 + body3
|
||||
result[:click_len] += click
|
||||
return numpy.tanh(result * 2.8)
|
||||
|
||||
|
||||
def _synth_tabla_ge_bend(n_samples):
|
||||
"""Tabla Ge with upward pitch bend — palm pressing into bayan head.
|
||||
|
||||
@@ -3012,6 +3089,10 @@ def _render_drum_hit(sound_value, n_samples):
|
||||
DrumSound.METAL_KICK.value: lambda n: _synth_metal_kick(n),
|
||||
DrumSound.METAL_SNARE.value: lambda n: _synth_metal_snare(n),
|
||||
DrumSound.METAL_HAT.value: lambda n: _synth_metal_hat(n),
|
||||
# Marching
|
||||
DrumSound.MARCH_SNARE.value: lambda n: _synth_march_snare(n),
|
||||
DrumSound.MARCH_RIMSHOT.value: lambda n: _synth_march_rimshot(n),
|
||||
DrumSound.MARCH_CLICK.value: lambda n: _synth_march_click(n),
|
||||
}
|
||||
|
||||
renderer = _dispatch.get(sound_value, lambda n: _synth_clave(n))
|
||||
@@ -4496,35 +4577,71 @@ def render_score(score):
|
||||
synth_kwargs["mod_index"] = part.fm_index
|
||||
_temperament = getattr(score, 'temperament', 'equal')
|
||||
_ref_pitch = getattr(score, 'reference_pitch', 440.0)
|
||||
if part.legato:
|
||||
_render_legato_to_buf(
|
||||
part.notes, part_buf, samples_per_beat, total_samples,
|
||||
synth_fn, env_tuple, part.volume, score.bpm,
|
||||
glide_time=part.glide, swing=effective_swing,
|
||||
tempo_map=tempo_map if has_tempo_changes else None,
|
||||
temperament=_temperament, reference_pitch=_ref_pitch)
|
||||
else:
|
||||
_render_notes_to_buf(
|
||||
part.notes, part_buf, samples_per_beat, total_samples,
|
||||
synth_fn, env_tuple, part.volume, score.bpm,
|
||||
swing=effective_swing,
|
||||
tempo_map=tempo_map if has_tempo_changes else None,
|
||||
humanize=part.humanize,
|
||||
detune=part.detune,
|
||||
spread=part.spread,
|
||||
stereo_buf=stereo_buf,
|
||||
sub_osc=part.sub_osc,
|
||||
noise_mix=part.noise_mix,
|
||||
filter_attack=part.filter_attack,
|
||||
filter_decay=part.filter_decay,
|
||||
filter_sustain=part.filter_sustain,
|
||||
filter_amount=part.filter_amount,
|
||||
vel_to_filter=part.vel_to_filter,
|
||||
filter_q=part.lowpass_q,
|
||||
synth_kwargs=synth_kwargs,
|
||||
temperament=_temperament,
|
||||
reference_pitch=_ref_pitch,
|
||||
analog=part.analog)
|
||||
|
||||
n_ensemble = max(1, getattr(part, 'ensemble', 1))
|
||||
|
||||
for _ens_i in range(n_ensemble):
|
||||
# Each ensemble voice gets its own buffer
|
||||
ens_buf = part_buf if n_ensemble == 1 else numpy.zeros(total_samples, dtype=numpy.float32)
|
||||
# Ensemble voices get micro-variations
|
||||
ens_humanize = part.humanize
|
||||
ens_analog = part.analog
|
||||
if n_ensemble > 1:
|
||||
import random as _ens_rnd
|
||||
_ens_rnd.seed(42 + _ens_i * 7)
|
||||
# Hybrid approach:
|
||||
# 1. Consistent player tendency (rush/drag) — seeded per player
|
||||
_player_tendency = _ens_rnd.gauss(0, 0.018)
|
||||
# 2. Tiny per-note wobble on top
|
||||
ens_humanize = max(part.humanize, 0.012)
|
||||
# Each player's drum tuned slightly different
|
||||
ens_analog = max(part.analog, 0.06 + _ens_rnd.uniform(0, 0.08))
|
||||
|
||||
if part.legato:
|
||||
_render_legato_to_buf(
|
||||
part.notes, ens_buf, samples_per_beat, total_samples,
|
||||
synth_fn, env_tuple, part.volume, score.bpm,
|
||||
glide_time=part.glide, swing=effective_swing,
|
||||
tempo_map=tempo_map if has_tempo_changes else None,
|
||||
temperament=_temperament, reference_pitch=_ref_pitch)
|
||||
else:
|
||||
_render_notes_to_buf(
|
||||
part.notes, ens_buf, samples_per_beat, total_samples,
|
||||
synth_fn, env_tuple, part.volume, score.bpm,
|
||||
swing=effective_swing,
|
||||
tempo_map=tempo_map if has_tempo_changes else None,
|
||||
humanize=ens_humanize,
|
||||
detune=part.detune,
|
||||
spread=part.spread,
|
||||
stereo_buf=stereo_buf,
|
||||
sub_osc=part.sub_osc,
|
||||
noise_mix=part.noise_mix,
|
||||
filter_attack=part.filter_attack,
|
||||
filter_decay=part.filter_decay,
|
||||
filter_sustain=part.filter_sustain,
|
||||
filter_amount=part.filter_amount,
|
||||
vel_to_filter=part.vel_to_filter,
|
||||
filter_q=part.lowpass_q,
|
||||
synth_kwargs=synth_kwargs,
|
||||
temperament=_temperament,
|
||||
reference_pitch=_ref_pitch,
|
||||
analog=ens_analog)
|
||||
|
||||
if n_ensemble > 1:
|
||||
# Shift the whole voice by the player's consistent tendency
|
||||
# (some players rush, some drag — this is fixed per player)
|
||||
shift_samples = int(_player_tendency * samples_per_beat)
|
||||
if shift_samples > 0 and shift_samples < total_samples:
|
||||
# Player drags — shift right
|
||||
shifted = numpy.zeros_like(ens_buf)
|
||||
shifted[shift_samples:] = ens_buf[:-shift_samples]
|
||||
ens_buf = shifted
|
||||
elif shift_samples < 0 and abs(shift_samples) < total_samples:
|
||||
# Player rushes — shift left
|
||||
shifted = numpy.zeros_like(ens_buf)
|
||||
shifted[:shift_samples] = ens_buf[-shift_samples:]
|
||||
ens_buf = shifted
|
||||
part_buf += ens_buf / n_ensemble
|
||||
|
||||
# Apply effects — segmented if automation exists
|
||||
auto_points = part._get_automation_points()
|
||||
@@ -4657,6 +4774,10 @@ def render_score(score):
|
||||
DrumSound.METAL_KICK.value: 0.0,
|
||||
DrumSound.METAL_SNARE.value: 0.0,
|
||||
DrumSound.METAL_HAT.value: 0.3,
|
||||
# Marching — centered
|
||||
DrumSound.MARCH_SNARE.value: 0.0,
|
||||
DrumSound.MARCH_RIMSHOT.value: 0.0,
|
||||
DrumSound.MARCH_CLICK.value: 0.0,
|
||||
}
|
||||
|
||||
# Render all drum Parts (may be one "drums" or split into kick/snare/hats/etc.)
|
||||
@@ -4672,6 +4793,7 @@ def render_score(score):
|
||||
# Track last hit position per sound for choke (new hit dampens
|
||||
# the previous ring on the same drum)
|
||||
_last_hit_start = {}
|
||||
_resonance = {} # sound_id → resonance level (0.0–1.0)
|
||||
|
||||
for hit in drum_part._drum_hits:
|
||||
pos = hit.position
|
||||
@@ -4741,6 +4863,39 @@ def render_score(score):
|
||||
vel_jitter = int(drum_humanize * 10)
|
||||
vel = max(1, min(127, vel + _drum_rnd.randint(-vel_jitter, vel_jitter)))
|
||||
vel_scale = vel / 127.0
|
||||
|
||||
# Sympathetic resonance: marching snare builds up buzz
|
||||
# as hits accumulate. Each hit adds to a resonance counter
|
||||
# that scales extra snare wire buzz into the sound.
|
||||
_RESONANCE_SOUNDS = {
|
||||
DrumSound.MARCH_SNARE.value, DrumSound.MARCH_RIMSHOT.value,
|
||||
}
|
||||
if sound_id in _RESONANCE_SOUNDS:
|
||||
reso = _resonance.get(sound_id, 0.0)
|
||||
# Decay based on gap since last hit
|
||||
if sound_id in _last_hit_start:
|
||||
gap_samples = start - _last_hit_start[sound_id]
|
||||
gap_sec = gap_samples / SAMPLE_RATE
|
||||
if gap_sec > 1.0:
|
||||
reso *= 0.2
|
||||
elif gap_sec > 0.5:
|
||||
reso *= 0.5
|
||||
elif gap_sec > 0.25:
|
||||
reso *= 0.8
|
||||
# Build up (caps at 0.6)
|
||||
reso = min(0.6, reso + 0.08)
|
||||
_resonance[sound_id] = reso
|
||||
# Add sympathetic buzz proportional to resonance
|
||||
if reso > 0.1:
|
||||
buzz_len = min(int(SAMPLE_RATE * 0.06), hit_len)
|
||||
buzz = _noise(buzz_len) * reso * 0.18
|
||||
if buzz_len > 20:
|
||||
bl, al = scipy.signal.butter(
|
||||
2, [3000, 9000], btype='band', fs=SAMPLE_RATE)
|
||||
buzz = scipy.signal.lfilter(bl, al, buzz)
|
||||
buzz *= _exp_decay(buzz_len, 25)
|
||||
wave[:buzz_len] = wave[:buzz_len] + buzz.astype(numpy.float32)
|
||||
|
||||
mono_hit = wave * vel_scale * 0.7
|
||||
# Sidechain trigger — kick only
|
||||
if hit.sound.value == DrumSound.KICK.value:
|
||||
|
||||
+158
-1
@@ -564,6 +564,10 @@ class DrumSound(Enum):
|
||||
METAL_KICK = 105 # clicky, punchy, tight
|
||||
METAL_SNARE = 106 # crack, bright, cutting
|
||||
METAL_HAT = 107 # tight, short, precise
|
||||
# Marching percussion
|
||||
MARCH_SNARE = 115 # tight, high-tension kevlar head, snare buzz
|
||||
MARCH_RIMSHOT = 116 # stick hits rim + head simultaneously, cracking
|
||||
MARCH_CLICK = 118 # stick click — sticks hit together, no drum
|
||||
|
||||
|
||||
class _DrumTone:
|
||||
@@ -1602,6 +1606,77 @@ Pattern._PRESETS["tabla solo"] = dict(
|
||||
],
|
||||
)
|
||||
|
||||
# ── Marching snare patterns ───────────────────────────────────────────────
|
||||
MS = DrumSound.MARCH_SNARE
|
||||
MR = DrumSound.MARCH_RIMSHOT
|
||||
|
||||
# Marching basic — standard 4/4 march with rimshot accents on 2 and 4
|
||||
Pattern._PRESETS["march"] = dict(
|
||||
name="march",
|
||||
time_signature="4/4",
|
||||
beats=4.0,
|
||||
hits=[
|
||||
_h(MS, 0.0, 80), _h(MS, 0.5, 55),
|
||||
_h(MR, 1.0, 100), _h(MS, 1.5, 55),
|
||||
_h(MS, 2.0, 80), _h(MS, 2.5, 55),
|
||||
_h(MR, 3.0, 100), _h(MS, 3.5, 55),
|
||||
],
|
||||
)
|
||||
|
||||
# Cadence — 8-beat street beat pattern (the classic drumline cadence)
|
||||
Pattern._PRESETS["cadence"] = dict(
|
||||
name="cadence",
|
||||
time_signature="4/4",
|
||||
beats=8.0,
|
||||
hits=[
|
||||
# Bar 1: syncopated groove
|
||||
_h(MR, 0.0, 105), _h(MS, 0.25, 60), _h(MS, 0.5, 65),
|
||||
_h(MS, 0.75, 55), _h(MR, 1.0, 100),
|
||||
_h(MS, 1.5, 60), _h(MS, 1.75, 58),
|
||||
_h(MR, 2.0, 105), _h(MS, 2.5, 62),
|
||||
_h(MS, 2.75, 55), _h(MR, 3.0, 100),
|
||||
_h(MS, 3.25, 58), _h(MS, 3.5, 60), _h(MS, 3.75, 55),
|
||||
# Bar 2: answer phrase with flams
|
||||
_h(MR, 4.0, 110), _h(MS, 4.25, 62), _h(MS, 4.5, 65),
|
||||
_h(MR, 5.0, 105), _h(MS, 5.25, 58),
|
||||
_h(MS, 5.5, 60), _h(MS, 5.75, 55),
|
||||
_h(MR, 6.0, 110), _h(MS, 6.25, 62),
|
||||
_h(MS, 6.5, 65), _h(MS, 6.75, 62),
|
||||
_h(MR, 7.0, 115), _h(MS, 7.25, 60),
|
||||
_h(MR, 7.5, 110), _h(MR, 7.75, 115),
|
||||
],
|
||||
)
|
||||
|
||||
# Paradiddle — RLRR LRLL on marching snare
|
||||
Pattern._PRESETS["march paradiddle"] = dict(
|
||||
name="march paradiddle",
|
||||
time_signature="4/4",
|
||||
beats=4.0,
|
||||
hits=[
|
||||
# RLRR (R=rimshot accent, L=tap)
|
||||
_h(MR, 0.0, 100), _h(MS, 0.25, 58), _h(MR, 0.5, 65), _h(MR, 0.75, 62),
|
||||
# LRLL
|
||||
_h(MS, 1.0, 58), _h(MR, 1.25, 100), _h(MS, 1.5, 58), _h(MS, 1.75, 55),
|
||||
# RLRR
|
||||
_h(MR, 2.0, 102), _h(MS, 2.25, 60), _h(MR, 2.5, 68), _h(MR, 2.75, 65),
|
||||
# LRLL
|
||||
_h(MS, 3.0, 60), _h(MR, 3.25, 102), _h(MS, 3.5, 60), _h(MS, 3.75, 58),
|
||||
],
|
||||
)
|
||||
|
||||
# March roll — buzz roll crescendo
|
||||
Pattern._PRESETS["march roll"] = dict(
|
||||
name="march roll",
|
||||
time_signature="4/4",
|
||||
beats=4.0,
|
||||
hits=[
|
||||
# Buzz roll as rapid 32nds, crescendo
|
||||
*[_h(MS, i * 0.125, 40 + i * 3) for i in range(28)],
|
||||
# Land on rimshot
|
||||
_h(MR, 3.5, 115), _h(MR, 3.75, 120),
|
||||
],
|
||||
)
|
||||
|
||||
# Chakradar — tihai of tihais (16 beats / 4 bars)
|
||||
# A phrase (Dha Tit Tit Dha Ge Na) is played 3x with increasing intensity,
|
||||
# and within each repetition the final 3 hits form a mini-tihai landing on sam.
|
||||
@@ -2633,6 +2708,7 @@ class Part:
|
||||
cabinet: float = 0.0,
|
||||
cabinet_brightness: float = 0.5,
|
||||
analog: float = 0.0,
|
||||
ensemble: int = 1,
|
||||
fm_ratio: float = 2.0,
|
||||
fm_index: float = 3.0):
|
||||
self.name = name
|
||||
@@ -2679,6 +2755,7 @@ class Part:
|
||||
self.cabinet = cabinet
|
||||
self.cabinet_brightness = cabinet_brightness
|
||||
self.analog = analog
|
||||
self.ensemble = ensemble
|
||||
self.fm_ratio = fm_ratio
|
||||
self.fm_index = fm_index
|
||||
self._system = "western" # default, overridden by Score.part()
|
||||
@@ -2773,6 +2850,85 @@ class Part:
|
||||
velocity=velocity, articulation=articulation))
|
||||
return self
|
||||
|
||||
def flam(self, sound, duration=Duration.QUARTER, *, velocity: int = 110,
|
||||
gap: float = 0.015, grace_vel: float = 0.3,
|
||||
articulation: str = "") -> "Part":
|
||||
"""Add a flam — a grace note immediately before the main hit.
|
||||
|
||||
The grace note is nearly simultaneous with the main hit,
|
||||
thickening the attack. Tighter gap = more like one fat hit,
|
||||
wider gap = audible double.
|
||||
|
||||
Args:
|
||||
sound: A :class:`DrumSound` enum member.
|
||||
duration: Total duration the flam occupies.
|
||||
velocity: Main hit velocity.
|
||||
gap: Beats between grace and main (default 0.008 ≈ 4ms at 120).
|
||||
grace_vel: Grace note velocity as fraction of main (default 0.3).
|
||||
articulation: Optional articulation for the main hit.
|
||||
|
||||
Example::
|
||||
|
||||
>>> p.flam(DrumSound.MARCH_SNARE, Duration.QUARTER, velocity=120)
|
||||
"""
|
||||
if isinstance(duration, (int, float)):
|
||||
dur_val = duration
|
||||
else:
|
||||
dur_val = duration.value if hasattr(duration, 'value') else float(duration)
|
||||
self.hit(sound, gap, velocity=int(velocity * grace_vel))
|
||||
self.hit(sound, dur_val - gap, velocity=velocity, articulation=articulation)
|
||||
return self
|
||||
|
||||
def diddle(self, sound, duration=Duration.EIGHTH, *,
|
||||
velocity: int = 70) -> "Part":
|
||||
"""Add a diddle — two equal strokes in the space of one note.
|
||||
|
||||
A double-stroke roll building block. Two hits split evenly
|
||||
across the duration.
|
||||
|
||||
Args:
|
||||
sound: A :class:`DrumSound` enum member.
|
||||
duration: Total duration (default 8th note). Each stroke
|
||||
gets half.
|
||||
velocity: Velocity for both strokes.
|
||||
|
||||
Example::
|
||||
|
||||
>>> p.diddle(DrumSound.MARCH_SNARE, Duration.EIGHTH, velocity=60)
|
||||
"""
|
||||
if isinstance(duration, (int, float)):
|
||||
dur_val = duration
|
||||
else:
|
||||
dur_val = duration.value if hasattr(duration, 'value') else float(duration)
|
||||
half = dur_val / 2
|
||||
self.hit(sound, half, velocity=velocity)
|
||||
self.hit(sound, half, velocity=int(velocity * 0.9))
|
||||
return self
|
||||
|
||||
def cheese(self, sound, duration=Duration.QUARTER, *, velocity: int = 110,
|
||||
gap: float = 0.008, grace_vel: float = 0.3) -> "Part":
|
||||
"""Add a cheese — a flam followed by a diddle.
|
||||
|
||||
Common marching rudiment: grace-MAIN-tap-tap.
|
||||
|
||||
Args:
|
||||
sound: A :class:`DrumSound` enum member.
|
||||
duration: Total duration.
|
||||
velocity: Main hit velocity.
|
||||
"""
|
||||
if isinstance(duration, (int, float)):
|
||||
dur_val = duration
|
||||
else:
|
||||
dur_val = duration.value if hasattr(duration, 'value') else float(duration)
|
||||
# Flam takes first half, diddle takes second half
|
||||
flam_dur = dur_val * 0.5
|
||||
diddle_dur = dur_val * 0.5
|
||||
self.hit(sound, gap, velocity=int(velocity * grace_vel))
|
||||
self.hit(sound, flam_dur - gap, velocity=velocity)
|
||||
self.hit(sound, diddle_dur / 2, velocity=int(velocity * 0.5))
|
||||
self.hit(sound, diddle_dur / 2, velocity=int(velocity * 0.45))
|
||||
return self
|
||||
|
||||
def crescendo(self, notes, duration=Duration.QUARTER, *,
|
||||
start_vel: int = 40, end_vel: int = 110,
|
||||
articulation: str = "") -> "Part":
|
||||
@@ -3588,6 +3744,7 @@ class Score:
|
||||
cabinet: float = None,
|
||||
cabinet_brightness: float = None,
|
||||
analog: float = None,
|
||||
ensemble: int = None,
|
||||
fm_ratio: float = None,
|
||||
fm_index: float = None,
|
||||
fretboard=None) -> Part:
|
||||
@@ -3700,7 +3857,7 @@ class Score:
|
||||
"tremolo_depth": tremolo_depth, "tremolo_rate": tremolo_rate,
|
||||
"phaser": phaser, "phaser_rate": phaser_rate,
|
||||
"cabinet": cabinet, "cabinet_brightness": cabinet_brightness,
|
||||
"analog": analog,
|
||||
"analog": analog, "ensemble": ensemble,
|
||||
"fm_ratio": fm_ratio, "fm_index": fm_index,
|
||||
}
|
||||
for k, v in _locals.items():
|
||||
|
||||
Reference in New Issue
Block a user