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:
2026-03-28 22:48:37 -04:00
parent db9726168a
commit b9dcad0454
3 changed files with 563 additions and 31 deletions
+221 -1
View File
@@ -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
View File
@@ -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.01.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
View File
@@ -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():