mirror of
https://github.com/kennethreitz/pytheory.git
synced 2026-06-05 23:00:20 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 840bfcc36c |
@@ -2,6 +2,16 @@
|
||||
|
||||
All notable changes to PyTheory are documented here.
|
||||
|
||||
## 0.37.0
|
||||
|
||||
- **5 new djembe patterns** — dununba, tiriba, yankadi, djansa, mendiani
|
||||
- **3 new djembe fills** — djembe call, djembe roll, djembe break (30 fills total)
|
||||
- **Cross-choke drum damping** — striking one sound on a hand drum fades
|
||||
out the ring of related sounds (djembe slap kills bass resonance, closed
|
||||
hat chokes open hat, cajón slap dampens bass, doumbek tek dampens dum)
|
||||
- **Improved djembe slap** — dry, high-pitched goatskin pop instead of
|
||||
snare-like noise rattle
|
||||
|
||||
## 0.36.6
|
||||
|
||||
- **6 new drum fills** — 3 cajón (flam, rumble, breakdown) and 3 metal
|
||||
|
||||
+13
-7
@@ -10,7 +10,7 @@ the genre -- they tell the listener's body how to move before a single
|
||||
melodic note is played.
|
||||
|
||||
PyTheory includes a complete drum system -- 51 synthesized percussion
|
||||
sounds, 80+ pattern presets across dozens of genres, and 27 fill presets.
|
||||
sounds, 85+ pattern presets across dozens of genres, and 30 fill presets.
|
||||
Every sound is generated from waveforms; no samples needed.
|
||||
|
||||
Drum Sounds
|
||||
@@ -252,14 +252,15 @@ ending and a new one is about to begin. Without fills, a drum pattern
|
||||
just loops. With them, it breathes and has structure.
|
||||
|
||||
``Pattern.fill()`` loads a 1-bar drum fill -- a short break that
|
||||
transitions between sections. 27 fill presets are available:
|
||||
transitions between sections. 30 fill presets are available:
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> Pattern.list_fills()
|
||||
['afrobeat', 'blast', 'bossa nova', 'breakdown', 'buildup',
|
||||
'cajon breakdown', 'cajon flam', 'cajon rumble',
|
||||
'cumbia', 'disco', 'funk', 'highlife', 'hip hop', 'house',
|
||||
'cumbia', 'disco', 'djembe break', 'djembe call', 'djembe roll',
|
||||
'funk', 'highlife', 'hip hop', 'house',
|
||||
'jazz', 'jazz brush', 'metal', 'metal blast', 'metal cascade',
|
||||
'metal triplet', 'reggae', 'rock', 'rock crash',
|
||||
'salsa', 'samba', 'second line', 'trap']
|
||||
@@ -435,14 +436,19 @@ central to the drum ensemble traditions of Mali, Guinea, and Senegal.
|
||||
**3 sounds** -- bass (open center strike), tone (edge strike), and
|
||||
slap (sharp edge strike).
|
||||
|
||||
**3 patterns:** djembe (a basic accompanying rhythm), kuku (a
|
||||
traditional rhythm from Guinea associated with fishing), and soli (a
|
||||
solo/celebration rhythm).
|
||||
**8 patterns:** djembe (basic accompanying rhythm), kuku (Guinean harvest
|
||||
dance), soli (powerful Mandinka rhythm), dununba (heavy bass-driven),
|
||||
tiriba (joyful Susu rhythm), yankadi (gentle greeting/welcome), djansa
|
||||
(fast Malinke dance), mendiani (women's celebratory dance).
|
||||
|
||||
**3 fills:** djembe call (bass-tone-slap conversation building to climax),
|
||||
djembe roll (rapid slaps accelerating into bass), djembe break (syncopated
|
||||
West African-style break).
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
score = Score("4/4", bpm=120)
|
||||
score.drums("djembe", repeats=4)
|
||||
score.drums("djembe", repeats=8, fill="djembe call", fill_every=4)
|
||||
|
||||
Metal Kit
|
||||
~~~~~~~~~
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
[project]
|
||||
name = "pytheory"
|
||||
version = "0.36.6"
|
||||
version = "0.37.0"
|
||||
description = "Music Theory for Humans"
|
||||
readme = "README.md"
|
||||
license = "MIT"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""PyTheory: Music Theory for Humans."""
|
||||
|
||||
__version__ = "0.36.6"
|
||||
__version__ = "0.37.0"
|
||||
|
||||
from .tones import Tone, Interval
|
||||
from .systems import System, SYSTEMS, TET
|
||||
|
||||
+47
-20
@@ -2890,29 +2890,27 @@ def _synth_djembe_tone(n_samples):
|
||||
def _synth_djembe_slap(n_samples):
|
||||
"""Djembe slap — edge strike with fingers spread, sharp crack.
|
||||
|
||||
The highest, sharpest djembe sound. Fingers fan out on contact
|
||||
creating a loud crack with minimal sustain.
|
||||
The highest, sharpest djembe sound. A dry, high-pitched pop from
|
||||
goatskin membrane — NOT a snare. Tight attack, very short decay,
|
||||
skin character rather than wire rattle.
|
||||
"""
|
||||
t = numpy.arange(n_samples, dtype=numpy.float32) / SAMPLE_RATE
|
||||
# Sharp crack — mostly noise
|
||||
crack_len = min(int(SAMPLE_RATE * 0.02), n_samples)
|
||||
crack = _noise(crack_len) * _exp_decay(crack_len, 100) * 1.0
|
||||
# Brief high-pitched ring
|
||||
ring = numpy.sin(2 * numpy.pi * 600 * t) * _exp_decay(n_samples, 25) * 0.4
|
||||
ring2 = numpy.sin(2 * numpy.pi * 1200 * t) * 0.2 * _exp_decay(n_samples, 35)
|
||||
# Brief membrane pop
|
||||
thump_len = min(int(SAMPLE_RATE * 0.02), n_samples)
|
||||
thump_raw = _noise(thump_len)
|
||||
if thump_len > 20:
|
||||
bl, al = scipy.signal.butter(2, [300, 2000], btype='band', fs=SAMPLE_RATE)
|
||||
thump = scipy.signal.lfilter(bl, al, numpy.pad(thump_raw, (0, max(0, n_samples - thump_len))))[:thump_len]
|
||||
# High membrane pop — goatskin resonance, much higher than snare
|
||||
pop = numpy.sin(2 * numpy.pi * 900 * t) * _exp_decay(n_samples, 50) * 0.5
|
||||
pop2 = numpy.sin(2 * numpy.pi * 1600 * t) * _exp_decay(n_samples, 60) * 0.25
|
||||
pop3 = numpy.sin(2 * numpy.pi * 2400 * t) * _exp_decay(n_samples, 80) * 0.12
|
||||
# Very short filtered click — hand-on-skin transient, not noise rattle
|
||||
click_len = min(int(SAMPLE_RATE * 0.008), n_samples)
|
||||
click_raw = _noise(click_len)
|
||||
if click_len > 20:
|
||||
bl, al = scipy.signal.butter(2, 1800 / (SAMPLE_RATE / 2), btype='high')
|
||||
click = scipy.signal.lfilter(bl, al, numpy.pad(click_raw, (0, max(0, n_samples - click_len))))[:click_len]
|
||||
else:
|
||||
thump = thump_raw
|
||||
thump *= _exp_decay(thump_len, 80) * 0.8
|
||||
result = ring + ring2
|
||||
result[:crack_len] += crack
|
||||
result[:thump_len] += thump
|
||||
return numpy.tanh(result * 1.7)
|
||||
click = click_raw
|
||||
click *= _exp_decay(click_len, 150) * 0.6
|
||||
result = pop + pop2 + pop3
|
||||
result[:click_len] += click
|
||||
return numpy.tanh(result * 1.5)
|
||||
|
||||
|
||||
def _synth_guiro(n_samples):
|
||||
@@ -4661,6 +4659,35 @@ def render_score(score):
|
||||
part_stereo[fade_start:start, ch] *= fade
|
||||
_last_hit_start[sound_id] = start
|
||||
|
||||
# Cross-choke: a new hit on one sound dampens the ring of
|
||||
# related sounds on the same instrument (e.g. djembe slap
|
||||
# kills the bass resonance, closed hat kills open hat).
|
||||
_CHOKE_GROUPS = {
|
||||
# Djembe — any strike dampens the others
|
||||
DrumSound.DJEMBE_BASS.value: (DrumSound.DJEMBE_TONE.value, DrumSound.DJEMBE_SLAP.value),
|
||||
DrumSound.DJEMBE_TONE.value: (DrumSound.DJEMBE_BASS.value, DrumSound.DJEMBE_SLAP.value),
|
||||
DrumSound.DJEMBE_SLAP.value: (DrumSound.DJEMBE_BASS.value, DrumSound.DJEMBE_TONE.value),
|
||||
# Hi-hats — closed chokes open
|
||||
DrumSound.CLOSED_HAT.value: (DrumSound.OPEN_HAT.value,),
|
||||
DrumSound.PEDAL_HAT.value: (DrumSound.OPEN_HAT.value,),
|
||||
# Cajón — slap dampens bass ring
|
||||
DrumSound.CAJON_SLAP.value: (DrumSound.CAJON_BASS.value,),
|
||||
DrumSound.CAJON_TAP.value: (DrumSound.CAJON_BASS.value,),
|
||||
# Doumbek — tek/ka dampen dum
|
||||
DrumSound.DOUMBEK_TEK.value: (DrumSound.DOUMBEK_DUM.value,),
|
||||
DrumSound.DOUMBEK_KA.value: (DrumSound.DOUMBEK_DUM.value,),
|
||||
}
|
||||
choke_targets = _CHOKE_GROUPS.get(sound_id, ())
|
||||
for target_id in choke_targets:
|
||||
if target_id in _last_hit_start:
|
||||
prev_start = _last_hit_start[target_id]
|
||||
fade_len = min(int(SAMPLE_RATE * 0.004), max(0, start - prev_start))
|
||||
if fade_len > 0 and start > 0:
|
||||
fade = numpy.linspace(1.0, 0.0, fade_len).astype(numpy.float32)
|
||||
fade_start = max(0, start - fade_len)
|
||||
for ch in range(2):
|
||||
part_stereo[fade_start:start, ch] *= fade
|
||||
|
||||
remaining = total_samples - start
|
||||
hit_len = min(int(SAMPLE_RATE * 0.5), remaining)
|
||||
wave = _render_drum_hit(hit.sound.value, hit_len)
|
||||
|
||||
@@ -1907,6 +1907,74 @@ Pattern._PRESETS["soli"] = dict(
|
||||
],
|
||||
)
|
||||
|
||||
# Dununba — heavy bass-driven rhythm (accompaniment djembe part)
|
||||
Pattern._PRESETS["dununba"] = dict(
|
||||
name="dununba",
|
||||
time_signature="4/4",
|
||||
beats=4.0,
|
||||
hits=[
|
||||
_h(JB, 0.0, 110), _h(JB, 0.5, 95),
|
||||
_h(JT, 1.0, 75), _h(JB, 1.5, 100),
|
||||
_h(JB, 2.0, 108), _h(JT, 2.5, 70),
|
||||
_h(JB, 3.0, 105), _h(JB, 3.5, 90), _h(JT, 3.75, 65),
|
||||
],
|
||||
)
|
||||
|
||||
# Tiriba — joyful Susu rhythm from Guinea
|
||||
Pattern._PRESETS["tiriba"] = dict(
|
||||
name="tiriba",
|
||||
time_signature="4/4",
|
||||
beats=4.0,
|
||||
hits=[
|
||||
_h(JT, 0.0, 85), _h(JS, 0.25, 95), _h(JT, 0.5, 80),
|
||||
_h(JB, 1.0, 100), _h(JT, 1.5, 75),
|
||||
_h(JS, 2.0, 92), _h(JT, 2.25, 78), _h(JT, 2.5, 80),
|
||||
_h(JB, 3.0, 105), _h(JS, 3.5, 88), _h(JT, 3.75, 72),
|
||||
],
|
||||
)
|
||||
|
||||
# Yankadi — gentle greeting/welcome rhythm from Guinea
|
||||
Pattern._PRESETS["yankadi"] = dict(
|
||||
name="yankadi",
|
||||
time_signature="4/4",
|
||||
beats=4.0,
|
||||
hits=[
|
||||
_h(JB, 0.0, 90), _h(JT, 0.5, 70),
|
||||
_h(JT, 1.0, 72), _h(JS, 1.5, 85),
|
||||
_h(JB, 2.0, 88), _h(JT, 2.5, 68),
|
||||
_h(JS, 3.0, 82), _h(JT, 3.5, 65),
|
||||
],
|
||||
)
|
||||
|
||||
# Djansa — fast Malinke dance rhythm
|
||||
Pattern._PRESETS["djansa"] = dict(
|
||||
name="djansa",
|
||||
time_signature="4/4",
|
||||
beats=4.0,
|
||||
hits=[
|
||||
_h(JS, 0.0, 100), _h(JT, 0.25, 72), _h(JT, 0.5, 70),
|
||||
_h(JB, 0.75, 95),
|
||||
_h(JS, 1.0, 98), _h(JT, 1.25, 68), _h(JB, 1.5, 92),
|
||||
_h(JS, 2.0, 102), _h(JT, 2.25, 75), _h(JT, 2.5, 72),
|
||||
_h(JB, 2.75, 90),
|
||||
_h(JS, 3.0, 105), _h(JT, 3.25, 70), _h(JB, 3.5, 95),
|
||||
_h(JS, 3.75, 88),
|
||||
],
|
||||
)
|
||||
|
||||
# Mendiani — women's dance rhythm, celebratory
|
||||
Pattern._PRESETS["mendiani"] = dict(
|
||||
name="mendiani",
|
||||
time_signature="4/4",
|
||||
beats=4.0,
|
||||
hits=[
|
||||
_h(JB, 0.0, 100), _h(JT, 0.25, 65), _h(JS, 0.5, 90),
|
||||
_h(JT, 1.0, 70), _h(JB, 1.5, 95), _h(JT, 1.75, 68),
|
||||
_h(JS, 2.0, 92), _h(JT, 2.5, 72), _h(JS, 2.75, 85),
|
||||
_h(JB, 3.0, 105), _h(JT, 3.25, 65), _h(JS, 3.5, 95),
|
||||
],
|
||||
)
|
||||
|
||||
# ── Fill presets ──────────────────────────────────────────────────────────
|
||||
|
||||
Pattern._FILLS["rock"] = dict(
|
||||
@@ -2292,6 +2360,57 @@ Pattern._FILLS["tabla call"] = dict(
|
||||
],
|
||||
)
|
||||
|
||||
# ── Djembe fills ─────────────────────────────────────────────────────────
|
||||
|
||||
# Djembe call — bass-tone-slap conversation building to climax
|
||||
Pattern._FILLS["djembe call"] = dict(
|
||||
name="djembe call fill",
|
||||
time_signature="4/4",
|
||||
beats=4.0,
|
||||
hits=[
|
||||
_h(JB, 0.0, 100), _h(JT, 0.25, 70), _h(JT, 0.5, 72),
|
||||
_h(JS, 0.75, 90),
|
||||
_h(JB, 1.0, 95), _h(JT, 1.25, 68), _h(JS, 1.5, 88),
|
||||
_h(JT, 1.75, 75),
|
||||
_h(JS, 2.0, 100), _h(JS, 2.25, 95), _h(JT, 2.5, 78),
|
||||
_h(JB, 2.75, 105),
|
||||
_h(JS, 3.0, 110), _h(JT, 3.25, 80), _h(JS, 3.5, 112),
|
||||
_h(JB, 3.75, 120),
|
||||
],
|
||||
)
|
||||
|
||||
# Djembe roll — rapid slaps accelerating into bass
|
||||
Pattern._FILLS["djembe roll"] = dict(
|
||||
name="djembe roll fill",
|
||||
time_signature="4/4",
|
||||
beats=4.0,
|
||||
hits=[
|
||||
# Accelerating slap roll
|
||||
*[_h(JS, i * 0.125, 50 + i * 4) for i in range(16)],
|
||||
# Bass accents punching through
|
||||
_h(JB, 2.0, 105), _h(JB, 2.5, 108),
|
||||
_h(JB, 3.0, 112), _h(JT, 3.25, 85),
|
||||
_h(JB, 3.5, 115), _h(JS, 3.75, 100),
|
||||
],
|
||||
)
|
||||
|
||||
# Djembe break — syncopated West African-style break
|
||||
Pattern._FILLS["djembe break"] = dict(
|
||||
name="djembe break fill",
|
||||
time_signature="4/4",
|
||||
beats=4.0,
|
||||
hits=[
|
||||
_h(JB, 0.0, 105), _h(JT, 0.25, 65), _h(JS, 0.5, 90),
|
||||
_h(JT, 0.75, 70), _h(JB, 1.0, 100),
|
||||
_h(JS, 1.25, 85), _h(JS, 1.5, 88),
|
||||
_h(JB, 1.75, 95), _h(JT, 2.0, 72),
|
||||
_h(JS, 2.25, 92), _h(JB, 2.5, 108),
|
||||
_h(JT, 2.75, 68), _h(JS, 2.875, 55),
|
||||
_h(JB, 3.0, 115), _h(JS, 3.25, 100),
|
||||
_h(JB, 3.5, 118), _h(JB, 3.75, 120),
|
||||
],
|
||||
)
|
||||
|
||||
# ── Cajón fills ──────────────────────────────────────────────────────────
|
||||
|
||||
# Cajón flam run — slaps accelerating into bass hit
|
||||
|
||||
Reference in New Issue
Block a user