mirror of
https://github.com/kennethreitz/pytheory.git
synced 2026-06-05 23:00:20 +00:00
Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| aa21bf0f2a | |||
| e7e35ad4e4 | |||
| 503dbce937 | |||
| c6bbfae7e6 | |||
| 64ef7f0803 | |||
| 406e5d7e54 | |||
| 267b7284ba | |||
| 9b62b56120 |
@@ -2,6 +2,45 @@
|
||||
|
||||
All notable changes to PyTheory are documented here.
|
||||
|
||||
## 0.34.0
|
||||
|
||||
- **16 dedicated instrument synths** — physical modeling and specialized
|
||||
synthesis for: piano (hammer + steel strings + soundboard), bass guitar
|
||||
(thick KS + pickup), flute (breath + tube resonance), trumpet (lip buzz
|
||||
+ bell), clarinet (odd harmonics + reed), oboe (double reed + conical
|
||||
bore), marimba (inharmonic bar modes), harpsichord (quill pluck),
|
||||
cello (deep bowed + body), harp (soft pluck + soundboard bloom),
|
||||
upright bass (pizzicato + wooden body), acoustic guitar (KS + body
|
||||
resonance), electric guitar (KS + pickup comb filter), sitar (jawari
|
||||
+ chikari), plus organ and bowed strings
|
||||
- **Speaker cabinet simulation** — tames distorted guitar fizz
|
||||
- **Guitar strumming** — `Part.strum("Am")` with fretboard lookup
|
||||
- **Analog oscillator drift** — subtle per-note pitch wobble on synth presets
|
||||
- **World percussion:** dhol, dholak, mridangam, djembe, metal kit
|
||||
with 22 new drum patterns
|
||||
- **Piano improvements:** brightness scales with pitch, two-stage decay,
|
||||
hammer impact with felt character
|
||||
- 27 synth waveforms, 10 envelopes, 40+ instrument presets, 80+ drum patterns
|
||||
|
||||
## 0.33.1
|
||||
|
||||
- **Electric guitar synth** — Karplus-Strong with magnetic pickup comb filter
|
||||
simulation (single-coil honk, proper sustain)
|
||||
- **Speaker cabinet simulation** — steep rolloff above 4-5kHz with presence
|
||||
bump. Makes distorted guitar sound warm instead of fizzy.
|
||||
- **6 guitar presets:** electric_guitar, clean_guitar, crunch_guitar,
|
||||
distorted_guitar, orange_crunch, metal_guitar — all with proper cab sim
|
||||
- **Sitar synth** — Karplus-Strong with jawari bridge buzz, chikari
|
||||
sympathetic strings, variable damping
|
||||
- **Guitar strumming** — `Part.strum("Am", Duration.HALF)` with
|
||||
fretboard fingering lookup, down/up direction, adjustable strum speed
|
||||
- **World drums:** dhol (bhangra, chaal), dholak (qawwali, folk),
|
||||
mridangam (adi talam, korvai), djembe (standard, kuku, soli)
|
||||
— all with bandpass-filtered membrane noise for realistic drum head sound
|
||||
- **Metal drum kit** — clicky kick, bright snare, tight hats
|
||||
with 4 patterns (double kick, metal blast, metal groove, metal gallop)
|
||||
- 15 synth waveforms, 10 envelopes, 40+ instrument presets
|
||||
|
||||
## 0.33.0
|
||||
|
||||
- **Non-12-TET support** — `TET(n)` factory creates any equal temperament
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
[project]
|
||||
name = "pytheory"
|
||||
version = "0.33.0"
|
||||
version = "0.34.0"
|
||||
description = "Music Theory for Humans"
|
||||
readme = "README.md"
|
||||
license = "MIT"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""PyTheory: Music Theory for Humans."""
|
||||
|
||||
__version__ = "0.33.0"
|
||||
__version__ = "0.34.0"
|
||||
|
||||
from .tones import Tone, Interval
|
||||
from .systems import System, SYSTEMS, TET
|
||||
|
||||
@@ -275,6 +275,30 @@ def cmd_demo(args):
|
||||
"lead": ("pluck_synth", "none", 0.3, 0.2),
|
||||
"pad": ("strings_synth", "pad", 0.0),
|
||||
"bass_lp": 200, "reverb_type": "taj_mahal"},
|
||||
{"name": "Classical", "key": ("D", "minor"), "drums": "bolero",
|
||||
"fill": "bossa nova", "bpm": 72,
|
||||
"prog": ("i", "iv", "V", "i"),
|
||||
"lead": ("flute_synth", "strings", 0.35, 0.2),
|
||||
"pad": ("cello_synth", "bowed", -0.2),
|
||||
"bass_lp": 400, "reverb_type": "cathedral"},
|
||||
{"name": "Harpsichord Suite", "key": ("A", "minor"), "drums": "bolero",
|
||||
"fill": "bossa nova", "bpm": 92,
|
||||
"prog": ("i", "iv", "V", "i"),
|
||||
"lead": ("harpsichord_synth", "none", 0.2, 0.1),
|
||||
"pad": ("strings_synth", "pad", -0.3),
|
||||
"bass_lp": 500, "reverb_type": "plate"},
|
||||
{"name": "Bhangra", "key": ("G", "minor"), "drums": "bhangra",
|
||||
"fill": "rock", "bpm": 140,
|
||||
"prog": ("i", "iv", "V", "i"),
|
||||
"lead": ("sitar_synth", "none", 0.3, 0.2),
|
||||
"pad": ("strings_synth", "pad", 0.0),
|
||||
"bass_lp": 400, "reverb_type": "taj_mahal"},
|
||||
{"name": "Jazz Trio", "key": ("F", "major"), "drums": "swing",
|
||||
"fill": "jazz", "bpm": 100,
|
||||
"prog": ("I", "vi", "ii", "V"),
|
||||
"lead": ("trumpet_synth", "bowed", 0.3, 0.2),
|
||||
"pad": ("piano_synth", "none", -0.2),
|
||||
"bass_lp": 600, "reverb_type": "plate"},
|
||||
]
|
||||
|
||||
mood = random.choice(moods)
|
||||
|
||||
+1278
-6
File diff suppressed because it is too large
Load Diff
+493
-43
@@ -14,11 +14,8 @@ from typing import Optional
|
||||
INSTRUMENTS = {
|
||||
# ── Keys ──
|
||||
"piano": {
|
||||
"synth": "fm", "envelope": "piano",
|
||||
"fm_ratio": 1.0, "fm_index": 1.5,
|
||||
"detune": 5, "chorus": 0.1, "chorus_rate": 0.3,
|
||||
"lowpass": 6000, "saturation": 0.1,
|
||||
"vel_to_filter": 3000, "noise_mix": 0.02,
|
||||
"synth": "piano_synth", "envelope": "none",
|
||||
"vel_to_filter": 3000,
|
||||
},
|
||||
"electric_piano": { # Rhodes/Wurlitzer
|
||||
"synth": "fm", "envelope": "piano",
|
||||
@@ -26,16 +23,17 @@ INSTRUMENTS = {
|
||||
"detune": 6, "chorus": 0.2, "chorus_rate": 1.0,
|
||||
"lowpass": 4000, "saturation": 0.15,
|
||||
"tremolo_depth": 0.15, "tremolo_rate": 4.5,
|
||||
"analog": 0.2,
|
||||
},
|
||||
"organ": {
|
||||
"synth": "organ_synth", "envelope": "organ",
|
||||
"chorus": 0.2, "chorus_rate": 5.5,
|
||||
"lowpass": 5000,
|
||||
"phaser": 0.15, "phaser_rate": 0.4,
|
||||
"analog": 0.15,
|
||||
},
|
||||
"harpsichord": {
|
||||
"synth": "pluck_synth", "envelope": "none",
|
||||
"lowpass": 3500,
|
||||
"synth": "harpsichord_synth", "envelope": "none",
|
||||
},
|
||||
"celesta": {
|
||||
"synth": "fm", "envelope": "mallet",
|
||||
@@ -63,10 +61,8 @@ INSTRUMENTS = {
|
||||
"noise_mix": 0.03,
|
||||
},
|
||||
"cello": {
|
||||
"synth": "strings_synth", "envelope": "bowed",
|
||||
"detune": 2, "lowpass": 2500,
|
||||
"synth": "cello_synth", "envelope": "bowed",
|
||||
"humanize": 0.15, "vel_to_filter": 1000,
|
||||
"noise_mix": 0.02,
|
||||
},
|
||||
"contrabass": {
|
||||
"synth": "strings_synth", "envelope": "bowed",
|
||||
@@ -84,21 +80,18 @@ INSTRUMENTS = {
|
||||
|
||||
# ── Woodwinds ──
|
||||
"flute": {
|
||||
"synth": "sine", "envelope": "strings",
|
||||
"lowpass": 4000,
|
||||
"humanize": 0.2, "noise_mix": 0.08,
|
||||
"synth": "flute_synth", "envelope": "strings",
|
||||
"humanize": 0.2,
|
||||
"vel_to_filter": 2000,
|
||||
},
|
||||
"clarinet": {
|
||||
"synth": "square", "envelope": "strings",
|
||||
"lowpass": 3000,
|
||||
"humanize": 0.15, "noise_mix": 0.05,
|
||||
"synth": "clarinet_synth", "envelope": "strings",
|
||||
"humanize": 0.15,
|
||||
"vel_to_filter": 1500,
|
||||
},
|
||||
"oboe": {
|
||||
"synth": "saw", "envelope": "strings",
|
||||
"lowpass": 3500, "lowpass_q": 1.2,
|
||||
"humanize": 0.15, "noise_mix": 0.04,
|
||||
"synth": "oboe_synth", "envelope": "strings",
|
||||
"humanize": 0.15,
|
||||
"vel_to_filter": 1000,
|
||||
},
|
||||
"bassoon": {
|
||||
@@ -110,16 +103,13 @@ INSTRUMENTS = {
|
||||
|
||||
# ── Brass ──
|
||||
"trumpet": {
|
||||
"synth": "saw", "envelope": "bowed",
|
||||
"detune": 3, "lowpass": 4000, "lowpass_q": 1.1,
|
||||
"synth": "trumpet_synth", "envelope": "bowed",
|
||||
"humanize": 0.15, "vel_to_filter": 2000,
|
||||
"saturation": 0.1,
|
||||
},
|
||||
"trombone": {
|
||||
"synth": "saw", "envelope": "strings",
|
||||
"detune": 3, "lowpass": 2500,
|
||||
"synth": "trumpet_synth", "envelope": "strings",
|
||||
"lowpass": 2500,
|
||||
"humanize": 0.15, "vel_to_filter": 1500,
|
||||
"saturation": 0.1,
|
||||
},
|
||||
"french_horn": {
|
||||
"synth": "saw", "envelope": "strings",
|
||||
@@ -143,34 +133,61 @@ INSTRUMENTS = {
|
||||
|
||||
# ── Plucked ──
|
||||
"acoustic_guitar": {
|
||||
"synth": "pluck_synth", "envelope": "none",
|
||||
"lowpass": 4000,
|
||||
"humanize": 0.2,
|
||||
"synth": "acoustic_guitar_synth", "envelope": "none",
|
||||
"humanize": 0.2, "saturation": 0.05,
|
||||
},
|
||||
"electric_guitar": {
|
||||
"synth": "saw", "envelope": "pluck",
|
||||
"detune": 5, "lowpass": 3500,
|
||||
"synth": "electric_guitar_synth", "envelope": "none",
|
||||
"cabinet": 1.0, "cabinet_brightness": 0.6,
|
||||
"humanize": 0.15,
|
||||
},
|
||||
"clean_guitar": {
|
||||
"synth": "electric_guitar_synth", "envelope": "none",
|
||||
"cabinet": 1.0, "cabinet_brightness": 0.7,
|
||||
"chorus": 0.15, "chorus_rate": 1.0,
|
||||
"reverb": 0.2, "reverb_type": "spring",
|
||||
"humanize": 0.15,
|
||||
},
|
||||
"crunch_guitar": {
|
||||
"synth": "electric_guitar_synth", "envelope": "none",
|
||||
"saturation": 0.3,
|
||||
"distortion": 0.5, "distortion_drive": 4.0,
|
||||
"cabinet": 1.0, "cabinet_brightness": 0.5,
|
||||
"humanize": 0.15,
|
||||
},
|
||||
"distorted_guitar": {
|
||||
"synth": "saw", "envelope": "pluck",
|
||||
"detune": 8, "distortion": 0.6, "distortion_drive": 5.0,
|
||||
"lowpass": 3000, "saturation": 0.3,
|
||||
"synth": "electric_guitar_synth", "envelope": "none",
|
||||
"saturation": 0.3,
|
||||
"distortion": 0.7, "distortion_drive": 5.0,
|
||||
"cabinet": 1.0, "cabinet_brightness": 0.5,
|
||||
"humanize": 0.15,
|
||||
},
|
||||
"orange_crunch": {
|
||||
"synth": "electric_guitar_synth", "envelope": "none",
|
||||
"saturation": 0.4,
|
||||
"distortion": 0.7, "distortion_drive": 6.0,
|
||||
"cabinet": 1.0, "cabinet_brightness": 0.4,
|
||||
"humanize": 0.15,
|
||||
},
|
||||
"metal_guitar": {
|
||||
"synth": "electric_guitar_synth", "envelope": "none",
|
||||
"saturation": 0.35,
|
||||
"distortion": 0.8, "distortion_drive": 7.0,
|
||||
"cabinet": 1.0, "cabinet_brightness": 0.5,
|
||||
"highpass": 80,
|
||||
"detune": 4,
|
||||
"humanize": 0.1,
|
||||
},
|
||||
"bass_guitar": {
|
||||
"synth": "triangle", "envelope": "pluck",
|
||||
"lowpass": 1000,
|
||||
"humanize": 0.1, "sub_osc": 0.2,
|
||||
"synth": "bass_guitar_synth", "envelope": "none",
|
||||
"humanize": 0.1, "sub_osc": 0.15,
|
||||
},
|
||||
"upright_bass": {
|
||||
"synth": "triangle", "envelope": "pluck",
|
||||
"lowpass": 800,
|
||||
"synth": "upright_bass_synth", "envelope": "none",
|
||||
"humanize": 0.15, "saturation": 0.1,
|
||||
},
|
||||
"harp": {
|
||||
"synth": "pluck_synth", "envelope": "none",
|
||||
"lowpass": 5000,
|
||||
"synth": "harp_synth", "envelope": "none",
|
||||
"reverb": 0.3, "reverb_type": "plate",
|
||||
},
|
||||
"sitar": {
|
||||
@@ -183,6 +200,11 @@ INSTRUMENTS = {
|
||||
"lowpass": 4000,
|
||||
"reverb": 0.2,
|
||||
},
|
||||
"sitar": {
|
||||
"synth": "sitar_synth", "envelope": "none",
|
||||
"lowpass": 4500,
|
||||
"humanize": 0.2,
|
||||
},
|
||||
|
||||
# ── Synth presets ──
|
||||
"synth_lead": {
|
||||
@@ -191,6 +213,7 @@ INSTRUMENTS = {
|
||||
"delay": 0.2, "delay_time": 0.25, "delay_feedback": 0.3,
|
||||
"filter_attack": 0.01, "filter_decay": 0.3,
|
||||
"filter_sustain": 0.2, "filter_amount": 3000,
|
||||
"analog": 0.3,
|
||||
},
|
||||
"synth_pad": {
|
||||
"synth": "supersaw", "envelope": "pad",
|
||||
@@ -198,6 +221,7 @@ INSTRUMENTS = {
|
||||
"chorus": 0.2,
|
||||
"phaser": 0.3, "phaser_rate": 0.3,
|
||||
"sub_osc": 0.2,
|
||||
"analog": 0.4,
|
||||
},
|
||||
"synth_bass": {
|
||||
"synth": "saw", "envelope": "pluck",
|
||||
@@ -205,6 +229,7 @@ INSTRUMENTS = {
|
||||
"filter_attack": 0.005, "filter_decay": 0.2,
|
||||
"filter_sustain": 0.0, "filter_amount": 2000,
|
||||
"sub_osc": 0.4,
|
||||
"analog": 0.2,
|
||||
},
|
||||
"acid_bass": {
|
||||
"synth": "saw", "envelope": "pad",
|
||||
@@ -214,6 +239,7 @@ INSTRUMENTS = {
|
||||
"filter_attack": 0.005, "filter_decay": 0.15,
|
||||
"filter_sustain": 0.0, "filter_amount": 4000,
|
||||
"vel_to_filter": 3000,
|
||||
"analog": 0.3,
|
||||
},
|
||||
"808_bass": {
|
||||
"synth": "sine", "envelope": "pluck",
|
||||
@@ -231,8 +257,7 @@ INSTRUMENTS = {
|
||||
"reverb": 0.3, "reverb_type": "plate",
|
||||
},
|
||||
"marimba": {
|
||||
"synth": "sine", "envelope": "mallet",
|
||||
"lowpass": 3000,
|
||||
"synth": "marimba_synth", "envelope": "mallet",
|
||||
},
|
||||
"xylophone": {
|
||||
"synth": "fm", "envelope": "pluck",
|
||||
@@ -376,6 +401,34 @@ class DrumSound(Enum):
|
||||
AGOGO_LOW = 68
|
||||
GUIRO = 73
|
||||
MARACAS = 70
|
||||
# Tabla sounds
|
||||
TABLA_NA = 86 # sharp dayan (right drum) rim hit
|
||||
TABLA_TIN = 87 # open dayan ring
|
||||
TABLA_GE = 88 # deep bayan (left drum) bass
|
||||
TABLA_DHA = 89 # both drums (Na + Ge)
|
||||
TABLA_TIT = 90 # light dayan flick
|
||||
TABLA_KE = 91 # muted bayan slap
|
||||
# Dhol sounds
|
||||
DHOL_DAGGA = 92 # heavy bass side (dagga stick)
|
||||
DHOL_TILLI = 93 # thin treble side (tilli stick)
|
||||
DHOL_BOTH = 94 # both sides
|
||||
# Dholak sounds
|
||||
DHOLAK_GE = 95 # bass side (open palm)
|
||||
DHOLAK_NA = 96 # treble side (fingers)
|
||||
DHOLAK_TIT = 97 # light treble tap
|
||||
# Mridangam sounds
|
||||
MRIDANGAM_THAM = 98 # bass stroke (thoppi/left head)
|
||||
MRIDANGAM_NAM = 99 # treble ring (valanthalai/right head)
|
||||
MRIDANGAM_DIN = 100 # both heads
|
||||
MRIDANGAM_THA = 101 # muted treble
|
||||
# Djembe sounds
|
||||
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)
|
||||
# Metal kit — tighter, punchier, more attack
|
||||
METAL_KICK = 105 # clicky, punchy, tight
|
||||
METAL_SNARE = 106 # crack, bright, cutting
|
||||
METAL_HAT = 107 # tight, short, precise
|
||||
|
||||
|
||||
class _Hit:
|
||||
@@ -1313,6 +1366,314 @@ Pattern._PRESETS["flamenco"] = dict(
|
||||
],
|
||||
)
|
||||
|
||||
# ── Tabla patterns ────────────────────────────────────────────────────────
|
||||
# Shortcuts for tabla sounds
|
||||
TNA = DrumSound.TABLA_NA
|
||||
TTI = DrumSound.TABLA_TIN
|
||||
TGE = DrumSound.TABLA_GE
|
||||
TDHA = DrumSound.TABLA_DHA
|
||||
TTIT = DrumSound.TABLA_TIT
|
||||
TKE = DrumSound.TABLA_KE
|
||||
|
||||
# Teental — the most common taal (16 beats / 4+4+4+4)
|
||||
Pattern._PRESETS["teental"] = dict(
|
||||
name="teental",
|
||||
time_signature="4/4",
|
||||
beats=16.0,
|
||||
hits=[
|
||||
# Vibhag 1: Dha Dhin Dhin Dha
|
||||
_h(TDHA, 0.0), _h(TNA, 1.0), _h(TNA, 2.0), _h(TDHA, 3.0),
|
||||
# Vibhag 2: Dha Dhin Dhin Dha
|
||||
_h(TDHA, 4.0), _h(TNA, 5.0), _h(TNA, 6.0), _h(TDHA, 7.0),
|
||||
# Vibhag 3 (khali): Dha Tin Tin Ta
|
||||
_h(TDHA, 8.0), _h(TTI, 9.0), _h(TTI, 10.0), _h(TNA, 11.0),
|
||||
# Vibhag 4: Dha Dhin Dhin Dha
|
||||
_h(TDHA, 12.0), _h(TNA, 13.0), _h(TNA, 14.0), _h(TDHA, 15.0),
|
||||
],
|
||||
)
|
||||
|
||||
# Jhaptaal — 10 beats (2+3+2+3)
|
||||
Pattern._PRESETS["jhaptaal"] = dict(
|
||||
name="jhaptaal",
|
||||
time_signature="4/4",
|
||||
beats=10.0,
|
||||
hits=[
|
||||
# Dhi Na | Dhi Dhi Na | Ti Na | Dhi Dhi Na
|
||||
_h(TDHA, 0.0), _h(TNA, 1.0),
|
||||
_h(TDHA, 2.0), _h(TDHA, 3.0), _h(TNA, 4.0),
|
||||
_h(TTI, 5.0), _h(TNA, 6.0),
|
||||
_h(TDHA, 7.0), _h(TDHA, 8.0), _h(TNA, 9.0),
|
||||
],
|
||||
)
|
||||
|
||||
# Rupak taal — 7 beats (3+2+2), starts on khali (unusual)
|
||||
Pattern._PRESETS["rupak"] = dict(
|
||||
name="rupak",
|
||||
time_signature="7/4",
|
||||
beats=7.0,
|
||||
hits=[
|
||||
# Tin Tin Na | Dhi Na | Dhi Na
|
||||
_h(TTI, 0.0), _h(TTI, 1.0), _h(TNA, 2.0),
|
||||
_h(TDHA, 3.0), _h(TNA, 4.0),
|
||||
_h(TDHA, 5.0), _h(TNA, 6.0),
|
||||
],
|
||||
)
|
||||
|
||||
# Dadra — 6 beats (3+3), light and folk
|
||||
Pattern._PRESETS["dadra"] = dict(
|
||||
name="dadra",
|
||||
time_signature="6/4",
|
||||
beats=6.0,
|
||||
hits=[
|
||||
# Dha Dhi Na | Dha Tin Na
|
||||
_h(TDHA, 0.0), _h(TNA, 1.0), _h(TNA, 2.0),
|
||||
_h(TDHA, 3.0), _h(TTI, 4.0), _h(TNA, 5.0),
|
||||
],
|
||||
)
|
||||
|
||||
# Keherwa — 8 beats (4+4), the most common light taal
|
||||
Pattern._PRESETS["keherwa"] = dict(
|
||||
name="keherwa",
|
||||
time_signature="4/4",
|
||||
beats=8.0,
|
||||
hits=[
|
||||
# Dha Ge Na Ti | Na Ke Dhi Na
|
||||
_h(TDHA, 0.0), _h(TGE, 1.0), _h(TNA, 2.0), _h(TTIT, 3.0),
|
||||
_h(TNA, 4.0), _h(TKE, 5.0), _h(TDHA, 6.0), _h(TNA, 7.0),
|
||||
],
|
||||
)
|
||||
|
||||
# Tabla solo theka — fast 16th note pattern for rhythmic display
|
||||
Pattern._PRESETS["tabla solo"] = dict(
|
||||
name="tabla solo",
|
||||
time_signature="4/4",
|
||||
beats=4.0,
|
||||
hits=[
|
||||
_h(TDHA, 0.0), _h(TTIT, 0.25), _h(TTIT, 0.5), _h(TKE, 0.75),
|
||||
_h(TNA, 1.0), _h(TTIT, 1.25), _h(TGE, 1.5), _h(TNA, 1.75),
|
||||
_h(TDHA, 2.0), _h(TNA, 2.25), _h(TTI, 2.5), _h(TNA, 2.75),
|
||||
_h(TDHA, 3.0), _h(TTIT, 3.5), _h(TGE, 3.75),
|
||||
],
|
||||
)
|
||||
|
||||
# ── Metal kit patterns ────────────────────────────────────────────────────
|
||||
MK = DrumSound.METAL_KICK
|
||||
MS = DrumSound.METAL_SNARE
|
||||
MH = DrumSound.METAL_HAT
|
||||
|
||||
# Metal double kick — the classic thrash/death metal beat
|
||||
Pattern._PRESETS["double kick"] = dict(
|
||||
name="double kick",
|
||||
time_signature="4/4",
|
||||
beats=4.0,
|
||||
hits=[
|
||||
# Double kick 16ths, snare on 2 and 4, tight hats
|
||||
*[_h(MK, i * 0.25) for i in range(16)],
|
||||
_h(MS, 1.0), _h(MS, 3.0),
|
||||
*[_h(MH, i * 0.5) for i in range(8)],
|
||||
],
|
||||
)
|
||||
|
||||
# Metal blast — blast beat with metal kit sounds
|
||||
Pattern._PRESETS["metal blast"] = dict(
|
||||
name="metal blast",
|
||||
time_signature="4/4",
|
||||
beats=4.0,
|
||||
hits=[
|
||||
*[_h(MK, i * 0.25) for i in range(16)],
|
||||
*[_h(MS, i * 0.25) for i in range(16)],
|
||||
*[_h(MH, i * 0.25) for i in range(16)],
|
||||
],
|
||||
)
|
||||
|
||||
# Metal groove — half time with double kick fills
|
||||
Pattern._PRESETS["metal groove"] = dict(
|
||||
name="metal groove",
|
||||
time_signature="4/4",
|
||||
beats=4.0,
|
||||
hits=[
|
||||
_h(MK, 0.0), _h(MH, 0.0),
|
||||
_h(MH, 0.5),
|
||||
_h(MS, 1.0), _h(MH, 1.0),
|
||||
_h(MK, 1.5), _h(MH, 1.5),
|
||||
_h(MK, 2.0), _h(MH, 2.0),
|
||||
_h(MK, 2.25),
|
||||
_h(MK, 2.5), _h(MH, 2.5),
|
||||
_h(MK, 2.75),
|
||||
_h(MS, 3.0), _h(MH, 3.0),
|
||||
_h(MH, 3.5),
|
||||
],
|
||||
)
|
||||
|
||||
# Metal gallop — the classic Iron Maiden triplet feel
|
||||
Pattern._PRESETS["metal gallop"] = dict(
|
||||
name="metal gallop",
|
||||
time_signature="4/4",
|
||||
beats=4.0,
|
||||
hits=[
|
||||
_h(MK, 0.0), _h(MH, 0.0),
|
||||
_h(MK, 0.33), _h(MK, 0.67),
|
||||
_h(MS, 1.0), _h(MH, 1.0),
|
||||
_h(MK, 1.33), _h(MK, 1.67),
|
||||
_h(MK, 2.0), _h(MH, 2.0),
|
||||
_h(MK, 2.33), _h(MK, 2.67),
|
||||
_h(MS, 3.0), _h(MH, 3.0),
|
||||
_h(MK, 3.33), _h(MK, 3.67),
|
||||
],
|
||||
)
|
||||
|
||||
# Tabla tiri-kita — rapid 16th-note dayan patter
|
||||
Pattern._PRESETS["tiri kita"] = dict(
|
||||
name="tiri kita",
|
||||
time_signature="4/4",
|
||||
beats=4.0,
|
||||
hits=[
|
||||
# Ti ri ki ta | dha ti ri ki | ta ka dhi na | dha — ti dha
|
||||
_h(TTIT, 0.0), _h(TTIT, 0.25), _h(TKE, 0.5), _h(TNA, 0.75),
|
||||
_h(TDHA, 1.0), _h(TTIT, 1.25), _h(TTIT, 1.5), _h(TKE, 1.75),
|
||||
_h(TNA, 2.0), _h(TKE, 2.25), _h(TDHA, 2.5), _h(TNA, 2.75),
|
||||
_h(TDHA, 3.0), _h(TTIT, 3.5), _h(TDHA, 3.75),
|
||||
],
|
||||
)
|
||||
|
||||
# ── Dhol patterns ────────────────────────────────────────────────────────
|
||||
DD = DrumSound.DHOL_DAGGA
|
||||
DT = DrumSound.DHOL_TILLI
|
||||
DB = DrumSound.DHOL_BOTH
|
||||
|
||||
# Bhangra — the classic punjabi groove
|
||||
Pattern._PRESETS["bhangra"] = dict(
|
||||
name="bhangra",
|
||||
time_signature="4/4",
|
||||
beats=4.0,
|
||||
hits=[
|
||||
# Dagga on 1, tilli fills, both on 3
|
||||
_h(DD, 0.0), _h(DT, 0.5), _h(DT, 0.75),
|
||||
_h(DT, 1.0), _h(DT, 1.5),
|
||||
_h(DB, 2.0), _h(DT, 2.5), _h(DT, 2.75),
|
||||
_h(DD, 3.0), _h(DT, 3.25), _h(DT, 3.5), _h(DT, 3.75),
|
||||
],
|
||||
)
|
||||
|
||||
# Dhol chaal — driving folk pattern
|
||||
Pattern._PRESETS["dhol chaal"] = dict(
|
||||
name="dhol chaal",
|
||||
time_signature="4/4",
|
||||
beats=4.0,
|
||||
hits=[
|
||||
_h(DB, 0.0), _h(DT, 0.25), _h(DD, 0.5),
|
||||
_h(DT, 1.0), _h(DT, 1.25), _h(DT, 1.5), _h(DD, 1.75),
|
||||
_h(DB, 2.0), _h(DT, 2.25), _h(DD, 2.5),
|
||||
_h(DT, 3.0), _h(DT, 3.25), _h(DT, 3.5), _h(DT, 3.75),
|
||||
],
|
||||
)
|
||||
|
||||
# ── Dholak patterns ─────────────────────────────────────────────────────
|
||||
DKG = DrumSound.DHOLAK_GE
|
||||
DKN = DrumSound.DHOLAK_NA
|
||||
DKT = DrumSound.DHOLAK_TIT
|
||||
|
||||
# Qawwali — driving devotional pattern
|
||||
Pattern._PRESETS["qawwali"] = dict(
|
||||
name="qawwali",
|
||||
time_signature="4/4",
|
||||
beats=4.0,
|
||||
hits=[
|
||||
_h(DKG, 0.0), _h(DKN, 0.5), _h(DKT, 0.75),
|
||||
_h(DKN, 1.0), _h(DKG, 1.5),
|
||||
_h(DKG, 2.0), _h(DKN, 2.5), _h(DKT, 2.75),
|
||||
_h(DKN, 3.0), _h(DKT, 3.25), _h(DKN, 3.5), _h(DKG, 3.75),
|
||||
],
|
||||
)
|
||||
|
||||
# Dholak folk — light folk music pattern
|
||||
Pattern._PRESETS["dholak folk"] = dict(
|
||||
name="dholak folk",
|
||||
time_signature="4/4",
|
||||
beats=4.0,
|
||||
hits=[
|
||||
_h(DKG, 0.0), _h(DKN, 1.0), _h(DKT, 1.5),
|
||||
_h(DKG, 2.0), _h(DKN, 3.0), _h(DKT, 3.5),
|
||||
],
|
||||
)
|
||||
|
||||
# ── Mridangam patterns ──────────────────────────────────────────────────
|
||||
MTH = DrumSound.MRIDANGAM_THAM
|
||||
MN = DrumSound.MRIDANGAM_NAM
|
||||
MD = DrumSound.MRIDANGAM_DIN
|
||||
MTA = DrumSound.MRIDANGAM_THA
|
||||
|
||||
# Adi talam — the fundamental Carnatic rhythm (8 beats: 4+2+2)
|
||||
Pattern._PRESETS["adi talam"] = dict(
|
||||
name="adi talam",
|
||||
time_signature="4/4",
|
||||
beats=8.0,
|
||||
hits=[
|
||||
# Tha Din | Tha ka | Dhi na | Tha ka
|
||||
_h(MD, 0.0), _h(MN, 1.0),
|
||||
_h(MTH, 2.0), _h(MTA, 3.0),
|
||||
_h(MD, 4.0), _h(MN, 5.0),
|
||||
_h(MTH, 6.0), _h(MTA, 7.0),
|
||||
],
|
||||
)
|
||||
|
||||
# Mridangam korvai — rhythmic cadence pattern
|
||||
Pattern._PRESETS["mridangam korvai"] = dict(
|
||||
name="mridangam korvai",
|
||||
time_signature="4/4",
|
||||
beats=4.0,
|
||||
hits=[
|
||||
_h(MD, 0.0), _h(MN, 0.25), _h(MTA, 0.5), _h(MN, 0.75),
|
||||
_h(MTH, 1.0), _h(MN, 1.25), _h(MN, 1.5), _h(MTH, 1.75),
|
||||
_h(MD, 2.0), _h(MTA, 2.25), _h(MN, 2.5), _h(MTA, 2.75),
|
||||
_h(MD, 3.0), _h(MN, 3.5), _h(MD, 3.75),
|
||||
],
|
||||
)
|
||||
|
||||
# ── Djembe patterns ─────────────────────────────────────────────────────
|
||||
JB = DrumSound.DJEMBE_BASS
|
||||
JT = DrumSound.DJEMBE_TONE
|
||||
JS = DrumSound.DJEMBE_SLAP
|
||||
|
||||
# Djembe — standard West African pattern
|
||||
Pattern._PRESETS["djembe"] = dict(
|
||||
name="djembe",
|
||||
time_signature="4/4",
|
||||
beats=4.0,
|
||||
hits=[
|
||||
_h(JB, 0.0), _h(JT, 0.5), _h(JT, 0.75),
|
||||
_h(JS, 1.0), _h(JT, 1.5),
|
||||
_h(JB, 2.0), _h(JT, 2.5), _h(JT, 2.75),
|
||||
_h(JS, 3.0), _h(JT, 3.25), _h(JS, 3.5),
|
||||
],
|
||||
)
|
||||
|
||||
# Kuku — traditional Guinean harvest dance rhythm
|
||||
Pattern._PRESETS["kuku"] = dict(
|
||||
name="kuku",
|
||||
time_signature="4/4",
|
||||
beats=4.0,
|
||||
hits=[
|
||||
_h(JS, 0.0), _h(JS, 0.5),
|
||||
_h(JT, 1.0), _h(JB, 1.5),
|
||||
_h(JS, 2.0), _h(JS, 2.5),
|
||||
_h(JT, 3.0), _h(JT, 3.25), _h(JB, 3.5),
|
||||
],
|
||||
)
|
||||
|
||||
# Soli — powerful Mandinka rhythm
|
||||
Pattern._PRESETS["soli"] = dict(
|
||||
name="soli",
|
||||
time_signature="4/4",
|
||||
beats=4.0,
|
||||
hits=[
|
||||
_h(JB, 0.0), _h(JT, 0.25), _h(JS, 0.5), _h(JT, 0.75),
|
||||
_h(JB, 1.0), _h(JS, 1.5),
|
||||
_h(JB, 2.0), _h(JT, 2.25), _h(JS, 2.5), _h(JT, 2.75),
|
||||
_h(JB, 3.0), _h(JT, 3.5), _h(JS, 3.75),
|
||||
],
|
||||
)
|
||||
|
||||
# ── Fill presets ──────────────────────────────────────────────────────────
|
||||
|
||||
Pattern._FILLS["rock"] = dict(
|
||||
@@ -1632,6 +1993,9 @@ class Part:
|
||||
tremolo_rate: float = 5.0,
|
||||
phaser: float = 0.0,
|
||||
phaser_rate: float = 0.5,
|
||||
cabinet: float = 0.0,
|
||||
cabinet_brightness: float = 0.5,
|
||||
analog: float = 0.0,
|
||||
fm_ratio: float = 2.0,
|
||||
fm_index: float = 3.0):
|
||||
self.name = name
|
||||
@@ -1675,9 +2039,13 @@ class Part:
|
||||
self.tremolo_rate = tremolo_rate
|
||||
self.phaser_mix = phaser
|
||||
self.phaser_rate = phaser_rate
|
||||
self.cabinet = cabinet
|
||||
self.cabinet_brightness = cabinet_brightness
|
||||
self.analog = analog
|
||||
self.fm_ratio = fm_ratio
|
||||
self.fm_index = fm_index
|
||||
self._system = "western" # default, overridden by Score.part()
|
||||
self._fretboard = None # set by Score.part(fretboard=...)
|
||||
self.notes: list[Note] = []
|
||||
self._drum_hits: list[_Hit] = []
|
||||
self._drum_pattern_beats: float = 0.0
|
||||
@@ -1761,6 +2129,7 @@ class Part:
|
||||
"delay_mix": self.delay_mix, "delay_time": self.delay_time,
|
||||
"delay_feedback": self.delay_feedback,
|
||||
"phaser_mix": self.phaser_mix, "phaser_rate": self.phaser_rate,
|
||||
"cabinet": self.cabinet, "cabinet_brightness": self.cabinet_brightness,
|
||||
"highpass": self.highpass, "highpass_q": self.highpass_q,
|
||||
"lowpass": self.lowpass, "lowpass_q": self.lowpass_q,
|
||||
"distortion_mix": self.distortion_mix,
|
||||
@@ -1978,6 +2347,80 @@ class Part:
|
||||
|
||||
return self
|
||||
|
||||
def strum(self, chord_name: str, duration=Duration.QUARTER, *,
|
||||
direction: str = "down", velocity: int = 100,
|
||||
strum_time: float = 0.08) -> "Part":
|
||||
"""Strum a chord using the part's fretboard fingering.
|
||||
|
||||
Looks up the chord on the fretboard, gets the fingering, and
|
||||
adds each string as a rapid sequence with tiny time offsets —
|
||||
like a real guitar strum. Muted strings are skipped.
|
||||
|
||||
Args:
|
||||
chord_name: Chord name (e.g. ``"Am"``, ``"G"``, ``"D"``).
|
||||
duration: Total duration of the strum (default QUARTER).
|
||||
direction: ``"down"`` (low→high, default) or ``"up"`` (high→low).
|
||||
velocity: Base velocity (each string gets slight variation).
|
||||
strum_time: Time in beats for the full strum sweep
|
||||
(default 0.03 = very fast). Larger values = slower,
|
||||
more audible strum. Try 0.1 for a lazy strum.
|
||||
|
||||
Returns:
|
||||
Self for chaining.
|
||||
|
||||
Example::
|
||||
|
||||
>>> guitar = score.part("guitar", instrument="acoustic_guitar",
|
||||
... fretboard=Fretboard.guitar())
|
||||
>>> guitar.strum("Am", Duration.HALF)
|
||||
>>> guitar.strum("G", Duration.HALF, direction="up")
|
||||
"""
|
||||
if self._fretboard is None:
|
||||
raise ValueError(
|
||||
"Cannot strum without a fretboard. "
|
||||
"Set fretboard= when creating the part."
|
||||
)
|
||||
from .charts import CHARTS
|
||||
|
||||
# Get the fingering
|
||||
system_name = self._system if isinstance(self._system, str) else "western"
|
||||
if system_name in CHARTS:
|
||||
chart = CHARTS[system_name]
|
||||
else:
|
||||
chart = CHARTS["western"]
|
||||
if chord_name in chart:
|
||||
fingering = chart[chord_name].fingering(fretboard=self._fretboard)
|
||||
else:
|
||||
# Try fretboard.chord() as fallback
|
||||
fingering = self._fretboard.chord(chord_name)
|
||||
|
||||
# Get the sounding tones (skips muted strings)
|
||||
tones = fingering.tones # list of Tone objects, high to low
|
||||
|
||||
if not tones:
|
||||
self.rest(duration)
|
||||
return self
|
||||
|
||||
# Order: down strum = low to high (reverse since tones are high-to-low)
|
||||
if direction == "down":
|
||||
strum_tones = list(reversed(tones))
|
||||
else:
|
||||
strum_tones = list(tones)
|
||||
|
||||
if hasattr(duration, 'value'):
|
||||
total_beats = duration.value
|
||||
else:
|
||||
total_beats = float(duration)
|
||||
|
||||
# Build a Chord — all strings ring together through the
|
||||
# shared body resonance, like a real guitar
|
||||
from .chords import Chord as ChordClass
|
||||
chord_obj = ChordClass(tones=strum_tones)
|
||||
|
||||
self.add(chord_obj, total_beats, velocity=velocity)
|
||||
|
||||
return self
|
||||
|
||||
@property
|
||||
def is_drums(self) -> bool:
|
||||
"""True if this part contains drum hits."""
|
||||
@@ -2180,8 +2623,12 @@ class Score:
|
||||
tremolo_rate: float = None,
|
||||
phaser: float = None,
|
||||
phaser_rate: float = None,
|
||||
cabinet: float = None,
|
||||
cabinet_brightness: float = None,
|
||||
analog: float = None,
|
||||
fm_ratio: float = None,
|
||||
fm_index: float = None) -> Part:
|
||||
fm_index: float = None,
|
||||
fretboard=None) -> Part:
|
||||
"""Create a named part with its own synth voice and effects.
|
||||
|
||||
Args:
|
||||
@@ -2290,6 +2737,8 @@ class Score:
|
||||
"saturation": saturation,
|
||||
"tremolo_depth": tremolo_depth, "tremolo_rate": tremolo_rate,
|
||||
"phaser": phaser, "phaser_rate": phaser_rate,
|
||||
"cabinet": cabinet, "cabinet_brightness": cabinet_brightness,
|
||||
"analog": analog,
|
||||
"fm_ratio": fm_ratio, "fm_index": fm_index,
|
||||
}
|
||||
for k, v in _locals.items():
|
||||
@@ -2300,6 +2749,7 @@ class Score:
|
||||
|
||||
p = Part(name, **merged)
|
||||
p._system = self.system
|
||||
p._fretboard = fretboard
|
||||
self.parts[name] = p
|
||||
return p
|
||||
|
||||
|
||||
+4
-10
@@ -5320,7 +5320,7 @@ def test_supersaw_wave():
|
||||
@needs_portaudio
|
||||
def test_all_synths_in_enum():
|
||||
from pytheory.play import Synth
|
||||
assert len(Synth) == 13
|
||||
assert len(Synth) == 27
|
||||
for s in Synth:
|
||||
wave = s(440, n_samples=1000)
|
||||
assert len(wave) == 1000
|
||||
@@ -6467,11 +6467,8 @@ def test_instrument_piano():
|
||||
from pytheory import Score, Duration
|
||||
score = Score("4/4", bpm=120)
|
||||
p = score.part("p", instrument="piano")
|
||||
assert p.synth == "fm"
|
||||
assert p.envelope == "piano"
|
||||
assert p.detune == 5
|
||||
assert p.lowpass == 6000
|
||||
assert p.chorus_mix == 0.1
|
||||
assert p.synth == "piano_synth"
|
||||
assert p.vel_to_filter == 3000
|
||||
|
||||
|
||||
def test_instrument_violin():
|
||||
@@ -6488,12 +6485,9 @@ def test_instrument_violin():
|
||||
def test_instrument_override():
|
||||
from pytheory import Score
|
||||
score = Score("4/4", bpm=120)
|
||||
# Explicit synth overrides the preset's "fm"
|
||||
# Explicit synth overrides the preset
|
||||
p = score.part("p", instrument="piano", synth="saw")
|
||||
assert p.synth == "saw"
|
||||
# Other preset values still apply
|
||||
assert p.envelope == "piano"
|
||||
assert p.detune == 5
|
||||
|
||||
|
||||
def test_instrument_unknown_raises():
|
||||
|
||||
Reference in New Issue
Block a user