Compare commits

..

1 Commits

Author SHA1 Message Date
kennethreitz 9624609f3b Add GitHub Pages deployment for Sphinx docs
Builds on push to master and deploys to kennethreitz.github.io/pytheory/

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 05:50:52 -04:00
145 changed files with 750 additions and 40216 deletions
+1 -4
View File
@@ -25,11 +25,8 @@ jobs:
- name: Set up Python
run: uv python install 3.13
- name: Install dependencies
run: uv sync --all-groups
- name: Build docs
run: uv run sphinx-build -b html docs docs/_build/html
run: uv run --group docs sphinx-build -b html docs docs/_build/html
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
-699
View File
@@ -1,699 +0,0 @@
# Changelog
All notable changes to PyTheory are documented here.
## 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.01.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
-38
View File
@@ -1,38 +0,0 @@
# 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
-21
View File
@@ -1,21 +0,0 @@
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.
+37 -141
View File
@@ -1,161 +1,57 @@
# PyTheory: Music Theory for Humans
Explore music theory, compose multi-part arrangements, and export to MIDI — all in Python.
This (work in progress) library attempts to make exploring music theory approachable to humans.
```
$ pip install pytheory
```
![logo](https://github.com/kennethreitz/pytheory/raw/master/ext/pytheory-small.png)
## Sketch Ideas Fast
```python
from pytheory import Score, Pattern, Key, Duration, Chord
from pytheory.play import play_score
score = Score("4/4", bpm=140)
score.drums("bossa nova", repeats=4)
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)
for sym in ["Am", "Dm", "E7", "Am"]:
chords.add(Chord.from_symbol(sym), Duration.WHOLE)
chords.add(Chord.from_symbol(sym), Duration.WHOLE)
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)
for n in ["A2", "E2", "A2", "C3"] * 4:
bass.add(n, Duration.QUARTER)
play_score(score) # hear it now
score.save_midi("sketch.mid") # open in your DAW
```
## Hear It Instantly
```
$ pytheory demo
```
## Music Theory
## True Scale -> Pitch Evaluation
```pycon
>>> from pytheory import Key, Chord, Tone
>>> from pytheory import TonedScale
>>> Key("C", "major").chords
['C major', 'D minor', 'E minor', 'F major', 'G major', 'A minor', 'B diminished']
>>> c_minor = TonedScale(tonic='C4')['minor']
>>> [c.symbol for c in Key("G", "major").progression("I", "V", "vi", "IV")]
['G', 'D', 'Em', 'C']
>>> c_minor
<Scale I=C4 II=D4 III=Eb4 IV=F4 V=G4 VI=Ab4 VII=Bb5 VIII=C5>
>>> Chord.from_symbol("F#m7b5").identify()
'F# half-diminished 7th'
>>> c_minor[0].pitch()
523.251130601197
>>> Tone.from_string("C4").interval_to(Tone.from_string("G4"))
'perfect 5th'
>>> c_minor["I"].pitch(symbolic=True)
440*2**(1/4)
>>> 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), ...]
>>> c_minor["tonic"].pitch(temperament='pythagorean', symbolic=True)
14080/27
```
## Composition
## Audibly play a note (or chord)
```python
score = Score("4/4", bpm=124)
score.drums("house", repeats=16, fill="house", fill_every=8)
>>> from pytheory import play
play(c_minor[0], t=1_000)
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)
# Song structure
score.section("verse")
# ... add notes ...
score.section("chorus")
lead.set(lowpass=5000, reverb=0.3)
# ... add notes ...
score.end_section()
## Chord Fingerings for Custom Tunings
score.repeat("verse")
score.repeat("chorus", times=2)
```pycon
>>> from pytheory import Tone, Fretboard, CHARTS
>>> tones = (
... Tone.from_string("F2"),
... Tone.from_string("C3"),
... Tone.from_string("G3"),
... Tone.from_string("D4"),
... Tone.from_string("A5"),
... Tone.from_string("E5")
... )
>>> fretboard = Fretboard(tones=tones)
>>>
>>> c_chord = CHARTS['western']["C"]
>>> print(c_chord.fingering(fretboard=fretboard))
(0, 0, 0, 3, 3, 3)
```
## 10 Synth Waveforms
It can also [generate charts for all known chords](https://gist.github.com/kennethreitz/b363660145064fc330c206294cff92fc) for any instrument (accuracy to be determined!).
sine, saw, triangle, square, pulse, FM, noise, supersaw, PWM slow, PWM fast — with detune, stereo pan, and spread.
## 58 Drum Patterns
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.
## 6 Effects with Automation
```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)
# Automate mid-song
lead.set(lowpass=4000, distortion=0.9)
# LFO modulation
lead.lfo("lowpass", rate=0.5, min=400, max=3000, bars=8)
```
Signal chain: distortion → chorus → lowpass → delay → reverb. Sidechain compression. Master bus compressor/limiter. Stereo output.
## Convolution Reverb
7 synthetic impulse responses: Taj Mahal (12s), cathedral, plate, spring, cave, parking garage, canyon.
```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
Guitar (8 tunings), bass, ukulele, mandolin family, violin family, banjo, harp, oud, sitar, erhu, and more — with chord fingering generation.
## Command Line
```
$ 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
```
## Why Python?
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
**[pytheory.kennethreitz.org](https://pytheory.kennethreitz.org)**
✨🍰✨
-1
View File
@@ -1 +0,0 @@
pytheory.kennethreitz.org
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 KiB

-4
View File
@@ -1,4 +0,0 @@
<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>
-18
View File
@@ -1,18 +0,0 @@
{% extends "!layout.html" %}
{% block footer %}
{{ super() }}
<script type="text/javascript">
var _gauges = _gauges || [];
(function() {
var t = document.createElement('script');
t.type = 'text/javascript';
t.async = true;
t.id = 'gauges-tracker';
t.setAttribute('data-site-id', '69bfc431e7e47c1200fc74bc');
t.setAttribute('data-track-path', 'https://track.gaug.es/track.gif');
t.src = 'https://d2fuc4clr7gvcn.cloudfront.net/track.js';
var s = document.getElementsByTagName('script')[0];
s.parentNode.insertBefore(t, s);
})();
</script>
{% endblock %}
-2
View File
@@ -1,2 +0,0 @@
```{include} ../CHANGELOG.md
```
+3 -23
View File
@@ -1,25 +1,18 @@
import os
import sys
from unittest.mock import MagicMock
sys.path.insert(0, os.path.abspath(".."))
# Mock sounddevice so Sphinx can import pytheory.play without PortAudio
sys.modules["sounddevice"] = MagicMock()
project = "PyTheory"
copyright = "2026, Kenneth Reitz"
copyright = "2024, Kenneth Reitz"
author = "Kenneth Reitz"
import pytheory
release = pytheory.__version__
version = pytheory.__version__
release = "0.2.0"
extensions = [
"sphinx.ext.autodoc",
"sphinx.ext.napoleon",
"sphinx.ext.viewcode",
"sphinx.ext.intersphinx",
"myst_parser",
]
autodoc_member_order = "bysource"
@@ -37,18 +30,5 @@ templates_path = ["_templates"]
exclude_patterns = ["_build"]
html_theme = "alabaster"
html_theme_options = {
"github_user": "kennethreitz",
"github_repo": "pytheory",
"github_banner": True,
"github_button": True,
"github_type": "star",
"github_count": True,
"description": "Music Theory for Humans",
"extra_nav_links": {
f"v{pytheory.__version__}": "https://pypi.org/project/pytheory/",
},
"show_powered_by": False,
}
html_title = "PyTheory"
html_static_path = ["_static"]
html_extra_path = ["CNAME"]
File diff suppressed because it is too large Load Diff
+47 -574
View File
@@ -1,610 +1,83 @@
Working with Chords
===================
A `chord <https://en.wikipedia.org/wiki/Chord_(music)>`_ is two or more tones sounding simultaneously. Chords are the
vertical dimension of music — while melody moves horizontally through
time, harmony stacks tones on top of each other.
Chords and Chord Charts
-----------------------
Chord Construction
------------------
PyTheory provides two chord-related classes:
Chords are built by stacking **intervals** above a **root** note. The
most common chord type is the `triad <https://en.wikipedia.org/wiki/Triad_(music)>`_ — three notes built from
alternating scale degrees (root, 3rd, 5th).
The four triad types::
Major root + major 3rd (4) + perfect 5th (7) Bright, stable
Minor root + minor 3rd (3) + perfect 5th (7) Dark, sad
Diminished root + minor 3rd (3) + diminished 5th (6) Tense, unstable
Augmented root + major 3rd (4) + augmented 5th (8) Eerie, unresolved
Adding a 7th creates a `seventh chord <https://en.wikipedia.org/wiki/Seventh_chord>`_ — the foundation of jazz
harmony::
Dominant 7th root + 4 + 7 + 10 Bluesy, wants to resolve (G7)
Major 7th root + 4 + 7 + 11 Dreamy, sophisticated (Cmaj7)
Minor 7th root + 3 + 7 + 10 Warm, mellow (Am7)
Diminished 7th root + 3 + 6 + 9 Dramatic, symmetrical
Inversions
----------
A chord is in **root position** when the root is the lowest note.
When a different chord tone is in the bass, the chord is `inverted <https://en.wikipedia.org/wiki/Inversion_(music)>`_:
- **Root position**: C E G (root in bass)
- **First inversion**: E G C (3rd in bass) — notated C/E
- **Second inversion**: G C E (5th in bass) — notated C/G
Inversions change the color and weight of a chord without changing its
identity. First inversion sounds lighter; second inversion sounds
suspended, often used as a passing chord.
For seventh chords, there's also **third inversion** (7th in bass):
- G7 in third inversion: F G B D (notated G7/F)
.. code-block:: pycon
>>> from pytheory import Chord, Tone
>>> 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'
Extended Chords
---------------
Beyond seventh chords, jazz harmony builds `extended chords <https://en.wikipedia.org/wiki/Extended_chord>`_ by
continuing to stack thirds:
- **9th chord**: adds the 9th (= 2nd, one octave up)
- **11th chord**: adds the 9th and 11th (= 4th)
- **13th chord**: adds the 9th, 11th, and 13th (= 6th)
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:: pycon
>>> from pytheory import TonedScale
>>> scale = TonedScale(tonic="C4")["major"]
>>> cmaj9 = scale.chord(0, 2, 4, 6, 8)
>>> c13 = scale.chord(0, 2, 4, 6, 8, 10, 12)
- :class:`~pytheory.chords.Chord` — a collection of tones played together
- :class:`~pytheory.charts.NamedChord` — a chord from the chart database with
fingering support
Using the Chord Chart
---------------------
PyTheory includes 144 pre-built chords (12 roots x 12 qualities):
The built-in chart contains 144 chords (12 roots x 12 qualities):
.. code-block:: pycon
.. code-block:: python
>>> from pytheory import Fretboard
from pytheory import CHARTS
>>> 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)
chart = CHARTS["western"]
You can also build chords directly with ``Chord.from_name()``:
# Access a chord
c_major = chart["C"]
a_minor = chart["Am"]
g_seven = chart["G7"]
.. code-block:: pycon
# Available qualities: "", "maj", "m", "5", "7", "9",
# "dim", "m6", "m7", "m9", "maj7", "maj9"
>>> from pytheory import Chord
Chord Tones
-----------
>>> Chord.from_name("G7").identify()
'G dominant 7th'
>>> Chord.from_name("Ddim").identify()
'D diminished'
Each named chord knows which tones it contains:
Available qualities:
============ ================ ================================
Quality Intervals Example tones (from C)
============ ================ ================================
``""`` 4, 7 C E G (major triad)
``"maj"`` 4, 7 C E G (explicit major)
``"m"`` 3, 7 C Eb G (minor triad)
``"5"`` 7 C G (power chord)
``"7"`` 4, 7, 10 C E G Bb (dominant 7th)
``"9"`` 4, 7, 10, 14 C E G Bb D (dominant 9th)
``"dim"`` 3, 6 C Eb Gb (diminished)
``"m6"`` 3, 7, 9 C Eb G A (minor 6th)
``"m7"`` 3, 7, 10 C Eb G Bb (minor 7th)
``"m9"`` 3, 7, 10, 14 C Eb G Bb D (minor 9th)
``"maj7"`` 4, 7, 11 C E G B (major 7th)
``"maj9"`` 4, 7, 11, 14 C E G B D (major 9th)
============ ================ ================================
.. code-block:: pycon
>>> from pytheory import CHARTS
>>> chart = CHARTS["western"]
.. code-block:: python
>>> chart["C"].acceptable_tone_names
('C', 'E', 'G')
>>> chart["Cm7"].acceptable_tone_names
('C', 'Eb', 'G', 'Bb')
>>> chart["Am"].acceptable_tone_names
('A', 'C', 'E')
Building Chords
---------------
>>> chart["G7"].acceptable_tone_names
('G', 'B', 'D', 'F')
Several convenience constructors make chord creation concise:
.. code-block:: pycon
>>> from pytheory import Chord
>>> Chord.from_tones("C", "E", "G").identify()
'C major'
>>> Chord.from_tones("A", "C", "E").identify()
'A minor'
>>> Chord.from_name("Am7").identify()
'A minor 7th'
>>> Chord.from_name("G7").identify()
'G dominant 7th'
>>> Chord.from_intervals("C", 4, 7).identify()
'C major'
>>> Chord.from_intervals("G", 4, 7, 10).identify()
'G dominant 7th'
>>> Chord.from_midi_message(60, 64, 67).identify()
'C major'
>>> len(Chord.from_name("C"))
3
>>> "C" in Chord.from_name("C")
True
Intervals
---------
The ``intervals`` property returns semitone distances between adjacent
tones — these are musically meaningful and octave-invariant:
.. code-block:: pycon
>>> Chord.from_tones("C", "E", "G").intervals
[4, 3]
>>> Chord.from_tones("C", "Eb", "G").intervals
[3, 4]
Consonance and Dissonance
Building Chords Manually
-------------------------
**Consonance** is the perception of stability and "pleasantness" when
tones sound together. **Dissonance** is the perception of tension and
roughness. Neither is inherently good or bad — music needs both.
.. code-block:: python
Harmony Score
~~~~~~~~~~~~~
from pytheory import Tone, Chord
The ``harmony`` property measures consonance using **frequency ratio
simplicity**. The insight dates back to Pythagoras (6th century BC):
intervals whose frequencies form simple integer ratios sound consonant.
c_major = Chord(tones=[
Tone.from_string("C4", system="western"),
Tone.from_string("E4", system="western"),
Tone.from_string("G4", system="western"),
])
=========== ===== ====================
Interval Ratio Why it sounds "good"
=========== ===== ====================
Octave 2:1 Every 2nd wave aligns
Perfect 5th 3:2 Every 3rd wave aligns
Perfect 4th 4:3 Every 4th wave aligns
Major 3rd 5:4 Every 5th wave aligns
Minor 3rd 6:5 Every 6th wave aligns
Tritone 45:32 Waves rarely align
=========== ===== ====================
# Iteration
for tone in c_major:
print(tone)
.. code-block:: pycon
len(c_major) # 3
"C" in c_major # True
>>> from pytheory import Chord, Tone
>>> C4 = Tone.from_string("C4", system="western")
>>> G4 = Tone.from_string("G4", system="western")
>>> fifth = Chord([C4, G4])
>>> tritone = Chord([C4, C4 + 6])
>>> fifth.harmony > tritone.harmony
True
Dissonance Score
~~~~~~~~~~~~~~~~
The ``dissonance`` property uses the Plomp-Levelt `roughness <https://en.wikipedia.org/wiki/Roughness_(psychoacoustics)>`_ model
(1965). When two frequencies are close together, their sound waves
interfere and produce rapid amplitude fluctuations called `beating <https://en.wikipedia.org/wiki/Beat_(acoustics)>`_.
This beating is perceived as roughness — the physiological basis of
dissonance.
The roughness depends on the frequency difference relative to the
**critical bandwidth** of the human ear (~25% of the frequency at
that register). Maximum roughness occurs when the difference equals
the critical bandwidth.
.. code-block:: pycon
>>> E4 = Tone.from_string("E4", system="western")
>>> octave = Chord([C4, C4 + 12])
>>> third = Chord([C4, E4])
>>> octave.dissonance < third.dissonance
True
Beat Frequencies
~~~~~~~~~~~~~~~~
When two tones with slightly different frequencies are played together,
you hear a pulsing at the **beat frequency**: ``|f1 - f2|`` Hz.
- **< 1 Hz**: Slow pulsing, used for tuning instruments
- **115 Hz**: Audible rhythmic beating
- **1530 Hz**: Perceived as buzzing/roughness
- **> 30 Hz**: No longer beating — becomes part of the timbre
.. code-block:: pycon
>>> A4 = Tone.from_string("A4", system="western")
>>> chord = Chord([A4, A4 + 7, A4 + 12])
>>> chord.beat_frequencies
[...]
>>> round(chord.beat_pulse, 1)
219.3
Transposition
-------------
Shift an entire chord up or down by any number of semitones:
.. code-block:: pycon
>>> Chord.from_name("C").transpose(7).identify()
'G major'
>>> Chord.from_name("Am7").transpose(-2).identify()
'G minor 7th'
Chord Manipulation
------------------
Add or remove individual tones from a chord:
.. code-block:: pycon
>>> from pytheory import Chord, Tone
>>> c_major = Chord.from_tones("C", "E", "G")
>>> b4 = Tone.from_string("B4", system="western")
>>> cmaj7 = c_major.add_tone(b4)
>>> cmaj7.identify()
'C major 7th'
>>> c_again = cmaj7.remove_tone("B")
>>> c_again.identify()
'C major'
Chord Identification
--------------------
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:: pycon
>>> from pytheory import Chord
>>> Chord.from_tones("A", "C", "E").identify()
'A minor'
>>> Chord.from_tones("G", "B", "D", "F").identify()
'G dominant 7th'
>>> Chord.from_tones("E", "G", "C").identify()
'C 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:: pycon
>>> chord = Chord.from_name("Am7")
>>> chord.root
<Tone A4>
>>> chord.quality
'minor 7th'
Harmonic Analysis
-----------------
`Roman numeral analysis <https://en.wikipedia.org/wiki/Roman_numeral_analysis>`_ labels each chord by its function within a
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:: pycon
>>> from pytheory import Chord, Tone
>>> 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'
>>> 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
----------------------
**Tension** is what makes music move forward. Without it, there's no
desire to resolve — no drama, no narrative. The ``tension`` property
quantifies this based on:
- **Tritones** (6 semitones): the most unstable interval. The tritone
between the 3rd and 7th of a dominant chord (e.g. B and F in G7)
creates the strongest pull toward resolution.
- **Minor 2nds**: semitone clashes that add bite and urgency.
- **Dominant function**: the specific combination of a major 3rd and
minor 7th above the root — the hallmark of the V7 chord.
.. code-block:: pycon
>>> c_major = Chord([C4, E4, G4])
>>> c_major.tension['score']
0.0
>>> c_major.tension['tritones']
0
>>> 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
-------------
`Voice leading <https://en.wikipedia.org/wiki/Voice_leading>`_ is the art of connecting chords smoothly. Instead of
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:: pycon
>>> 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)
Tritone Substitution
--------------------
In jazz harmony, any `dominant chord <https://en.wikipedia.org/wiki/Dominant_seventh_chord>`_
can be replaced by the dominant chord a
`tritone <https://en.wikipedia.org/wiki/Tritone_substitution>`_ (6
semitones) away. This works because the two chords share the same
tritone interval — the 3rd and 7th simply swap roles.
Common tritone subs: G7 <-> Db7, C7 <-> F#7, D7 <-> Ab7.
.. code-block:: pycon
>>> from pytheory import Chord
>>> g7 = Chord.from_name("G7")
>>> sub = g7.tritone_sub()
>>> sub.identify()
'C# dominant 7th'
The Overtone Series
-------------------
Every musical tone is actually a stack of frequencies — the
**fundamental** plus its `overtones <https://en.wikipedia.org/wiki/Overtone>`_ (harmonics). The overtone series
is nature's chord: it contains the octave, perfect fifth, perfect
fourth, major third, and more, in that order.
This is *why* consonance exists. When you play C and G together, the
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:: pycon
>>> from pytheory import Tone
>>> 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
Chord Properties
----------------
The ``extensions()`` method suggests available extensions (9th, 11th, 13th)
that don't clash with existing chord tones:
.. code-block:: python
.. code-block:: pycon
# Frequency intervals between adjacent tones (Hz)
c_major.intervals
>>> from pytheory import Chord, TonedScale
>>> cm = Chord.from_symbol("C")
>>> cm.extensions()
[...]
# Harmony score (higher = more consonant intervals)
c_major.harmony
>>> # Filter extensions against a scale for diatonic correctness:
>>> scale = TonedScale(tonic="C4")["major"]
>>> cm.extensions(scale=scale)
[...]
# Dissonance score (higher = wider intervals)
c_major.dissonance
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 (011, 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.
# Beat frequency between closest tone pair
c_major.beat_pulse
-226
View File
@@ -1,226 +0,0 @@
Command-Line Interface
======================
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
-----------
Look up any note's frequency, MIDI number, enharmonic spelling, and
overtones::
$ pytheory tone A4
Note: A4
Frequency: 440.00 Hz (equal temperament)
MIDI: 69
Overtones: 440.0, 880.0, 1320.0, 1760.0, 2200.0, 2640.0
Compare temperaments with ``--temperament``::
$ pytheory tone C5 --temperament pythagorean
Note: C5
Frequency: 521.48 Hz (pythagorean temperament)
Equal temp: 523.25 Hz (diff: -5.9 cents)
Scale Display
-------------
Show any scale in any system::
$ pytheory scale C major
C major: C D E F G A B C
Intervals: C4 -2- D4 -2- E4 -1- F4 -2- G4 -2- A4 -2- B4 -1- C5
$ pytheory scale C dorian
$ pytheory scale Sa bhairav --system indian
Chord Identification
--------------------
Identify a chord from its notes::
$ pytheory chord C E G
Chord: C major
Tones: C4 E4 G4
Intervals: [4, 3]
Harmony: 0.5833
Dissonance: 0.0712
Tension: 0.00 (tritones=0)
$ pytheory chord G B D F
Chord: G dominant 7th
Key Explorer
------------
Get a complete breakdown of any key — signature, diatonic triads,
seventh chords, relative and parallel keys::
$ pytheory key G major
Key: G major
Signature: 1 sharps, 0 flats (F#)
Scale: G A B C D E F#
Triads:
I G major
ii A minor
iii B minor
IV C major
V D major
vi E minor
vii° F# diminished
7th chords:
G major 7th
A minor 7th
...
Relative: <Key E minor>
Parallel: <Key G minor>
Guitar Fingerings
-----------------
Get tablature for any of the 144 built-in chords::
$ pytheory fingering Am
Am
E|--0--
B|--1--
G|--2--
D|--2--
A|--0--
E|--0--
Use ``--capo`` to see fingerings with a capo::
$ pytheory fingering G --capo 2
Chord Progressions
------------------
Build progressions from Roman numerals::
$ pytheory progression G major I V vi IV
Key: G major
Progression: I → V → vi → IV
I G major
V D major
vi E minor
IV C major
Key Detection
-------------
Detect the most likely key from a set of notes::
$ pytheory detect C E G A D
Detected key: C major
Scale: C D E F G A B C
Audio Playback
--------------
Play individual notes or chords (requires PortAudio)::
$ pytheory play A4 # Single note
$ pytheory play C E G # Notes as chord
$ pytheory play Am7 # Chord by name
$ 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.
-547
View File
@@ -1,547 +0,0 @@
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.
-614
View File
@@ -1,614 +0,0 @@
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.
-912
View File
@@ -1,912 +0,0 @@
Effects
=======
Effects are how recorded music gets its character. A guitar without
reverb sounds like it's being played in a closet. A vocal without
compression sounds thin and amateur. A synth without filtering sounds
like a test signal. Effects are the difference between "notes" and
"music" -- they put the sound in a space, give it texture, and make it
feel alive.
Every record you've ever loved was shaped by effects. The cavernous
reverb on a Phil Collins drum hit. The tape delay on a reggae vocal.
The distortion on a Hendrix guitar. The chorus on an 80s synth pad.
These aren't decorations added after the fact; they're fundamental to
the sound itself.
Each part in a Score can have its own effects chain. Effects are set at
part creation and applied per-part before mixing, so every voice gets
independent processing.
Signal Chain
------------
The order of effects matters -- a lot. Distortion before a lowpass
filter means you're generating all those rich, crunchy harmonics and
then sculpting them with the filter. That's warm, controllable,
musical. Filter before distortion means you're distorting the already-
filtered signal -- a different, often harsher character. The fixed
order in PyTheory matches classic analog synth architecture, the same
signal path used by the Moog, the TB-303, and most hardware synths.
It's a well-tested order that sounds good by default.
Effects are applied in this fixed order::
Signal --> Saturation --> Tremolo --> Distortion --> Cabinet --> Chorus
--> Phaser --> Highpass --> Lowpass --> Delay --> Reverb --> Mix
Additionally, these per-note effects are applied before the part effects chain:
- **Sub-oscillator**: octave-below sine mixed in at the oscillator stage
- **Noise layer**: filtered noise mixed per-note for breath/transients
- **Filter envelope**: per-note lowpass sweep (attack/decay/sustain)
- **Velocity → brightness**: harder velocity = brighter filter cutoff
Part-level effects:
- **Saturation** first: subtle even-harmonic warmth (tape/tube color).
- **Tremolo** second: amplitude LFO modulation.
- **Distortion** third: drives the signal before filtering.
- **Cabinet** fourth: speaker cab simulation (rolloff + presence bump).
- **Chorus** fifth: thickens the signal.
- **Phaser** sixth: swept allpass notches.
- **Highpass** seventh: removes low-frequency mud.
- **Lowpass** eighth: shapes the tone (like a tone knob on an amp).
- **Delay** ninth: echoes the shaped signal (tap delay / tape echo).
- **Reverb** last: places everything in a space (room / hall).
Distortion
----------
You know what distortion sounds like -- it's the sound of rock and roll.
An electric guitar through a cranked amplifier. But at lower levels,
distortion is subtler: it adds warmth, presence, and harmonic richness.
This is why producers run clean signals through tape machines and tube
preamps. A little saturation makes everything sound more "real."
Soft-clip waveshaping using ``tanh`` -- models the warm saturation of an
overdriven tube amplifier. At low drive levels it adds harmonic warmth;
at high levels it becomes an aggressive fuzz.
Parameters:
- ``distortion``: Wet/dry mix, 0.0--1.0.
- ``distortion_drive``: Gain before clipping (default 3.0).
- 0.5--2 = subtle warmth (tube preamp)
- 3--8 = overdrive (cranked amp)
- 10+ = fuzz
.. code-block:: python
# Warm tube saturation on a bass
bass = score.part(
"bass",
synth="sine",
envelope="pluck",
distortion=0.3,
distortion_drive=2.0,
)
# Heavy fuzz on a lead
lead = score.part(
"lead",
synth="saw",
envelope="staccato",
distortion=0.8,
distortion_drive=10.0,
)
Cabinet Simulation
------------------
A real guitar amp doesn't just distort the signal -- the speaker
cabinet shapes the tone dramatically. A 12-inch speaker in a closed
cabinet rolls off the harsh high frequencies above 5 kHz and adds a
presence bump around 2--3 kHz that gives the sound its "in the room"
quality. Without a cabinet, distortion sounds thin and fizzy. With
one, it sounds like a real amp.
PyTheory's cabinet simulation applies a speaker rolloff curve (lowpass
at ~5 kHz) combined with a presence resonance bump, placed in the
signal chain immediately after distortion -- exactly where it sits in
a real amp.
Parameters:
- ``cabinet``: Wet/dry mix, 0.0--1.0 (default 0, off).
- 0.3--0.5 = subtle speaker coloring
- 0.6--0.8 = classic amp-in-a-room
- 1.0 = full cabinet, no dry signal
.. code-block:: python
# Classic rock amp tone: distortion into cabinet
guitar = score.part(
"guitar",
synth="saw",
envelope="pluck",
distortion=0.6,
distortion_drive=5.0,
cabinet=0.8,
)
# Clean amp with just cabinet warmth (no distortion)
clean = score.part(
"clean",
synth="triangle",
envelope="pluck",
cabinet=0.5,
)
Analog Drift
------------
Real analog synthesizers are never perfectly in tune. The voltage-
controlled oscillators drift slightly over time as components warm up
and temperature fluctuates. This imperfection is actually a big part
of why vintage analog synths sound so appealing -- the subtle pitch
wandering gives each note a unique, living quality that static digital
oscillators lack.
The ``analog_drift`` parameter adds slow, random pitch variation to
each oscillator, modeling this vintage behavior.
Parameters:
- ``analog_drift``: Drift amount, 0.0--1.0 (default 0, off).
- 0.05--0.1 = subtle warmth (studio-grade analog)
- 0.15--0.25 = noticeable drift (vintage gear warming up)
- 0.3+ = unstable, wobbly (broken tape machine)
.. code-block:: python
# Warm vintage pad
pad = score.part(
"pad",
synth="supersaw",
envelope="pad",
analog_drift=0.1,
chorus=0.3,
)
# Lo-fi detuned lead
lead = score.part(
"lead",
synth="saw",
envelope="pluck",
analog_drift=0.25,
)
Chorus
------
That shimmery, wide, slightly-out-of-focus sound that defined the
1980s? That's chorus. Think of the intro to "Come As You Are" by
Nirvana, or literally any synth pad from 1983 to 1989. It makes one
instrument sound like two or three playing together, slightly out of
tune with each other -- which is exactly how a real string section or
choir sounds rich and full.
A slightly detuned, LFO-modulated delayed copy mixed back in. Thickens
the sound like two musicians playing the same part -- the signature
effect of the Roland Juno synthesizers.
Parameters:
- ``chorus``: Wet/dry mix, 0.0--1.0.
- ``chorus_rate``: LFO speed in Hz. 0.5--1 = slow shimmer, 2--4 = vibrato.
- ``chorus_depth``: Modulation depth in seconds (default 0.003).
.. code-block:: python
# Juno-style pad chorus
pad = score.part(
"pad",
synth="supersaw",
envelope="pad",
chorus=0.5,
chorus_rate=1.5,
chorus_depth=0.003,
)
# Subtle thickening on a clean lead
lead = score.part(
"lead",
synth="triangle",
envelope="pluck",
chorus=0.2,
chorus_rate=0.8,
)
Lowpass Filter
--------------
You know that sound when a DJ turns the knob and everything goes
underwater? That's a lowpass filter closing down. It removes
high-frequency content, leaving only the warm, round, bassy
frequencies below the cutoff point. The lowpass filter is arguably the
most important effect in all of electronic music -- it's the entire
sound of acid house, the "wah" in auto-wah, and the reason analog
synths sound warm instead of harsh.
A 12 dB/octave biquad lowpass filter with resonance -- the sound of
analog synthesizers. Removes frequencies above the cutoff; the resonance
(Q) parameter adds a peak at the cutoff frequency for that classic
"acid squelch."
Parameters:
- ``lowpass``: Cutoff frequency in Hz (0 = off). Reference points:
- 200--400 Hz = deep sub bass
- 800--1500 Hz = warm / muffled
- 2000--4000 Hz = present lead
- 5000+ Hz = subtle rolloff
- ``lowpass_q``: Resonance / Q factor (default 0.707 = Butterworth flat).
- 1.0 = slight peak
- 2.0 = pronounced
- 5.0+ = aggressive acid squelch
.. code-block:: python
# Round bass with gentle filtering
bass = score.part(
"bass",
synth="sine",
envelope="pluck",
lowpass=400,
lowpass_q=1.5,
)
# Acid squelch on a saw lead
acid = score.part(
"acid",
synth="saw",
envelope="staccato",
lowpass=1500,
lowpass_q=5.0,
legato=True,
glide=0.03,
)
Delay
-----
Delay is echo. Literally. The Edge from U2 built his entire guitar
sound around dotted-eighth-note delays. Dub reggae producers like Lee
"Scratch" Perry and King Tubby turned delay into an art form, feeding
echoes back into themselves until they spiraled into infinity. At short
times with low feedback, delay adds rhythmic interest. At long times
with high feedback, it creates cascading, psychedelic soundscapes.
Tempo-synced echoes with feedback. Each repeat feeds back into the
delay line, creating rhythmic echo trails. High feedback values produce
the cascading, self-oscillating echoes of dub reggae.
Parameters:
- ``delay``: Wet/dry mix, 0.0--1.0.
- ``delay_time``: Time between echoes in seconds. Musically useful
values at 120 bpm: 0.25 (8th note), 0.375 (dotted 8th),
0.5 (quarter note).
- ``delay_feedback``: How much each echo feeds back (0.0--1.0).
- 0.3 = a few repeats
- 0.5 = many repeats
- 0.7+ = runaway (dub style)
.. code-block:: python
# Dotted-eighth slapback on a lead
lead = score.part(
"lead",
synth="triangle",
envelope="strings",
delay=0.3,
delay_time=0.375,
delay_feedback=0.4,
)
# Dub-style runaway echoes
melodica = score.part(
"melodica",
synth="triangle",
envelope="pluck",
delay=0.5,
delay_time=0.66,
delay_feedback=0.55,
)
Reverb
------
Everyone knows what reverb sounds like, even if they don't know the
word -- it's the sound of singing in the shower, or clapping in a
cathedral. It's the natural echo of a space. Without reverb, sounds
feel uncomfortably close and dry, like someone whispering directly into
your ear. With it, sounds feel like they exist in a real place. Reverb
is the most universally used effect in all of recorded music.
PyTheory offers two reverb engines: a fast **algorithmic** reverb for
general use, and **convolution** reverb for photorealistic acoustic
spaces.
Algorithmic Reverb
~~~~~~~~~~~~~~~~~~
A Schroeder reverb using 4 parallel comb filters and 2 series allpass
filters. Fast, lightweight, and good for general-purpose room
simulation.
Parameters:
- ``reverb``: Wet/dry mix, 0.0--1.0.
- 0.2--0.4 = subtle space
- 0.5--0.8 = ambient / dub
- ``reverb_decay``: Tail length in seconds.
- 0.5 = small room
- 1.5 = hall
- 3.0+ = cathedral / dub
.. code-block:: python
# Jazz club ambience
rhodes = score.part(
"rhodes",
synth="fm",
envelope="piano",
reverb=0.4,
reverb_decay=1.8,
)
Convolution Reverb
~~~~~~~~~~~~~~~~~~
Convolution reverb works by convolving your audio with an *impulse
response* -- a recording (or simulation) of a real acoustic space.
Where algorithmic reverb approximates the math of reflections,
convolution reverb *is* the space. You hear every surface, every
angle, every material.
PyTheory generates synthetic impulse responses that model the acoustic
properties of real spaces: early reflection patterns, exponential
decay envelopes, frequency-dependent absorption (high frequencies die
faster in stone), diffusion density, and subtle pitch modulation from
irregular surfaces. The result is dramatically more realistic than
algorithmic reverb, especially for long tails and large spaces.
Set ``reverb_type`` to any preset name instead of ``"algorithmic"``:
- ``"taj_mahal"`` -- Massive marble dome. 12-second tail, bright early
reflections, enormously dense and diffuse. The most dramatic verb
you've ever heard.
- ``"cathedral"`` -- Gothic stone cathedral. 6 seconds, strong early
reflections off parallel walls, dark reverberant tail.
- ``"plate"`` -- EMT 140 plate reverb. 4 seconds, dense, bright, smooth.
The studio classic that defined pop records from the 60s onward.
- ``"spring"`` -- Spring reverb tank. 3 seconds, metallic, boingy, lo-fi.
The sound of surf rock and guitar amps.
- ``"cave"`` -- Natural cave. 8 seconds, very dark, irregular reflections.
High frequencies are aggressively absorbed by rock.
- ``"parking_garage"`` -- Concrete box. 3 seconds, bright, flutter echoes
from parallel hard walls.
- ``"canyon"`` -- Open canyon. 5 seconds, sparse discrete echoes (the
walls are far apart) dissolving into a diffuse tail.
Parameters:
- ``reverb``: Wet/dry mix, 0.0--1.0.
- ``reverb_type``: Preset name (default ``"algorithmic"``).
.. code-block:: python
# FM flute through the Taj Mahal
flute = score.part(
"flute",
synth="fm",
envelope="bell",
reverb=0.85,
reverb_type="taj_mahal",
delay=0.65,
delay_time=0.375,
delay_feedback=0.55,
)
# Cathedral wash for ambient pads
pad = score.part(
"pad",
synth="supersaw",
envelope="pad",
reverb=0.7,
reverb_type="cathedral",
)
# Classic plate on a vocal-style lead
lead = score.part(
"lead",
synth="triangle",
envelope="strings",
reverb=0.5,
reverb_type="plate",
)
# Algorithmic reverb still works as before
rhodes = score.part(
"rhodes",
synth="fm",
envelope="piano",
reverb=0.4,
reverb_decay=1.8,
)
You can switch reverb types mid-song with automation:
.. code-block:: python
lead = score.part("lead", synth="fm", envelope="bell",
reverb=0.5, reverb_type="plate")
lead.add("C5", Duration.WHOLE)
# Switch to cathedral for the big section
lead.set(reverb_type="cathedral", reverb=0.8)
lead.add("E5", Duration.WHOLE)
Combining Effects
-----------------
Effects stack naturally. Here are some real-world combinations:
Dub
~~~
Distortion warmth into filtered delay into deep reverb:
.. code-block:: python
melodica = score.part(
"melodica",
synth="triangle",
envelope="pluck",
distortion=0.2,
distortion_drive=2.0,
lowpass=2000,
lowpass_q=1.2,
delay=0.5,
delay_time=0.66,
delay_feedback=0.55,
reverb=0.4,
reverb_decay=2.5,
)
Acid
~~~~
Resonant lowpass with distortion and delay:
.. code-block:: python
acid = score.part(
"acid",
synth="saw",
envelope="staccato",
lowpass=1500,
lowpass_q=3.0,
distortion=0.4,
distortion_drive=4.0,
delay=0.3,
delay_time=0.242,
delay_feedback=0.4,
)
Ambient
~~~~~~~
Wide chorus, long reverb, gentle delay:
.. code-block:: python
ambient = score.part(
"ambient",
synth="supersaw",
envelope="pad",
chorus=0.4,
chorus_rate=0.5,
delay=0.3,
delay_time=0.5,
delay_feedback=0.5,
reverb=0.7,
reverb_decay=4.0,
)
808 Bass
~~~~~~~~
Subtle saturation and deep filtering for hip-hop sub bass:
.. code-block:: python
bass = score.part(
"bass",
synth="sine",
envelope="pluck",
lowpass=200,
lowpass_q=1.8,
distortion=0.4,
distortion_drive=2.0,
)
Sidechain Compression
---------------------
If you've ever heard a house track where the pad *breathes* — gets
quiet every time the kick hits and swells back up between beats —
that's sidechain compression. It's the pumping effect that defines
modern electronic music. The kick drum triggers a compressor on
another part, ducking its volume in rhythm with the beat.
In PyTheory, the drum hits are the trigger. Any part with
``sidechain > 0`` gets ducked whenever the kick (or any drum) hits:
.. code-block:: python
# Classic EDM pump — pad ducks hard on every kick
pad = score.part(
"pad",
synth="supersaw",
envelope="pad",
sidechain=0.85,
sidechain_release=0.15,
)
# Bass breathes with the kick too, but less aggressively
bass = score.part(
"bass",
synth="sine",
lowpass=250,
sidechain=0.7,
sidechain_release=0.1,
)
Parameters:
- ``sidechain``: How much to duck, 0.01.0 (default 0, off).
0.5 = subtle pump, 0.7 = noticeable, 0.85 = classic EDM, 1.0 = full silence on hits.
- ``sidechain_release``: How fast the volume comes back, in seconds
(default 0.1). Shorter = tighter, longer = more dramatic pump.
The lead stays above the pump — don't sidechain everything or the
whole mix will gasp for air:
.. code-block:: python
# Lead cuts through — no sidechain
lead = score.part(
"lead",
synth="saw",
envelope="pluck",
delay=0.2,
)
Saturation
----------
Saturation is the warm, subtle harmonic enhancement of analog tape
machines and tube preamps. Unlike distortion (which uses ``tanh`` and
adds harsh odd harmonics), saturation uses a polynomial waveshaper
that adds even harmonics -- 2nd and 4th -- which the ear perceives as
warmth and fullness. It's why records mixed through a Neve console
sound "bigger" than the same mix done in the box.
Parameters:
- ``saturation``: Amount, 0.0--1.0 (default 0, off).
- 0.05--0.15 = subtle analog warmth (tape machine)
- 0.2--0.4 = noticeable color (tube preamp)
- 0.5+ = heavy coloring
.. code-block:: python
# Warm up a bass
bass = score.part("bass", synth="saw", saturation=0.2)
# Glue a string ensemble
strings = score.part("strings", instrument="string_ensemble",
saturation=0.1)
Tremolo
-------
Amplitude modulation by a sine LFO. The classic vibrating-amp sound.
Essential for vibraphone (the rotating discs in the resonator tubes),
Rhodes electric piano, and surf guitar. Not to be confused with
vibrato (pitch modulation).
Parameters:
- ``tremolo_depth``: Modulation depth, 0.0--1.0 (default 0, off).
- ``tremolo_rate``: LFO speed in Hz (default 5.0).
- 3--5 Hz = classic tremolo
- 5--7 Hz = vibraphone motor speed
- 8+ Hz = ring-mod territory
.. code-block:: python
# Classic Fender amp tremolo
guitar = score.part("guitar", synth="saw", envelope="pluck",
tremolo_depth=0.3, tremolo_rate=4.0)
# Vibraphone with motor
vib = score.part("vib", instrument="vibraphone") # built in
Phaser
------
A chain of allpass filters whose center frequencies are swept by an
LFO, creating moving notches in the spectrum. The classic "jet
engine" or "underwater" effect. Think Small Stone, MXR Phase 90, or
the intro to "Eruption." Different from chorus -- chorus adds a
detuned copy, phaser cancels specific frequencies.
Parameters:
- ``phaser``: Wet/dry mix, 0.0--1.0 (default 0, off).
- ``phaser_rate``: LFO sweep speed in Hz (default 0.5).
- 0.1--0.3 = slow, lush sweep
- 0.5--1.0 = classic phaser
- 2.0+ = fast, Leslie-like
.. code-block:: python
# Slow sweep on a pad
pad = score.part("pad", synth="supersaw", envelope="pad",
phaser=0.4, phaser_rate=0.2)
# Leslie sim on organ (built in)
organ = score.part("organ", instrument="organ")
Highpass Filter
---------------
The opposite of lowpass -- removes low-frequency content below the
cutoff. Useful for cleaning up mud from pads, keeping multiple bass
parts from masking each other, or thinning out a sound to sit better
in a mix.
Parameters:
- ``highpass``: Cutoff frequency in Hz (0 = off).
- 80--150 Hz = clean up sub rumble
- 200--400 Hz = thin out a pad
- 500+ Hz = telephone / radio effect
- ``highpass_q``: Resonance / Q factor (default 0.707).
.. code-block:: python
# Clean up sub rumble from a pad
pad = score.part("pad", synth="supersaw", highpass=120)
# Thin out rhythm guitar to leave room for bass
rhythm = score.part("rhythm", synth="saw", highpass=250)
Filter Envelope
---------------
A per-note lowpass filter whose cutoff sweeps over time. This is the
core of subtractive synthesis -- the reason a Moog bass goes "bwow"
instead of "boop." The filter opens on the attack and closes during
decay, giving each note a distinctive timbral shape.
Parameters:
- ``filter_amount``: Sweep range in Hz (0 = off). How far the filter
opens above the base cutoff.
- ``filter_attack``: Time to reach peak cutoff, in seconds (default 0.01).
- ``filter_decay``: Time to fall to sustain level (default 0.3).
- ``filter_sustain``: Sustain level as fraction of amount, 0.0--1.0
(default 0.0 = filter closes completely after decay).
.. code-block:: python
# Classic synth bass "bwow"
bass = score.part("bass", instrument="synth_bass") # built in
# Acid squelch
acid = score.part("acid", instrument="acid_bass") # built in
# Custom filter sweep on a lead
lead = score.part("lead", synth="saw",
filter_amount=4000, filter_attack=0.01,
filter_decay=0.4, filter_sustain=0.1)
Velocity to Brightness
~~~~~~~~~~~~~~~~~~~~~~
Real instruments get brighter when played harder. ``vel_to_filter``
maps note velocity to filter cutoff boost, so louder notes have more
high-frequency content.
- ``vel_to_filter``: Cutoff boost in Hz at max velocity (default 0, off).
.. code-block:: python
# Piano: soft = mellow, loud = bright
piano = score.part("piano", instrument="piano") # built in
# Manual: custom velocity mapping on a lead
lead = score.part("lead", synth="saw", vel_to_filter=3000)
Sub-Oscillator
--------------
An octave-below sine wave mixed in with the main oscillator. Adds
low-end weight without muddiness -- the sub fills in the fundamental
while the main oscillator provides harmonic character above.
- ``sub_osc``: Mix level, 0.0--1.0 (default 0, off).
- 0.1--0.2 = subtle weight (tuba, bass guitar)
- 0.3--0.5 = heavy sub (808, synth bass)
.. code-block:: python
# Fat 808 kick-bass
bass = score.part("bass", instrument="808_bass") # built in
# Add weight to any part
lead = score.part("lead", synth="saw", sub_osc=0.3)
Noise Layer
-----------
White noise mixed into each note, following the same amplitude
envelope. Adds breath for woodwinds, hammer/felt noise for piano,
bow rosin for strings, and attack transients for percussion.
- ``noise_mix``: Mix level, 0.0--1.0 (default 0, off).
- 0.02--0.04 = subtle texture (strings, piano)
- 0.05--0.08 = noticeable breath (woodwinds)
- 0.1+ = heavy air/texture
.. code-block:: python
# Breathy flute
flute = score.part("flute", instrument="flute") # noise_mix=0.08
# Add air to any synth
pad = score.part("pad", synth="supersaw", noise_mix=0.05)
Configurable FM
---------------
The FM synth now accepts ``fm_ratio`` and ``fm_index`` parameters,
letting you dial in specific FM timbres instead of using the defaults.
- ``fm_ratio``: Modulator frequency as multiple of carrier (default 2.0).
Integer ratios = harmonic timbres; non-integer = metallic/inharmonic.
- ``fm_index``: Modulation depth (default 3.0). Higher = more harmonics.
.. code-block:: python
# Warm electric piano (low ratio, low index)
ep = score.part("ep", synth="fm", fm_ratio=1.0, fm_index=1.5)
# Bright metallic bell (high ratio, high index)
bell = score.part("bell", synth="fm", fm_ratio=3.5, fm_index=5.0)
# Glockenspiel
glock = score.part("glock", instrument="glockenspiel") # built in
Automation
----------
Static effects are fine for a loop, but music breathes. The filter
*opens* during the chorus. The reverb *swells* before the drop. The
distortion *kicks in* when the guitar solo starts. Automation is what
makes a track feel alive instead of robotic -- it's the difference
between a static loop and a piece of music that has dynamics, tension,
and release. If you've ever felt a song "build" toward something,
you're hearing automation at work.
``Part.set()`` changes effect parameters mid-song at the current beat
position. The renderer splits the audio at automation points and
processes each section independently:
.. code-block:: python
lead = score.part("lead", synth="saw", lowpass=400, lowpass_q=3.0)
# Verse: filtered and clean
lead.arpeggio("Cm", bars=4, pattern="up", octaves=2)
# Chorus: filter opens, chorus kicks in
lead.set(lowpass=2000, chorus=0.3)
lead.arpeggio("Fm", bars=4, pattern="updown", octaves=2)
# Drop: full send
lead.set(lowpass=4000, distortion=0.7, reverb=0.3)
lead.arpeggio("Gm", bars=4, pattern="updown", octaves=2)
Any parameter can be automated: ``lowpass``, ``lowpass_q``, ``highpass``,
``reverb``, ``reverb_decay``, ``reverb_type``, ``delay``, ``delay_time``,
``delay_feedback``, ``distortion``, ``distortion_drive``, ``chorus``,
``phaser``, ``phaser_rate``, ``saturation``, ``tremolo_depth``,
``tremolo_rate``, ``cabinet``, ``cabinet_brightness``, ``analog_drift``,
``volume``.
LFO Automation
--------------
An LFO -- Low Frequency Oscillator -- is just automation that repeats.
Instead of manually setting parameter changes, you let a wave shape do
it for you, cycling back and forth continuously. You already know what
LFOs sound like, even if you don't know the term. The wobble bass in
dubstep? That's an LFO on the filter cutoff. Tremolo on a guitar amp?
LFO on volume. Auto-wah? LFO on filter cutoff with resonance cranked
up. Vibrato? LFO on pitch. It's one simple concept that produces a
huge range of effects.
``Part.lfo()`` automates a parameter with a low-frequency oscillator,
generating smooth sweeps over time. This is how filter sweeps, tremolo,
and auto-wah effects work.
.. code-block:: python
lead = score.part("lead", synth="saw", lowpass=400)
# Slow filter sweep: 400 -> 3000 Hz over 8 bars
lead.lfo("lowpass", rate=0.125, min=400, max=3000, bars=8)
lead.arpeggio("Cm", bars=8, pattern="up", octaves=2)
Parameters:
- ``param``: Parameter name to modulate (``"lowpass"``, ``"reverb"``,
``"distortion"``, ``"volume"``, ``"chorus"``, ``"delay"``).
- ``rate``: LFO speed in cycles per bar (default 0.5 = one sweep
every 2 bars). 0.25 = very slow, 1 = once per bar, 4 = four times
per bar.
- ``min`` / ``max``: Parameter value range.
- ``bars``: Number of bars to run the LFO over (default 4).
- ``shape``: Waveform shape.
- ``"sine"`` -- smooth, natural sweep
- ``"triangle"`` -- linear up/down
- ``"saw"`` -- ramp up, snap back
- ``"square"`` -- abrupt on/off
- ``resolution``: How often to insert automation points, in beats
(default 0.25 = every 16th note). Lower values = smoother curves.
Stacking Multiple LFOs
~~~~~~~~~~~~~~~~~~~~~~~
Call ``.lfo()`` multiple times to modulate different parameters
simultaneously:
.. code-block:: python
lead = score.part("lead", synth="saw", lowpass=800, reverb=0.1)
# Filter opens over 8 bars
lead.lfo("lowpass", rate=0.125, min=400, max=4000, bars=8)
# Reverb swells in and out every 2 bars
lead.lfo("reverb", rate=0.5, min=0.1, max=0.6, bars=8, shape="triangle")
# Volume tremolo
lead.lfo("volume", rate=2, min=0.3, max=0.6, bars=8, shape="sine")
lead.arpeggio("Cm", bars=8, pattern="updown", octaves=2)
Effects are what turn notes into music -- the space, the movement, the character. A dry signal is just information; reverb, delay, and filtering are what make it feel like something. Experiment freely, trust your ears.
+48 -291
View File
@@ -1,321 +1,78 @@
Instruments and Fingerings
==========================
Fretboard and Fingerings
========================
The :class:`~pytheory.chords.Fretboard` class models any stringed
instrument and generates chord fingerings. PyTheory includes **25
instrument presets** spanning Western, Asian, Middle Eastern, Latin
American, and Russian traditions.
The :class:`~pytheory.chords.Fretboard` class represents a fretted instrument's
tuning and generates chord fingerings.
How It Works
------------
Preset Tunings
--------------
Each `fret <https://en.wikipedia.org/wiki/Fret>`_ on a stringed
instrument raises the pitch by exactly **one semitone**. The open
string is fret 0; fret 1 is one semitone up, and so on. Even fretless
instruments (violin, oud, erhu) can be modeled this way — the "fret"
positions are just semitone steps along the fingerboard.
.. code-block:: python
Guitars
-------
from pytheory import Fretboard
`Standard guitar tuning <https://en.wikipedia.org/wiki/Guitar_tunings>`_
(high to low)::
guitar = Fretboard.guitar() # E4 B3 G3 D3 A2 E2
bass = Fretboard.bass() # G2 D2 A1 E1
ukulele = Fretboard.ukulele() # A4 E4 C4 G4
String 1: E4 (highest)
String 2: B3
String 3: G3
String 4: D3
String 5: A2
String 6: E2 (lowest)
Custom Tunings
--------------
This tuning uses intervals of a perfect 4th (5 semitones) between most
strings, except between G and B which is a major 3rd (4 semitones).
.. code-block:: python
.. code-block:: pycon
from pytheory import Tone, Fretboard
>>> from pytheory import Fretboard
>>> guitar = Fretboard.guitar() # Standard EADGBE
>>> twelve = Fretboard.twelve_string() # 12-string (6 doubled courses)
>>> bass = Fretboard.bass() # Standard 4-string EADG
>>> bass5 = Fretboard.bass(five_string=True) # 5-string with low B
**Alternate tunings** — 8 built-in presets:
.. code-block:: pycon
>>> Fretboard.guitar("drop d") # DADGBE — heavy riffs, metal
>>> Fretboard.guitar("open g") # DGDGBD — slide guitar, Keith Richards
>>> Fretboard.guitar("open d") # DADF#AD — slide, folk
>>> Fretboard.guitar("open e") # EBEG#BE — slide blues
>>> Fretboard.guitar("open a") # EAC#EAE
>>> Fretboard.guitar("dadgad") # DADGAD — Celtic, fingerstyle
>>> Fretboard.guitar("half step down") # Eb standard — Hendrix, SRV
>>> # Custom tuning with any notes
>>> Fretboard.guitar(("C4", "G3", "C3", "G2", "C2", "G1"))
**Capo** — a `capo <https://en.wikipedia.org/wiki/Capo>`_ raises all
strings by a number of frets, letting you play open chord shapes in
higher keys:
.. code-block:: pycon
>>> # Capo on fret 2 — open G shape now sounds as A major
>>> fb = Fretboard.guitar(capo=2)
>>> # Or apply a capo to an existing fretboard
>>> fb = Fretboard.guitar()
>>> fb_capo3 = fb.capo(3)
The Mandolin Family
-------------------
The `mandolin family <https://en.wikipedia.org/wiki/Mandolin_family>`_
mirrors the `violin family <https://en.wikipedia.org/wiki/Violin_family>`_
— all tuned in perfect fifths, with each member a fifth or octave
lower than the last:
.. code-block:: pycon
>>> Fretboard.mandolin() # E5 A4 D4 G3 — soprano (= violin)
>>> Fretboard.mandola() # A4 D4 G3 C3 — alto (= viola)
>>> Fretboard.octave_mandolin() # E4 A3 D3 G2 — tenor (octave below mandolin)
>>> Fretboard.mandocello() # A3 D3 G2 C2 — bass (= cello)
The mandolin's doubled courses (pairs of strings) create a natural
chorus effect. The `octave mandolin <https://en.wikipedia.org/wiki/Octave_mandolin>`_
is popular in Irish and Celtic folk music.
The Bowed String Family
-----------------------
The orchestral `string family <https://en.wikipedia.org/wiki/String_section>`_
is tuned in perfect fifths (except the double bass, which uses fourths):
.. code-block:: pycon
>>> Fretboard.violin() # E5 A4 D4 G3 — soprano
>>> Fretboard.viola() # A4 D4 G3 C3 — alto (5th below violin)
>>> Fretboard.cello() # A3 D3 G2 C2 — tenor/bass (octave below viola)
>>> Fretboard.double_bass() # G2 D2 A1 E1 — bass (tuned in 4ths!)
Bowed strings have no frets — the player can produce any pitch along
the fingerboard, enabling continuous
`vibrato <https://en.wikipedia.org/wiki/Vibrato>`_ and microtonal
inflections not possible on fretted instruments.
The `erhu <https://en.wikipedia.org/wiki/Erhu>`_ — a 2-stringed Chinese
bowed instrument with a hauntingly vocal quality:
.. code-block:: pycon
>>> Fretboard.erhu() # A4 D4 — tuned a 5th apart, no fingerboard
Plucked Strings
---------------
.. code-block:: pycon
>>> Fretboard.ukulele() # A4 E4 C4 G4 — re-entrant tuning
>>> Fretboard.banjo() # Open G (bluegrass, 5th string is high drone)
>>> Fretboard.banjo("open d") # Open D (clawhammer, old-time)
>>> Fretboard.harp() # 47 strings, C1 to G7 (concert pedal harp)
The `banjo <https://en.wikipedia.org/wiki/Banjo>`_'s short 5th string
is a high drone — a defining feature of the instrument's sound.
The `harp <https://en.wikipedia.org/wiki/Harp>`_ has one string per
diatonic note across nearly 7 octaves. Pedals alter each note name
by up to two semitones across all octaves simultaneously.
World Instruments
-----------------
.. code-block:: pycon
>>> # Middle Eastern
>>> Fretboard.oud() # C4 G3 D3 A2 G2 C2 — fretless, ancestor of the lute
>>> Fretboard.sitar() # 7 main strings — Indian classical
>>> # East Asian
>>> Fretboard.shamisen() # C4 G3 C3 — 3-string Japanese, honchoshi tuning
>>> Fretboard.pipa() # D4 A3 E3 A2 — 4-string Chinese lute
>>> Fretboard.erhu() # A4 D4 — 2-string Chinese bowed
>>> # European
>>> Fretboard.bouzouki() # D4 A3 D3 G2 — Irish (Celtic music)
>>> Fretboard.bouzouki("greek") # D4 A3 F3 C3 — Greek
>>> Fretboard.lute() # G4 D4 A3 F3 C3 G2 — Renaissance (6 courses)
>>> Fretboard.balalaika() # A4 E4 E4 — Russian (2 unison strings)
>>> # Latin American
>>> Fretboard.charango() # E5 A4 E5 C5 G4 — Andean (re-entrant tuning)
>>> # Steel guitar
>>> Fretboard.pedal_steel() # 10 strings, E9 Nashville — country music
The `oud <https://en.wikipedia.org/wiki/Oud>`_ is fretless, allowing
the quarter-tone inflections essential to
`maqam <https://en.wikipedia.org/wiki/Maqam>`_ performance. The
`sitar <https://en.wikipedia.org/wiki/Sitar>`_ has moveable frets and
sympathetic strings that resonate in harmony with the played notes.
Keyboards
---------
.. code-block:: pycon
>>> Fretboard.keyboard() # 88-key piano (A0 to C8)
>>> Fretboard.keyboard(61, "C2") # 61-key synth controller
>>> Fretboard.keyboard(49, "C2") # 49-key controller
>>> Fretboard.keyboard(25, "C3") # 25-key mini MIDI controller
While keyboards don't have strings or frets, they map naturally to a
sequence of tones. A full 88-key piano spans over 7 octaves — the
widest range of any standard acoustic instrument.
# Open D tuning
open_d = Fretboard(tones=[
Tone.from_string("D4"),
Tone.from_string("A3"),
Tone.from_string("F#3"),
Tone.from_string("D3"),
Tone.from_string("A2"),
Tone.from_string("D2"),
])
Getting Fingerings
------------------
The fingering algorithm finds the most playable voicing for any chord
on any instrument. It scores each possibility by:
.. code-block:: python
1. Preferring **open strings** (fret 0) — they ring freely
2. Preferring **ascending** fret patterns — easier hand position
3. Minimizing the number of **fingers needed**
from pytheory import Fretboard, CHARTS
.. code-block:: pycon
fb = Fretboard.guitar()
>>> from pytheory import Fretboard
# Best fingering for a chord
c = CHARTS["western"]["C"]
print(c.fingering(fretboard=fb))
# (0, 1, 0, 2, 3, 0)
>>> fb = Fretboard.guitar()
>>> f = fb.chord("C")
>>> f
Fingering(e=0, B=1, G=0, D=2, A=3, E=x)
# All possible fingerings
all_c = c.fingering(fretboard=fb, multiple=True)
>>> f['A']
3
>>> f[1]
1
>>> f.identify()
'C major'
>>> chord = f.to_chord()
>>> chord.identify()
'C major'
You can also go from fret positions to chord identification:
.. code-block:: pycon
>>> # "What chord am I playing?"
>>> fb = Fretboard.guitar()
>>> f = fb.fingering(0, 0, 0, 2, 2, 0)
>>> f
Fingering(e=0, B=0, G=0, D=2, A=2, E=0)
>>> f.identify()
'E minor'
Reading Fingerings
~~~~~~~~~~~~~~~~~~
Each position is labeled with its string name. Duplicate string names
are disambiguated — on a standard guitar, high E appears as ``e`` and
low E as ``E``::
e|--0-- (open — E)
B|--1-- (fret 1 — C)
G|--0-- (open — G)
D|--2-- (fret 2 — E)
A|--3-- (fret 3 — C)
E|--x-- (muted)
A value of ``x`` (``None``) means the string is muted (not played).
ASCII Tablature
~~~~~~~~~~~~~~~
For a more visual representation, use ``tab()``:
.. code-block:: pycon
>>> print(fb.tab("C"))
C major
e|--0--
B|--1--
G|--0--
D|--2--
A|--3--
E|--x--
# Muted strings appear as None
f = CHARTS["western"]["F"]
print(f.fingering(fretboard=fb))
Generating Full Charts
----------------------
Generate fingerings for every chord at once:
.. code-block:: pycon
.. code-block:: python
>>> fb = Fretboard.guitar()
>>> chart = fb.chart()
from pytheory import Fretboard, charts_for_fretboard
>>> chart["C"]
Fingering(e=0, B=1, G=0, D=2, A=3, E=x)
fb = Fretboard.guitar()
chart = charts_for_fretboard(fretboard=fb)
>>> # Works with any instrument
>>> uke_chart = Fretboard.ukulele().chart()
>>> mando_chart = Fretboard.mandolin().chart()
for name, fingering in chart.items():
print(f"{name:6s} {fingering}")
Scale Diagrams with Chord Highlighting
---------------------------------------
Ukulele Example
---------------
The ``scale_diagram()`` method renders an ASCII fretboard showing where
scale notes fall on each string. Pass an optional ``chord`` argument to
highlight chord tones in UPPERCASE while scale-only tones appear in
lowercase — a quick way to visualize target notes for soloing:
.. code-block:: python
.. code-block:: pycon
>>> from pytheory import Fretboard, TonedScale, Chord
>>> fb = Fretboard.guitar()
>>> pentatonic = TonedScale(tonic="A4")["minor pentatonic"]
>>> print(fb.scale_diagram(pentatonic, frets=5))
>>> # Highlight Am chord tones within the scale:
>>> am = Chord.from_symbol("Am")
>>> print(fb.scale_diagram(pentatonic, frets=5, chord=am))
Non-String Instruments
----------------------
Looking for drums and percussion? PyTheory also supports drum pattern
programming through the sequencing engine. See the :doc:`drums` guide
for drum kits, patterns, and fills.
Custom Instruments
------------------
Any instrument can be modeled with custom string tunings:
.. code-block:: pycon
>>> from pytheory import Tone, Fretboard
>>> # Baritone ukulele (DGBE — top 4 guitar strings)
>>> bari_uke = Fretboard(tones=[
... Tone.from_string("E4"),
... Tone.from_string("B3"),
... Tone.from_string("G3"),
... Tone.from_string("D3"),
... ])
>>> # Tres cubano (Cuban guitar, 3 doubled courses)
>>> tres = Fretboard(tones=[
... Tone.from_string("E4"),
... Tone.from_string("B3"),
... Tone.from_string("G3"),
... ])
If it has strings, you can model it. Define the tuning, and PyTheory handles the rest -- fingerings, charts, scale diagrams, all of it. Got a weird instrument or a custom tuning? That's what the ``Fretboard`` constructor is for.
fb = Fretboard.ukulele()
c = CHARTS["western"]["C"]
print(c.fingering(fretboard=fb)) # 4-string fingering
+31 -195
View File
@@ -1,21 +1,7 @@
Playback and Export
===================
Audio Playback
==============
This is the output layer. You've built your theory, composed your
arrangement, shaped your sounds -- now you need to hear it. PyTheory
gives you three ways to get your music out: speakers, WAV files, and
MIDI files.
Use **speakers** for immediate feedback while you're sketching and
experimenting. Use **WAV export** when you want to share actual audio
-- post it, send it, drop it into a video. Use **MIDI export** when you
want to bring your sketch into a real DAW and finish it with
professional instruments, mixing, and mastering. Each output serves a
different stage of the creative process.
PyTheory can play audio through your speakers, save to WAV, or export
to MIDI. Everything is synthesized from waveforms -- no samples or
external audio files needed.
PyTheory can synthesize and play tones and chords through your speakers.
.. note::
@@ -23,202 +9,52 @@ external audio files needed.
installed on your system. On macOS: ``brew install portaudio``.
On Ubuntu: ``apt install libportaudio2``.
play() -- Single Tones and Chords
---------------------------------
The simplest way to hear something:
Playing a Tone
--------------
.. code-block:: python
from pytheory import Tone, Chord, play
from pytheory import Tone, play
play(Tone.from_string("A4"), t=1_000) # A440 for 1 second
play(Chord.from_symbol("Am7"), t=2_000) # chord for 2 seconds
a4 = Tone.from_string("A4", system="western")
play(a4, t=1_000) # Play A440 for 1 second
Optional parameters for synth, envelope, and temperament:
Playing a Chord
---------------
.. code-block:: python
from pytheory import Synth, Envelope
from pytheory import Chord, Tone, play
play(Tone.from_string("C4"), synth=Synth.SAW, envelope=Envelope.PLUCK, t=1_000)
play(Tone.from_string("C4"), temperament="pythagorean", t=1_000)
c_major = Chord(tones=[
Tone.from_string("C4", system="western"),
Tone.from_string("E4", system="western"),
Tone.from_string("G4", system="western"),
])
play(c_major, t=2_000) # Play for 2 seconds
play_score() -- Full Arrangements
---------------------------------
Waveform Types
--------------
Plays a ``Score`` with all its parts and drums mixed together.
Output is **stereo** — each part is panned according to its ``pan``
setting, drums are stereo-panned like a real kit, and reverb tails
have natural stereo width. A **master bus compressor/limiter** (4:1
ratio, brick-wall at 0.95) is applied to prevent clipping and make
the mix louder and punchier:
Choose between sine, sawtooth, and triangle wave synthesis:
.. code-block:: python
from pytheory import Score, Duration, Chord
from pytheory.play import play_score
from pytheory import play, Synth, Tone
score = Score("4/4", bpm=140)
score.drums("bossa nova", repeats=4)
chords = score.part("chords", synth="sine", envelope="pad")
for sym in ["Am", "Dm", "E7", "Am"]:
chords.add(Chord.from_symbol(sym), Duration.WHOLE)
play_score(score)
tone = Tone.from_string("C4", system="western")
.. raw:: html
play(tone, synth=Synth.SINE) # Smooth, pure tone
play(tone, synth=Synth.SAW) # Bright, buzzy
play(tone, synth=Synth.TRIANGLE) # Softer than sawtooth
<audio controls style="width:100%;margin:0.5em 0 1.5em"><source src="../_static/audio/playback_basic.wav" type="audio/wav"></audio>
Temperaments
------------
The render pipeline respects the Score's ``temperament`` and
``reference_pitch`` settings, so Baroque or microtonal scores play back
at the correct tuning:
Play in different tuning systems:
.. code-block:: python
score = Score("4/4", bpm=80, temperament="meantone", reference_pitch=415.0)
Press **Ctrl+C** at any time during playback to stop — PyTheory catches
``KeyboardInterrupt`` and stops audio cleanly.
See :doc:`sequencing` for how to build scores and parts.
render_score() -- Headless Rendering
------------------------------------
Returns a raw audio buffer (numpy float32 array) without playing it.
Useful for saving to WAV or further processing:
.. code-block:: pycon
>>> from pytheory.play import render_score
>>> buf = render_score(score) # numpy float32 array
>>> len(buf)
604800
save() -- WAV Export
--------------------
Render tones or chords to a WAV file. Works without speakers or
PortAudio:
.. code-block:: python
from pytheory import save, Chord, Tone, Synth
save(Tone.from_string("A4"), "a440.wav", t=1_000)
save(Chord.from_name("Am7"), "am7.wav", t=2_000)
save(
Chord.from_name("C"),
"c_triangle.wav",
synth=Synth.TRIANGLE,
temperament="meantone",
t=3_000,
)
save_midi() -- MIDI Export
--------------------------
MIDI export is probably the most useful feature here for working
musicians. The idea is simple: sketch your ideas in Python -- where
iteration is fast, where you can use loops and randomness and music
theory functions -- and then export to MIDI. Open that MIDI file in
Logic, Ableton, Reaper, FL Studio, or whatever you use, and now you've
got your chord progressions, melodies, and bass lines on real tracks.
Swap in your favorite soft synths, add real mixing, finish the track
properly. Python is the sketchpad; the DAW is the canvas.
Export tones, chords, progressions, or full scores as Standard MIDI
Files. MIDI files can be opened in any DAW, edited, transposed, and
assigned to any instrument.
Simple export (single tone, chord, or progression):
.. code-block:: python
from pytheory import save_midi, Key, Tone, Chord
save_midi(Tone.from_string("C4"), "middle_c.mid", t=1000)
save_midi(Chord.from_symbol("Am7"), "am7.mid")
chords = Key("C", "major").progression("I", "V", "vi", "IV")
save_midi(chords, "pop.mid", t=500, bpm=120)
Score-based export (with time signature, tempo, and parts):
.. code-block:: python
from pytheory import Score, Duration, Key
score = Score("4/4", bpm=140)
for chord in Key("G", "major").progression("I", "IV", "V", "I"):
score.add(chord, Duration.WHOLE)
score.save_midi("progression.mid")
play_pattern() -- Drum Patterns
-------------------------------
Play a drum pattern through the speakers:
.. 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)
See :doc:`drums` for the full list of 80+ presets and 21 fills.
play_progression() -- Quick Chord Playback
------------------------------------------
Play a chord progression in sequence with a single call:
.. code-block:: python
from pytheory import Key, play_progression
chords = Key("C", "major").progression("I", "V", "vi", "IV")
play_progression(chords, t=800)
Optional synth, envelope, and gap parameters:
.. code-block:: python
from pytheory import Synth, Envelope
play_progression(chords, t=1000, synth=Synth.TRIANGLE, gap=200)
play_progression(chords, t=2000, envelope=Envelope.PAD)
That's the workflow: hear it, tweak it, hear it again. When it sounds right, export to WAV or MIDI and take it somewhere bigger.
MIDI Import
-----------
Load any Standard MIDI File into a Score — then play it through
PyTheory's synth engine with effects, or analyze the theory:
.. code-block:: python
from pytheory import Score
from pytheory.play import play_score
score = Score.from_midi("song.mid")
# See what's inside
for name, part in score.parts.items():
print(f"{name}: {len(part.notes)} notes")
# Change the synth and add effects
score.parts["ch1"].synth = "saw"
score.parts["ch1"].reverb_mix = 0.3
play_score(score)
Each MIDI channel becomes a named Part (``ch1``, ``ch2``, etc.).
Channel 10 (drums) becomes drum hits. Tempo, time signature,
note durations, and velocities are all preserved.
Download any MIDI file from the internet, load it, play it through
the synth engine with reverb and delay. That's the whole idea.
play(tone, temperament="equal") # Default, modern tuning
play(tone, temperament="pythagorean") # Ancient Greek tuning
play(tone, temperament="meantone") # Renaissance tuning
+39 -216
View File
@@ -1,239 +1,62 @@
Quickstart
==========
PyTheory works at two levels — pick the one that fits what you need:
1. **Music theory** — explore scales, chords, keys, intervals, and
harmony. No audio required. Works anywhere Python runs.
2. **Composition** — build multi-part arrangements with drums, synths,
effects, and export to MIDI. Needs PortAudio for live playback.
Both are first-class. You can use PyTheory purely as a theory
reference and never touch the audio side, or you can jump straight
into composing. This guide covers both paths.
Installation
------------
::
.. code-block:: bash
$ pip install pytheory
pip install pytheory
For audio playback through your speakers, you'll also need
`PortAudio <http://www.portaudio.com/>`_:
Or with `uv <https://github.com/astral-sh/uv>`_:
- macOS: ``brew install portaudio``
- Ubuntu: ``apt install libportaudio2``
- Windows: included with the ``sounddevice`` package
.. code-block:: bash
PortAudio is only needed for live playback. MIDI export, WAV export,
and all theory functions work without it.
uv add pytheory
Hear Something Immediately
--------------------------
Basic Usage
-----------
::
Create tones, build scales, and explore music theory:
$ pytheory demo
.. code-block:: python
This generates and plays a random track — different every time. It's
the fastest way to hear what PyTheory can do.
from pytheory import Tone, TonedScale, Fretboard, CHARTS
Explore Music Theory
--------------------
# Create a tone
c4 = Tone.from_string("C4")
print(c4) # C4
print(c4.frequency) # 261.63 Hz
The theory layer is where most people start. No audio setup needed —
this works everywhere Python runs. Every concept in Western music
theory (and five other systems) has a clean Python API.
# Tone arithmetic
e4 = c4 + 4 # Major third up
g4 = c4 + 7 # Perfect fifth up
print(e4, g4) # E4 G4
Tones and intervals:
# Measure intervals
print(g4 - c4) # 7 (semitones)
.. code-block:: pycon
# Build a scale
c_major = TonedScale(tonic="C4")["major"]
print(c_major.note_names)
# ['C', 'D', 'E', 'F', 'G', 'A', 'B', 'C']
>>> from pytheory import Tone
# Build chords from the scale
I = c_major.triad(0) # C major
IV = c_major.triad(3) # F major
V = c_major.triad(4) # G major
>>> c4 = Tone.from_string("C4", system="western")
>>> c4.frequency
261.6255653005986
>>> c4.midi
60
# Guitar chord fingerings
fb = Fretboard.guitar()
fingering = CHARTS["western"]["Am"].fingering(fretboard=fb)
print(fingering) # (0, 1, 2, 2, 0, 0)
>>> c4 + 7
<Tone G4>
>>> c4.interval_to(c4 + 7)
'perfect 5th'
>>> Tone.from_frequency(440)
<Tone A4>
>>> Tone.from_midi(69)
<Tone A4>
Keys, scales, and chords:
.. code-block:: pycon
>>> from pytheory import Key, Chord
>>> key = Key("C", "major")
>>> key.chords
['C major', 'D minor', 'E minor', 'F major', 'G major', 'A minor', 'B diminished']
>>> [c.symbol for c in key.progression("I", "V", "vi", "IV")]
['C', 'G', 'Am', 'F']
>>> key.signature
{'sharps': 0, 'flats': 0, 'accidentals': []}
>>> Key("F", "major").signature
{'sharps': 0, 'flats': 1, 'accidentals': ['Bb']}
>>> Chord.from_symbol("Am7").identify()
'A minor 7th'
>>> Chord.from_tones("G", "B", "D", "F").analyze("C")
'V7'
Harmonic analysis and modulation:
.. code-block:: pycon
>>> Key("C", "major").pivot_chords(Key("G", "major"))
['A minor', 'B minor', 'C major', 'D major', 'E minor', 'G major']
>>> Key("C", "major").relative
<Key A minor>
>>> key.suggest_next(key.triad(4)) # what follows V?
[<Chord C major>, <Chord A minor>, <Chord F major>]
Scales across 6 musical systems:
.. code-block:: pycon
>>> from pytheory import TonedScale
>>> TonedScale(tonic="Sa4", system="indian")["bhairav"].note_names
['Sa', 'komal Re', 'Ga', 'Ma', 'Pa', 'komal Dha', 'Ni', 'Sa']
>>> 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']
Guitar fingerings:
.. code-block:: pycon
>>> from pytheory import Fretboard
>>> fb = Fretboard.guitar()
>>> fb.chord("Am")
Fingering(e=0, B=1, G=2, D=2, A=0, E=x)
All of the above works without PortAudio, without sounddevice,
without any audio setup at all. It's pure Python music theory.
Compose a Track
What's Included
---------------
This is where it gets fun. A ``Score`` is your arrangement — drums,
chords, melody, bass, each with their own synth and effects:
.. code-block:: python
from pytheory import Score, Key, Duration
from pytheory.play import play_score
score = Score("4/4", bpm=120)
score.drums("rock", repeats=8, fill="rock", fill_every=4)
piano = score.part("piano", instrument="piano", reverb=0.3)
lead = score.part("lead", synth="saw", envelope="pluck",
delay=0.2, reverb=0.2, lowpass=4000)
bass = score.part("bass", synth="triangle", lowpass=900)
for chord in Key("G", "major").progression("I", "V", "vi", "IV") * 2:
piano.add(chord, Duration.WHOLE)
lead.add("D5", 1).add("B4", 0.5).add("D5", 0.5)
lead.add("G5", 1).add("E5", 1)
lead.add("D5", 0.5).add("B4", 0.5).add("A4", 1)
lead.add("G4", 2).rest(2)
for n in ["G2", "G2", "D2", "D2", "E2", "E2", "C2", "C2"] * 2:
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/quickstart.wav" type="audio/wav"></audio>
Export to Your DAW
------------------
The whole point: sketch in Python, finish in Logic / Ableton / Reaper.
.. code-block:: python
score.save_midi("my_sketch.mid")
Open that file in any DAW and you'll see all the notes laid out on
the timeline, ready to assign to real instruments and mix.
You can also save rendered audio:
.. code-block:: python
from pytheory import save
save(Chord.from_symbol("Am7"), "am7.wav", t=2_000)
What's in the Box
-----------------
**Theory** — tones, scales (40+ across 6 musical systems), chords
(17 types, Roman numeral analysis, figured bass, tension scoring,
voice leading, pitch class sets with Forte numbers), keys (detection,
signatures, modulation paths, borrowed chords), scale recommendation.
**Sequencing** — Score, Part, Duration, TimeSignature. Arpeggiator
with 5 patterns. Legato with pitch glide. Per-note velocity. Swing.
Tempo changes. Fade in/out. Song sections with repeat. Humanize.
**Synthesis** — 10 waveforms: sine, saw, triangle, square, pulse, FM,
noise, supersaw, PWM slow, PWM fast. 8 ADSR envelopes. Detune.
Stereo pan and spread.
**Effects** — distortion, chorus, lowpass filter (with resonance),
delay, reverb (algorithmic + 7 stereo convolution presets including
Taj Mahal with 12-second tail). All per-part with automation and
LFO modulation. Sidechain compression. Master bus compressor/limiter.
**Drums** — 58 pattern presets (rock, jazz, salsa, bossa nova,
afrobeat, house, trap, and 50+ more). 21 fill presets. 27 synthesized
drum voices with stereo panning.
**Instruments** — 25 presets (guitar with 8 tunings, bass, ukulele,
mandolin family, violin family, banjo, harp, oud, sitar, erhu, and
more) with chord fingering generation and scale diagrams.
**Output** — stereo playback, WAV export, MIDI import/export.
**Interface** — REPL with tab completion (``pytheory repl``), CLI with
15 commands. ``pytheory demo``, ``pytheory key``, ``pytheory chord``,
``pytheory identify``, ``pytheory midi``, ``pytheory play``, and more.
Where to Go Next
-----------------
- :doc:`theory` — music theory fundamentals
- :doc:`tones` — working with individual notes
- :doc:`scales` — scales, modes, and keys
- :doc:`chords` — chord construction, analysis, and progressions
- :doc:`sequencing` — composing multi-part arrangements
- :doc:`synths` — the 10 waveforms and 8 envelopes
- :doc:`effects` — reverb, delay, distortion, chorus, lowpass, automation
- :doc:`drums` — 58 patterns, 21 fills, drum synthesis
- :doc:`playback` — play, save, export
- **12-tone Western system** with all chromatic notes
- **Scales**: major, minor, harmonic minor, and all 7 modes
- **Pitch calculation** in equal, Pythagorean, and meantone temperaments
- **Chord charts** with 144 pre-built chords (12 roots x 12 qualities)
- **Fingering generation** for any fretted instrument
- **Audio playback** with sine, sawtooth, and triangle wave synthesis

Some files were not shown because too many files have changed in this diff Show More