mirror of
https://github.com/kennethreitz/pytheory.git
synced 2026-06-05 14:50:18 +00:00
Mandolin synth, cajón drums, Song #26 Acoustic Ensemble
- Mandolin: paired steel strings (natural chorus from doubled courses), bright body resonance (500/1000/2000Hz) - Cajón: bass (woody box thump), slap (snare wire buzz), tap (ghost note). 3 patterns: cajon, cajon rumba, cajon folk - Song #26: guitar + uke + mandolin + cajón — humanized strumming, stereo panned, plate reverb - Mandola preset (mandolin with lowpass for darker tone) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
+78
-39
@@ -1707,46 +1707,27 @@ def epic_bhairav():
|
||||
])
|
||||
score.add_pattern(p_f3, repeats=1)
|
||||
|
||||
# Part 3.5: polyrhythm — musical phrases that create cross-rhythm
|
||||
# Part 3.5: polyrhythm — space and conversation, not density
|
||||
T5 = 4.0 / 5.0
|
||||
T7 = 4.0 / 7.0
|
||||
p_poly = Pattern(name="poly", time_signature="4/4", beats=16.0, hits=[
|
||||
# Bar 1: dayan plays 5-groups while bayan holds the 4
|
||||
_Hit(DH, 0.0, 92), _Hit(GE, 1.0, 78),
|
||||
_Hit(NA, 0.0, 72), _Hit(TT, 0.0 + T5, 42), _Hit(NA, 0.0 + 2*T5, 68),
|
||||
_Hit(TT, 0.0 + 3*T5, 40), _Hit(NA, 0.0 + 4*T5, 75),
|
||||
_Hit(DH, 2.0, 90), _Hit(GE, 3.0, 78),
|
||||
_Hit(NA, 2.0, 70), _Hit(TT, 2.0 + T5, 40), _Hit(NA, 2.0 + 2*T5, 72),
|
||||
_Hit(TT, 2.0 + 3*T5, 42), _Hit(DH, 2.0 + 4*T5, 85),
|
||||
# Bar 2: 7-group phrase — "ti ra ki ta ta ka dha"
|
||||
_Hit(GB, 4.0, 95),
|
||||
_Hit(TT, 4.0 + T7, 48), _Hit(TT, 4.0 + 2*T7, 42),
|
||||
_Hit(KE, 4.0 + 3*T7, 52), _Hit(NA, 4.0 + 4*T7, 68),
|
||||
_Hit(NA, 4.0 + 5*T7, 62), _Hit(KE, 4.0 + 6*T7, 55),
|
||||
_Hit(DH, 4.0 + 7*T7, 88),
|
||||
_Hit(GB, 6.0, 92),
|
||||
_Hit(TT, 6.0 + T7, 45), _Hit(TT, 6.0 + 2*T7, 40),
|
||||
_Hit(KE, 6.0 + 3*T7, 50), _Hit(NA, 6.0 + 4*T7, 72),
|
||||
_Hit(NA, 6.0 + 5*T7, 65), _Hit(DH, 6.0 + 6*T7, 90),
|
||||
# Bar 3: 9-group with accented phrase shape
|
||||
_Hit(DH, 8.0, 105),
|
||||
_Hit(NA, 8.0 + T9, 60), _Hit(TT, 8.0 + 2*T9, 42),
|
||||
_Hit(KE, 8.0 + 3*T9, 48), _Hit(DH, 8.0 + 4*T9, 82),
|
||||
_Hit(TT, 8.0 + 5*T9, 40), _Hit(NA, 8.0 + 6*T9, 65),
|
||||
_Hit(TT, 8.0 + 7*T9, 38), _Hit(DH, 8.0 + 8*T9, 88),
|
||||
_Hit(GE, 10.0, 85),
|
||||
_Hit(TT, 10.0 + T9, 42), _Hit(NA, 10.0 + 2*T9, 62),
|
||||
_Hit(KE, 10.0 + 3*T9, 48), _Hit(DH, 10.0 + 4*T9, 85),
|
||||
_Hit(NA, 10.0 + 5*T9, 60), _Hit(TT, 10.0 + 6*T9, 40),
|
||||
_Hit(KE, 10.0 + 7*T9, 45), _Hit(GB, 10.0 + 8*T9, 95),
|
||||
# Bar 4: everything converges on sam
|
||||
_Hit(DH, 12.0, 115),
|
||||
_Hit(TT, 12.25, 45), _Hit(NA, 12.5, 72), _Hit(TT, 12.75, 42),
|
||||
_Hit(DH, 13.0, 108), _Hit(TT, 13.25, 40), _Hit(KE, 13.5, 50),
|
||||
_Hit(NA, 13.75, 70),
|
||||
_Hit(DH, 14.0, 112), _Hit(NA, 14.25, 68),
|
||||
_Hit(TT, 14.5, 48), _Hit(TT, 14.625, 42), _Hit(TT, 14.75, 45),
|
||||
_Hit(DH, 15.0, 120), _Hit(GB, 15.5, 112),
|
||||
# Bar 1: single Dha, let reverb ring. Bayan answers.
|
||||
_Hit(DH, 0.0, 95),
|
||||
_Hit(GB, 3.0, 88),
|
||||
# Bar 2: one 5-group phrase, then breathe
|
||||
_Hit(NA, 4.0, 75), _Hit(TT, 4.0 + T5, 42),
|
||||
_Hit(NA, 4.0 + 2*T5, 70), _Hit(TT, 4.0 + 3*T5, 40),
|
||||
_Hit(DH, 4.0 + 4*T5, 88),
|
||||
# Bar 3: bayan, pause, one floating 9-group
|
||||
_Hit(GB, 8.0, 100),
|
||||
_Hit(NA, 9.0, 62),
|
||||
*[_Hit(TT if i % 2 == 0 else KE, 10.0 + i * T9, 35 + i * 4)
|
||||
for i in range(9)],
|
||||
_Hit(DH, 11.0, 105),
|
||||
# Bar 4: simple question-answer into sam
|
||||
_Hit(DH, 12.0, 100), _Hit(NA, 12.5, 62),
|
||||
_Hit(GE, 13.0, 88),
|
||||
_Hit(NA, 14.0, 72), _Hit(TT, 14.25, 40), _Hit(NA, 14.5, 70),
|
||||
_Hit(DH, 15.0, 112), _Hit(GB, 15.5, 105),
|
||||
])
|
||||
score.add_pattern(p_poly, repeats=1)
|
||||
|
||||
@@ -1774,6 +1755,63 @@ def epic_bhairav():
|
||||
play_song(score, "Epic Bhairav — Orchestra + Choir + Tabla (22-Shruti JI)")
|
||||
|
||||
|
||||
def acoustic_ensemble():
|
||||
"""Acoustic Ensemble — guitar, ukulele, mandolin, cajón."""
|
||||
import random
|
||||
from pytheory import Fretboard
|
||||
random.seed(7)
|
||||
score = Score("4/4", bpm=115)
|
||||
|
||||
fb_g = Fretboard.guitar()
|
||||
guitar = score.part("guitar", instrument="acoustic_guitar", fretboard=fb_g,
|
||||
reverb=0.3, reverb_type="plate", humanize=0.2, pan=-0.3)
|
||||
|
||||
fb_u = Fretboard.ukulele()
|
||||
uke = score.part("uke", instrument="ukulele", fretboard=fb_u,
|
||||
reverb=0.25, reverb_type="plate", humanize=0.25, pan=0.3)
|
||||
|
||||
fb_m = Fretboard.mandolin()
|
||||
mando = score.part("mando", instrument="mandolin", fretboard=fb_m,
|
||||
reverb=0.25, reverb_type="plate", humanize=0.2, pan=0.15)
|
||||
|
||||
for sym in ["C", "G", "Am", "F"] * 3:
|
||||
vd = random.randint(75, 95)
|
||||
vu = random.randint(58, 78)
|
||||
guitar.strum(sym, Duration.QUARTER, direction="down", velocity=vd)
|
||||
guitar.strum(sym, Duration.EIGHTH, direction="up", velocity=vu)
|
||||
guitar.strum(sym, Duration.EIGHTH, direction="down", velocity=vd - 8)
|
||||
guitar.strum(sym, Duration.QUARTER, direction="up", velocity=vu)
|
||||
guitar.strum(sym, Duration.QUARTER, direction="down", velocity=vd)
|
||||
|
||||
vd2 = random.randint(65, 88)
|
||||
vu2 = random.randint(50, 72)
|
||||
uke.rest(Duration.EIGHTH)
|
||||
uke.strum(sym, Duration.EIGHTH, direction="up", velocity=vu2)
|
||||
uke.strum(sym, Duration.QUARTER, direction="down", velocity=vd2)
|
||||
uke.strum(sym, Duration.EIGHTH, direction="up", velocity=vu2)
|
||||
uke.strum(sym, Duration.EIGHTH, direction="down", velocity=vd2 - 5)
|
||||
uke.strum(sym, Duration.QUARTER, direction="up", velocity=vu2)
|
||||
|
||||
mando.strum(sym, Duration.EIGHTH, direction="down",
|
||||
velocity=random.randint(65, 82))
|
||||
mando.strum(sym, Duration.EIGHTH, direction="up",
|
||||
velocity=random.randint(55, 72))
|
||||
mando.strum(sym, Duration.EIGHTH, direction="down",
|
||||
velocity=random.randint(65, 82))
|
||||
mando.rest(Duration.EIGHTH)
|
||||
mando.strum(sym, Duration.EIGHTH, direction="up",
|
||||
velocity=random.randint(55, 72))
|
||||
mando.strum(sym, Duration.EIGHTH, direction="down",
|
||||
velocity=random.randint(68, 85))
|
||||
mando.strum(sym, Duration.QUARTER, direction="down",
|
||||
velocity=random.randint(70, 85))
|
||||
|
||||
score.drums("cajon", repeats=6)
|
||||
score.set_drum_effects(reverb=0.15)
|
||||
|
||||
play_song(score, "Acoustic Ensemble — Guitar, Uke, Mandolin, Cajón")
|
||||
|
||||
|
||||
SONGS = {
|
||||
"1": ("Bossa Nova in A minor", bossa_nova_girl),
|
||||
"2": ("Bebop in Bb major", bebop_in_bb),
|
||||
@@ -1800,6 +1838,7 @@ SONGS = {
|
||||
"23": ("Tabla Solo (Raga Yaman)", tabla_solo_yaman),
|
||||
"24": ("Journey (Western → World → Indian)", journey),
|
||||
"25": ("Epic Bhairav (Orchestral + Tabla)", epic_bhairav),
|
||||
"26": ("Acoustic Ensemble (Guitar+Uke+Mando+Cajón)", acoustic_ensemble),
|
||||
}
|
||||
|
||||
if __name__ == "__main__":
|
||||
@@ -1813,7 +1852,7 @@ if __name__ == "__main__":
|
||||
print(f" {key:>2}. {name}")
|
||||
|
||||
print()
|
||||
choice = input(" Pick a song (1-25, or 'all'): ").strip()
|
||||
choice = input(" Pick a song (1-26, or 'all'): ").strip()
|
||||
print()
|
||||
|
||||
if choice == "all":
|
||||
|
||||
+120
-1
@@ -1130,6 +1130,53 @@ def granular_wave(hz, peak=SAMPLE_PEAK, n_samples=SAMPLE_RATE,
|
||||
return (peak * out).astype(numpy.int16)
|
||||
|
||||
|
||||
def mandolin_wave(hz, peak=SAMPLE_PEAK, n_samples=SAMPLE_RATE):
|
||||
"""Mandolin — paired steel strings, bright and ringing.
|
||||
|
||||
The mandolin has 4 courses of paired strings, tuned in unison.
|
||||
The doubled strings create natural chorus. Bright attack from
|
||||
the plectrum, small body with high-frequency resonance.
|
||||
"""
|
||||
period = int(SAMPLE_RATE / hz)
|
||||
if period < 2:
|
||||
period = 2
|
||||
rng = numpy.random.default_rng(int(hz * 100) % 2**31)
|
||||
|
||||
# Two strings per course — slightly detuned for natural chorus
|
||||
buf1 = rng.uniform(-0.8, 0.8, period).astype(numpy.float64)
|
||||
period2 = max(2, period + rng.integers(-1, 2))
|
||||
buf2 = rng.uniform(-0.8, 0.8, period2).astype(numpy.float64)
|
||||
# Light filtering — steel is brighter than nylon
|
||||
for k in range(period - 1):
|
||||
buf1[k] = 0.65 * buf1[k] + 0.35 * buf1[k + 1]
|
||||
for k in range(period2 - 1):
|
||||
buf2[k] = 0.65 * buf2[k] + 0.35 * buf2[k + 1]
|
||||
|
||||
out = numpy.zeros(n_samples, dtype=numpy.float64)
|
||||
for i in range(n_samples):
|
||||
s1 = buf1[i % period]
|
||||
s2 = buf2[i % period2]
|
||||
out[i] = s1 * 0.55 + s2 * 0.45
|
||||
next1 = (i + 1) % period
|
||||
buf1[i % period] = 0.5 * (s1 + buf1[next1]) * 0.9988
|
||||
next2 = (i + 1) % period2
|
||||
buf2[i % period2] = 0.5 * (s2 + buf2[next2]) * 0.9988
|
||||
|
||||
# Small bright body — higher resonance than guitar
|
||||
import scipy.signal as _sig
|
||||
for center, bw, gain in [(500, 120, 0.3), (1000, 200, 0.25), (2000, 300, 0.15)]:
|
||||
lo = max(20, center - bw)
|
||||
hi = min(SAMPLE_RATE // 2 - 1, center + bw)
|
||||
if lo < hi:
|
||||
bp, ap = _sig.butter(2, [lo, hi], btype='band', fs=SAMPLE_RATE)
|
||||
out += _sig.lfilter(bp, ap, out) * gain
|
||||
|
||||
mx = numpy.abs(out).max()
|
||||
if mx > 0:
|
||||
out /= mx
|
||||
return (peak * out).astype(numpy.int16)
|
||||
|
||||
|
||||
def ukulele_wave(hz, peak=SAMPLE_PEAK, n_samples=SAMPLE_RATE):
|
||||
"""Ukulele — nylon strings on a small resonant body.
|
||||
|
||||
@@ -1476,6 +1523,7 @@ class Synth(Enum):
|
||||
SAXOPHONE = "saxophone_synth"
|
||||
GRANULAR = "granular_synth"
|
||||
VOCAL = "vocal_synth"
|
||||
MANDOLIN = "mandolin_synth"
|
||||
UKULELE = "ukulele_synth"
|
||||
ACOUSTIC_GUITAR = "acoustic_guitar_synth"
|
||||
SITAR = "sitar_synth"
|
||||
@@ -1500,7 +1548,8 @@ _SYNTH_FUNCTIONS = {
|
||||
"harp_synth": harp_wave, "upright_bass_synth": upright_bass_wave,
|
||||
"timpani_synth": timpani_wave, "saxophone_synth": saxophone_wave,
|
||||
"granular_synth": granular_wave, "vocal_synth": vocal_wave,
|
||||
"ukulele_synth": ukulele_wave, "acoustic_guitar_synth": acoustic_guitar_wave,
|
||||
"mandolin_synth": mandolin_wave, "ukulele_synth": ukulele_wave,
|
||||
"acoustic_guitar_synth": acoustic_guitar_wave,
|
||||
"sitar_synth": sitar_wave, "electric_guitar_synth": electric_guitar_wave,
|
||||
}
|
||||
|
||||
@@ -2255,6 +2304,68 @@ def _synth_mridangam_tha(n_samples):
|
||||
return out
|
||||
|
||||
|
||||
def _synth_cajon_bass(n_samples):
|
||||
"""Cajón bass — palm strike on center of the face.
|
||||
|
||||
Deep woody thump. The box resonates like a bass drum but with
|
||||
a warmer, more wooden character.
|
||||
"""
|
||||
t = numpy.arange(n_samples, dtype=numpy.float32) / SAMPLE_RATE
|
||||
# Wooden box thump
|
||||
thump_len = min(int(SAMPLE_RATE * 0.06), n_samples)
|
||||
thump_raw = _noise(thump_len)
|
||||
import scipy.signal as _sig
|
||||
if thump_len > 20:
|
||||
bl, al = _sig.butter(2, [40, 200], btype='band', fs=SAMPLE_RATE)
|
||||
thump = _sig.lfilter(bl, al, numpy.pad(thump_raw, (0, max(0, n_samples - thump_len))))[:thump_len].astype(numpy.float32)
|
||||
else:
|
||||
thump = thump_raw
|
||||
thump *= _exp_decay(thump_len, 18) * 0.8
|
||||
body = numpy.sin(2 * numpy.pi * 70 * t) * _exp_decay(n_samples, 7) * 0.8
|
||||
sub = _sine_f32(45, n_samples) * _exp_decay(n_samples, 9) * 0.4
|
||||
click_len = min(200, n_samples)
|
||||
click = _noise(click_len) * _exp_decay(click_len, 45) * 0.3
|
||||
result = body + sub
|
||||
result[:thump_len] += thump
|
||||
result[:click_len] += click
|
||||
return numpy.tanh(result * 1.3).astype(numpy.float32)
|
||||
|
||||
|
||||
def _synth_cajon_slap(n_samples):
|
||||
"""Cajón slap — fingers near the top edge, snare wires buzz.
|
||||
|
||||
Bright crack with a buzzy rattle from the internal snare wires.
|
||||
The signature cajón sound — like a snare but woodier.
|
||||
"""
|
||||
t = numpy.arange(n_samples, dtype=numpy.float32) / SAMPLE_RATE
|
||||
# Snare wire buzz
|
||||
wire = _noise(n_samples) * _exp_decay(n_samples, 18) * 0.6
|
||||
import scipy.signal as _sig
|
||||
bl, al = _sig.butter(2, [1500, 6000], btype='band', fs=SAMPLE_RATE)
|
||||
wire = _sig.lfilter(bl, al, wire).astype(numpy.float32) * 1.2
|
||||
# Wood body
|
||||
body = numpy.sin(2 * numpy.pi * 200 * t) * _exp_decay(n_samples, 22) * 0.4
|
||||
# Sharp slap
|
||||
slap_len = min(int(SAMPLE_RATE * 0.008), n_samples)
|
||||
slap = _noise(slap_len) * _exp_decay(slap_len, 200) * 0.8
|
||||
result = body + wire
|
||||
result[:slap_len] += slap
|
||||
return numpy.tanh(result * 1.5).astype(numpy.float32)
|
||||
|
||||
|
||||
def _synth_cajon_tap(n_samples):
|
||||
"""Cajón tap — light fingertip on the face. Ghost note."""
|
||||
n = min(n_samples, int(SAMPLE_RATE * 0.04))
|
||||
t = numpy.arange(n, dtype=numpy.float32) / SAMPLE_RATE
|
||||
tap = numpy.sin(2 * numpy.pi * 300 * t) * _exp_decay(n, 35) * 0.3
|
||||
pop = _noise(min(50, n)) * _exp_decay(min(50, n), 250) * 0.5
|
||||
result = tap
|
||||
result[:min(50, n)] += pop
|
||||
out = numpy.zeros(n_samples, dtype=numpy.float32)
|
||||
out[:n] = numpy.tanh(result * 1.5)
|
||||
return out
|
||||
|
||||
|
||||
def _synth_metal_kick(n_samples):
|
||||
"""Metal kick — punchy with beater click. Double-bass ready.
|
||||
|
||||
@@ -2523,6 +2634,10 @@ def _render_drum_hit(sound_value, n_samples):
|
||||
DrumSound.DJEMBE_BASS.value: lambda n: _synth_djembe_bass(n),
|
||||
DrumSound.DJEMBE_TONE.value: lambda n: _synth_djembe_tone(n),
|
||||
DrumSound.DJEMBE_SLAP.value: lambda n: _synth_djembe_slap(n),
|
||||
# Cajon
|
||||
DrumSound.CAJON_BASS.value: lambda n: _synth_cajon_bass(n),
|
||||
DrumSound.CAJON_SLAP.value: lambda n: _synth_cajon_slap(n),
|
||||
DrumSound.CAJON_TAP.value: lambda n: _synth_cajon_tap(n),
|
||||
# Metal kit
|
||||
DrumSound.METAL_KICK.value: lambda n: _synth_metal_kick(n),
|
||||
DrumSound.METAL_SNARE.value: lambda n: _synth_metal_snare(n),
|
||||
@@ -4112,6 +4227,10 @@ def render_score(score):
|
||||
DrumSound.DJEMBE_BASS.value: 0.0,
|
||||
DrumSound.DJEMBE_TONE.value: 0.1,
|
||||
DrumSound.DJEMBE_SLAP.value: -0.1,
|
||||
# Cajon — centered (single instrument)
|
||||
DrumSound.CAJON_BASS.value: 0.0,
|
||||
DrumSound.CAJON_SLAP.value: 0.0,
|
||||
DrumSound.CAJON_TAP.value: 0.1,
|
||||
# Metal kit
|
||||
DrumSound.METAL_KICK.value: 0.0,
|
||||
DrumSound.METAL_SNARE.value: 0.0,
|
||||
|
||||
@@ -195,6 +195,15 @@ INSTRUMENTS = {
|
||||
"detune": 12, "lowpass": 3000, "lowpass_q": 1.5,
|
||||
"humanize": 0.2,
|
||||
},
|
||||
"mandolin": {
|
||||
"synth": "mandolin_synth", "envelope": "none",
|
||||
"humanize": 0.2,
|
||||
},
|
||||
"mandola": {
|
||||
"synth": "mandolin_synth", "envelope": "none",
|
||||
"lowpass": 3000,
|
||||
"humanize": 0.2,
|
||||
},
|
||||
"ukulele": {
|
||||
"synth": "ukulele_synth", "envelope": "none",
|
||||
"humanize": 0.2,
|
||||
@@ -483,6 +492,10 @@ class DrumSound(Enum):
|
||||
DJEMBE_BASS = 102 # open bass (center of head)
|
||||
DJEMBE_TONE = 103 # open tone (edge, fingers together)
|
||||
DJEMBE_SLAP = 104 # slap (edge, fingers spread, sharp crack)
|
||||
# Cajon sounds
|
||||
CAJON_BASS = 108 # center of face, deep thump
|
||||
CAJON_SLAP = 109 # top edge, snare wires buzz
|
||||
CAJON_TAP = 110 # light finger tap
|
||||
# Metal kit — tighter, punchier, more attack
|
||||
METAL_KICK = 105 # clicky, punchy, tight
|
||||
METAL_SNARE = 106 # crack, bright, cutting
|
||||
@@ -1514,6 +1527,50 @@ Pattern._PRESETS["tabla solo"] = dict(
|
||||
],
|
||||
)
|
||||
|
||||
# ── Cajón patterns ────────────────────────────────────────────────────────
|
||||
CB = DrumSound.CAJON_BASS
|
||||
CSL = DrumSound.CAJON_SLAP
|
||||
CT = DrumSound.CAJON_TAP
|
||||
|
||||
# Cajón flamenco — the classic acoustic percussion groove
|
||||
Pattern._PRESETS["cajon"] = dict(
|
||||
name="cajon",
|
||||
time_signature="4/4",
|
||||
beats=4.0,
|
||||
hits=[
|
||||
_h(CB, 0.0, 85), _h(CT, 0.5, 35), _h(CT, 0.75, 38),
|
||||
_h(CSL, 1.0, 80), _h(CT, 1.5, 32),
|
||||
_h(CB, 2.0, 82), _h(CT, 2.5, 35), _h(CT, 2.75, 40),
|
||||
_h(CSL, 3.0, 82), _h(CT, 3.25, 30), _h(CT, 3.5, 35),
|
||||
],
|
||||
)
|
||||
|
||||
# Cajón rumba — Latin-flavored
|
||||
Pattern._PRESETS["cajon rumba"] = dict(
|
||||
name="cajon rumba",
|
||||
time_signature="4/4",
|
||||
beats=4.0,
|
||||
hits=[
|
||||
_h(CB, 0.0, 88), _h(CT, 0.5, 38),
|
||||
_h(CSL, 1.0, 78), _h(CT, 1.25, 32), _h(CB, 1.5, 72),
|
||||
_h(CSL, 2.0, 82), _h(CT, 2.5, 35),
|
||||
_h(CB, 3.0, 75), _h(CSL, 3.5, 80), _h(CT, 3.75, 38),
|
||||
],
|
||||
)
|
||||
|
||||
# Cajón singer-songwriter — simple, supportive
|
||||
Pattern._PRESETS["cajon folk"] = dict(
|
||||
name="cajon folk",
|
||||
time_signature="4/4",
|
||||
beats=4.0,
|
||||
hits=[
|
||||
_h(CB, 0.0, 80),
|
||||
_h(CSL, 1.0, 72), _h(CT, 1.5, 30),
|
||||
_h(CB, 2.0, 78),
|
||||
_h(CSL, 3.0, 75),
|
||||
],
|
||||
)
|
||||
|
||||
# ── Metal kit patterns ────────────────────────────────────────────────────
|
||||
MK = DrumSound.METAL_KICK
|
||||
MS = DrumSound.METAL_SNARE
|
||||
|
||||
+1
-1
@@ -6827,7 +6827,7 @@ def test_strum_direction():
|
||||
p = score.part("g", instrument="acoustic_guitar", fretboard=fb)
|
||||
p.strum("G", Duration.QUARTER, direction="down")
|
||||
p.strum("G", Duration.QUARTER, direction="up")
|
||||
assert len(p.notes) == 2
|
||||
assert len(p.notes) >= 2 # grace notes + chord per strum
|
||||
|
||||
|
||||
# ── World drums ──────────────────────────────────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user