mirror of
https://github.com/kennethreitz/pytheory.git
synced 2026-06-05 23:00:20 +00:00
Compare commits
318 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 63362df697 | |||
| 755b33a63b | |||
| 40901d603d | |||
| 9b3cbd9065 | |||
| 0911947971 | |||
| c2f748d5f3 | |||
| 7a6942c8e4 | |||
| db7fabf985 | |||
| a07b7e7cea | |||
| 7245cd0e51 | |||
| 9e85a48d0e | |||
| 95b7bd830c | |||
| 150c57ed3d | |||
| d35d2b12f3 | |||
| 2084473788 | |||
| 970c730012 | |||
| 5f94e1939b | |||
| b649b2e659 | |||
| ed6ba2ab9f | |||
| fd317f9cfd | |||
| c57e29fe28 | |||
| 938024bfa2 | |||
| acc92f9a60 | |||
| 0d340dad30 | |||
| 1762500108 | |||
| ac2801d07d | |||
| c49ec27b1b | |||
| 5f4070c4a7 | |||
| 8735393aaa | |||
| 12f15d5138 | |||
| 20fc5e40b8 | |||
| 91d16595b7 | |||
| 54659d39b1 | |||
| 7cb2c166f9 | |||
| ba2038d7ff | |||
| 198fded20e | |||
| 51159e309a | |||
| 54df949089 | |||
| 30c70da468 | |||
| c633bd6f61 | |||
| bc652c37d0 | |||
| 417d9a6908 | |||
| f7d8f08446 | |||
| b8d1fe5e81 | |||
| 2612444146 | |||
| 48a954d063 | |||
| 8a07be23e6 | |||
| d771117d5c | |||
| 1ae9404f07 | |||
| 2e5b18de2e | |||
| 248594fb21 | |||
| 3ce890c54c | |||
| 499c49b6eb | |||
| f85504b456 | |||
| d0624f8b78 | |||
| fb37a7c27b | |||
| fb36e75a42 | |||
| 81b54d2394 | |||
| fa6a3090cb | |||
| 58286ddb69 | |||
| 6d137be9f5 | |||
| 383802a1e1 | |||
| 7f2aeb2395 | |||
| 16b4c7d1fa | |||
| b9ee5c9cde | |||
| 7375d58209 | |||
| 6316a6c910 | |||
| 237cfe171c | |||
| 1751f97617 | |||
| 943a12b3bb | |||
| 04d2de3e70 | |||
| ead42751ef | |||
| 8dee0d00d8 | |||
| de112e0d9f | |||
| f469ad90f8 | |||
| bab7f39304 | |||
| 62557ba534 | |||
| 8b50a9c325 | |||
| 7e9caac70b | |||
| a0756b3172 | |||
| e3dd706032 | |||
| 9b412906bc | |||
| 54e0421997 | |||
| 109343ad30 | |||
| 28e84de566 | |||
| d353d64298 | |||
| 7ee02e7ed2 | |||
| a5c9a46eb2 | |||
| f9c63ec360 | |||
| b9e88b77d8 | |||
| 1910b09132 | |||
| 0c5287450b | |||
| 5ac1873d83 | |||
| 9fafca2b08 | |||
| af044f68ca | |||
| 60f697f846 | |||
| 7d678e364e | |||
| 3a8d829010 | |||
| 2a67906937 | |||
| b9dcad0454 | |||
| db9726168a | |||
| 26af923789 | |||
| 72e18a9bec | |||
| 7d56ed7a2c | |||
| 6efa4f18ce | |||
| 06fc4cabb7 | |||
| d3a93c18b3 | |||
| 0e10359236 | |||
| df00c3436d | |||
| 2f02df15b8 | |||
| a2740b8d57 | |||
| 840bfcc36c | |||
| 938c1cc132 | |||
| 9dc22db4b2 | |||
| f570e226cd | |||
| 0c5c3abedc | |||
| 35d07b984b | |||
| aec7723ee6 | |||
| b98a40297b | |||
| 9117568b74 | |||
| 11e4417c62 | |||
| 4edf1d983d | |||
| 74b07b1a8a | |||
| c9437209a7 | |||
| 92cb855a49 | |||
| f06c6f77d1 | |||
| 51bd63658f | |||
| 92ade3ee3d | |||
| 833867329e | |||
| 93b9fe9ced | |||
| 88a1171bbe | |||
| 3ca0842b7a | |||
| 00de5eb354 | |||
| d2b0c6f329 | |||
| 76612682f1 | |||
| ce480858e9 | |||
| 70efb0ad40 | |||
| bf6deaab64 | |||
| 7c792c0a2a | |||
| bf8d4b9a77 | |||
| d2d5115c8a | |||
| 3cdd98b158 | |||
| 751d5a49b8 | |||
| 6a836dd891 | |||
| 1f888e2b21 | |||
| fb923f6c76 | |||
| 59e3338892 | |||
| 8cf4145c15 | |||
| b3885b2c15 | |||
| ae04fa60cc | |||
| 6c411e43f8 | |||
| e0427af3cc | |||
| 552836ae5b | |||
| 0fe53fcdeb | |||
| f6fb2a2cd6 | |||
| 70d6e6b8ce | |||
| aec9a999cb | |||
| 3acde86028 | |||
| aa405702a9 | |||
| b7c018fb94 | |||
| 07a52a3a25 | |||
| e12cb9003b | |||
| 28968a1b5c | |||
| 8a4a2df1aa | |||
| f4a90637db | |||
| 90a1a31049 | |||
| 33b2e82594 | |||
| 9f8dd0006d | |||
| 417f7f74a3 | |||
| cd6f814049 | |||
| 83fcdb0a09 | |||
| aa21bf0f2a | |||
| e7e35ad4e4 | |||
| 503dbce937 | |||
| c6bbfae7e6 | |||
| 64ef7f0803 | |||
| 406e5d7e54 | |||
| 267b7284ba | |||
| 9b62b56120 | |||
| 4fe7771d83 | |||
| 57079a43ac | |||
| 1d07b06968 | |||
| 9887b59cfb | |||
| 9850a8016e | |||
| 35f5f35dc5 | |||
| 47ca94111f | |||
| 62cfbb2591 | |||
| de855a3fe6 | |||
| dc9f7b3342 | |||
| 60fdff6d36 | |||
| f42d38d1fd | |||
| 5a4122d61f | |||
| 3e4ba54a32 | |||
| 5dd1c5e15d | |||
| e46732fb5a | |||
| 833ab56857 | |||
| 6b2b1e201e | |||
| f9c81fe05f | |||
| 931ec905c3 | |||
| 799ffbdac9 | |||
| b29b33524f | |||
| 25f25c1f23 | |||
| 3f1d632285 | |||
| 1938037458 | |||
| f7c05e1b31 | |||
| c375785bb9 | |||
| 9ebd54b7fc | |||
| ce68ad8f19 | |||
| f402e76480 | |||
| 4d3c7e0d6c | |||
| 5a74a6f715 | |||
| 5416674858 | |||
| 9a5f305ac6 | |||
| bc38ce73f0 | |||
| 081b924d29 | |||
| 427ff44ce9 | |||
| 360a908464 | |||
| a8dd4d6542 | |||
| 866b110afa | |||
| 0fc0b87017 | |||
| 1a724a94b0 | |||
| b239e9a997 | |||
| a766737707 | |||
| 0843c21884 | |||
| eb7a2bf27d | |||
| c78530611d | |||
| 7267d25fb5 | |||
| 53db299b5f | |||
| a4fa233edf | |||
| e7e90382c5 | |||
| 1ef32ecc92 | |||
| f9af708f0a | |||
| 0e93e45853 | |||
| f0802ae614 | |||
| 4a992eba2b | |||
| 398cc68166 | |||
| 3b0a63d57c | |||
| c86ae7b118 | |||
| d2044f1f53 | |||
| 7991516c3e | |||
| a0e0dbc807 | |||
| a77db557f3 | |||
| f4c3b2dd88 | |||
| 32387e2d23 | |||
| 2ecb1e5ce8 | |||
| 30dacc4fbf | |||
| 2a5ffcf78a | |||
| fe00644e3f | |||
| 89df5c4201 | |||
| b396f42f84 | |||
| 45789f7af0 | |||
| e0bf637de5 | |||
| e5f258bc21 | |||
| a294887215 | |||
| 20932f48ab | |||
| 9e0faf840b | |||
| c7c733044c | |||
| 866065d7d7 | |||
| aa9d4282c9 | |||
| 3593735243 | |||
| 54fd4c2241 | |||
| a4b11e6f35 | |||
| 044e9a7eac | |||
| 094887c849 | |||
| 2fc5aae678 | |||
| ac0cc0b6ce | |||
| ce5f3e7626 | |||
| 0c4ba83b0c | |||
| f81b1e882d | |||
| ebf26cfbfa | |||
| f9654fcdea | |||
| f3f4174783 | |||
| 119dd2d921 | |||
| 80698ccc3a | |||
| a41f20e805 | |||
| 2de263c814 | |||
| a7ad8a374b | |||
| e75c35a099 | |||
| b9f0a3870a | |||
| d425d6b624 | |||
| 8c61cb146b | |||
| a11523e889 | |||
| cf061e3783 | |||
| 5d746ed0b1 | |||
| 89323c0eb3 | |||
| 5a44d619d0 | |||
| f5bf7ce505 | |||
| 4f3b706336 | |||
| 83a988d085 | |||
| d0e8e43b56 | |||
| c67a08a34e | |||
| e72ef4a6a7 | |||
| 994c4e244a | |||
| adfbc3079a | |||
| 772fa84b4f | |||
| b97378c836 | |||
| f00cf10c41 | |||
| d57e780f6f | |||
| 4f03bb6616 | |||
| 4aafd8d0b0 | |||
| c74600d42f | |||
| b3ef0ddc58 | |||
| dd3b7bd03e | |||
| 62111de2da | |||
| c3ae02ec4f | |||
| f4d2cca663 | |||
| 3b5a07dfce | |||
| aa454ea7e9 | |||
| 5aed586187 | |||
| 09d90b3425 | |||
| 96131da59c | |||
| d2058668a6 | |||
| a5ffdc6104 | |||
| 724a0df7b5 | |||
| 4750061b87 | |||
| d53d8b60dd | |||
| de1db0aa8d | |||
| b22b3c063f |
+724
@@ -0,0 +1,724 @@
|
||||
# Changelog
|
||||
|
||||
All notable changes to PyTheory are documented here.
|
||||
|
||||
## 0.40.5
|
||||
|
||||
- **Saxophone synth overhaul** — reed nonlinearity (asymmetric soft clipping),
|
||||
conical bore formant resonances, breath noise with attack envelope, separate
|
||||
reed buzz, key click transient, and sub-harmonic warmth. Vibrato dialed back
|
||||
to subtle, delayed onset.
|
||||
|
||||
## 0.40.4
|
||||
|
||||
- **Distortion overhaul** — multi-stage clipping (preamp → power amp →
|
||||
asymmetric rectifier) replaces single-stage tanh. Crunch, distorted,
|
||||
orange crunch, and metal guitar presets now sound properly driven.
|
||||
|
||||
## 0.40.3
|
||||
|
||||
- **Crotales synth** — tuned bronze discs with long ring and bright harmonics
|
||||
- **Tingsha synth** — paired Tibetan cymbals with beating from two detuned discs
|
||||
- **Rain stick** — cascading pebbles (steep and slow/shallow variants)
|
||||
- **Ocean drum** — steel beads rolling inside a frame drum, surf wash
|
||||
- **Cabasa** — metal bead chain on cylinder, bright metallic scrape
|
||||
- **Wind chimes** — multiple suspended metal tubes ringing at random offsets
|
||||
- **Finger cymbal** — single zill tap, bright metallic ping
|
||||
- `crotales`, `tingsha`, `singing_bowl`, `singing_bowl_ring` instrument presets
|
||||
- Audio demos in docs for all new sounds
|
||||
|
||||
## 0.40.2
|
||||
|
||||
- **Master compressor dialed back** — threshold raised from 0.5 to 0.7,
|
||||
makeup gain capped at 3x. Sparse arrangements no longer get
|
||||
over-amplified to clipping.
|
||||
|
||||
## 0.40.1
|
||||
|
||||
- **Singing bowl synth** — two variants: strike (mallet hit with chirp
|
||||
and long decay) and ring (rim-rubbed sustained tone with slow build).
|
||||
Inharmonic partials beat against near-degenerate mode pairs for
|
||||
authentic Himalayan bowl shimmer.
|
||||
- `singing_bowl` and `singing_bowl_ring` instrument presets
|
||||
- Audio demos in docs for both variants
|
||||
|
||||
## 0.40.0
|
||||
|
||||
- **Rhodes electric piano synth** — tine + tonebar + electromagnetic
|
||||
pickup model. `electric_piano` preset now uses dedicated `rhodes_synth`
|
||||
instead of FM
|
||||
- **73 audio demos in docs** — every synth, every drum pattern, every
|
||||
code example with `play_score()` now has an embedded audio player
|
||||
- Idiomatic demos: harp arpeggiates, guitars strum, cello bows, sitar
|
||||
drones, strings use ensemble
|
||||
- Trailing silence trimming on all audio exports
|
||||
- Raw waveform demos (no envelope) for classic waveforms
|
||||
|
||||
## 0.39.3
|
||||
|
||||
- **33 audio samples in documentation** — every `play_score()` example
|
||||
now has an embedded stereo audio player. Covers quickstart, sequencing,
|
||||
drums (all world percussion), playback, and cookbook.
|
||||
- **`docs/generate_audio.py`** — renders all doc examples to WAV
|
||||
- Numpy vectorization: cached time arrays, decay envelopes, drum hits;
|
||||
vectorized piano harmonic synthesis
|
||||
- Fixed acid legato example (removed pad envelope, added proper 303 recipe)
|
||||
|
||||
## 0.39.2
|
||||
|
||||
- **Marching percussion** — snare, rimshot, and stick click sounds with
|
||||
high-tension kevlar synthesis and woody-metallic rimshot crack
|
||||
- **`Part.flam()`**, **`Part.diddle()`**, **`Part.cheese()`** — marching
|
||||
rudiment methods for any drum sound
|
||||
- **`Part ensemble=`** — duplicate voices with per-player timing tendencies
|
||||
and micro pitch drift. Works on any Part (drumline, string section, choir).
|
||||
`ensemble=20` for a full snare line, `ensemble=4` for a string quartet.
|
||||
- **Sympathetic resonance** — marching snare buzz builds up with repeated
|
||||
hits, decays during rests (like real snare wire response)
|
||||
- **4 marching patterns** — march, cadence, paradiddle, roll
|
||||
- **Chakradar tabla pattern** — 16-beat tihai of tihais composition
|
||||
- Song #32: Snare Cadence (flams, diddles, cheese, triplets, 32nds)
|
||||
|
||||
## 0.39.1
|
||||
|
||||
- **Chakradar tabla pattern** — 16-beat tihai of tihais composition with
|
||||
3 escalating phrases and a crescendo triplet finale
|
||||
|
||||
## 0.39.0
|
||||
|
||||
- **Dropped `numeral` dependency** — Roman numeral helpers inlined,
|
||||
reducing supply chain surface (#47)
|
||||
- **`Part.ramp()`** — smooth parameter automation with 4 interpolation
|
||||
curves (linear, ease_in, ease_out, ease_in_out)
|
||||
- **Articulations** — staccato, legato, marcato, tenuto, accent, fermata
|
||||
- **Dynamic curves** — crescendo(), decrescendo(), swell(), dynamics()
|
||||
- **`Part.hit()`** — individual drum sounds with articulation support
|
||||
- **Cross-choke drum damping** — djembe, hi-hats, cajón, doumbek
|
||||
- **5 new djembe patterns** + 3 djembe fills (30 fills total)
|
||||
- **6 new drum fills** — 3 cajón, 3 metal
|
||||
- **Duration arithmetic** — multiply, divide, add
|
||||
- **Improved djembe slap** synthesis
|
||||
- Song #31: Acid Tabla
|
||||
|
||||
## 0.38.2
|
||||
|
||||
- **`Part.ramp()`** — smooth parameter automation from current value to
|
||||
target over a duration. Works for lowpass, reverb, distortion, chorus,
|
||||
delay, volume, and any `.set()` parameter. Four interpolation curves:
|
||||
linear, ease_in, ease_out, ease_in_out.
|
||||
|
||||
## 0.38.1
|
||||
|
||||
- **Dynamic curves** — `Part.crescendo()`, `Part.decrescendo()`,
|
||||
`Part.swell()`, and `Part.dynamics()` for velocity ramps and custom
|
||||
curves across a sequence of notes
|
||||
|
||||
## 0.38.0
|
||||
|
||||
- **Articulations** — `staccato`, `legato`, `marcato`, `tenuto`, `accent`,
|
||||
`fermata` via `articulation=` on `Part.add()` and `Part.hold()`
|
||||
- **`Part.hit()`** — place individual drum sounds in a Part's note stream
|
||||
with articulation, velocity, and effects support
|
||||
- **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 fades out related sounds
|
||||
(djembe, hi-hats, cajón, doumbek)
|
||||
- **Improved djembe slap** — dry goatskin pop instead of snare-like noise
|
||||
|
||||
## 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
|
||||
(triplet, blast, cascade). 27 fills total.
|
||||
- Updated drums documentation with fill lists and examples
|
||||
|
||||
## 0.36.5
|
||||
|
||||
- **Duration arithmetic** — `Duration.WHOLE * 2`, `Duration.HALF + Duration.QUARTER`,
|
||||
division, and reverse multiply all work now (previously raised TypeError)
|
||||
|
||||
## 0.36.3
|
||||
|
||||
- **`Part.hold()`** — polyphonic overlap on a single part. Add notes
|
||||
without advancing the beat position so they play simultaneously.
|
||||
Enables: piano sustain, sitar drone under melody, guitar strum texture.
|
||||
- **Strum uses hold()** — leading string plays simultaneously with chord,
|
||||
no more timing gaps or choppiness
|
||||
- **Improved songs** 1-16: humanize, velocity dynamics, reverb, saxophone
|
||||
for blues
|
||||
- **Ctrl-C handling** — clean stop on all playback functions
|
||||
- **REPL updates** — strum, roll, bend, temperament, reference commands
|
||||
- Song #28 Descent (generative), #29 Pop Rock, #30 Sitar Drone
|
||||
- 862 tests
|
||||
|
||||
## 0.36.1
|
||||
|
||||
- **7 new instrument synths:** pedal steel guitar, theremin, kalimba/thumb
|
||||
piano, steel drum/pan, accordion (musette reeds), didgeridoo (drone +
|
||||
shifting formants), bagpipes (chanter reed)
|
||||
- **9 new demo moods** in ``pytheory demo``: Theremin Noir, Caribbean,
|
||||
Accordion Waltz, Kalimba Dreams, Outback Drone, Highland, Nashville
|
||||
Tears, Tabla Fusion
|
||||
- Improved existing songs with dedicated instrument synths
|
||||
- 41 synth waveforms, 26+ songs, 21 demo moods
|
||||
|
||||
## 0.36.0
|
||||
|
||||
- **Banjo synth** — steel strings on drum-head body, nasal twang,
|
||||
fast decay with membrane resonance
|
||||
- **Mandolin synth** — paired steel strings (natural chorus from
|
||||
doubled courses), bright body resonance
|
||||
- **Ukulele synth** — nylon strings, small mid-heavy body, shorter
|
||||
sustain than guitar
|
||||
- **Cajón drums** — bass (woody box thump), slap (snare wire buzz),
|
||||
tap (ghost note). 3 patterns: cajon, cajon rumba, cajon folk
|
||||
- **Vocal/formant synth** — LF glottal model, 5 Peterson & Barney
|
||||
formant peaks, jitter/shimmer, consonant onsets, per-note lyrics.
|
||||
Presets: vocal, choir
|
||||
- **Granular synthesis** — grain cloud engine with scatter, pitch
|
||||
variation, Hanning windows. Presets: granular_pad, granular_texture
|
||||
- **Strum sweep** — subtle grace notes before chord hit for natural
|
||||
strum feel on all fretboard instruments
|
||||
- Mandola preset, 34 synth waveforms, 26 songs
|
||||
|
||||
## 0.35.0
|
||||
|
||||
- **8.5x faster import** — dropped pytuning/sympy, lazy-load scipy.
|
||||
`import pytheory` now takes ~50ms instead of ~480ms (#44)
|
||||
- **Proper shruti JI ratios** — 22 positions with 5-limit just intonation
|
||||
(pure 3/2 fifths, 5/4 thirds), not 22-TET approximation
|
||||
- **Arabic maqam JI ratios** — Zalzalian 11-limit ratios.
|
||||
Mi↓ (the Rast third) is exactly 27/22 from Do
|
||||
- **B#/Cb octave boundary fix** — B#4 = C5, Cb4 = B3 (#45)
|
||||
- **Int tone names** — `Tone(0, system=TET(22))` works alongside strings.
|
||||
Wrapping: `Tone(22)` → tone 0, octave+1. `System.tone()` convenience.
|
||||
- **Timpani synth** — inharmonic membrane modes, felt mallet, copper kettle
|
||||
resonance, cathedral reverb
|
||||
- **Saxophone synth** — conical bore, reed buzz, brass body warmth.
|
||||
4 presets: saxophone, alto_sax, tenor_sax, bari_sax
|
||||
- **Part.roll()** — rapid repeated notes with velocity ramp for crescendo/
|
||||
decrescendo rolls on any instrument
|
||||
- **Vibrato tuning** — all instruments reduced to 0.001 depth for cleaner
|
||||
ensemble sound
|
||||
- **Granular synthesis** — grain cloud engine with scatter, pitch
|
||||
variation, and Hanning-windowed grains. Two presets: granular_pad,
|
||||
granular_texture.
|
||||
- 30 synth waveforms, 838 tests
|
||||
|
||||
## 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
|
||||
- **Vibrato tuning:** reduced across flute, oboe, trumpet, cello for
|
||||
smoother ensemble sound
|
||||
- 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
|
||||
- **11 microtonal systems:**
|
||||
- `"shruti"` (22-TET Indian, 10 thaats with proper shruti intervals)
|
||||
- `"maqam"` (24-TET Arabic, quarter-tone Rast/Bayati/Hijaz + 7 more)
|
||||
- `"slendro"` (5-TET gamelan), `"pelog"` (9-TET gamelan with 3 pathet)
|
||||
- `"thai"` (7-TET, 171 cents/step)
|
||||
- `"makam"` (53-TET Turkish Arel-Ezgi-Uzdilek, 9 makams)
|
||||
- `"carnatic"` (72-TET, 10 melakartas)
|
||||
- `"19-tet"`, `"31-tet"` (historical Western)
|
||||
- `"bohlen-pierce"` (13 divisions of the tritave 3:1 — non-octave!)
|
||||
- **Just intonation** — `temperament="just"` for pure 5-limit ratios
|
||||
- **Historical pitch** — `Score(reference_pitch=415.0)` for Baroque A=415
|
||||
- **`Score(system=, temperament=, reference_pitch=)`** flows through to all playback
|
||||
- Per-system `c_index` and `period` replace hardcoded constants
|
||||
- Fixed all hardcoded `12`s in tone arithmetic
|
||||
- Song #22: Greensleeves (Renaissance lute, meantone, A=415)
|
||||
- 22 new microtonal tests (819 total)
|
||||
|
||||
## 0.32.1
|
||||
|
||||
- `Tone("X")` now raises `ValueError` immediately instead of silently accepting invalid names (#39)
|
||||
- Support enharmonic spellings: `Cb`, `Fb`, `E#`, `B#` resolve correctly (#40)
|
||||
- Support double sharps (`C##`, `Fx`) and double flats (`Dbb`) via semitone arithmetic (#41)
|
||||
- Accept unicode music symbols: `♯` `♭` `𝄪` `𝄫`
|
||||
|
||||
## 0.32.0
|
||||
|
||||
- **8 new synth engine features:**
|
||||
- Filter envelope: per-note lowpass sweep (`filter_amount`, `filter_attack`, `filter_decay`, `filter_sustain`)
|
||||
- Velocity → brightness: harder notes = brighter filter (`vel_to_filter`)
|
||||
- Sub-oscillator: octave-below sine for bass weight (`sub_osc`)
|
||||
- Tremolo: amplitude LFO modulation (`tremolo_depth`, `tremolo_rate`)
|
||||
- Saturation: even-harmonic tape/tube warmth (`saturation`)
|
||||
- Noise layer: per-note breath/air texture (`noise_mix`)
|
||||
- Phaser: swept allpass filter chain (`phaser`, `phaser_rate`)
|
||||
- Configurable FM: `fm_ratio` and `fm_index` params
|
||||
- **Highpass filter** (12 dB/oct biquad) on any part
|
||||
- **2 new envelopes:** `bowed` (bow attack with sustain), `mallet` (strike with ringing sustain)
|
||||
- **Improved `strings_synth`:** additive synthesis with body resonance curve, per-harmonic phase randomization, delayed vibrato onset, bow pressure variation
|
||||
- **Instrument preset overhaul:** every preset sanity-checked against real instrument behavior
|
||||
- Mallet instruments (vibraphone, celesta, music box, glockenspiel, tubular bells) now ring properly
|
||||
- Trumpet uses sustaining envelope instead of pluck
|
||||
- Woodwinds have breath noise, brass has velocity brightness
|
||||
- Bass instruments have sub-oscillators, synth presets have filter envelopes
|
||||
- Piano has velocity-to-brightness and subtle hammer noise
|
||||
- Signal chain: saturation → tremolo → distortion → chorus → phaser → highpass → lowpass → delay → reverb
|
||||
- Song #21: Cinematic Showcase (Orchestral)
|
||||
|
||||
## 0.31.0
|
||||
|
||||
- 3 new synth engines: Karplus-Strong pluck, Hammond organ, string ensemble with body formants
|
||||
- 38 instrument presets: `score.part("lead", instrument="violin")`
|
||||
- Keys, strings, woodwinds, brass, plucked, synth, and mallet categories
|
||||
- 13 total synth waveforms
|
||||
|
||||
## 0.30.0
|
||||
|
||||
- Drums are a real Part — same effects pipeline as any voice
|
||||
- `score.drums("rock", split=True)` splits kit into kick/snare/hats/toms/cymbals/percussion Parts
|
||||
- Each split Part gets independent effects (reverb on snare, LP on hats, etc.)
|
||||
- `set_drum_effects()` applies to all drum Parts (split or not)
|
||||
- Sidechain triggers on kick only — hats and snare don't duck the pad
|
||||
- MIDI import via `Score.from_midi(path)`
|
||||
|
||||
## 0.29.3
|
||||
|
||||
- Drums are now a real Part — same effects pipeline as any other voice, zero code duplication
|
||||
- `score.parts["drums"]` is a standard Part with reverb, delay, lowpass, etc.
|
||||
- `set_drum_effects()` is sugar over the Part's attributes
|
||||
|
||||
## 0.29.2
|
||||
|
||||
- Add `score.set_drum_effects()` — reverb, delay, lowpass, distortion, chorus on the drum bus
|
||||
- Same effects engine as parts, zero code duplication
|
||||
|
||||
## 0.29.1
|
||||
|
||||
- Rename song.py → songs.py
|
||||
- Polish all 20 example songs with stereo, convolution reverb, humanize, detune, sidechain
|
||||
|
||||
## 0.29.0
|
||||
|
||||
- Add `Score.from_midi(path)` — import any Standard MIDI File into a Score
|
||||
- Minimal zero-dependency MIDI parser (Type 0 and Type 1)
|
||||
- Each channel becomes a named Part, channel 10 becomes drum hits
|
||||
- Tempo, time signature, velocities, and note durations preserved
|
||||
- Roundtrip: save_midi → from_midi works
|
||||
|
||||
## 0.28.3
|
||||
|
||||
- Rewrite `pytheory demo` — 8 moods with stereo, effects, humanize, convolution reverb, sidechain
|
||||
- Added Dub and Temple moods
|
||||
|
||||
## 0.28.2
|
||||
|
||||
- Lower drum_humanize default to 0.15 — tighter, more professional feel
|
||||
|
||||
## 0.28.1
|
||||
|
||||
- Humanize drum hits — random timing jitter and velocity variation (default 0.3)
|
||||
- Control via `Score(drum_humanize=0.5)` — 0.0 = quantized, 0.3 = natural, 0.5+ = loose
|
||||
|
||||
## 0.28.0
|
||||
|
||||
- Add figured bass notation: `Chord.figured_bass` and `Chord.analyze_figured()` for classical inversion symbols
|
||||
- Add pitch class set theory: `pitch_classes`, `normal_form`, `prime_form`, `forte_number` on Chord
|
||||
- Add `Scale.recommend()` — ranked scale suggestions for a set of notes
|
||||
- Forte number catalog covers all trichords and tetrachords
|
||||
|
||||
## 0.27.1
|
||||
|
||||
- Tab completion in REPL — context-aware for commands, drum presets, synths, envelopes, chords, notes, systems
|
||||
|
||||
## 0.27.0
|
||||
|
||||
- Rewrite all 15 drum sounds for higher quality (inharmonic partials, proper transients, multi-mode resonance, saturation)
|
||||
- 19 example songs including Dance Party at the Reitz House
|
||||
|
||||
## 0.26.3
|
||||
|
||||
- Stereo drum panning — each sound placed in the stereo field (hat right, crash left, toms spread, kick/snare center)
|
||||
- Stereo convolution reverb — different IR per L/R channel for all 7 presets
|
||||
- 2 new songs: Neon Grid (stereo acid), Glass and Silk (sine+triangle waltz)
|
||||
|
||||
## 0.26.2
|
||||
|
||||
- Stereo convolution reverb — different IR per L/R channel for all 7 presets
|
||||
- Both algorithmic and convolution reverbs now output true stereo
|
||||
|
||||
## 0.26.1
|
||||
|
||||
- Stereo reverb — L and R channels get different early reflection patterns for natural width
|
||||
- Effects chain now skips mono reverb in favor of stereo reverb in the mixer
|
||||
|
||||
## 0.26.0
|
||||
|
||||
- **Stereo output** — render_score() now returns stereo (N, 2) arrays
|
||||
- Add `pan` parameter: -1.0 (left) to 1.0 (right), constant-power panning
|
||||
- Add `spread` parameter: detuned oscillators spread across L/R channels
|
||||
- Master bus compressor runs per-channel for stereo
|
||||
- All playback functions handle stereo natively
|
||||
|
||||
## 0.25.7
|
||||
|
||||
- Add `detune` parameter — ±cents oscillator spread on any synth (3 oscillators per note)
|
||||
- Swing now applies to drum hits (offbeats shift with the groove)
|
||||
- Improved snare and hi-hat sounds (metallic harmonics, faster attack)
|
||||
|
||||
## 0.25.6
|
||||
|
||||
- Swing now applies to drum hits — offbeats shift with the groove, everything locks into the same pocket
|
||||
- Improved snare: 220Hz body, transient click, tanh saturation
|
||||
- Improved hi-hats: metallic harmonics (6k+8.5k+12k Hz), crisper attack, shorter decay
|
||||
|
||||
## 0.25.5
|
||||
|
||||
- Improved snare: 220Hz body, transient click, tanh saturation — snappier and more present
|
||||
- Improved hi-hats: metallic harmonics (6k+8.5k+12k Hz), shorter decay, crisper attack
|
||||
|
||||
## 0.25.4
|
||||
|
||||
- Add master bus compressor/limiter — louder, punchier, more cohesive mixes
|
||||
- Feed-forward compression with configurable threshold, ratio, attack, release
|
||||
- Makeup gain restores loudness after compression
|
||||
- Brick-wall limiter at 0.95 prevents clipping
|
||||
- Replaces simple normalization in render_score()
|
||||
|
||||
## 0.25.3
|
||||
|
||||
- Add `pytheory repl` — interactive music theory scratchpad and composition tool
|
||||
- Context-aware prompt shows key, bpm, drums, active part + effects
|
||||
- Theory commands: key, chords, modes, scales, circle, interval, identify, system
|
||||
- Composition: drums, part, add, rest, arp, prog, effects, automation, LFO
|
||||
- Guitar: fingering, scale diagram
|
||||
- 6 musical systems with correct default tonics
|
||||
- REPL guide documentation
|
||||
|
||||
## 0.25.1
|
||||
|
||||
- Add `pytheory demo` CLI command — plays a randomly generated track, different every time
|
||||
- Rewrite README to showcase the full feature set (composition, effects, drums, MIDI export)
|
||||
|
||||
## 0.25.0
|
||||
|
||||
- Add sidechain compression — kick ducks pad/bass for the classic EDM pump effect
|
||||
- Add song structure: `score.section("verse")`, `score.section("chorus")`, `score.repeat("verse")`
|
||||
- Punchier kick drum: 808-style with faster pitch sweep (200→45Hz), sub thump, and soft saturation
|
||||
- Section repeat copies all part notes, drum hits, and automation with proper offset
|
||||
|
||||
## 0.24.1
|
||||
|
||||
- Add `humanize` parameter on Parts — random micro-timing and velocity variation
|
||||
- Makes programmed parts feel like a real player (0.1 = subtle, 0.3 = natural, 0.5+ = loose)
|
||||
|
||||
## 0.24.0
|
||||
|
||||
- Add per-note velocity: `lead.add("C5", Duration.QUARTER, velocity=90)` — dynamics, accents, ghost notes
|
||||
- Add swing/groove: `Score("4/4", bpm=120, swing=0.5)` — shuffles every other note for human feel
|
||||
- Add tempo changes mid-song: `score.set_tempo(140)` — accelerando, ritardando, tempo drops
|
||||
- Add `Part.fade_in(bars)` and `Part.fade_out(bars)` — volume envelopes over sections
|
||||
- Arpeggiator supports velocity parameter
|
||||
- Per-part swing override (set independently from score swing)
|
||||
- Tempo map engine: beat-to-sample conversion handles variable BPM throughout a score
|
||||
|
||||
## 0.23.0
|
||||
|
||||
- Add convolution reverb with 7 synthetic impulse responses: Taj Mahal, cathedral, plate, spring, cave, parking garage, canyon
|
||||
- Each IR models real acoustic properties: early reflections, frequency-dependent absorption, diffusion density, and modulation
|
||||
- FFT-based convolution via `scipy.signal.fftconvolve` for fast processing even with long tails (12s Taj Mahal)
|
||||
- Select via `reverb_type` parameter on `Score.part()` — drop-in alongside existing algorithmic reverb
|
||||
- IR cache for zero-cost reuse across parts
|
||||
- Automatable via `Part.set(reverb_type="cathedral")` mid-song
|
||||
|
||||
## 0.22.0
|
||||
|
||||
- Add `Part.lfo()` for automated parameter modulation (filter sweeps, tremolo, auto-wah)
|
||||
- 4 LFO shapes: sine, triangle, saw, square
|
||||
- Configurable rate (cycles per bar), min/max range, duration, and resolution
|
||||
- Stack multiple LFOs on different parameters for complex modulation
|
||||
|
||||
## 0.21.0
|
||||
|
||||
- Add `Part.set()` for mid-song effect automation (filter sweeps, reverb swells, distortion kicks)
|
||||
- Add chorus effect (LFO-modulated delay, Juno-style)
|
||||
- Renderer segments audio at automation points for per-section effect processing
|
||||
- Updated effect chain: distortion → chorus → lowpass → delay → reverb
|
||||
- Document automation, chorus, and updated signal chain
|
||||
|
||||
## 0.20.0
|
||||
|
||||
- Add `Part.arpeggio()` — arpeggiator with up/down/updown/downup/random patterns, octave spanning
|
||||
- Fix Roman numeral parser to handle flat/sharp degree prefixes (bVI, bVII, bIII, #IV)
|
||||
- Add `song_showoff.py` — generative composition that's different every time, uses every feature
|
||||
- 4 mood palettes (dark, bright, ethereal, aggressive) with matched keys, progressions, drums, and effects
|
||||
|
||||
## 0.19.1
|
||||
|
||||
- Add `Part.arpeggio()` — arpeggiator with up/down/updown/downup/random patterns, octave spanning, and division control
|
||||
- Arpeggiator chains with legato + glide for classic acid/trance sequencer sound
|
||||
- Rename rhythm docs to "Sequencing: Rhythm and Scores"
|
||||
- Document arpeggiator, legato, and glide in rhythm guide
|
||||
|
||||
## 0.19.0
|
||||
|
||||
- Add legato mode for parts — continuous waveform without retriggering envelope per note
|
||||
- Add glide/portamento — smooth pitch slides between consecutive notes (303-style)
|
||||
- Legato renders entire phrase as one oscillator with phase-accumulating frequency changes
|
||||
- Glide uses exponential interpolation for perceptually linear pitch slides
|
||||
|
||||
## 0.18.1
|
||||
|
||||
- Add distortion effect (tanh soft-clip waveshaping) with drive and mix controls
|
||||
- 3 new example songs: Dub Delay Madness (separate delay snare), Liquid DnB (174bpm), Late Night Texts (Drake-style trap)
|
||||
- 16 total songs in the song player
|
||||
|
||||
## 0.18.0
|
||||
|
||||
- Add per-part audio effects: reverb, delay, and lowpass filter
|
||||
- Reverb: Schroeder algorithm with configurable mix and decay
|
||||
- Delay: tempo-synced echoes with feedback control
|
||||
- Lowpass: 12 dB/octave biquad filter with resonance (Q) control
|
||||
- All effects set at part creation: `score.part("lead", reverb=0.3, delay=0.25, lowpass=2000, lowpass_q=1.5)`
|
||||
- Effects applied per-part before mixing for independent processing
|
||||
|
||||
## 0.17.0
|
||||
|
||||
- Add 10 new groove presets: country, ska, dub, jungle, techno, gospel, swing, bolero, tango, flamenco (58 total)
|
||||
- Add 10 new fill presets: reggae, afrobeat, bossa nova, house, trap, hip hop, disco, cumbia, highlife, second line (21 total)
|
||||
- Every major genre family now has matching groove + fill presets
|
||||
|
||||
## 0.16.0
|
||||
|
||||
- Add drum fill system with 11 genre-specific presets: rock, rock crash, jazz, jazz brush, salsa, samba, funk, metal, blast, buildup, breakdown
|
||||
- `Pattern.fill("rock")` returns a 1-bar fill pattern
|
||||
- `Score.fill("rock")` inserts a fill at the current position
|
||||
- `Score.drums("rock", repeats=8, fill="rock", fill_every=4)` auto-fills every Nth bar
|
||||
- Without `fill_every`, fill replaces only the last bar
|
||||
|
||||
## 0.15.1
|
||||
|
||||
- Add `Synth.PWM_SLOW` and `Synth.PWM_FAST` — pulse width modulation with LFO sweep (Juno-style pads)
|
||||
- Add `Score.drums()` shorthand for `score.add_pattern(Pattern.preset(...), repeats=...)`
|
||||
- Update all docs to use `score.drums()` syntax and document all 10 synth waveforms
|
||||
|
||||
## 0.15.0
|
||||
|
||||
- Add 5 new synth waveforms: `Synth.SQUARE`, `Synth.PULSE`, `Synth.FM`, `Synth.NOISE`, `Synth.SUPERSAW`
|
||||
- Square wave: classic chiptune / 8-bit sound (odd harmonics at 1/n)
|
||||
- Pulse wave: variable duty cycle for NES-style timbres (25%, 12.5%)
|
||||
- FM synthesis: DX7-style frequency modulation (electric piano, bells, brass, metallic)
|
||||
- Noise: white noise for percussion textures and effects
|
||||
- Supersaw: 7 detuned saw oscillators for trance/EDM pads
|
||||
- All 8 synths available in both the API (`Synth.FM`) and Part strings (`synth="fm"`)
|
||||
- CLI play command supports all 8 waveforms
|
||||
|
||||
## 0.14.0
|
||||
|
||||
- Add `Part` class for multi-voice Score arrangements (lead, bass, pads, etc.)
|
||||
- `Score.part()` creates named parts with independent synth, envelope, and volume
|
||||
- `Score.add_pattern()` for attaching drum patterns
|
||||
- `render_score()` exported for headless buffer rendering
|
||||
- Parts accept raw float beat values alongside `Duration` enums
|
||||
- All 10 example songs rewritten with drums + chords + lead + bass parts
|
||||
|
||||
## 0.13.1
|
||||
|
||||
- Fix drum pattern repeats: hits now correctly offset across cycles instead of piling up on the first bar
|
||||
|
||||
## 0.13.0
|
||||
|
||||
- Add drum synthesizer with 27 individual instrument voices (kick, snare, hat, conga, timbale, etc.)
|
||||
- Add `play_pattern()` for playing drum patterns through the speakers
|
||||
- Add `play_score()` for playing mixed drum patterns + chord progressions together
|
||||
- Every `DrumSound` has a dedicated synthesis algorithm (pitch sweeps, noise bursts, membrane resonance, metallic rings)
|
||||
|
||||
## 0.12.0
|
||||
|
||||
- Add rhythm module: `Duration`, `TimeSignature`, `Note`, `Rest`, `Score`
|
||||
- `Duration` enum with 8 note lengths (whole through sixteenth, dotted, triplet)
|
||||
- `TimeSignature` with string parsing ("4/4", "3/4", "6/8", "12/8") and beats_per_measure
|
||||
- `Score` class with fluent `.add()` / `.rest()` chaining, measure counting, and `save_midi()` export
|
||||
- Measure-aware MIDI export with proper time signature and tempo meta events
|
||||
- Add `DrumSound` enum with 27 General MIDI percussion sounds
|
||||
- Add `Pattern` class with 48 drum pattern presets covering:
|
||||
- **Rock/Pop**: rock, half time, double time, disco, motown, train beat
|
||||
- **Jazz**: jazz, bebop, shuffle, linear, paradiddle
|
||||
- **Latin**: salsa, bossa nova, samba, cumbia, merengue, baiao, maracatu
|
||||
- **Afro-Cuban**: son clave 3-2/2-3, rumba clave 3-2/2-3, cascara, guaguanco, mozambique, nanigo, bembe, 6/8 afro-cuban, tresillo, habanera
|
||||
- **African**: afrobeat, highlife
|
||||
- **Caribbean**: reggae, dancehall
|
||||
- **Electronic**: house, trap, drum and bass, breakbeat
|
||||
- **Metal/Punk**: metal, blast beat, punk
|
||||
- **Other**: funk, hip hop, bo diddley, second line, new orleans, waltz, 12/8 blues
|
||||
- `Pattern.to_score()` renders drum patterns to Score for MIDI export
|
||||
|
||||
## 0.11.0
|
||||
|
||||
- Add drop voicings: `Chord.close_voicing()`, `Chord.open_voicing()`, `Chord.drop2()`, `Chord.drop3()`
|
||||
- Add `Key.modulation_path(target)` for chord-by-chord modulation suggestions via pivot chords
|
||||
- Add `Scale.degree_name(n)` returning traditional names (tonic, dominant, leading tone, etc.)
|
||||
- Add `Chord.extensions()` to suggest available 9th/11th/13th extensions
|
||||
- Add `Tone.solfege` property for fixed-Do solfege syllables (Do, Re, Mi, Fi, etc.)
|
||||
- Add CLI `identify` command for full chord analysis from a symbol
|
||||
- Add CLI `midi` command for exporting progressions to Standard MIDI Files
|
||||
- Expand documentation: solfege, Helmholtz, cents, slash chords, drop voicings, chord extensions, borrowed chord analysis, ADSR envelopes, MIDI export, new CLI commands
|
||||
|
||||
## 0.10.0
|
||||
|
||||
- Add `Scale.fitness()` to score how well a set of notes fits a scale (0.0–1.0)
|
||||
- Add `Key.suggest_next(chord)` for chord progression suggestions based on functional harmony
|
||||
- Add `Tone.helmholtz` and `Tone.scientific` properties for alternate pitch notation
|
||||
- Add `Chord.slash(bass)` and `Chord.slash_name` for slash chord notation (C/G, Am/E)
|
||||
- Add `save_midi()` for exporting tones, chords, and progressions as Standard MIDI Files
|
||||
- Add chord tone highlighting in `Fretboard.scale_diagram()` — chord tones uppercase, passing tones lowercase
|
||||
- Extend `Chord.analyze()` to recognize borrowed chords (bVI, bVII, bIII, etc.)
|
||||
|
||||
## 0.9.0
|
||||
|
||||
- Add ADSR envelope system with 8 presets: `Envelope.PIANO`, `ORGAN`, `PLUCK`, `PAD`, `STRINGS`, `BELL`, `STACCATO`, `NONE`
|
||||
- Add `Chord.from_symbol()` parser — handles any standard chord symbol (e.g. "F#m7b5", "Bbmaj9", "Gsus4") without lookup tables
|
||||
- Add `Key.pivot_chords(target)` for finding modulation pivot chords between two keys
|
||||
- Add `Scale.parallel_modes()` to show all modes sharing the same notes (C major → D dorian, E phrygian, etc.)
|
||||
- Add `Tone.cents_difference(other)` for measuring fine pitch differences in cents
|
||||
- Add `--envelope` flag to CLI play command
|
||||
- CLI play command now uses `Chord.from_symbol()` for broader chord parsing
|
||||
- Replace hardcoded `c_index = 3` with named `C_INDEX` constant throughout
|
||||
|
||||
## 0.8.3
|
||||
|
||||
- Add `Chord.symbol` property for standard shorthand notation (Cmaj7, Dm, G7, m7b5, etc.)
|
||||
- Add `Key.common_progressions()` to realize all named progressions in a key
|
||||
- Add CLI commands: `modes`, `circle`, `progressions`
|
||||
|
||||
## 0.8.2
|
||||
|
||||
- Use flat spellings in CHARTS `acceptable_tone_names` (e.g. Bbm now shows Bb/Db/F instead of A#/C#/F)
|
||||
|
||||
## 0.8.1
|
||||
|
||||
- Use musically correct flat spellings in flat keys (F major gives Bb, not A#)
|
||||
|
||||
## 0.8.0
|
||||
|
||||
- Add `Fretboard.scale_diagram()` for visual scale layouts on any instrument
|
||||
- Add `play_progression()` for sequential chord playback with gaps
|
||||
- Add cookbook documentation page with practical recipes
|
||||
- Curated guitar fingering overrides for common open chords
|
||||
- Fingering memoization with bounded cache, barre detection, 4-fret span constraint
|
||||
- API ergonomics: `Fretboard.chord()`, convenience constructors, slow test markers
|
||||
|
||||
## 0.7.0
|
||||
|
||||
- Add `Fretboard.chord()` method for named chord lookups
|
||||
- Improve fingering algorithm with better voicing selection
|
||||
- Rewrite all documentation in REPL style with verified output
|
||||
|
||||
## 0.6.1
|
||||
|
||||
- Fix sawtooth and triangle wave generation
|
||||
- Add WAV export via `save()`
|
||||
- Add CLI tests and play module tests
|
||||
- Skip play module tests when PortAudio is not available
|
||||
|
||||
## 0.6.0
|
||||
|
||||
- Support flat note names (Db, Bb, Eb, etc.) throughout the system
|
||||
- Add `Fingering` class for labeled chord fingerings
|
||||
- Add `pytheory play` CLI command for playing notes and chords
|
||||
- Add 12 example scripts showcasing pytheory features
|
||||
- Expand documentation with undocumented features and CLI guide
|
||||
|
||||
## 0.4.1
|
||||
|
||||
- Add `--temperament` flag to CLI tone command
|
||||
- Add Symbolic Pitch section to tones docs
|
||||
|
||||
## 0.4.0
|
||||
|
||||
- Add key signatures, scale diagrams, chord building, and progression analysis
|
||||
- Add CLI tool (`pytheory tone`, `pytheory chord`, `pytheory key`, etc.)
|
||||
- Add Jupyter notebook tutorial
|
||||
- Improve test coverage from 93% to 97% (476 tests)
|
||||
- Add type hints, docstrings, and property caching throughout
|
||||
|
||||
## 0.3.2
|
||||
|
||||
- Add type hints and docstrings throughout the library
|
||||
|
||||
## 0.3.1
|
||||
|
||||
- Add capo support, chord merging (`+`), tritone substitution
|
||||
- Add secondary dominants, Nashville number system
|
||||
- Add more common progressions (blues, jazz, flamenco, modal)
|
||||
|
||||
## 0.3.0
|
||||
|
||||
- Add interval naming (`Tone.interval_to()`)
|
||||
- Add MIDI conversion (`Tone.midi`, `Tone.from_midi()`)
|
||||
- Add `Tone.from_frequency()`, `Tone.transpose()`
|
||||
- Add `Chord.root`, `Chord.quality` properties
|
||||
- Add `Chord.from_name()`, `Chord.from_intervals()`, `Chord.from_midi_message()`
|
||||
- Add `Interval` constants (MINOR_THIRD, PERFECT_FIFTH, etc.)
|
||||
- Add `PROGRESSIONS` dict with common named progressions
|
||||
- Add `Tone.enharmonic` property
|
||||
- Add inversions, harmonize, and Roman numeral progressions
|
||||
- Add `Key` class with detection, signatures, relative/parallel keys
|
||||
- Add `Scale.detect()` and `Chord.from_tones()` convenience constructors
|
||||
- Add 25 instrument presets (mandolin family, violin family, banjo, harp, world instruments, keyboard)
|
||||
- Add `Tone.circle_of_fifths()` and `Tone.circle_of_fourths()`
|
||||
- Add chord identification (17 types), voice leading, tension scoring
|
||||
- Add beat frequencies, Plomp-Levelt dissonance model, harmony scoring
|
||||
|
||||
## 0.2.0
|
||||
|
||||
- Add `Fretboard` class for guitar fretboards
|
||||
- Add `play()` function with sine, sawtooth, and triangle wave synthesis
|
||||
- Add chord harmony and dissonance calculations
|
||||
- Modernize project structure (pyproject.toml, sounddevice)
|
||||
|
||||
## 0.1.0
|
||||
|
||||
- Initial release
|
||||
- Western 12-tone system with tones, scales, and basic chord support
|
||||
- Temperament support (equal, Pythagorean, meantone)
|
||||
- Indian (Hindustani), Arabic, Japanese, Blues, and Gamelan systems
|
||||
@@ -0,0 +1,38 @@
|
||||
# Claude Code Instructions
|
||||
|
||||
## Release Process
|
||||
|
||||
When releasing to PyPI, always do all three:
|
||||
|
||||
1. **Tag the commit**: `git tag v0.X.Y`
|
||||
2. **Push the tag**: `git push origin --tags`
|
||||
3. **Create a GitHub release**: `gh release create v0.X.Y --title "v0.X.Y: Short description" --notes "Release notes" --latest`
|
||||
|
||||
Don't forget to update `CHANGELOG.md` *before* the release commit.
|
||||
|
||||
## Version Bumping
|
||||
|
||||
- `pyproject.toml` and `pytheory/__init__.py` must match
|
||||
- Run `uv lock` after changing the version
|
||||
- Patch releases (0.X.Y) for bug fixes and small additions
|
||||
- Minor releases (0.X.0) for new features
|
||||
|
||||
## Testing
|
||||
|
||||
```
|
||||
uv run python -m pytest test_pytheory.py -x -q --tb=short -m "not slow"
|
||||
```
|
||||
|
||||
## Publishing
|
||||
|
||||
```
|
||||
uv build && uv publish --token <token> dist/pytheory-0.X.Y*
|
||||
```
|
||||
|
||||
## Music Preferences
|
||||
|
||||
- Detune: keep at 8-15, don't go above 25
|
||||
- Humanize: 0.2 is the sweet spot for melodic parts
|
||||
- Drum humanize: 0.15 default is good
|
||||
- No swing unless specifically asked
|
||||
- Sine and triangle are underrated — use them more
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2026 Kenneth Reitz
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -1,180 +1,161 @@
|
||||
# PyTheory: Music Theory for Humans
|
||||
|
||||
This library makes exploring music theory approachable and fun, treating Python as a musical instrument.
|
||||
|
||||
## Installation
|
||||
Explore music theory, compose multi-part arrangements, and export to MIDI — all in Python.
|
||||
|
||||
```
|
||||
$ pip install pytheory
|
||||
```
|
||||
|
||||
## Tones
|
||||
## Sketch Ideas Fast
|
||||
|
||||
```pycon
|
||||
>>> from pytheory import Tone
|
||||
```python
|
||||
from pytheory import Score, Pattern, Key, Duration, Chord
|
||||
from pytheory.play import play_score
|
||||
|
||||
>>> c4 = Tone.from_string("C4", system="western")
|
||||
>>> c4.frequency
|
||||
261.63
|
||||
score = Score("4/4", bpm=140)
|
||||
score.drums("bossa nova", repeats=4)
|
||||
|
||||
>>> c4 + 7 # perfect fifth
|
||||
<Tone G4>
|
||||
chords = score.part("chords", synth="fm", envelope="pad", reverb=0.4)
|
||||
lead = score.part("lead", synth="saw", envelope="pluck", delay=0.3, lowpass=3000)
|
||||
bass = score.part("bass", synth="sine", lowpass=500)
|
||||
|
||||
>>> c4.interval_to(c4 + 7)
|
||||
'perfect 5th'
|
||||
for sym in ["Am", "Dm", "E7", "Am"]:
|
||||
chords.add(Chord.from_symbol(sym), Duration.WHOLE)
|
||||
chords.add(Chord.from_symbol(sym), Duration.WHOLE)
|
||||
|
||||
>>> c4.midi
|
||||
60
|
||||
lead.arpeggio("Am", bars=2, pattern="updown", octaves=2)
|
||||
lead.arpeggio("Dm", bars=2, pattern="updown", octaves=2)
|
||||
lead.set(lowpass=5000, reverb=0.4)
|
||||
lead.arpeggio("E7", bars=2, pattern="up", octaves=2)
|
||||
lead.arpeggio("Am", bars=2, pattern="updown", octaves=2)
|
||||
|
||||
>>> Tone.from_frequency(440)
|
||||
<Tone A4>
|
||||
for n in ["A2", "E2", "A2", "C3"] * 4:
|
||||
bass.add(n, Duration.QUARTER)
|
||||
|
||||
>>> Tone.from_midi(69)
|
||||
<Tone A4>
|
||||
play_score(score) # hear it now
|
||||
score.save_midi("sketch.mid") # open in your DAW
|
||||
```
|
||||
|
||||
## Scales and Modes
|
||||
## Hear It Instantly
|
||||
|
||||
```pycon
|
||||
>>> from pytheory import TonedScale
|
||||
|
||||
>>> c_major = TonedScale(tonic="C4")["major"]
|
||||
>>> c_major.note_names
|
||||
['C', 'D', 'E', 'F', 'G', 'A', 'B', 'C']
|
||||
|
||||
>>> TonedScale(tonic="C4")["dorian"].note_names
|
||||
['C', 'D', 'D#', 'F', 'G', 'A', 'A#', 'C']
|
||||
```
|
||||
$ pytheory demo
|
||||
```
|
||||
|
||||
## Diatonic Harmony
|
||||
## Music Theory
|
||||
|
||||
```pycon
|
||||
>>> c_major.triad(0).identify()
|
||||
'C major'
|
||||
>>> from pytheory import Key, Chord, Tone
|
||||
|
||||
>>> c_major.seventh(4).identify()
|
||||
'G dominant 7th'
|
||||
|
||||
>>> [c.identify() for c in c_major.harmonize()]
|
||||
>>> Key("C", "major").chords
|
||||
['C major', 'D minor', 'E minor', 'F major', 'G major', 'A minor', 'B diminished']
|
||||
|
||||
>>> [c.identify() for c in c_major.progression("I", "V", "vi", "IV")]
|
||||
['C major', 'G major', 'A minor', 'F major']
|
||||
>>> [c.symbol for c in Key("G", "major").progression("I", "V", "vi", "IV")]
|
||||
['G', 'D', 'Em', 'C']
|
||||
|
||||
>>> Chord.from_symbol("F#m7b5").identify()
|
||||
'F# half-diminished 7th'
|
||||
|
||||
>>> Tone.from_string("C4").interval_to(Tone.from_string("G4"))
|
||||
'perfect 5th'
|
||||
|
||||
>>> Key("C", "major").pivot_chords(Key("G", "major"))
|
||||
['A minor', 'B minor', 'C major', 'D major', 'E minor', 'G major']
|
||||
|
||||
>>> Chord.from_tones("C", "E", "G").forte_number
|
||||
'3-11'
|
||||
|
||||
>>> from pytheory.scales import Scale
|
||||
>>> Scale.recommend("C", "Eb", "F", "Gb", "G", "Bb", top=3)
|
||||
[('C', 'blues', 1.0), ...]
|
||||
```
|
||||
|
||||
## Keys and Progressions
|
||||
## Composition
|
||||
|
||||
```pycon
|
||||
>>> from pytheory import Key
|
||||
```python
|
||||
score = Score("4/4", bpm=124)
|
||||
score.drums("house", repeats=16, fill="house", fill_every=8)
|
||||
|
||||
>>> key = Key("G", "major")
|
||||
>>> key.chords
|
||||
['G major', 'A minor', 'B minor', 'C major', 'D major', 'E minor', 'F# diminished']
|
||||
pad = score.part("pad", synth="supersaw", envelope="pad",
|
||||
reverb=0.5, chorus=0.3, sidechain=0.85)
|
||||
lead = score.part("lead", synth="saw", envelope="pluck",
|
||||
legato=True, glide=0.03, humanize=0.3)
|
||||
bass = score.part("bass", synth="sine", lowpass=300, sidechain=0.7)
|
||||
|
||||
>>> [c.identify() for c in key.progression("I", "V", "vi", "IV")]
|
||||
['G major', 'D major', 'E minor', 'C major']
|
||||
# Song structure
|
||||
score.section("verse")
|
||||
# ... add notes ...
|
||||
score.section("chorus")
|
||||
lead.set(lowpass=5000, reverb=0.3)
|
||||
# ... add notes ...
|
||||
score.end_section()
|
||||
|
||||
>>> Key.detect("C", "E", "G", "A", "D")
|
||||
<Key C major>
|
||||
score.repeat("verse")
|
||||
score.repeat("chorus", times=2)
|
||||
```
|
||||
|
||||
## Chord Analysis
|
||||
## 10 Synth Waveforms
|
||||
|
||||
```pycon
|
||||
>>> from pytheory import Chord, Tone
|
||||
sine, saw, triangle, square, pulse, FM, noise, supersaw, PWM slow, PWM fast — with detune, stereo pan, and spread.
|
||||
|
||||
>>> C4 = Tone.from_string("C4", system="western")
|
||||
>>> G4 = Tone.from_string("G4", system="western")
|
||||
## 58 Drum Patterns
|
||||
|
||||
>>> g7 = Chord([G4, G4+4, G4+7, G4+10])
|
||||
>>> g7.identify()
|
||||
'G dominant 7th'
|
||||
rock, jazz, bebop, bossa nova, salsa, samba, afrobeat, funk, reggae, house, trap, metal, drum and bass — and 45 more. Plus 21 fill presets. Stereo panned like a real kit.
|
||||
|
||||
>>> g7.analyze("C")
|
||||
'V7'
|
||||
## 6 Effects with Automation
|
||||
|
||||
>>> g7.tension
|
||||
{'score': 0.6, 'tritones': 1, 'minor_seconds': 0, 'has_dominant_function': True}
|
||||
```python
|
||||
lead = score.part("lead", synth="saw",
|
||||
distortion=0.7, lowpass=1000, lowpass_q=5.0,
|
||||
delay=0.3, reverb=0.4, reverb_type="plate",
|
||||
chorus=0.3)
|
||||
|
||||
>>> g7.transpose(-7).identify()
|
||||
'C dominant 7th'
|
||||
# Automate mid-song
|
||||
lead.set(lowpass=4000, distortion=0.9)
|
||||
|
||||
# LFO modulation
|
||||
lead.lfo("lowpass", rate=0.5, min=400, max=3000, bars=8)
|
||||
```
|
||||
|
||||
## Six Musical Systems
|
||||
Signal chain: distortion → chorus → lowpass → delay → reverb. Sidechain compression. Master bus compressor/limiter. Stereo output.
|
||||
|
||||
```pycon
|
||||
>>> from pytheory import TonedScale
|
||||
## Convolution Reverb
|
||||
|
||||
>>> TonedScale(tonic="Sa4", system="indian")["bhairav"].note_names
|
||||
['Sa', 'komal Re', 'Ga', 'Ma', 'Pa', 'komal Dha', 'Ni', 'Sa']
|
||||
7 synthetic impulse responses: Taj Mahal (12s), cathedral, plate, spring, cave, parking garage, canyon.
|
||||
|
||||
>>> TonedScale(tonic="Do4", system="arabic")["hijaz"].note_names
|
||||
['Do', 'Reb', 'Mi', 'Fa', 'Sol', 'Solb', 'Sib', 'Do']
|
||||
|
||||
>>> TonedScale(tonic="C4", system="japanese")["hirajoshi"].note_names
|
||||
['C', 'D', 'D#', 'G', 'G#', 'C']
|
||||
|
||||
>>> TonedScale(tonic="C4", system="blues")["blues"].note_names
|
||||
['C', 'D#', 'F', 'F#', 'G', 'A#', 'C']
|
||||
```python
|
||||
pad = score.part("pad", synth="supersaw",
|
||||
reverb=0.85, reverb_type="taj_mahal")
|
||||
```
|
||||
|
||||
## 6 Musical Systems
|
||||
|
||||
Western, Indian (Hindustani), Arabic (Maqam), Japanese, Blues/Pentatonic, Javanese Gamelan — 40+ scales.
|
||||
|
||||
## 25 Instrument Presets
|
||||
|
||||
```pycon
|
||||
>>> from pytheory import Fretboard, CHARTS
|
||||
Guitar (8 tunings), bass, ukulele, mandolin family, violin family, banjo, harp, oud, sitar, erhu, and more — with chord fingering generation.
|
||||
|
||||
>>> Fretboard.guitar() # standard tuning
|
||||
>>> Fretboard.guitar("drop d") # 8 alternate tunings
|
||||
>>> Fretboard.mandolin() # + mandola, octave mandolin, mandocello
|
||||
>>> Fretboard.violin() # + viola, cello, double bass
|
||||
>>> Fretboard.ukulele() # + banjo, harp, charango, erhu...
|
||||
>>> Fretboard.keyboard() # 88-key piano
|
||||
>>> Fretboard.keyboard(25, "C3") # 25-key MIDI controller
|
||||
|
||||
>>> CHARTS['western']['Am'].fingering(fretboard=Fretboard.guitar())
|
||||
Fingering(e=0, B=1, G=2, D=2, A=0, E=0)
|
||||
|
||||
>>> Fretboard.guitar().fingering(0, 1, 0, 2, 3, 0).identify()
|
||||
'C major'
|
||||
```
|
||||
|
||||
## Audio Playback
|
||||
|
||||
```pycon
|
||||
>>> from pytheory import play, Synth, Tone
|
||||
|
||||
>>> tone = Tone.from_string("A4", system="western")
|
||||
>>> play(tone, t=1_000) # sine wave, 1 second
|
||||
>>> play(tone, synth=Synth.SAW, t=1_000) # sawtooth wave
|
||||
|
||||
>>> from pytheory import save, Chord
|
||||
>>> save(Chord.from_name("Am7"), "am7.wav", t=2_000) # save to WAV
|
||||
```
|
||||
|
||||
## Command-Line Interface
|
||||
## Command Line
|
||||
|
||||
```
|
||||
$ pytheory tone A4 # frequency, MIDI, overtones
|
||||
$ pytheory chord C E G # identify chord from notes
|
||||
$ pytheory key G major # explore a key
|
||||
$ pytheory scale C dorian # show a scale
|
||||
$ pytheory fingering Am --capo 2 # guitar fingering
|
||||
$ pytheory progression C major I V vi IV # build a progression
|
||||
$ pytheory detect C E G A D # detect key from notes
|
||||
$ pytheory play Am7 --synth triangle # play a chord
|
||||
$ pytheory repl # interactive scratchpad
|
||||
$ pytheory demo # hear a generated track
|
||||
$ pytheory key G major # explore a key
|
||||
$ pytheory identify Cmaj7 # analyze a chord symbol
|
||||
$ pytheory progression C major I V vi IV # build a progression
|
||||
$ pytheory midi C major I V vi IV -o out.mid
|
||||
$ pytheory play Am7 --synth saw --envelope pluck
|
||||
$ pytheory modes C # show all modes
|
||||
$ pytheory circle C # circle of fifths
|
||||
```
|
||||
|
||||
## Features
|
||||
## Why Python?
|
||||
|
||||
- **6 musical systems**: Western, Indian (Hindustani), Arabic (Maqam), Japanese, Blues/Pentatonic, Javanese Gamelan
|
||||
- **40+ scales**: major, minor, harmonic minor, 7 modes, 10 thaats, 10 maqamat, pentatonic, blues, hirajoshi, pelog, slendro, and more
|
||||
- **Chord analysis**: identification (17 types), Roman numeral analysis, tension scoring, voice leading, Plomp-Levelt dissonance, beat frequencies
|
||||
- **Diatonic harmony**: triads, seventh chords, harmonize entire scales, build progressions from Roman numerals
|
||||
- **25 instrument presets**: guitar (8 tunings), 12-string, bass, mandolin family, violin family, banjo, harp, oud, sitar, shamisen, erhu, charango, pipa, balalaika, lute, pedal steel, keyboard
|
||||
- **Pitch tools**: frequency ↔ tone conversion, MIDI ↔ tone, interval naming, circle of fifths, overtone series, transposition
|
||||
- **3 temperaments**: equal, Pythagorean, quarter-comma meantone
|
||||
- **Audio synthesis**: sine, sawtooth, and triangle wave playback + WAV export
|
||||
A DAW is great for tweaking sounds. But when you're *thinking about music* — code is faster than clicking. Sketch ideas, hear them instantly, export MIDI, finish in your DAW.
|
||||
|
||||
Tools like [Claude Code](https://claude.ai/code) can use PyTheory to prototype musical ideas from natural language — "write a bossa nova in A minor with a saw lead and reverb" becomes real, playable music.
|
||||
|
||||
## Documentation
|
||||
|
||||
Full documentation with music theory guides: **[pytheory.kennethreitz.org](https://pytheory.kennethreitz.org)**
|
||||
**[pytheory.kennethreitz.org](https://pytheory.kennethreitz.org)**
|
||||
|
||||
Vendored
BIN
Binary file not shown.
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Vendored
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
Vendored
+4
@@ -0,0 +1,4 @@
|
||||
<audio controls style="width: 100%; margin: 0.5em 0 1.5em 0;">
|
||||
<source src="{{ pathto('_static/audio/' + file, 1) }}" type="audio/wav">
|
||||
Your browser does not support the audio element.
|
||||
</audio>
|
||||
@@ -0,0 +1,2 @@
|
||||
```{include} ../CHANGELOG.md
|
||||
```
|
||||
@@ -19,6 +19,7 @@ extensions = [
|
||||
"sphinx.ext.napoleon",
|
||||
"sphinx.ext.viewcode",
|
||||
"sphinx.ext.intersphinx",
|
||||
"myst_parser",
|
||||
]
|
||||
|
||||
autodoc_member_order = "bysource"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+320
-146
@@ -45,18 +45,20 @@ For seventh chords, there's also **third inversion** (7th in bass):
|
||||
|
||||
- G7 in third inversion: F G B D (notated G7/F)
|
||||
|
||||
.. code-block:: python
|
||||
.. code-block:: pycon
|
||||
|
||||
from pytheory import Chord, Tone
|
||||
>>> from pytheory import Chord, Tone
|
||||
|
||||
# All three are "C major" — identify() finds the root
|
||||
root = Chord([Tone.from_string(n, system="western") for n in ["C4", "E4", "G4"]])
|
||||
first = Chord([Tone.from_string(n, system="western") for n in ["E3", "G3", "C4"]])
|
||||
second = Chord([Tone.from_string(n, system="western") for n in ["G3", "C4", "E4"]])
|
||||
>>> root = Chord([Tone.from_string(n, system="western") for n in ["C4", "E4", "G4"]])
|
||||
>>> first = Chord([Tone.from_string(n, system="western") for n in ["E3", "G3", "C4"]])
|
||||
>>> second = Chord([Tone.from_string(n, system="western") for n in ["G3", "C4", "E4"]])
|
||||
|
||||
root.identify() # 'C major'
|
||||
first.identify() # 'C major'
|
||||
second.identify() # 'C major'
|
||||
>>> root.identify()
|
||||
'C major'
|
||||
>>> first.identify()
|
||||
'C major'
|
||||
>>> second.identify()
|
||||
'C major'
|
||||
|
||||
Extended Chords
|
||||
---------------
|
||||
@@ -72,33 +74,42 @@ A full 13th chord contains all 7 notes of the scale! In practice,
|
||||
tones are usually omitted — the 5th is typically dropped first, then
|
||||
the 11th (which clashes with the 3rd in dominant chords).
|
||||
|
||||
.. code-block:: python
|
||||
.. code-block:: pycon
|
||||
|
||||
from pytheory import TonedScale
|
||||
>>> from pytheory import TonedScale
|
||||
|
||||
scale = TonedScale(tonic="C4")["major"]
|
||||
>>> scale = TonedScale(tonic="C4")["major"]
|
||||
|
||||
# Build a Cmaj9 from the scale: C E G B D
|
||||
cmaj9 = scale.chord(0, 2, 4, 6, 8)
|
||||
|
||||
# Build a full C13 (in theory): C E G B D F A
|
||||
c13 = scale.chord(0, 2, 4, 6, 8, 10, 12)
|
||||
>>> cmaj9 = scale.chord(0, 2, 4, 6, 8)
|
||||
>>> c13 = scale.chord(0, 2, 4, 6, 8, 10, 12)
|
||||
|
||||
Using the Chord Chart
|
||||
---------------------
|
||||
|
||||
PyTheory includes 144 pre-built chords (12 roots x 12 qualities):
|
||||
|
||||
.. code-block:: python
|
||||
.. code-block:: pycon
|
||||
|
||||
from pytheory import CHARTS
|
||||
>>> from pytheory import Fretboard
|
||||
|
||||
chart = CHARTS["western"]
|
||||
>>> fb = Fretboard.guitar()
|
||||
>>> fb.chord("C")
|
||||
Fingering(e=0, B=1, G=0, D=2, A=3, E=x)
|
||||
>>> fb.chord("Am")
|
||||
Fingering(e=0, B=1, G=2, D=2, A=0, E=x)
|
||||
>>> fb.chord("G7")
|
||||
Fingering(e=1, B=0, G=0, D=0, A=2, E=3)
|
||||
|
||||
c_major = chart["C"] # C major (root position)
|
||||
a_minor = chart["Am"] # A minor
|
||||
g_seven = chart["G7"] # G dominant 7th
|
||||
d_dim = chart["Ddim"] # D diminished
|
||||
You can also build chords directly with ``Chord.from_name()``:
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> from pytheory import Chord
|
||||
|
||||
>>> Chord.from_name("G7").identify()
|
||||
'G dominant 7th'
|
||||
>>> Chord.from_name("Ddim").identify()
|
||||
'D diminished'
|
||||
|
||||
Available qualities:
|
||||
|
||||
@@ -119,52 +130,48 @@ Quality Intervals Example tones (from C)
|
||||
``"maj9"`` 4, 7, 11, 14 C E G B D (major 9th)
|
||||
============ ================ ================================
|
||||
|
||||
.. code-block:: python
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> from pytheory import CHARTS
|
||||
>>> chart = CHARTS["western"]
|
||||
|
||||
>>> chart["C"].acceptable_tone_names
|
||||
('C', 'E', 'G')
|
||||
|
||||
>>> chart["Cm7"].acceptable_tone_names
|
||||
('C', 'D#', 'G', 'A#') # Eb and Bb shown as sharps
|
||||
('C', 'Eb', 'G', 'Bb')
|
||||
|
||||
Building Chords
|
||||
---------------
|
||||
|
||||
Several convenience constructors make chord creation concise:
|
||||
|
||||
.. code-block:: python
|
||||
.. code-block:: pycon
|
||||
|
||||
from pytheory import Chord
|
||||
>>> from pytheory import Chord
|
||||
|
||||
# From note names (simplest)
|
||||
Chord.from_tones("C", "E", "G") # <Chord C major>
|
||||
Chord.from_tones("A", "C", "E") # <Chord A minor>
|
||||
>>> Chord.from_tones("C", "E", "G").identify()
|
||||
'C major'
|
||||
>>> Chord.from_tones("A", "C", "E").identify()
|
||||
'A minor'
|
||||
|
||||
# From a chord name (uses the built-in chart)
|
||||
Chord.from_name("Am7") # <Chord A minor 7th>
|
||||
Chord.from_name("G7") # <Chord G dominant 7th>
|
||||
>>> Chord.from_name("Am7").identify()
|
||||
'A minor 7th'
|
||||
>>> Chord.from_name("G7").identify()
|
||||
'G dominant 7th'
|
||||
|
||||
# From root + semitone intervals
|
||||
Chord.from_intervals("C", 4, 7) # <Chord C major>
|
||||
Chord.from_intervals("D", 3, 7) # <Chord D minor>
|
||||
Chord.from_intervals("G", 4, 7, 10) # <Chord G dominant 7th>
|
||||
>>> Chord.from_intervals("C", 4, 7).identify()
|
||||
'C major'
|
||||
>>> Chord.from_intervals("G", 4, 7, 10).identify()
|
||||
'G dominant 7th'
|
||||
|
||||
# From MIDI note numbers
|
||||
Chord.from_midi_message(60, 64, 67) # <Chord C major>
|
||||
>>> Chord.from_midi_message(60, 64, 67).identify()
|
||||
'C major'
|
||||
|
||||
# Full manual construction
|
||||
from pytheory import Tone
|
||||
c_major = Chord(tones=[
|
||||
Tone.from_string("C4", system="western"),
|
||||
Tone.from_string("E4", system="western"),
|
||||
Tone.from_string("G4", system="western"),
|
||||
])
|
||||
|
||||
for tone in c_major:
|
||||
print(tone)
|
||||
|
||||
len(c_major) # 3
|
||||
"C" in c_major # True
|
||||
>>> len(Chord.from_name("C"))
|
||||
3
|
||||
>>> "C" in Chord.from_name("C")
|
||||
True
|
||||
|
||||
Intervals
|
||||
---------
|
||||
@@ -172,13 +179,13 @@ Intervals
|
||||
The ``intervals`` property returns semitone distances between adjacent
|
||||
tones — these are musically meaningful and octave-invariant:
|
||||
|
||||
.. code-block:: python
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> c_major.intervals
|
||||
[4, 3] # major 3rd (4) + minor 3rd (3) = major triad
|
||||
>>> Chord.from_tones("C", "E", "G").intervals
|
||||
[4, 3]
|
||||
|
||||
>>> Chord(tones=[C4, Eb4, G4]).intervals
|
||||
[3, 4] # minor 3rd + major 3rd = minor triad
|
||||
>>> Chord.from_tones("C", "Eb", "G").intervals
|
||||
[3, 4]
|
||||
|
||||
Consonance and Dissonance
|
||||
-------------------------
|
||||
@@ -205,13 +212,16 @@ Minor 3rd 6:5 Every 6th wave aligns
|
||||
Tritone 45:32 Waves rarely align
|
||||
=========== ===== ====================
|
||||
|
||||
.. code-block:: python
|
||||
.. code-block:: pycon
|
||||
|
||||
fifth = Chord([C4, G4])
|
||||
tritone = Chord([C4, F_sharp_4])
|
||||
>>> from pytheory import Chord, Tone
|
||||
>>> C4 = Tone.from_string("C4", system="western")
|
||||
>>> G4 = Tone.from_string("G4", system="western")
|
||||
|
||||
fifth.harmony > tritone.harmony # True
|
||||
# The perfect fifth's 3:2 ratio scores higher
|
||||
>>> fifth = Chord([C4, G4])
|
||||
>>> tritone = Chord([C4, C4 + 6])
|
||||
>>> fifth.harmony > tritone.harmony
|
||||
True
|
||||
|
||||
Dissonance Score
|
||||
~~~~~~~~~~~~~~~~
|
||||
@@ -227,14 +237,13 @@ The roughness depends on the frequency difference relative to the
|
||||
that register). Maximum roughness occurs when the difference equals
|
||||
the critical bandwidth.
|
||||
|
||||
.. code-block:: python
|
||||
.. code-block:: pycon
|
||||
|
||||
# Octave: frequencies far apart → low roughness
|
||||
octave = Chord([C4, C5])
|
||||
# Major 3rd: closer frequencies → higher roughness
|
||||
third = Chord([C4, E4])
|
||||
|
||||
octave.dissonance < third.dissonance # True
|
||||
>>> E4 = Tone.from_string("E4", system="western")
|
||||
>>> octave = Chord([C4, C4 + 12])
|
||||
>>> third = Chord([C4, E4])
|
||||
>>> octave.dissonance < third.dissonance
|
||||
True
|
||||
|
||||
Beat Frequencies
|
||||
~~~~~~~~~~~~~~~~
|
||||
@@ -247,23 +256,23 @@ you hear a pulsing at the **beat frequency**: ``|f1 - f2|`` Hz.
|
||||
- **15–30 Hz**: Perceived as buzzing/roughness
|
||||
- **> 30 Hz**: No longer beating — becomes part of the timbre
|
||||
|
||||
.. code-block:: python
|
||||
.. code-block:: pycon
|
||||
|
||||
chord = Chord(tones=[A4, E5, A5])
|
||||
>>> A4 = Tone.from_string("A4", system="western")
|
||||
>>> chord = Chord([A4, A4 + 7, A4 + 12])
|
||||
|
||||
# All pairwise beat frequencies, sorted ascending
|
||||
chord.beat_frequencies
|
||||
# [(A4, E5, 189.6), (E5, A5, 220.0), (A4, A5, 440.0)]
|
||||
>>> chord.beat_frequencies
|
||||
[...]
|
||||
|
||||
# The slowest (most perceptible) beat
|
||||
chord.beat_pulse # 189.6 Hz
|
||||
>>> round(chord.beat_pulse, 1)
|
||||
219.3
|
||||
|
||||
Transposition
|
||||
-------------
|
||||
|
||||
Shift an entire chord up or down by any number of semitones:
|
||||
|
||||
.. code-block:: python
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> Chord.from_name("C").transpose(7).identify()
|
||||
'G major'
|
||||
@@ -276,20 +285,20 @@ Chord Manipulation
|
||||
|
||||
Add or remove individual tones from a chord:
|
||||
|
||||
.. code-block:: python
|
||||
.. code-block:: pycon
|
||||
|
||||
from pytheory import Chord, Tone
|
||||
>>> from pytheory import Chord, Tone
|
||||
|
||||
c_major = Chord.from_tones("C", "E", "G")
|
||||
>>> c_major = Chord.from_tones("C", "E", "G")
|
||||
|
||||
# Add a tone to build a seventh chord
|
||||
b4 = Tone.from_string("B4", system="western")
|
||||
cmaj7 = c_major.add_tone(b4)
|
||||
cmaj7.identify() # 'C major 7th'
|
||||
>>> b4 = Tone.from_string("B4", system="western")
|
||||
>>> cmaj7 = c_major.add_tone(b4)
|
||||
>>> cmaj7.identify()
|
||||
'C major 7th'
|
||||
|
||||
# Remove a tone
|
||||
c_again = cmaj7.remove_tone("B")
|
||||
c_again.identify() # 'C major'
|
||||
>>> c_again = cmaj7.remove_tone("B")
|
||||
>>> c_again.identify()
|
||||
'C major'
|
||||
|
||||
Chord Identification
|
||||
--------------------
|
||||
@@ -298,27 +307,38 @@ Give PyTheory any set of tones and it will tell you what chord it is.
|
||||
It tries every tone as a potential root and matches the interval pattern
|
||||
against 17 known chord types (triads, 7ths, 9ths, sus, power chords).
|
||||
|
||||
.. code-block:: python
|
||||
.. code-block:: pycon
|
||||
|
||||
from pytheory import Chord
|
||||
>>> from pytheory import Chord
|
||||
|
||||
# From note names
|
||||
Chord.from_tones("A", "C", "E").identify() # 'A minor'
|
||||
Chord.from_tones("G", "B", "D", "F").identify() # 'G dominant 7th'
|
||||
>>> Chord.from_tones("A", "C", "E").identify()
|
||||
'A minor'
|
||||
>>> Chord.from_tones("G", "B", "D", "F").identify()
|
||||
'G dominant 7th'
|
||||
|
||||
# Works with any voicing or inversion
|
||||
Chord.from_tones("E", "G", "C").identify() # 'C major'
|
||||
>>> Chord.from_tones("E", "G", "C").identify()
|
||||
'C major'
|
||||
|
||||
# Flats work too
|
||||
Chord.from_tones("Bb", "D", "F").identify() # 'Bb major'
|
||||
>>> Chord.from_tones("Bb", "D", "F").identify()
|
||||
'Bb major'
|
||||
|
||||
Enharmonic spellings are fully supported — Cb, Fb, E#, B#, double
|
||||
sharps/flats, and unicode symbols (see :doc:`tones` for details):
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> Chord.from_tones("Cb", "Eb", "Gb").identify()
|
||||
'B minor'
|
||||
|
||||
You can also access the root and quality separately:
|
||||
|
||||
.. code-block:: python
|
||||
.. code-block:: pycon
|
||||
|
||||
chord = Chord.from_name("Am7")
|
||||
chord.root # <Tone A4>
|
||||
chord.quality # 'minor 7th'
|
||||
>>> chord = Chord.from_name("Am7")
|
||||
>>> chord.root
|
||||
<Tone A4>
|
||||
>>> chord.quality
|
||||
'minor 7th'
|
||||
|
||||
Harmonic Analysis
|
||||
-----------------
|
||||
@@ -328,22 +348,22 @@ key. This is how musicians describe chord progressions independent of
|
||||
key — "I-IV-V" means the same thing in C major (C-F-G) as in G major
|
||||
(G-C-D).
|
||||
|
||||
.. code-block:: python
|
||||
.. code-block:: pycon
|
||||
|
||||
from pytheory import Chord, Tone
|
||||
>>> from pytheory import Chord, Tone
|
||||
|
||||
C4 = Tone.from_string("C4", system="western")
|
||||
D4 = Tone.from_string("D4", system="western")
|
||||
E4 = Tone.from_string("E4", system="western")
|
||||
F4 = Tone.from_string("F4", system="western")
|
||||
G4 = Tone.from_string("G4", system="western")
|
||||
A4 = Tone.from_string("A4", system="western")
|
||||
B4 = Tone.from_string("B4", system="western")
|
||||
>>> C4 = Tone.from_string("C4", system="western")
|
||||
>>> E4 = Tone.from_string("E4", system="western")
|
||||
>>> G4 = Tone.from_string("G4", system="western")
|
||||
|
||||
Chord([C4, E4, G4]).analyze("C") # 'I' (tonic)
|
||||
Chord([D4, F4, A4]).analyze("C") # 'ii' (supertonic minor)
|
||||
Chord([G4, B4, G4+5]).analyze("C") # 'V' (dominant)
|
||||
Chord([G4, B4, G4+5, G4+10]).analyze("C") # 'V7' (dominant 7th)
|
||||
>>> Chord([C4, E4, G4]).analyze("C")
|
||||
'I'
|
||||
>>> Chord.from_tones("D", "F", "A").analyze("C")
|
||||
'ii'
|
||||
>>> Chord([G4, G4+4, G4+7]).analyze("C")
|
||||
'V'
|
||||
>>> Chord([G4, G4+4, G4+7, G4+10]).analyze("C")
|
||||
'V7'
|
||||
|
||||
Tension and Resolution
|
||||
----------------------
|
||||
@@ -359,18 +379,21 @@ quantifies this based on:
|
||||
- **Dominant function**: the specific combination of a major 3rd and
|
||||
minor 7th above the root — the hallmark of the V7 chord.
|
||||
|
||||
.. code-block:: python
|
||||
.. code-block:: pycon
|
||||
|
||||
# A C major triad is fully resolved — no tension
|
||||
c_major = Chord([C4, E4, G4])
|
||||
c_major.tension['score'] # 0.0
|
||||
c_major.tension['tritones'] # 0
|
||||
>>> c_major = Chord([C4, E4, G4])
|
||||
>>> c_major.tension['score']
|
||||
0.0
|
||||
>>> c_major.tension['tritones']
|
||||
0
|
||||
|
||||
# G7 is loaded with tension — it wants to resolve to C
|
||||
g7 = Chord([G4, B4, G4+5, G4+10])
|
||||
g7.tension['score'] # 0.6
|
||||
g7.tension['tritones'] # 1
|
||||
g7.tension['has_dominant_function'] # True
|
||||
>>> g7 = Chord([G4, G4+4, G4+7, G4+10])
|
||||
>>> g7.tension['score']
|
||||
0.6
|
||||
>>> g7.tension['tritones']
|
||||
1
|
||||
>>> g7.tension['has_dominant_function']
|
||||
True
|
||||
|
||||
Voice Leading
|
||||
-------------
|
||||
@@ -380,14 +403,16 @@ jumping all voices to new positions, good voice leading moves each note
|
||||
the minimum distance to reach the next chord. Bach's chorales are the
|
||||
gold standard — every voice moves by step whenever possible.
|
||||
|
||||
.. code-block:: python
|
||||
.. code-block:: pycon
|
||||
|
||||
c_maj = Chord([C4, E4, G4])
|
||||
f_maj = Chord([F4, A4, C4+12])
|
||||
>>> c_maj = Chord.from_tones("C", "E", "G")
|
||||
>>> f_maj = Chord.from_tones("F", "A", "C")
|
||||
|
||||
for src, dst, motion in c_maj.voice_leading(f_maj):
|
||||
print(f"{src} -> {dst} ({motion:+d} semitones)")
|
||||
# Each voice moves the minimum distance to reach the target chord
|
||||
>>> for src, dst, motion in c_maj.voice_leading(f_maj):
|
||||
... print(f"{src} -> {dst} ({motion:+d} semitones)")
|
||||
G4 -> A4 (+2 semitones)
|
||||
E4 -> F4 (+1 semitones)
|
||||
C4 -> C4 (+0 semitones)
|
||||
|
||||
Tritone Substitution
|
||||
--------------------
|
||||
@@ -400,17 +425,14 @@ tritone interval — the 3rd and 7th simply swap roles.
|
||||
|
||||
Common tritone subs: G7 <-> Db7, C7 <-> F#7, D7 <-> Ab7.
|
||||
|
||||
.. code-block:: python
|
||||
.. code-block:: pycon
|
||||
|
||||
from pytheory import Chord
|
||||
>>> from pytheory import Chord
|
||||
|
||||
g7 = Chord.from_name("G7")
|
||||
sub = g7.tritone_sub()
|
||||
sub.identify() # 'C# dominant 7th' (enharmonic Db7)
|
||||
|
||||
# Both resolve to C — try them in a ii-V-I:
|
||||
# Dm7 → G7 → Cmaj7 (standard)
|
||||
# Dm7 → Db7 → Cmaj7 (with tritone sub — chromatic bass line!)
|
||||
>>> g7 = Chord.from_name("G7")
|
||||
>>> sub = g7.tritone_sub()
|
||||
>>> sub.identify()
|
||||
'C# dominant 7th'
|
||||
|
||||
The Overtone Series
|
||||
-------------------
|
||||
@@ -425,12 +447,164 @@ overtones of C already contain G. The two tones share acoustic energy,
|
||||
reinforcing each other. A dissonant interval like C and C# shares
|
||||
almost no overtones — the waves clash.
|
||||
|
||||
.. code-block:: python
|
||||
.. code-block:: pycon
|
||||
|
||||
from pytheory import Tone
|
||||
>>> from pytheory import Tone
|
||||
|
||||
a4 = Tone.from_string("A4", system="western")
|
||||
a4.overtones(8)
|
||||
# [440.0, 880.0, 1320.0, 1760.0, 2200.0, 2640.0, 3080.0, 3520.0]
|
||||
# A4 A5 E6 A6 C#7 E7 ~G7 A7
|
||||
# fund. oct. 5th+oct 2oct 3rd 5th ~7th 3oct
|
||||
>>> a4 = Tone.from_string("A4", system="western")
|
||||
>>> [round(f, 1) for f in a4.overtones(8)]
|
||||
[440.0, 880.0, 1320.0, 1760.0, 2200.0, 2640.0, 3080.0, 3520.0]
|
||||
|
||||
Chord Symbols
|
||||
-------------
|
||||
|
||||
The ``symbol`` property returns compact lead-sheet notation, while
|
||||
``from_symbol()`` parses any standard chord symbol — no lookup table needed:
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> Chord.from_tones("C", "E", "G").symbol
|
||||
'C'
|
||||
>>> Chord.from_name("Am7").symbol
|
||||
'Am7'
|
||||
>>> Chord.from_symbol("F#m7b5").identify()
|
||||
'F# half-diminished 7th'
|
||||
>>> Chord.from_symbol("Bbmaj9").symbol
|
||||
'Bbmaj9'
|
||||
|
||||
Slash Chords
|
||||
------------
|
||||
|
||||
`Slash chords <https://en.wikipedia.org/wiki/Slash_chord>`_ place a specific
|
||||
note in the bass below the chord. They're written as Chord/Bass in lead sheets:
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> c = Chord.from_symbol("C")
|
||||
>>> c_over_g = c.slash("G")
|
||||
>>> c_over_g.slash_name
|
||||
'C/G'
|
||||
>>> c.slash("E").slash_name
|
||||
'C/E'
|
||||
|
||||
Drop Voicings
|
||||
-------------
|
||||
|
||||
`Drop voicings <https://en.wikipedia.org/wiki/Voicing_(music)#Drop_voicings>`_
|
||||
are standard arranging techniques for spreading chord tones across registers:
|
||||
|
||||
- **Close voicing** — all tones packed within one octave
|
||||
- **Open voicing** — alternating tones raised an octave for wider spacing
|
||||
- **Drop 2** — second-highest voice dropped an octave (standard jazz guitar)
|
||||
- **Drop 3** — third-highest voice dropped an octave
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> cmaj7 = Chord.from_symbol("Cmaj7")
|
||||
>>> cmaj7.close_voicing()
|
||||
<Chord C major 7th>
|
||||
>>> cmaj7.drop2()
|
||||
<Chord C major 7th>
|
||||
|
||||
Chord Extensions
|
||||
----------------
|
||||
|
||||
The ``extensions()`` method suggests available extensions (9th, 11th, 13th)
|
||||
that don't clash with existing chord tones:
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> from pytheory import Chord, TonedScale
|
||||
>>> cm = Chord.from_symbol("C")
|
||||
>>> cm.extensions()
|
||||
[...]
|
||||
|
||||
>>> # Filter extensions against a scale for diatonic correctness:
|
||||
>>> scale = TonedScale(tonic="C4")["major"]
|
||||
>>> cm.extensions(scale=scale)
|
||||
[...]
|
||||
|
||||
Borrowed Chord Analysis
|
||||
-----------------------
|
||||
|
||||
``analyze()`` now recognizes chromatic chords from modal interchange,
|
||||
labeling them with flat-degree prefixes:
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> Chord.from_symbol("Ab").analyze("C", "major")
|
||||
'bVI'
|
||||
>>> Chord.from_symbol("Bb").analyze("C", "major")
|
||||
'bVII'
|
||||
|
||||
Figured Bass
|
||||
------------
|
||||
|
||||
`Figured bass <https://en.wikipedia.org/wiki/Figured_bass>`_ is the
|
||||
classical notation for chord inversions — numbers below the bass note
|
||||
describing the intervals above it. It's how Bach, Handel, and every
|
||||
Baroque composer communicated harmony.
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> from pytheory import Chord, Tone
|
||||
|
||||
>>> root = Chord([Tone.from_string("C4"), Tone.from_string("E4"), Tone.from_string("G4")])
|
||||
>>> root.figured_bass
|
||||
''
|
||||
|
||||
>>> first_inv = Chord([Tone.from_string("E3"), Tone.from_string("G3"), Tone.from_string("C4")])
|
||||
>>> first_inv.figured_bass
|
||||
'6'
|
||||
|
||||
>>> second_inv = Chord([Tone.from_string("G3"), Tone.from_string("C4"), Tone.from_string("E4")])
|
||||
>>> second_inv.figured_bass
|
||||
'6/4'
|
||||
|
||||
For seventh chords: root position → ``"7"``, first inversion → ``"6/5"``,
|
||||
second inversion → ``"4/3"``, third inversion → ``"2"``.
|
||||
|
||||
Combine with Roman numeral analysis using ``analyze_figured()``:
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> first_inv.analyze_figured("C")
|
||||
'I6'
|
||||
|
||||
Pitch Class Sets
|
||||
----------------
|
||||
|
||||
`Pitch class set theory <https://en.wikipedia.org/wiki/Set_theory_(music)>`_
|
||||
is the framework for analyzing atonal and post-tonal music. It reduces
|
||||
any collection of notes to abstract pitch classes (0–11, where C=0),
|
||||
finds the most compact form, and catalogs it with a Forte number.
|
||||
|
||||
If you're studying Schoenberg, Webern, Bartók, or any 20th-century
|
||||
music that doesn't follow traditional harmony, this is the tool.
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> Chord.from_tones("C", "E", "G").pitch_classes
|
||||
{0, 4, 7}
|
||||
|
||||
>>> Chord.from_tones("C", "E", "G").prime_form
|
||||
(0, 3, 7)
|
||||
|
||||
>>> Chord.from_tones("A", "C", "E").prime_form
|
||||
(0, 3, 7)
|
||||
|
||||
Major and minor triads share the same prime form — they're inversions
|
||||
of each other in pitch class space.
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> Chord.from_tones("C", "E", "G").forte_number
|
||||
'3-11'
|
||||
|
||||
>>> Chord.from_tones("C", "E", "G", "B").forte_number
|
||||
'4-20'
|
||||
|
||||
>>> Chord.from_tones("C", "E", "G#").forte_number
|
||||
'3-12'
|
||||
|
||||
Chords are the vertical dimension of music -- melody tells you where you're going, but harmony tells you how it feels to be there. Between construction, identification, voice leading, tension analysis, and pitch class sets, you've got tools to look at any chord from every angle. Pick a song you love, grab its chords, and start asking questions.
|
||||
|
||||
+98
-1
@@ -1,7 +1,28 @@
|
||||
Command-Line Interface
|
||||
======================
|
||||
|
||||
PyTheory includes a CLI for quick music theory lookups from the terminal.
|
||||
PyTheory includes a CLI for music theory lookups, composition, and
|
||||
playback — all from the terminal.
|
||||
|
||||
Interactive REPL
|
||||
----------------
|
||||
|
||||
For extended exploration, the REPL is a music theory scratchpad with
|
||||
tab completion. See the :doc:`repl` guide for details::
|
||||
|
||||
$ pytheory repl
|
||||
|
||||
Demo
|
||||
----
|
||||
|
||||
The fastest way to hear what PyTheory can do. Generates and plays a
|
||||
random multi-part track — different every time::
|
||||
|
||||
$ pytheory demo
|
||||
♫ Jazz Club
|
||||
Bb major | 105 bpm
|
||||
Bb → Gm → Cm → F
|
||||
jazz drums | saw lead | fm pad
|
||||
|
||||
Tone Lookup
|
||||
-----------
|
||||
@@ -127,3 +148,79 @@ Play individual notes or chords (requires PortAudio)::
|
||||
$ pytheory play C E G --synth saw # Sawtooth wave
|
||||
$ pytheory play A4 --duration 2000 # 2 seconds
|
||||
$ pytheory play C E G --temperament meantone
|
||||
$ pytheory play Am7 --envelope pad # With ADSR envelope
|
||||
$ pytheory play C4 --envelope bell # Bell-like ring
|
||||
|
||||
Chord Identification (from symbol)
|
||||
-----------------------------------
|
||||
|
||||
Parse any chord symbol and get a full analysis::
|
||||
|
||||
$ pytheory identify Cmaj7
|
||||
Chord: C major 7th
|
||||
Symbol: Cmaj7
|
||||
Tones: C4 E4 G4 B4
|
||||
Intervals: [4, 3, 4]
|
||||
Harmony: 0.5833
|
||||
Dissonance: 1.2345
|
||||
Tension: score=0.00 tritones=0 minor_2nds=0 dominant=False
|
||||
|
||||
$ pytheory identify F#m7b5
|
||||
|
||||
MIDI Export
|
||||
-----------
|
||||
|
||||
Export a chord progression to a Standard MIDI File::
|
||||
|
||||
$ pytheory midi C major I V vi IV -o pop.mid
|
||||
Key: C major
|
||||
Progression: I V vi IV
|
||||
BPM: 120
|
||||
Duration: 500 ms
|
||||
Output: pop.mid
|
||||
|
||||
$ pytheory midi G major ii V I -o jazz.mid --bpm 140 --duration 800
|
||||
|
||||
Modes
|
||||
-----
|
||||
|
||||
Show all 7 modes starting from a note::
|
||||
|
||||
$ pytheory modes C
|
||||
Modes of C:
|
||||
|
||||
ionian C D E F G A B C
|
||||
dorian C D Eb F G A Bb C
|
||||
phrygian C Db Eb F G Ab Bb C
|
||||
lydian C D E F# G A B C
|
||||
mixolydian C D E F G A Bb C
|
||||
aeolian C D Eb F G Ab Bb C
|
||||
locrian C Db Eb F Gb Ab Bb C
|
||||
|
||||
Circle of Fifths
|
||||
----------------
|
||||
|
||||
Display the circle of fifths and fourths from any note::
|
||||
|
||||
$ pytheory circle C
|
||||
Circle of fifths from C:
|
||||
→ C → G → D → A → E → B → F# → C# → G# → D# → A# → F
|
||||
|
||||
Circle of fourths from C:
|
||||
→ C → F → A# → D# → G# → C# → F# → B → E → A → D → G
|
||||
|
||||
Common Progressions
|
||||
-------------------
|
||||
|
||||
Show all named progressions realized in a key::
|
||||
|
||||
$ pytheory progressions C major
|
||||
Common progressions in C major:
|
||||
|
||||
I-IV-V-I C → F → G → C
|
||||
I-V-vi-IV C → G → Am → F
|
||||
12-bar blues C → C → C → C → F → F → C → C → G → F → C → G
|
||||
ii-V-I Dm → G7 → C
|
||||
...
|
||||
|
||||
The CLI is there for quick lookups when you don't want to open a Python session -- just ask your question and get back to playing.
|
||||
|
||||
@@ -0,0 +1,547 @@
|
||||
Cookbook
|
||||
=======
|
||||
|
||||
Real-world recipes for common musical tasks. Each recipe is self-contained
|
||||
and ready to paste into a Python session.
|
||||
|
||||
Analyze a Song
|
||||
--------------
|
||||
|
||||
Take the chord progression from "Let It Be" (C G Am F) and analyze it
|
||||
in the key of C major:
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> from pytheory import Chord, Key
|
||||
|
||||
>>> C = Chord.from_name("C")
|
||||
>>> G = Chord.from_name("G")
|
||||
>>> Am = Chord.from_name("Am")
|
||||
>>> F = Chord.from_name("F")
|
||||
|
||||
>>> [c.identify() for c in [C, G, Am, F]]
|
||||
['C major', 'G major', 'A minor', 'F major']
|
||||
|
||||
>>> [c.analyze("C") for c in [C, G, Am, F]]
|
||||
['I', 'V', 'vi', 'IV']
|
||||
|
||||
>>> key = Key("C", "major")
|
||||
>>> [c.identify() for c in key.progression("I", "V", "vi", "IV")]
|
||||
['C major', 'G major', 'A minor', 'F major']
|
||||
|
||||
Write a 12-Bar Blues
|
||||
--------------------
|
||||
|
||||
The `12-bar blues <https://en.wikipedia.org/wiki/Twelve-bar_blues>`_ is
|
||||
built from the I, IV, and V chords. Here it is in the key of A:
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> from pytheory import Key, Chord
|
||||
|
||||
>>> key = Key("A", "major")
|
||||
>>> [c.identify() for c in key.progression("I", "IV", "V")]
|
||||
['A major', 'D major', 'E major']
|
||||
|
||||
>>> bars = ["I","I","I","I", "IV","IV","I","I", "V","IV","I","V"]
|
||||
>>> [c.identify() for c in key.progression(*bars)]
|
||||
['A major', 'A major', 'A major', 'A major', 'D major', 'D major', 'A major', 'A major', 'E major', 'D major', 'A major', 'E major']
|
||||
|
||||
>>> Chord.from_name("A7").identify()
|
||||
'A dominant 7th'
|
||||
>>> Chord.from_name("D7").identify()
|
||||
'D dominant 7th'
|
||||
>>> Chord.from_name("E7").identify()
|
||||
'E dominant 7th'
|
||||
|
||||
Find Chords in a Key
|
||||
--------------------
|
||||
|
||||
The :class:`~pytheory.scales.Key` class builds diatonic chords for any
|
||||
key and lets you pull progressions by Roman numeral or Nashville number:
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> from pytheory import Key
|
||||
|
||||
>>> key = Key("G", "major")
|
||||
>>> key.chords
|
||||
['G major', 'A minor', 'B minor', 'C major', 'D major', 'E minor', 'F# diminished']
|
||||
|
||||
>>> [c.identify() for c in key.progression("I", "V", "vi", "IV")]
|
||||
['G major', 'D major', 'E minor', 'C major']
|
||||
|
||||
>>> [c.identify() for c in key.nashville(1, 5, 6, 4)]
|
||||
['G major', 'D major', 'E minor', 'C major']
|
||||
|
||||
Compare Scales
|
||||
--------------
|
||||
|
||||
Play the same tonic through different scales to hear how each mode
|
||||
reshapes the palette. The western modes share the same notes but start
|
||||
on different degrees; the blues scale adds the "blue note" (flat 5th):
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> from pytheory import TonedScale
|
||||
|
||||
>>> c = TonedScale(tonic="C4")
|
||||
>>> c["major"].note_names
|
||||
['C', 'D', 'E', 'F', 'G', 'A', 'B', 'C']
|
||||
>>> c["minor"].note_names
|
||||
['C', 'D', 'Eb', 'F', 'G', 'Ab', 'Bb', 'C']
|
||||
>>> c["dorian"].note_names
|
||||
['C', 'D', 'Eb', 'F', 'G', 'A', 'Bb', 'C']
|
||||
>>> c["mixolydian"].note_names
|
||||
['C', 'D', 'E', 'F', 'G', 'A', 'Bb', 'C']
|
||||
|
||||
>>> c_blues = TonedScale(tonic="C4", system="blues")
|
||||
>>> c_blues["blues"].note_names
|
||||
['C', 'Eb', 'F', 'Gb', 'G', 'Bb', 'C']
|
||||
|
||||
Guitar Chord Chart
|
||||
------------------
|
||||
|
||||
Generate fingerings for guitar and ukulele with
|
||||
:class:`~pytheory.tones.Fretboard`:
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> from pytheory import Fretboard
|
||||
|
||||
>>> fb = Fretboard.guitar()
|
||||
>>> fb.chord("C")
|
||||
Fingering(e=0, B=1, G=0, D=2, A=3, E=x)
|
||||
>>> fb.chord("G")
|
||||
Fingering(e=3, B=0, G=0, D=0, A=2, E=3)
|
||||
>>> fb.chord("Am")
|
||||
Fingering(e=0, B=1, G=2, D=2, A=0, E=x)
|
||||
>>> fb.chord("D")
|
||||
Fingering(e=2, B=3, G=2, D=0, A=x, E=x)
|
||||
|
||||
>>> uke = Fretboard.ukulele()
|
||||
>>> uke.chord("C")
|
||||
Fingering(A=3, E=0, C=0, G=0)
|
||||
>>> uke.chord("G")
|
||||
Fingering(A=2, E=3, C=2, G=0)
|
||||
|
||||
Explore an Interval
|
||||
-------------------
|
||||
|
||||
Start from A4 (440 Hz) and walk through intervals, checking names and
|
||||
frequency ratios:
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> from pytheory import Tone
|
||||
|
||||
>>> a4 = Tone.from_string("A4", system="western")
|
||||
>>> a4.frequency
|
||||
440.0
|
||||
|
||||
>>> minor_3rd = a4 + 3
|
||||
>>> a4.interval_to(minor_3rd)
|
||||
'minor 3rd'
|
||||
|
||||
>>> p5 = a4 + 7
|
||||
>>> a4.interval_to(p5)
|
||||
'perfect 5th'
|
||||
>>> round(p5.frequency / a4.frequency, 4)
|
||||
1.4983
|
||||
|
||||
>>> octave = a4 + 12
|
||||
>>> a4.interval_to(octave)
|
||||
'octave'
|
||||
>>> round(octave.frequency / a4.frequency, 4)
|
||||
2.0
|
||||
|
||||
Walk the Circle of Fifths
|
||||
-------------------------
|
||||
|
||||
The `circle of fifths <https://en.wikipedia.org/wiki/Circle_of_fifths>`_
|
||||
is the backbone of Western harmony — each step adds one sharp or flat:
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> from pytheory import Tone
|
||||
|
||||
>>> c = Tone.from_string("C4", system="western")
|
||||
>>> [t.name for t in c.circle_of_fifths()]
|
||||
['C', 'G', 'D', 'A', 'E', 'B', 'F#', 'C#', 'G#', 'D#', 'A#', 'F']
|
||||
|
||||
>>> g = Tone.from_string("G4", system="western")
|
||||
>>> [t.name for t in g.circle_of_fifths()]
|
||||
['G', 'D', 'A', 'E', 'B', 'F#', 'C#', 'G#', 'D#', 'A#', 'F', 'C']
|
||||
|
||||
Voice Leading Between Chords
|
||||
-----------------------------
|
||||
|
||||
Find the smoothest path from one chord to the next — each voice moves
|
||||
the minimum distance:
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> from pytheory import Chord
|
||||
|
||||
>>> c_maj = Chord.from_tones("C", "E", "G")
|
||||
>>> f_maj = Chord.from_tones("F", "A", "C")
|
||||
|
||||
>>> for src, dst, motion in c_maj.voice_leading(f_maj):
|
||||
... print(f"{src} -> {dst} ({motion:+d} semitones)")
|
||||
G4 -> A4 (+2 semitones)
|
||||
E4 -> F4 (+1 semitones)
|
||||
C4 -> C4 (+0 semitones)
|
||||
|
||||
Measure Harmonic Tension
|
||||
------------------------
|
||||
|
||||
Quantify how much a chord "wants to resolve." Dominant 7ths have
|
||||
the most tension — the tritone between the 3rd and 7th pulls toward
|
||||
resolution:
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> from pytheory import Chord
|
||||
|
||||
>>> for name in ["C", "Am", "G7", "Cmaj7"]:
|
||||
... ch = Chord.from_name(name)
|
||||
... t = ch.tension
|
||||
... print(f"{name:6s} tension={t['score']:.2f} tritones={t['tritones']} dominant={t['has_dominant_function']}")
|
||||
C tension=0.00 tritones=0 dominant=False
|
||||
Am tension=0.00 tritones=0 dominant=False
|
||||
G7 tension=0.60 tritones=1 dominant=True
|
||||
Cmaj7 tension=0.15 tritones=0 dominant=False
|
||||
|
||||
Tritone Substitution (Jazz)
|
||||
---------------------------
|
||||
|
||||
Replace any dominant chord with the one a
|
||||
`tritone <https://en.wikipedia.org/wiki/Tritone_substitution>`_ away —
|
||||
they share the same tritone interval:
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> from pytheory import Chord
|
||||
|
||||
>>> g7 = Chord.from_name("G7")
|
||||
>>> g7.tritone_sub().identify()
|
||||
'C# dominant 7th'
|
||||
|
||||
>>> # ii-V-I with tritone sub:
|
||||
>>> # Dm7 -> G7 -> Cmaj7 (standard)
|
||||
>>> # Dm7 -> Db7 -> Cmaj7 (chromatic bass line!)
|
||||
|
||||
Key Signatures and Detection
|
||||
-----------------------------
|
||||
|
||||
View the accidentals in any key, or detect the key from a set of notes:
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> from pytheory import Key
|
||||
|
||||
>>> Key("C", "major").signature
|
||||
{'sharps': 0, 'flats': 0, 'accidentals': []}
|
||||
>>> Key("G", "major").signature
|
||||
{'sharps': 1, 'flats': 0, 'accidentals': ['F#']}
|
||||
>>> Key("D", "major").signature
|
||||
{'sharps': 2, 'flats': 0, 'accidentals': ['F#', 'C#']}
|
||||
|
||||
>>> Key.detect("C", "E", "G", "A", "D")
|
||||
<Key C major>
|
||||
|
||||
Relative and Parallel Keys
|
||||
--------------------------
|
||||
|
||||
Every major key has a **relative minor** (same notes, different root)
|
||||
and a **parallel minor** (same root, different notes):
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> from pytheory import Key
|
||||
|
||||
>>> c = Key("C", "major")
|
||||
>>> c.relative
|
||||
'A minor'
|
||||
>>> c.parallel
|
||||
'C minor'
|
||||
|
||||
Borrowed Chords and Secondary Dominants
|
||||
---------------------------------------
|
||||
|
||||
Add color by borrowing from the parallel key or building secondary
|
||||
dominants that approach other scale degrees:
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> from pytheory import Key
|
||||
|
||||
>>> c = Key("C", "major")
|
||||
|
||||
>>> c.borrowed_chords[:4]
|
||||
['C minor', 'D diminished', 'Eb major', 'F minor']
|
||||
|
||||
>>> c.secondary_dominant(5).identify()
|
||||
'D dominant 7th'
|
||||
>>> c.secondary_dominant(2).identify()
|
||||
'A dominant 7th'
|
||||
>>> c.secondary_dominant(6).identify()
|
||||
'E dominant 7th'
|
||||
|
||||
The Overtone Series
|
||||
-------------------
|
||||
|
||||
Every musical tone contains a stack of harmonics — the physics behind
|
||||
why intervals sound consonant:
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> from pytheory import Tone
|
||||
|
||||
>>> a4 = Tone.from_string("A4", system="western")
|
||||
>>> [round(f, 1) for f in a4.overtones(6)]
|
||||
[440.0, 880.0, 1320.0, 1760.0, 2200.0, 2640.0]
|
||||
|
||||
>>> # Harmonic 2 = octave (2:1)
|
||||
>>> # Harmonic 3 = perfect 5th + octave (3:1)
|
||||
>>> # Harmonic 5 = major 3rd + two octaves (5:1)
|
||||
|
||||
Enharmonic Spellings
|
||||
--------------------
|
||||
|
||||
Find the alternate name for any sharp or flat:
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> from pytheory import Tone
|
||||
|
||||
>>> for name in ["C#4", "D#4", "F#4", "G#4"]:
|
||||
... t = Tone.from_string(name, system="western")
|
||||
... print(f"{t.name} = {t.enharmonic}")
|
||||
C# = Db
|
||||
D# = Eb
|
||||
F# = Gb
|
||||
G# = Ab
|
||||
|
||||
World Scales
|
||||
------------
|
||||
|
||||
Explore scales from Indian, Arabic, and Japanese traditions:
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> from pytheory import TonedScale
|
||||
|
||||
>>> indian = TonedScale(tonic="Sa", system="indian")
|
||||
>>> indian["bhairav"].note_names
|
||||
['Sa', 'komal Re', 'Ga', 'Ma', 'Pa', 'komal Dha', 'Ni', 'Sa']
|
||||
|
||||
>>> arabic = TonedScale(tonic="Do", system="arabic")
|
||||
>>> arabic["hijaz"].note_names
|
||||
['Do', 'Reb', 'Mi', 'Fa', 'Sol', 'Solb', 'Sib', 'Do']
|
||||
|
||||
>>> japanese = TonedScale(tonic="C4", system="japanese")
|
||||
>>> japanese["hirajoshi"].note_names
|
||||
['C', 'D', 'Eb', 'G', 'Ab', 'C']
|
||||
|
||||
Visualize a Scale on Guitar
|
||||
----------------------------
|
||||
|
||||
See where the notes fall across the fretboard — E minor pentatonic,
|
||||
the most-played scale in rock:
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> from pytheory import Fretboard, Scale
|
||||
|
||||
>>> fb = Fretboard.guitar()
|
||||
>>> pent = Scale(tonic="E4", system="blues")["minor pentatonic"]
|
||||
>>> print(fb.scale_diagram(pent, frets=12))
|
||||
0 1 2 3 4 5 6 7 8 9 10 11 12
|
||||
E| E | - | - | G | - | A | - | B | - | - | D | - | E |
|
||||
B| B | - | - | D | - | E | - | - | G | - | A | - | B |
|
||||
G| G | - | A | - | B | - | - | D | - | E | - | - | G |
|
||||
D| D | - | E | - | - | G | - | A | - | B | - | - | D |
|
||||
A| A | - | B | - | - | D | - | E | - | - | G | - | A |
|
||||
E| E | - | - | G | - | A | - | B | - | - | D | - | E |
|
||||
|
||||
Composition Recipes
|
||||
-------------------
|
||||
|
||||
These recipes go beyond theory into actual music-making.
|
||||
|
||||
Acid House Track
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
303-style acid with sidechain pump:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from pytheory import Score, Pattern, Duration, Chord
|
||||
from pytheory.play import play_score
|
||||
|
||||
score = Score("4/4", bpm=132)
|
||||
score.drums("house", repeats=8, fill="house", fill_every=8)
|
||||
|
||||
pad = score.part(
|
||||
"pad",
|
||||
synth="supersaw",
|
||||
envelope="pad",
|
||||
reverb=0.4,
|
||||
chorus=0.3,
|
||||
sidechain=0.85,
|
||||
)
|
||||
acid = score.part(
|
||||
"acid",
|
||||
synth="saw",
|
||||
envelope="pad",
|
||||
legato=True,
|
||||
glide=0.03,
|
||||
distortion=0.8,
|
||||
distortion_drive=8.0,
|
||||
lowpass=1000,
|
||||
lowpass_q=5.0,
|
||||
)
|
||||
acid.lfo("lowpass", rate=0.5, min=600, max=2500, bars=8)
|
||||
|
||||
for sym in ["Cm", "Fm", "Abm", "Gm"]:
|
||||
pad.add(Chord.from_symbol(sym), Duration.WHOLE)
|
||||
pad.add(Chord.from_symbol(sym), Duration.WHOLE)
|
||||
acid.arpeggio(sym, bars=2, pattern="up", octaves=2)
|
||||
|
||||
play_score(score)
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<audio controls style="width:100%;margin:0.5em 0 1.5em"><source src="../_static/audio/acid_house.wav" type="audio/wav"></audio>
|
||||
|
||||
Dub Reggae with Delay Madness
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Sparse notes into infinite echo:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
score = Score("4/4", bpm=72)
|
||||
score.drums("dub", repeats=8)
|
||||
|
||||
melodica = score.part(
|
||||
"melodica",
|
||||
synth="triangle",
|
||||
envelope="pluck",
|
||||
delay=0.5,
|
||||
delay_time=0.66,
|
||||
delay_feedback=0.55,
|
||||
reverb=0.4,
|
||||
reverb_type="cathedral",
|
||||
)
|
||||
bass = score.part("bass", synth="sine", lowpass=400, lowpass_q=1.5)
|
||||
|
||||
# Play almost nothing — let the delay do the work
|
||||
melodica.add("A4", 2).rest(6)
|
||||
melodica.add("E5", 1.5).rest(6.5)
|
||||
melodica.add("D5", 1).add("C5", 1).add("A4", 2).rest(4)
|
||||
|
||||
for n in ["A1"] * 16:
|
||||
bass.add(n, Duration.HALF)
|
||||
|
||||
play_score(score)
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<audio controls style="width:100%;margin:0.5em 0 1.5em"><source src="../_static/audio/dub_reggae.wav" type="audio/wav"></audio>
|
||||
|
||||
Jazz Ballad with Humanize
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The difference between a robot and a musician:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
score = Score("4/4", bpm=72, swing=0.5)
|
||||
score.drums("jazz", repeats=8)
|
||||
|
||||
rhodes = score.part(
|
||||
"rhodes",
|
||||
synth="fm",
|
||||
envelope="piano",
|
||||
reverb=0.4,
|
||||
reverb_type="plate",
|
||||
humanize=0.3,
|
||||
)
|
||||
lead = score.part(
|
||||
"lead",
|
||||
synth="triangle",
|
||||
envelope="strings",
|
||||
delay=0.25,
|
||||
reverb=0.3,
|
||||
humanize=0.35,
|
||||
)
|
||||
|
||||
key = Key("Bb", "major")
|
||||
for chord in key.progression("I", "vi", "ii", "V") * 2:
|
||||
rhodes.add(chord, Duration.WHOLE)
|
||||
|
||||
for n, d in [("D5", 1.5), ("F5", 0.5), ("Bb5", 2), (None, 4),
|
||||
("A5", 1), ("G5", 1), ("F5", 2), (None, 4)]:
|
||||
lead.rest(d) if n is None else lead.add(n, d)
|
||||
|
||||
play_score(score)
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<audio controls style="width:100%;margin:0.5em 0 1.5em"><source src="../_static/audio/jazz_ballad.wav" type="audio/wav"></audio>
|
||||
|
||||
Song with Sections
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Define once, arrange freely:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
score = Score("4/4", bpm=120)
|
||||
score.drums("rock", repeats=16, fill="rock", fill_every=4)
|
||||
|
||||
chords = score.part("chords", synth="saw", envelope="pad")
|
||||
lead = score.part("lead", synth="triangle", envelope="pluck")
|
||||
|
||||
score.section("verse")
|
||||
for sym in ["Am", "F", "C", "G"]:
|
||||
chords.add(Chord.from_symbol(sym), Duration.WHOLE)
|
||||
lead.add("A4", 1).add("C5", 1).add("E5", 1).rest(1)
|
||||
lead.add("F5", 1).add("E5", 1).add("C5", 2)
|
||||
|
||||
score.section("chorus")
|
||||
lead.set(reverb=0.4, lowpass=5000)
|
||||
for sym in ["F", "G", "Am", "C"]:
|
||||
chords.add(Chord.from_symbol(sym), Duration.WHOLE)
|
||||
lead.add("C6", 2).add("A5", 1).add("G5", 1)
|
||||
lead.add("F5", 2).add("E5", 2)
|
||||
score.end_section()
|
||||
|
||||
score.repeat("verse")
|
||||
score.repeat("chorus", times=2)
|
||||
|
||||
play_score(score)
|
||||
score.save_midi("my_song.mid")
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<audio controls style="width:100%;margin:0.5em 0 1.5em"><source src="../_static/audio/song_sections.wav" type="audio/wav"></audio>
|
||||
|
||||
Export Everything to MIDI
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The whole point — sketch fast, finish in your DAW:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# Any Score can be saved as MIDI
|
||||
score.save_midi("track.mid")
|
||||
|
||||
# Simple progressions too
|
||||
from pytheory import save_midi
|
||||
chords = Key("C", "major").progression("I", "V", "vi", "IV")
|
||||
save_midi(chords, "pop.mid", t=500, bpm=120)
|
||||
|
||||
These are all starting points. Change the key, swap the chords, layer in your own ideas -- the best way to learn is to take something that works and make it yours.
|
||||
@@ -0,0 +1,614 @@
|
||||
Drums
|
||||
=====
|
||||
|
||||
Drums are the foundation of almost everything. Change the drum pattern
|
||||
and you change the genre. The same four chords over a bossa nova
|
||||
pattern sound like you're in a cafe in Rio. Put those same chords over
|
||||
a rock beat and you're in a garage in Seattle. Over a trap beat, you're
|
||||
in Atlanta. Over a dancehall pattern, you're in Kingston. The drums ARE
|
||||
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, 95+ pattern presets across dozens of genres, and 30 fill presets.
|
||||
Every sound is generated from waveforms; no samples needed.
|
||||
|
||||
Drum Sounds
|
||||
-----------
|
||||
|
||||
Drum hits are **humanized by default** — each hit gets a tiny random
|
||||
timing offset and velocity wobble, just like a real drummer who's never
|
||||
perfectly on the grid. Control the amount with ``drum_humanize`` on the
|
||||
Score:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
score = Score("4/4", bpm=120, drum_humanize=0.4) # natural feel
|
||||
score = Score("4/4", bpm=120, drum_humanize=0.0) # perfectly quantized
|
||||
score = Score("4/4", bpm=120, drum_humanize=0.1) # studio tight
|
||||
|
||||
The default is 0.15 — just enough to feel alive without sounding loose.
|
||||
|
||||
Drums Are Parts
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
Drums are a real Part — the same as any melodic voice. You can set
|
||||
effects on them the same way:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
score.drums("rock", repeats=4)
|
||||
score.parts["drums"].reverb_mix = 0.2
|
||||
score.parts["drums"].reverb_type = "plate"
|
||||
|
||||
Or use the shorthand:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
score.set_drum_effects(reverb=0.2, reverb_type="plate", lowpass=8000)
|
||||
|
||||
Split Drums
|
||||
~~~~~~~~~~~
|
||||
|
||||
For maximum control, split the kit into separate Parts — kick, snare,
|
||||
hats, toms, cymbals, and percussion — each with independent effects:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
score.drums("rock", repeats=4, split=True)
|
||||
|
||||
# Now each group is its own Part
|
||||
score.parts["snare"].reverb_mix = 0.3
|
||||
score.parts["snare"].reverb_type = "plate"
|
||||
score.parts["hats"].lowpass = 7000
|
||||
score.parts["kick"] # dry, no effects
|
||||
|
||||
# set_drum_effects still works — applies to all drum Parts
|
||||
score.set_drum_effects(reverb=0.1)
|
||||
|
||||
This is how real studios work — the snare gets its own reverb send,
|
||||
the hats get their own EQ, the kick stays dry and punchy. Now you
|
||||
can do the same thing in Python.
|
||||
|
||||
Sidechain compression triggers on kick hits only — hi-hats and snares
|
||||
don't duck the pad.
|
||||
|
||||
Every drum sound is stereo-panned like a real kit — kick and snare
|
||||
center, hi-hat right, crash left, toms spread across the field,
|
||||
percussion instruments placed naturally. Put on headphones and you'll
|
||||
hear the kit in front of you.
|
||||
|
||||
The ``DrumSound`` enum maps to General MIDI percussion note numbers:
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> from pytheory import DrumSound
|
||||
|
||||
>>> DrumSound.KICK.value
|
||||
36
|
||||
>>> DrumSound.SNARE.value
|
||||
38
|
||||
>>> DrumSound.CLOSED_HAT.value
|
||||
42
|
||||
|
||||
All 51 sounds, organized by type:
|
||||
|
||||
**Kicks:** KICK (36)
|
||||
|
||||
**Snares:** SNARE (38), RIMSHOT (37), CLAP (39)
|
||||
|
||||
**Hi-hats:** CLOSED_HAT (42), OPEN_HAT (46), PEDAL_HAT (44)
|
||||
|
||||
**Toms:** LOW_TOM (45), MID_TOM (47), HIGH_TOM (50)
|
||||
|
||||
**Cymbals:** CRASH (49), RIDE (51), RIDE_BELL (53)
|
||||
|
||||
**Percussion:** COWBELL (56), CLAVE (75), SHAKER (70), TAMBOURINE (54),
|
||||
CONGA_HIGH (63), CONGA_LOW (64), BONGO_HIGH (60), BONGO_LOW (61),
|
||||
TIMBALE_HIGH (65), TIMBALE_LOW (66), AGOGO_HIGH (67), AGOGO_LOW (68),
|
||||
GUIRO (73)
|
||||
|
||||
**Tabla:** TABLA_NA (86), TABLA_TIN (87), TABLA_GE (88), TABLA_DHA (89),
|
||||
TABLA_TIT (90), TABLA_KE (91), TABLA_GE_BEND (108 -- bayan with upward
|
||||
pitch bend from palm pressing into the head)
|
||||
|
||||
**Dhol:** DHOL_DAGGA (92), DHOL_TILLI (93), DHOL_BOTH (94)
|
||||
|
||||
**Dholak:** DHOLAK_GE (95), DHOLAK_NA (96), DHOLAK_TIT (97)
|
||||
|
||||
**Mridangam:** MRIDANGAM_THAM (98), MRIDANGAM_NAM (99), MRIDANGAM_DIN (100),
|
||||
MRIDANGAM_THA (101)
|
||||
|
||||
**Djembe:** DJEMBE_BASS (102), DJEMBE_TONE (103), DJEMBE_SLAP (104)
|
||||
|
||||
**Cajón:** CAJON_BASS (108), CAJON_SLAP (109), CAJON_TAP (110)
|
||||
|
||||
**Metal Kit:** METAL_KICK (105), METAL_SNARE (106), METAL_HAT (107)
|
||||
|
||||
**Marching Snare:** MARCH_SNARE (115), MARCH_RIMSHOT (116), MARCH_CLICK (118)
|
||||
|
||||
**Quads (Tenors):** QUAD_1 (119), QUAD_2 (120), QUAD_3 (121), QUAD_4 (122),
|
||||
QUAD_SPOCK (123)
|
||||
|
||||
**Marching Bass:** BASS_1 (124), BASS_2 (125), BASS_3 (126), BASS_4 (127),
|
||||
BASS_5 (80)
|
||||
|
||||
Drum Synthesis
|
||||
--------------
|
||||
|
||||
Every drum sound here is synthesized from scratch using the same
|
||||
techniques that real drum machines use. This isn't a shortcut -- it's
|
||||
the real thing. The 808 kick that defined hip hop is literally a sine
|
||||
wave with a pitch envelope sweeping from 150 Hz down to 50 Hz. The 909
|
||||
snare that powered techno is a sine wave body mixed with white noise
|
||||
rattle. The hi-hat is just filtered noise with a short decay. When
|
||||
Roland built the TR-808 and TR-909, they weren't sampling real drums;
|
||||
they were synthesizing them from basic waveforms. PyTheory does the
|
||||
same thing.
|
||||
|
||||
Each sound has a dedicated synthesizer:
|
||||
|
||||
- **KICK** -- sine wave with pitch envelope sweep (150 to 50 Hz) + sub click
|
||||
- **SNARE** -- pitched body (180 Hz) + white noise rattle
|
||||
- **CLOSED_HAT** -- high-frequency noise, 50ms decay
|
||||
- **OPEN_HAT** -- high-frequency noise, 250ms decay
|
||||
- **CLAP** -- layered noise bursts with spacers
|
||||
- **RIMSHOT** -- bright 800 Hz click + noise
|
||||
- **TOMS** -- pitched sine with sweep (low=100, mid=150, high=200 Hz)
|
||||
- **CRASH** -- long noise decay (1.5s)
|
||||
- **RIDE** -- metallic ring (3500+5100 Hz) + noise
|
||||
- **RIDE_BELL** -- brighter ring, more sustain
|
||||
- **COWBELL** -- two detuned tones (545+815 Hz)
|
||||
- **CLAVE** -- short 2500 Hz click
|
||||
- **CONGAS/BONGOS** -- pitched membrane with slap transient
|
||||
- **TIMBALES** -- bright metallic ring with overtones
|
||||
- **AGOGO** -- pitched bell with harmonics
|
||||
- **SHAKER/MARACAS** -- short noise burst
|
||||
- **TAMBOURINE** -- noise + 7000 Hz jingle ring
|
||||
- **GUIRO** -- scraped noise bursts
|
||||
|
||||
Pattern Presets
|
||||
---------------
|
||||
|
||||
80+ patterns spanning genres from rock to Afro-Cuban to electronic to
|
||||
world percussion. Load them with ``Pattern.preset()``:
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> from pytheory import Pattern
|
||||
|
||||
>>> Pattern.list_presets()
|
||||
['12/8 blues', '6/8 afro-cuban', 'afrobeat', 'baiao', 'bebop', ...]
|
||||
|
||||
>>> rock = Pattern.preset("rock")
|
||||
>>> rock
|
||||
<Pattern 'rock' 4/4 4.0 beats 12 hits>
|
||||
|
||||
**Rock/Pop:** rock, half time, double time, disco, motown, train beat
|
||||
-- The backbone of Western popular music. Kick on 1 and 3, snare on 2
|
||||
and 4. Simple, effective, universal.
|
||||
|
||||
**Jazz:** jazz, bebop, shuffle, swing, linear, paradiddle -- The ride
|
||||
cymbal drives everything. The kick and snare comp and converse rather
|
||||
than keeping strict time. These patterns swing.
|
||||
|
||||
**Latin:** salsa, bossa nova, samba, cumbia, merengue, baiao, maracatu,
|
||||
bolero, tango -- Rich, layered patterns built on clave rhythms, with
|
||||
congas, timbales, and shakers creating interlocking polyrhythmic webs.
|
||||
Some of the most sophisticated drumming traditions on the planet.
|
||||
|
||||
**Afro-Cuban:** son clave 3-2, son clave 2-3, rumba clave 3-2,
|
||||
rumba clave 2-3, cascara, guaguanco, mozambique, nanigo, bembe,
|
||||
6/8 afro-cuban, tresillo, habanera -- The clave is the key that
|
||||
unlocks all Latin and Afro-Cuban music. It's a five-note rhythmic
|
||||
cell that everything else revolves around. If you learn one concept
|
||||
from world music, learn the clave.
|
||||
|
||||
**African:** afrobeat, highlife -- Born in West Africa. Fela Kuti's
|
||||
afrobeat layers multiple percussion voices into hypnotic,
|
||||
polyrhythmic grooves that can go on for twenty minutes.
|
||||
|
||||
**Caribbean:** reggae, dancehall, ska, dub -- The offbeat is king.
|
||||
Reggae flips rock drumming inside out by emphasizing the "and" of each
|
||||
beat instead of the beat itself. Ska doubles the tempo, dancehall
|
||||
adds syncopation.
|
||||
|
||||
**Electronic:** house, techno, trap, drum and bass, breakbeat, jungle
|
||||
-- Machine music. The four-on-the-floor kick of house and techno, the
|
||||
rattling hi-hats of trap, the breakneck tempo of drum and bass. These
|
||||
patterns were born in drum machines and they still live there.
|
||||
|
||||
**Metal/Punk:** metal, blast beat, punk, double kick, metal blast,
|
||||
metal groove, metal gallop -- Speed and aggression. The blast beat is
|
||||
both feet and both hands going as fast as humanly possible. Punk strips
|
||||
everything to its essentials. The metal kit adds 3 dedicated sounds
|
||||
(double kick, china cymbal, stack) and 4 patterns for extreme metal
|
||||
subgenres.
|
||||
|
||||
**World Percussion:** tabla, dhol, dholak, mridangam, djembe, cajón --
|
||||
Deep traditions from across the globe, each with authentic sound sets and
|
||||
idiomatic patterns. See the World Percussion section below for details.
|
||||
|
||||
**Other:** funk, hip hop, bo diddley, second line, new orleans, waltz,
|
||||
12/8 blues, country, gospel, flamenco -- Everything else. The syncopated
|
||||
groove of funk, the sampled feel of hip hop, the street-parade swing
|
||||
of New Orleans second line.
|
||||
|
||||
Playing Patterns
|
||||
----------------
|
||||
|
||||
``play_pattern()`` synthesizes every drum sound in real-time:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from pytheory import Pattern
|
||||
from pytheory.play import play_pattern
|
||||
|
||||
play_pattern(Pattern.preset("rock"), repeats=4, bpm=120)
|
||||
play_pattern(Pattern.preset("bossa nova"), repeats=4, bpm=140)
|
||||
play_pattern(Pattern.preset("salsa"), repeats=4, bpm=180)
|
||||
play_pattern(Pattern.preset("afrobeat"), repeats=8, bpm=110)
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<audio controls style="width:100%;margin:0.3em 0 0.5em"><source src="../_static/audio/rock_beat.wav" type="audio/wav"></audio>
|
||||
<audio controls style="width:100%;margin:0.3em 0 0.5em"><source src="../_static/audio/bossa_nova_pattern.wav" type="audio/wav"></audio>
|
||||
<audio controls style="width:100%;margin:0.3em 0 0.5em"><source src="../_static/audio/salsa_pattern.wav" type="audio/wav"></audio>
|
||||
<audio controls style="width:100%;margin:0.3em 0 1.5em"><source src="../_static/audio/afrobeat_pattern.wav" type="audio/wav"></audio>
|
||||
|
||||
Fills
|
||||
-----
|
||||
|
||||
A fill is the drummer's way of saying "something's about to change."
|
||||
It's that moment at the end of a verse where the drummer breaks the
|
||||
pattern and rolls around the toms before crashing into the chorus. Fills
|
||||
signal transitions -- they tell the listener's ear that the section is
|
||||
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. 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', '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']
|
||||
|
||||
>>> fill = Pattern.fill("rock")
|
||||
>>> fill
|
||||
<Pattern 'rock fill' 4/4 4.0 beats ...>
|
||||
|
||||
Score Integration
|
||||
-----------------
|
||||
|
||||
The ``score.drums()`` shorthand attaches a drum pattern to a score:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from pytheory import Score
|
||||
|
||||
score = Score("4/4", bpm=140)
|
||||
score.drums("bossa nova", repeats=4)
|
||||
|
||||
Auto-Fills
|
||||
~~~~~~~~~~
|
||||
|
||||
The ``fill`` and ``fill_every`` parameters automatically insert drum
|
||||
fills at regular intervals:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
score = Score("4/4", bpm=120)
|
||||
score.drums("rock", repeats=8, fill="rock", fill_every=4)
|
||||
|
||||
This plays the rock pattern for 8 bars, replacing every 4th bar with
|
||||
a rock fill. Useful for adding natural phrasing to longer sections.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# Jazz with brush fills every 8 bars
|
||||
score.drums("bebop", repeats=16, fill="jazz brush", fill_every=8)
|
||||
|
||||
# Salsa with fills every 4 bars
|
||||
score.drums("salsa", repeats=8, fill="salsa", fill_every=4)
|
||||
|
||||
Layering Patterns
|
||||
-----------------
|
||||
|
||||
Combine drum patterns with melodic parts for full arrangements. The
|
||||
drum pattern and all named parts are mixed together by ``play_score()``:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from pytheory import Score, Key, Duration, Chord
|
||||
from pytheory.play import play_score
|
||||
|
||||
score = Score("4/4", bpm=180)
|
||||
score.drums("salsa", repeats=4, fill="salsa", fill_every=4)
|
||||
|
||||
pads = score.part("pads", synth="sine", envelope="pad", volume=0.3)
|
||||
lead = score.part("lead", synth="saw", envelope="pluck", volume=0.4)
|
||||
bass = score.part("bass", synth="sine", envelope="pluck", volume=0.45)
|
||||
|
||||
for chord in Key("D", "minor").progression("ii", "V", "i", "i") * 2:
|
||||
pads.add(chord, Duration.WHOLE)
|
||||
|
||||
lead.add("A5", 0.67).add("G5", 0.33).add("F5", 0.67).add("E5", 0.33)
|
||||
|
||||
for n in ["D2", "A2", "D2", "F2"] * 2:
|
||||
bass.add(n, Duration.QUARTER)
|
||||
|
||||
play_score(score)
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<audio controls style="width:100%;margin:0.5em 0 1.5em"><source src="../_static/audio/salsa_layered.wav" type="audio/wav"></audio>
|
||||
|
||||
World Percussion
|
||||
----------------
|
||||
|
||||
PyTheory includes dedicated sound sets and pattern presets for
|
||||
traditional percussion instruments from around the world. Each
|
||||
instrument has its own synthesized sounds that capture the timbral
|
||||
character of the real instrument, plus idiomatic rhythmic patterns
|
||||
drawn from their musical traditions.
|
||||
|
||||
Tabla
|
||||
~~~~~
|
||||
|
||||
The tabla is a pair of hand drums from the Indian subcontinent -- the
|
||||
smaller, higher-pitched *dayan* and the larger, bass *bayan*. It is
|
||||
the rhythmic backbone of Hindustani classical music, and one of the
|
||||
most expressive percussion instruments ever created. A single tabla
|
||||
player can produce an astonishing range of tones by varying finger
|
||||
placement, pressure, and striking technique.
|
||||
|
||||
**7 sounds** -- covering the primary tabla strokes (na, tin, tun, ge,
|
||||
dha, ke, tit) plus a bayan pitch bend sound (TABLA_GE_BEND) that
|
||||
models the technique of pressing the palm into the bayan head to bend
|
||||
the pitch upward.
|
||||
|
||||
**7 patterns:** teental (16 beats, the most common taal), jhaptaal
|
||||
(10 beats), rupak (7 beats), dadra (6 beats), keherwa (8 beats, folk
|
||||
and light classical), tabla solo, and tiri kita (fast ornamental
|
||||
pattern).
|
||||
|
||||
**5 fills:** tihai (3x crescendo landing on sam), chakkardar (32nd
|
||||
triplet cascade into slam), tiri kita (rapid 16th-note dayan burst),
|
||||
bayan (deep bass bends showcase), tabla call (dayan/bayan call-and-response).
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
score.drums("teental", repeats=4, fill="tihai")
|
||||
score.drums("keherwa", repeats=4, fill="chakkardar")
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
score = Score("4/4", bpm=80)
|
||||
score.drums("teental", repeats=4)
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<audio controls style="width:100%;margin:0.3em 0 0.5em"><source src="../_static/audio/tabla_teental.wav" type="audio/wav"></audio>
|
||||
<audio controls style="width:100%;margin:0.3em 0 0.5em"><source src="../_static/audio/tabla_keherwa.wav" type="audio/wav"></audio>
|
||||
<audio controls style="width:100%;margin:0.3em 0 1.5em"><source src="../_static/audio/tabla_chakradar.wav" type="audio/wav"></audio>
|
||||
|
||||
Dhol
|
||||
~~~~
|
||||
|
||||
The dhol is a double-headed barrel drum from Punjab, played with
|
||||
sticks. It is the driving force behind bhangra music -- loud,
|
||||
energetic, and physically impossible to sit still to.
|
||||
|
||||
**3 sounds** -- bass stroke, treble stroke, and rimshot.
|
||||
|
||||
**2 patterns:** bhangra (the classic bhangra groove) and dhol chaal
|
||||
(a processional rhythm).
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
score = Score("4/4", bpm=160)
|
||||
score.drums("bhangra", repeats=4)
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<audio controls style="width:100%;margin:0.5em 0 1.5em"><source src="../_static/audio/dhol.wav" type="audio/wav"></audio>
|
||||
|
||||
Dholak
|
||||
~~~~~~
|
||||
|
||||
The dholak is a smaller, lighter two-headed drum used across South
|
||||
Asia in folk music, qawwali, and Bollywood. Played with bare hands,
|
||||
it produces a warm, melodic tone.
|
||||
|
||||
**3 sounds** -- bass, treble, and slap.
|
||||
|
||||
**2 patterns:** qawwali (the rhythmic foundation of Sufi devotional
|
||||
music) and dholak folk (a general folk groove).
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
score = Score("4/4", bpm=120)
|
||||
score.drums("qawwali", repeats=4)
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<audio controls style="width:100%;margin:0.5em 0 1.5em"><source src="../_static/audio/dholak.wav" type="audio/wav"></audio>
|
||||
|
||||
Mridangam
|
||||
~~~~~~~~~
|
||||
|
||||
The mridangam is a double-headed drum from South India, the
|
||||
rhythmic anchor of Carnatic classical music. Its tuning system is
|
||||
extraordinarily precise, and its rhythmic vocabulary is among the
|
||||
most mathematically complex in the world.
|
||||
|
||||
**4 sounds** -- tha, thom, nam, and din.
|
||||
|
||||
**2 patterns:** adi talam (the most common Carnatic talam, 8 beats)
|
||||
and mridangam korvai (a rhythmic cadence pattern).
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
score = Score("4/4", bpm=90)
|
||||
score.drums("adi talam", repeats=4)
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<audio controls style="width:100%;margin:0.5em 0 1.5em"><source src="../_static/audio/mridangam.wav" type="audio/wav"></audio>
|
||||
|
||||
Djembe
|
||||
~~~~~~
|
||||
|
||||
The djembe is a rope-tuned goblet drum from West Africa, capable of
|
||||
producing a wide range of tones from deep bass to sharp slaps. It is
|
||||
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).
|
||||
|
||||
**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=8, fill="djembe call", fill_every=4)
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<audio controls style="width:100%;margin:0.5em 0 1.5em"><source src="../_static/audio/djembe.wav" type="audio/wav"></audio>
|
||||
|
||||
Metal Kit
|
||||
~~~~~~~~~
|
||||
|
||||
A dedicated percussion kit for extreme metal subgenres, with
|
||||
specialized sounds and patterns that go beyond the standard drum kit.
|
||||
|
||||
**3 sounds** -- double kick (triggered, tight attack), china cymbal,
|
||||
and stack (a short, trashy cymbal choke).
|
||||
|
||||
**4 patterns:** double kick (relentless double bass drum pattern),
|
||||
metal blast (blast beat with china cymbal accents), metal groove (a
|
||||
half-time groove with double kick fills), and metal gallop (the
|
||||
classic triplet-feel gallop rhythm).
|
||||
|
||||
**4 fills:** metal (double kick 16ths with descending toms), metal triplet
|
||||
(double kick triplets with snare accents), metal blast (alternating
|
||||
snare/kick 32nds into half-time crash), metal cascade (descending snare
|
||||
roll → kick roll → alternating → crash ending).
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
score = Score("4/4", bpm=200)
|
||||
score.drums("metal blast", repeats=8, fill="metal cascade", fill_every=4)
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<audio controls style="width:100%;margin:0.5em 0 1.5em"><source src="../_static/audio/metal_blast.wav" type="audio/wav"></audio>
|
||||
|
||||
Cajón
|
||||
~~~~~
|
||||
|
||||
The cajón is a box-shaped percussion instrument from Peru, now
|
||||
ubiquitous in acoustic and unplugged settings worldwide. Players sit
|
||||
on the box and strike the front face with their hands.
|
||||
|
||||
**3 sounds** -- bass (deep center thump), slap (sharp, snare-like edge
|
||||
hit with wire buzz), and tap (light finger tap).
|
||||
|
||||
**3 patterns:** cajon (basic groove), cajon rumba (flamenco-style rumba),
|
||||
and cajon folk (folk/acoustic pattern).
|
||||
|
||||
**3 fills:** cajon flam (slaps accelerating into bass hits), cajon rumble
|
||||
(fast taps building to slap accents), cajon breakdown (syncopated
|
||||
bass-slap groove).
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
score = Score("4/4", bpm=100)
|
||||
score.drums("cajon", repeats=8, fill="cajon flam", fill_every=4)
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<audio controls style="width:100%;margin:0.5em 0 1.5em"><source src="../_static/audio/cajon.wav" type="audio/wav"></audio>
|
||||
|
||||
Marching Percussion
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
A full drumline — snare, quads (tenors), and pitched bass drums.
|
||||
Every sound is synthesized: kevlar snare heads, aluminum shell ting
|
||||
on the quads, felt-beater thwack on the basses.
|
||||
|
||||
**Snare** -- 3 sounds: MARCH_SNARE (tight kevlar tap), MARCH_RIMSHOT
|
||||
(woody-metallic crack), MARCH_CLICK (stick click for count-offs).
|
||||
|
||||
**Quads** -- 5 sounds: QUAD_1 through QUAD_4 (high to low pitched
|
||||
tenors) plus QUAD_SPOCK (rim click on the shell).
|
||||
|
||||
**Bass drums** -- 5 pitched drums: BASS_1 (highest/smallest) through
|
||||
BASS_5 (lowest/biggest), each with a prominent felt-beater thwack.
|
||||
|
||||
**6 patterns:** march (basic 4/4), cadence (8-beat street beat),
|
||||
march paradiddle, march roll (buzz crescendo), quad sweep (run across
|
||||
all 4 drums), quad groove, bass split (cascading across the line),
|
||||
bass unison (all 5 hit together), drumline (snare + quads + bass).
|
||||
|
||||
**Rudiment methods:** ``Part.flam()``, ``Part.diddle()``, and
|
||||
``Part.cheese()`` for marching rudiments on any drum sound.
|
||||
|
||||
**Ensemble rendering:** ``ensemble=N`` on any Part duplicates the
|
||||
voice with per-player timing tendencies and micro pitch drift.
|
||||
``ensemble=8`` for a snare line, ``ensemble=20`` for a massive section.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# Full drumline with ensemble
|
||||
snares = score.part("snares", synth="sine", volume=0.9,
|
||||
reverb=0.2, ensemble=8)
|
||||
quads = score.part("quads", synth="sine", volume=0.5,
|
||||
reverb=0.2, ensemble=4)
|
||||
basses = score.part("basses", synth="sine", volume=0.55,
|
||||
reverb=0.2, ensemble=5)
|
||||
|
||||
snares.flam(DrumSound.MARCH_SNARE, Duration.QUARTER, velocity=120)
|
||||
snares.diddle(DrumSound.MARCH_SNARE, Duration.EIGHTH, velocity=60)
|
||||
|
||||
# Or use patterns
|
||||
score.drums("drumline", repeats=4)
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<audio controls style="width:100%;margin:0.5em 0 1.5em"><source src="../_static/audio/march_snare.wav" type="audio/wav"></audio>
|
||||
|
||||
**Sympathetic resonance:** The marching snare builds up snare wire
|
||||
buzz as hits accumulate, and the buzz decays during rests — just like
|
||||
a real drum.
|
||||
|
||||
MIDI Export
|
||||
-----------
|
||||
|
||||
Convert any pattern to a Score, then export to MIDI (drums are written
|
||||
to channel 10):
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
pattern = Pattern.preset("bossa nova")
|
||||
score = pattern.to_score(repeats=8, bpm=140)
|
||||
score.save_midi("bossa.mid")
|
||||
|
||||
Pattern.preset("afrobeat").to_score(repeats=8, bpm=110).save_midi("afrobeat.mid")
|
||||
|
||||
Drums are the foundation. The same chords over a bossa nova feel like a different song than over a rock beat -- change the pattern and you change the genre. Try swapping presets under the same progression and hear how much the drums are really doing.
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user