diff --git a/examples/songs.py b/examples/songs.py index 821a98e..37e1c8c 100644 --- a/examples/songs.py +++ b/examples/songs.py @@ -2441,222 +2441,367 @@ def acid_tabla(): def snare_cadence(): - """Snare Cadence — marching snare with click count-off, flams, diddles, cheese.""" + """Snare Cadence — full drumline with ensemble, 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 + Q1 = DrumSound.QUAD_1 + Q2 = DrumSound.QUAD_2 + Q3 = DrumSound.QUAD_3 + Q4 = DrumSound.QUAD_4 + QS = DrumSound.QUAD_SPOCK + B1 = DrumSound.BASS_1 + B2 = DrumSound.BASS_2 + B3 = DrumSound.BASS_3 + B4 = DrumSound.BASS_4 + B5 = DrumSound.BASS_5 + + # Snare line — 8 players + p = score.part("snares", synth="sine", volume=0.9, reverb=0.2, ensemble=8) + # Quad line — 4 players + q = score.part("quads", synth="sine", volume=0.5, reverb=0.2, ensemble=4) + # Bass line — 5 players + b = score.part("basses", synth="sine", volume=0.55, reverb=0.2, ensemble=5) + + _trip = 1.0 / 3 + + # Helper: bass split run (down or up) + def bass_down(dur=Duration.SIXTEENTH): + b.hit(B1, dur, velocity=95) + b.hit(B2, dur, velocity=90) + b.hit(B3, dur, velocity=85) + b.hit(B4, dur, velocity=90) + + def bass_up(dur=Duration.SIXTEENTH): + b.hit(B4, dur, velocity=90) + b.hit(B3, dur, velocity=85) + b.hit(B2, dur, velocity=90) + b.hit(B1, dur, velocity=95) + + def bass_hit(dur=Duration.QUARTER): + b.hit(B3, dur, velocity=100) + + def quad_sweep_down(): + q.hit(Q1, Duration.SIXTEENTH, velocity=95) + q.hit(Q2, Duration.SIXTEENTH, velocity=88) + q.hit(Q3, Duration.SIXTEENTH, velocity=82) + q.hit(Q4, Duration.SIXTEENTH, velocity=78) + + def quad_sweep_up(): + q.hit(Q4, Duration.SIXTEENTH, velocity=78) + q.hit(Q3, Duration.SIXTEENTH, velocity=82) + q.hit(Q2, Duration.SIXTEENTH, velocity=88) + q.hit(Q1, Duration.SIXTEENTH, velocity=95) # ── Click count-off ── for _ in range(4): p.hit(C, Duration.QUARTER, velocity=95) + q.rest(Duration.QUARTER) + b.rest(Duration.QUARTER) - _trip = 1.0 / 3 # triplet 8th + # ── Section 1: 16th groove — snares only (4 bars) ── + for _ in range(16): + q.rest(Duration.QUARTER) + b.rest(Duration.QUARTER) - # ── 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(R, Duration.SIXTEENTH, velocity=118) 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(R, Duration.SIXTEENTH, velocity=115) 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=35) + p.hit(S, Duration.SIXTEENTH, velocity=30) + p.hit(S, Duration.SIXTEENTH, velocity=32) + p.hit(R, Duration.SIXTEENTH, velocity=120) 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 + # Triplets mixed in for _ in range(2): - p.hit(R, _trip, velocity=115) + p.hit(R, _trip, velocity=118) p.hit(S, _trip, velocity=32) p.hit(S, _trip, velocity=30) - p.hit(R, _trip, velocity=112) + p.hit(R, _trip, velocity=115) 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(R, _trip, velocity=118) p.hit(S, _trip, velocity=30) p.hit(S, _trip, velocity=35) - # ── Section 2: Add flams + triplets (4 bars) ── + # ── Section 2: Quads + bass enter (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(R, _trip, velocity=118) p.hit(S, _trip, velocity=28) p.hit(S, _trip, velocity=30) - p.flam(S, Duration.QUARTER, velocity=115) + p.flam(S, Duration.QUARTER, velocity=118) + + quad_sweep_down() + q.hit(QS, Duration.QUARTER, velocity=100) + quad_sweep_up() + q.hit(QS, Duration.QUARTER, velocity=100) + + bass_hit() + b.hit(B5, Duration.QUARTER, velocity=95) + bass_hit() + b.hit(B1, Duration.QUARTER, velocity=95) 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.flam(S, Duration.QUARTER, velocity=118) p.hit(S, _trip, velocity=28) - p.hit(R, _trip, velocity=120) + p.hit(R, _trip, velocity=122) p.hit(S, _trip, velocity=35) - # ── Section 3: Flams + diddles + triplets (4 bars) ── + quad_sweep_down() + quad_sweep_up() + q.hit(Q1, Duration.EIGHTH, velocity=95) + q.hit(Q4, Duration.EIGHTH, velocity=85) + q.hit(QS, Duration.QUARTER, velocity=100) + + bass_down() + bass_up() + b.hit(B3, Duration.HALF, velocity=100) + + # ── Section 3: Flams + diddles + full line (4 bars) ── for _ in range(2): - p.flam(S, Duration.QUARTER, velocity=118) + p.flam(S, Duration.QUARTER, velocity=120) 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.hit(R, Duration.EIGHTH, velocity=122) p.diddle(S, Duration.EIGHTH, velocity=42) + q.hit(Q1, Duration.QUARTER, velocity=95) + q.hit(Q3, Duration.EIGHTH, velocity=55) + q.hit(Q2, _trip, velocity=55) + q.hit(Q3, _trip, velocity=55) + q.hit(Q4, _trip, velocity=55) + q.hit(QS, Duration.EIGHTH, velocity=100) + q.hit(Q1, Duration.EIGHTH, velocity=55) + + bass_hit() + b.hit(B1, Duration.EIGHTH, velocity=90) + b.hit(B5, Duration.EIGHTH, velocity=95) + bass_hit() + b.hit(B5, Duration.EIGHTH, velocity=90) + b.hit(B1, Duration.EIGHTH, velocity=95) + for _ in range(2): p.diddle(S, Duration.EIGHTH, velocity=45) - p.hit(R, _trip, velocity=118) + p.hit(R, _trip, velocity=120) 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(R, _trip, velocity=118) p.hit(S, _trip, velocity=28) p.hit(S, _trip, velocity=30) - p.flam(S, Duration.EIGHTH, velocity=120) + p.flam(S, Duration.EIGHTH, velocity=122) p.hit(S, Duration.EIGHTH, velocity=35) - # ── Section 4: Syncopation + triplet accents (4 bars) ── + quad_sweep_down() + quad_sweep_up() + quad_sweep_down() + quad_sweep_up() + + bass_down() + bass_up() + bass_down() + bass_up() + + # ── Section 4: Cheese + 32nds (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.cheese(S, Duration.QUARTER, velocity=120) 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.cheese(S, Duration.QUARTER, velocity=118) p.diddle(S, Duration.EIGHTH, velocity=48) - p.hit(R, Duration.EIGHTH, velocity=122) + p.hit(R, Duration.EIGHTH, velocity=125) - p.cheese(S, Duration.QUARTER, velocity=120) - p.cheese(S, Duration.QUARTER, velocity=118) + q.hit(QS, Duration.QUARTER, velocity=105) + q.hit(Q1, Duration.SIXTEENTH, velocity=55) + q.hit(Q2, Duration.SIXTEENTH, velocity=55) + q.hit(Q3, Duration.SIXTEENTH, velocity=55) + q.hit(Q4, Duration.SIXTEENTH, velocity=55) + q.hit(QS, Duration.QUARTER, velocity=105) + q.hit(Q4, Duration.EIGHTH, velocity=55) + q.hit(Q1, Duration.EIGHTH, velocity=90) + + bass_hit() + b.hit(B1, Duration.EIGHTH, velocity=90) + b.hit(B3, Duration.EIGHTH, velocity=85) + b.hit(B5, Duration.EIGHTH, velocity=95) + b.hit(B3, Duration.EIGHTH, velocity=85) + bass_hit() + b.rest(Duration.QUARTER) + + # All cheese p.cheese(S, Duration.QUARTER, velocity=122) + p.cheese(S, Duration.QUARTER, velocity=120) p.cheese(S, Duration.QUARTER, velocity=125) + p.cheese(S, Duration.QUARTER, velocity=122) + + q.hit(QS, Duration.QUARTER, velocity=105) + q.hit(QS, Duration.QUARTER, velocity=105) + q.hit(QS, Duration.QUARTER, velocity=108) + q.hit(QS, Duration.QUARTER, velocity=105) + + b.hit(B5, Duration.QUARTER, velocity=100) + b.hit(B3, Duration.QUARTER, velocity=100) + b.hit(B1, Duration.QUARTER, velocity=100) + b.hit(B3, Duration.QUARTER, velocity=100) - 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.diddle(S, Duration.EIGHTH, velocity=50) p.flam(S, Duration.EIGHTH, velocity=122) + p.diddle(S, Duration.EIGHTH, velocity=52) + p.flam(S, Duration.EIGHTH, velocity=125) p.diddle(S, Duration.EIGHTH, velocity=55) - p.hit(R, Duration.EIGHTH, velocity=125) + p.hit(R, Duration.EIGHTH, velocity=127) 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) + quad_sweep_down() + quad_sweep_up() + quad_sweep_down() + quad_sweep_up() + + bass_down() + bass_up() + bass_down() + bass_up() + + # ── Section 5: 16ths + triplet 16ths + 32nds (4 bars) ── + _trip16 = 1.0 / 6 - # 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(R, _trip, velocity=118) p.hit(S, _trip, velocity=35) p.hit(S, _trip, velocity=32) - # 32nd note run crescendo into rimshot + quad_sweep_down() + quad_sweep_up() + quad_sweep_down() + quad_sweep_up() + + bass_hit() + b.hit(B5, Duration.QUARTER, velocity=95) + bass_hit() + b.hit(B1, Duration.QUARTER, velocity=95) + + # 32nd run crescendo 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) + for _ in range(4): + q.hit(Q1, 0.0625, velocity=55) + q.hit(Q2, 0.0625, velocity=55) + q.hit(Q3, 0.0625, velocity=55) + q.hit(Q4, 0.0625, velocity=55) + q.hit(QS, Duration.QUARTER, velocity=108) - play_song(score, "Snare Cadence — marching snare (flams, diddles, cheese, resonance)") + bass_down() + bass_up() + bass_down() + b.hit(B5, Duration.QUARTER, velocity=100) + b.hit(B1, Duration.QUARTER, velocity=100) + + # Triplet 16ths — all sections + for _ in range(2): + for beat in range(4): + p.hit(R, _trip16, velocity=115) + p.hit(S, _trip16, velocity=30) + p.hit(S, _trip16, velocity=32) + p.hit(R, _trip16, velocity=112) + p.hit(S, _trip16, velocity=28) + p.hit(S, _trip16, velocity=30) + + for beat in range(4): + q.hit(Q1, _trip16, velocity=90) + q.hit(Q2, _trip16, velocity=55) + q.hit(Q3, _trip16, velocity=55) + q.hit(Q4, _trip16, velocity=55) + q.hit(Q3, _trip16, velocity=55) + q.hit(Q2, _trip16, velocity=55) + + bass_down() + bass_up() + bass_down() + bass_up() + + # ── Section 6: Buzz roll climax (2 bars) ── + for i in range(64): + p.hit(S, 0.0625, velocity=min(20 + i * 1.5, 100)) + p.hit(R, Duration.EIGHTH, velocity=127) + p.hit(R, Duration.EIGHTH, velocity=127) + + for i in range(32): + q.hit([Q1, Q2, Q3, Q4][i % 4], 0.0625, velocity=min(40 + i * 2, 95)) + q.hit(QS, Duration.QUARTER, velocity=110) + + for i in range(16): + b.hit([B1, B2, B3, B4, B5, B4, B3, B2, + B1, B2, B3, B4, B5, B4, B3, B2][i], Duration.SIXTEENTH, velocity=90) + b.hit(B3, Duration.HALF, velocity=100) + b.hit(B3, Duration.HALF, velocity=100) + + # ── Ending: big unison hits ── + p.hit(R, Duration.EIGHTH, velocity=127) + q.hit(QS, Duration.EIGHTH, velocity=110) + b.hit(B3, Duration.EIGHTH, velocity=100) + + p.rest(Duration.QUARTER + Duration.EIGHTH) + q.rest(Duration.QUARTER + Duration.EIGHTH) + b.rest(Duration.QUARTER + Duration.EIGHTH) + + p.hit(R, Duration.EIGHTH, velocity=127) + q.hit(QS, Duration.EIGHTH, velocity=110) + b.hit(B3, Duration.EIGHTH, velocity=100) + + p.rest(Duration.QUARTER + Duration.EIGHTH) + q.rest(Duration.QUARTER + Duration.EIGHTH) + b.rest(Duration.QUARTER + Duration.EIGHTH) + + # Flam into final CRACK — all sections + p.flam(S, Duration.EIGHTH, velocity=127) + q.hit(QS, Duration.EIGHTH, velocity=110) + b.hit(B3, Duration.EIGHTH, velocity=100) + + p.rest(Duration.QUARTER + Duration.EIGHTH) + q.rest(Duration.QUARTER + Duration.EIGHTH) + b.rest(Duration.QUARTER + Duration.EIGHTH) + + p.hit(R, Duration.QUARTER, velocity=127) + q.hit(QS, Duration.QUARTER, velocity=110) + b.hit(B3, Duration.QUARTER, velocity=100) + + p.rest(Duration.HALF) + q.rest(Duration.HALF) + b.rest(Duration.HALF) + + play_song(score, "Snare Cadence — full drumline (8 snares, 4 quads, 5 basses)") SONGS = { diff --git a/pytheory/play.py b/pytheory/play.py index 8e85cf9..1d5306d 100644 --- a/pytheory/play.py +++ b/pytheory/play.py @@ -2873,6 +2873,74 @@ def _synth_march_click(n_samples): return numpy.tanh(result * 2.8) +def _synth_quad(n_samples, pitch=300): + """Marching tenor/quad drum — tuned mylar head, bright and ringy. + + Quads have a distinctive metallic ting from the high-tension + mylar head and aluminum shell. More ring than a kit tom, + brighter attack, clear pitch. + """ + t = numpy.arange(n_samples, dtype=numpy.float32) / SAMPLE_RATE + # Pitched body — more ring/sustain than snare + body = numpy.sin(2 * numpy.pi * pitch * t) * _exp_decay(n_samples, 22) * 0.5 + # Metallic overtones — the ting + ting = numpy.sin(2 * numpy.pi * pitch * 2.3 * t) * _exp_decay(n_samples, 35) * 0.25 + ting2 = numpy.sin(2 * numpy.pi * pitch * 3.1 * t) * _exp_decay(n_samples, 45) * 0.12 + # Shell ring + shell = numpy.sin(2 * numpy.pi * pitch * 4.7 * t) * _exp_decay(n_samples, 55) * 0.06 + # Sharp stick attack + click_len = min(int(SAMPLE_RATE * 0.001), n_samples) + click = _noise(click_len) * _exp_decay(click_len, 400) * 0.8 + result = body + ting + ting2 + shell + result[:click_len] += click + return numpy.tanh(result * 2.5) + + +def _synth_quad_spock(n_samples): + """Quad spock — rim shot on the tenor shell. Bright, ringy, cutting.""" + t = numpy.arange(n_samples, dtype=numpy.float32) / SAMPLE_RATE + ring = numpy.sin(2 * numpy.pi * 1400 * t) * _exp_decay(n_samples, 40) * 0.5 + ring2 = numpy.sin(2 * numpy.pi * 2100 * t) * _exp_decay(n_samples, 55) * 0.25 + click_len = min(int(SAMPLE_RATE * 0.001), n_samples) + click = _noise(click_len) * _exp_decay(click_len, 400) * 1.0 + result = ring + ring2 + result[:click_len] += click + return numpy.tanh(result * 2.8) + + +def _synth_march_bass(n_samples, pitch=60): + """Marching bass drum — deep, boomy, pitched, felt beater thwack. + + The beater hitting the head is a big part of the sound — a round, + pillowy thwack followed by the deep pitched boom. More beater + sound than a kit bass drum because marching bass drums project + outward. + """ + t = numpy.arange(n_samples, dtype=numpy.float32) / SAMPLE_RATE + # Deep pitched body — sustains and rings + body = numpy.sin(2 * numpy.pi * pitch * t) * _exp_decay(n_samples, 10) * 0.7 + body2 = numpy.sin(2 * numpy.pi * pitch * 2 * t) * _exp_decay(n_samples, 16) * 0.2 + # Sub thump + sub = numpy.sin(2 * numpy.pi * pitch * 0.5 * t) * _exp_decay(n_samples, 8) * 0.3 + # BIG beater thwack — dominant part of the attack + thwack_len = min(int(SAMPLE_RATE * 0.025), n_samples) + thwack_raw = _noise(thwack_len) + if thwack_len > 10: + bl, al = scipy.signal.butter(2, [150, 2500], btype='band', fs=SAMPLE_RATE) + thwack = scipy.signal.lfilter(bl, al, numpy.pad(thwack_raw, (0, max(0, n_samples - thwack_len))))[:thwack_len] + else: + thwack = thwack_raw + thwack *= _exp_decay(thwack_len, 55) * 1.5 + # Head slap — the mylar flexing on impact + slap_len = min(int(SAMPLE_RATE * 0.008), n_samples) + slap = numpy.sin(2 * numpy.pi * pitch * 3 * numpy.arange(slap_len, dtype=numpy.float32) / SAMPLE_RATE) + slap *= _exp_decay(slap_len, 90) * 0.4 + result = body + body2 + sub + result[:thwack_len] += thwack + result[:slap_len] += slap + return numpy.tanh(result * 2.0) + + def _synth_tabla_ge_bend(n_samples): """Tabla Ge with upward pitch bend — palm pressing into bayan head. @@ -3093,6 +3161,18 @@ def _render_drum_hit(sound_value, n_samples): 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), + # Quads (tenor drums) — pitched high to low + DrumSound.QUAD_1.value: lambda n: _synth_quad(n, pitch=400), + DrumSound.QUAD_2.value: lambda n: _synth_quad(n, pitch=330), + DrumSound.QUAD_3.value: lambda n: _synth_quad(n, pitch=270), + DrumSound.QUAD_4.value: lambda n: _synth_quad(n, pitch=220), + DrumSound.QUAD_SPOCK.value: lambda n: _synth_quad_spock(n), + # Marching bass drums — pitched high to low + DrumSound.BASS_1.value: lambda n: _synth_march_bass(n, pitch=90), + DrumSound.BASS_2.value: lambda n: _synth_march_bass(n, pitch=75), + DrumSound.BASS_3.value: lambda n: _synth_march_bass(n, pitch=62), + DrumSound.BASS_4.value: lambda n: _synth_march_bass(n, pitch=52), + DrumSound.BASS_5.value: lambda n: _synth_march_bass(n, pitch=42), } renderer = _dispatch.get(sound_value, lambda n: _synth_clave(n)) @@ -4778,6 +4858,18 @@ def render_score(score): DrumSound.MARCH_SNARE.value: 0.0, DrumSound.MARCH_RIMSHOT.value: 0.0, DrumSound.MARCH_CLICK.value: 0.0, + # Quads — spread across the field + DrumSound.QUAD_1.value: -0.3, + DrumSound.QUAD_2.value: -0.1, + DrumSound.QUAD_3.value: 0.1, + DrumSound.QUAD_4.value: 0.3, + DrumSound.QUAD_SPOCK.value: 0.0, + # Bass drums — spread wide + DrumSound.BASS_1.value: -0.5, + DrumSound.BASS_2.value: -0.25, + DrumSound.BASS_3.value: 0.0, + DrumSound.BASS_4.value: 0.25, + DrumSound.BASS_5.value: 0.5, } # Render all drum Parts (may be one "drums" or split into kick/snare/hats/etc.) diff --git a/pytheory/rhythm.py b/pytheory/rhythm.py index b69e989..463fd57 100644 --- a/pytheory/rhythm.py +++ b/pytheory/rhythm.py @@ -568,6 +568,18 @@ class DrumSound(Enum): 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 + # Quads (tenor drums) — 4 drums high to low + spock (rim) + QUAD_1 = 119 # highest tenor drum + QUAD_2 = 120 # second tenor + QUAD_3 = 121 # third tenor + QUAD_4 = 122 # lowest tenor (floor tom-ish) + QUAD_SPOCK = 123 # rim click on quad shell + # Marching bass drums — 5 drums pitched high to low + BASS_1 = 124 # highest (smallest) bass drum + BASS_2 = 125 # second + BASS_3 = 126 # middle + BASS_4 = 127 # fourth + BASS_5 = 80 # lowest (biggest) bass drum class _DrumTone: @@ -1609,6 +1621,17 @@ Pattern._PRESETS["tabla solo"] = dict( # ── Marching snare patterns ─────────────────────────────────────────────── MS = DrumSound.MARCH_SNARE MR = DrumSound.MARCH_RIMSHOT +MC = DrumSound.MARCH_CLICK +Q1 = DrumSound.QUAD_1 +Q2 = DrumSound.QUAD_2 +Q3 = DrumSound.QUAD_3 +Q4 = DrumSound.QUAD_4 +QS = DrumSound.QUAD_SPOCK +B1 = DrumSound.BASS_1 +B2 = DrumSound.BASS_2 +B3 = DrumSound.BASS_3 +B4 = DrumSound.BASS_4 +B5 = DrumSound.BASS_5 # Marching basic — standard 4/4 march with rimshot accents on 2 and 4 Pattern._PRESETS["march"] = dict( @@ -1677,6 +1700,96 @@ Pattern._PRESETS["march roll"] = dict( ], ) +# Quad sweep — run across all 4 drums +Pattern._PRESETS["quad sweep"] = dict( + name="quad sweep", + time_signature="4/4", + beats=4.0, + hits=[ + # Sweep down + _h(Q1, 0.0, 95), _h(Q2, 0.25, 90), _h(Q3, 0.5, 85), _h(Q4, 0.75, 80), + # Sweep up + _h(Q4, 1.0, 80), _h(Q3, 1.25, 85), _h(Q2, 1.5, 90), _h(Q1, 1.75, 95), + # Double sweep with spocks + _h(Q1, 2.0, 98), _h(Q2, 2.125, 92), _h(Q3, 2.25, 88), _h(Q4, 2.375, 82), + _h(Q4, 2.5, 82), _h(Q3, 2.625, 88), _h(Q2, 2.75, 92), _h(Q1, 2.875, 98), + # Spock accents + _h(QS, 3.0, 105), _h(Q1, 3.25, 90), _h(QS, 3.5, 105), _h(Q4, 3.75, 85), + ], +) + +# Quad groove — accented pattern with sweeps +Pattern._PRESETS["quad groove"] = dict( + name="quad groove", + time_signature="4/4", + beats=4.0, + hits=[ + _h(Q1, 0.0, 100), _h(Q3, 0.25, 55), _h(Q1, 0.5, 60), + _h(Q2, 0.75, 55), _h(Q3, 1.0, 95), _h(Q1, 1.25, 55), + _h(Q4, 1.5, 58), _h(Q2, 1.75, 55), + _h(Q1, 2.0, 100), _h(Q2, 2.25, 55), _h(Q3, 2.5, 58), + _h(Q4, 2.75, 55), _h(QS, 3.0, 105), _h(Q3, 3.25, 55), + _h(Q2, 3.5, 58), _h(Q1, 3.75, 60), + ], +) + +# Bass split — classic bass drum splits across the line +Pattern._PRESETS["bass split"] = dict( + name="bass split", + time_signature="4/4", + beats=4.0, + hits=[ + # Each bass drum takes a 16th, cascading down then up + _h(B1, 0.0, 95), _h(B2, 0.25, 90), _h(B3, 0.5, 85), + _h(B4, 0.75, 80), _h(B5, 1.0, 95), + _h(B5, 1.5, 90), _h(B4, 1.75, 85), + _h(B3, 2.0, 95), _h(B2, 2.25, 90), _h(B1, 2.5, 95), + _h(B1, 2.75, 85), _h(B3, 3.0, 100), + _h(B5, 3.25, 95), _h(B3, 3.5, 90), _h(B1, 3.75, 95), + ], +) + +# Bass unison — all bass drums hit together on accents +Pattern._PRESETS["bass unison"] = dict( + name="bass unison", + time_signature="4/4", + beats=4.0, + hits=[ + # All 5 hit on beat 1 + _h(B1, 0.0, 100), _h(B2, 0.0, 100), _h(B3, 0.0, 100), + _h(B4, 0.0, 100), _h(B5, 0.0, 100), + # Split on beat 2 + _h(B1, 1.0, 90), _h(B3, 1.25, 85), _h(B5, 1.5, 90), + # All on beat 3 + _h(B1, 2.0, 100), _h(B2, 2.0, 100), _h(B3, 2.0, 100), + _h(B4, 2.0, 100), _h(B5, 2.0, 100), + # Cascade into beat 4 + _h(B5, 2.75, 80), _h(B4, 3.0, 85), _h(B3, 3.25, 90), + _h(B2, 3.5, 95), _h(B1, 3.75, 100), + ], +) + +# Full drumline — snare + quads + bass together +Pattern._PRESETS["drumline"] = dict( + name="drumline", + time_signature="4/4", + beats=4.0, + hits=[ + # Snare backbone + _h(MR, 0.0, 115), _h(MS, 0.25, 35), _h(MS, 0.5, 38), _h(MS, 0.75, 32), + _h(MR, 1.0, 112), _h(MS, 1.25, 35), _h(MS, 1.5, 32), _h(MS, 1.75, 38), + _h(MR, 2.0, 115), _h(MS, 2.25, 38), _h(MS, 2.5, 32), _h(MS, 2.75, 35), + _h(MR, 3.0, 118), _h(MS, 3.25, 35), _h(MS, 3.5, 32), _h(MS, 3.75, 38), + # Quads on accents + _h(Q1, 0.0, 95), _h(Q3, 0.5, 55), _h(Q2, 1.0, 90), + _h(Q4, 1.5, 55), _h(Q1, 2.0, 95), _h(Q3, 2.5, 55), + _h(QS, 3.0, 100), _h(Q2, 3.5, 55), + # Bass on the big beats + _h(B3, 0.0, 100), _h(B5, 1.0, 95), + _h(B1, 2.0, 100), _h(B3, 3.0, 95), + ], +) + # 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.