Compare commits

..

20 Commits

Author SHA1 Message Date
kennethreitz 6c83dbe5aa Low-to-high fingerings by default (high_to_low opt-out) — v0.43.0
Fretboard string lists and Fingering positions/string-names now read
low-to-high (lowest-pitched string first), matching how chord diagrams
and tablature are conventionally written. Pass high_to_low=True to any
fretboard constructor to restore the pre-0.43 high-to-low behavior.

Design: each board keeps a private canonical (high-to-low) tone store
so the fingering scorer and GUITAR_OVERRIDES table stay untouched; a
single _orient() helper re-orients at the user-facing boundary (and,
being self-inverse, also canonicalizes custom tuning/position input).
Fingering carries its own orientation flag and presents oriented
positions/names via properties. The fingering cache key now includes
orientation so the two orderings don't collide.

to_tab() and Part.strum() now sort by pitch internally, so their output
is identical regardless of board orientation.

- All 25 instrument presets gain a high_to_low param, routed through a
  canonical build path.
- Tests updated for the new default; added orientation-specific tests.
- Docs/examples flipped to low-to-high; chord_charts.py example now uses
  the built-in Fingering.tab() instead of a hand-rolled renderer.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 10:42:11 -04:00
kennethreitz b3f3e985b4 Document missing API features across guides
- chords: open_voicing() alongside other voicings, normal_form() in
  pitch class sets section
- tones: is_natural, is_sharp, is_flat accidental properties
- scales: Key.seventh() for individual degrees, expanded
  Scale.recommend() explanation of how ranking works

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 17:33:47 -04:00
kennethreitz c1925af69d Add Nashville numbers, blues scales, and tablature guide
New documentation section covering the Nashville number system,
blues scale theory, and tablature export — topics that were
previously scattered across cookbook and fretboard docs.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 16:11:29 -04:00
kennethreitz 7883c978f7 Support Fretboard objects in to_tab() — v0.42.1
to_tab(tuning=Fretboard.guitar()) now works, along with bass,
ukulele, mandolin, banjo, and any custom Fretboard with capo.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 10:03:52 -04:00
kennethreitz 36d558573c Remove worktree submodules, add to gitignore
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 10:02:17 -04:00
kennethreitz 1e2f09e2ab LilyPond, MusicXML, and tablature export — v0.42.0
Three new export methods on Score:
- to_lilypond() — complete LilyPond source files for PDF engraving
- to_musicxml() — MusicXML 4.0 for MuseScore/Sibelius/Finale
- to_tab() — ASCII guitar/bass tablature (also on Part)

All three handle multi-part scores, bass clef detection, tied notes
across barlines, chords, and drum tone filtering.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 10:02:09 -04:00
kennethreitz 9404afc1f3 Document ABC notation export in playback guide
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 07:59:46 -04:00
kennethreitz 72aa097552 Tie long notes across barlines in to_abc() — v0.41.4
Notes longer than one measure are split into tied pieces so abcjs
can render them correctly (e.g. 16-beat choir drone becomes four
tied whole notes).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 07:57:23 -04:00
kennethreitz 5ebf0bdd97 Skip unpitched parts in to_abc(), fix 'pitch is undefined' — v0.41.3
Parts with only drum tones or rests are excluded from ABC output.
Chords correctly recognized as pitched content.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 07:47:44 -04:00
kennethreitz 1d897c6609 Auto bass clef detection in to_abc() — v0.41.2
Parts with average note octave below C4 get clef=bass automatically.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 07:43:51 -04:00
kennethreitz 4113aad5d0 Fix to_abc() crash on parts with drum tones — v0.41.1
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 07:40:07 -04:00
kennethreitz 6ecef688e1 ABC notation export via Score.to_abc() — v0.41.0
New method converts scores to ABC notation with support for multi-voice,
chords, rests, accidentals, and all durations. Pass html=True for a
self-contained HTML page with abcjs sheet music rendering.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 07:37:56 -04:00
kennethreitz fcc5db8e3d Add Score.to_abc() for ABC notation export with optional HTML rendering
Supports multi-voice scores, chords, rests, accidentals, and all durations.
Pass html=True to get a self-contained page using abcjs for sheet music.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 07:31:02 -04:00
kennethreitz 9de113b6e7 Add sound examples for hard sync, ring mod, wavefold, drift, karplus-strong, mellotron docs
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 14:04:37 -04:00
kennethreitz 0b98f7bd77 Mellotron, hard sync, ring mod, wavefold, drift synths + analog presets — v0.40.9
Five new synth waveforms: tape-replay Mellotron (strings/flute/choir
tapes with wow, flutter, saturation, 8s fadeout), hard sync oscillator,
ring modulation, wavefolding, and analog drift VCO with pitch
instability. 14 new instrument presets for Score.part(). Synth kwargs
now pass through play()/save()/_render(). 808 bass envelope fixed
from pluck to piano.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 23:14:18 -04:00
kennethreitz e0a1ce9d18 Fix hold() inflating Part.total_beats and Score.duration_ms — v0.40.8
Note.beats now returns 0.0 for held notes (_hold=True), matching the
renderer which already skipped advancing the beat position. Previously
every hold() call added its full duration to the part's total, causing
duration reports to be 2-3x too long on tracks with drone notes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 13:27:08 -04:00
kennethreitz de7575fe0a Expose rhodes, wurlitzer, vibraphone, pipe organ, choir in Synth enum — v0.40.7
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 01:59:11 -04:00
kennethreitz 665a6f5de5 Remove lowpass/vel_to_filter from sax presets, let wave shape its own tone — v0.40.6
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 00:44:01 -04:00
kennethreitz 63362df697 Saxophone synth overhaul: reed clipping, formants, breath noise — v0.40.5
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 00:38:10 -04:00
kennethreitz 755b33a63b Fix test: update Synth enum count 42 → 46
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 07:12:43 -04:00
108 changed files with 2981 additions and 389 deletions
+1
View File
@@ -7,3 +7,4 @@ t2.py
__pycache__
pytheory.egg-info
docs/_build
.claude/worktrees/
+127
View File
@@ -2,6 +2,133 @@
All notable changes to PyTheory are documented here.
## 0.43.0
- **BREAKING — fingerings now read low-to-high by default.** `Fretboard`
string lists and `Fingering` positions/string-names now run from the
**lowest-pitched string first** (e.g. standard guitar reads `E A D G B E`),
matching how chord diagrams and tablature are conventionally written.
Previously they ran high-to-low (`E B G D A E`). This affects
`Fretboard.tones`, iteration over a fretboard, `repr`, `chord()`, `tab()`,
`chart()`, and `fingering()` output.
To restore the pre-0.43 high-to-low behavior, pass **`high_to_low=True`**
to any fretboard constructor — `Fretboard.guitar(high_to_low=True)`,
`Fretboard(tones=..., high_to_low=True)`, and likewise on every instrument
preset (`bass`, `ukulele`, `mandolin`, … `keyboard`).
The flip also applies to **input**: a custom tuning tuple passed to
`Fretboard.guitar(...)` and manual fret positions passed to
`fingering(*positions)` are now read in the board's orientation
(low-to-high by default).
`to_tab()` and `Part.strum()` are unaffected — they sort by pitch
internally and produce identical output regardless of orientation.
## 0.42.1
- **Fretboard tuning support** — `to_tab()` now accepts `Fretboard` objects as
the `tuning` parameter. Works with `Fretboard.guitar()`, `Fretboard.bass()`,
`Fretboard.ukulele()`, `Fretboard.mandolin()`, `Fretboard.banjo()`, and any
custom Fretboard with capo.
## 0.42.0
- **LilyPond export** — `Score.to_lilypond()` generates complete LilyPond source
files with multi-staff scores, key/time signatures, tempo markings, and
automatic bass clef detection. Output can be compiled to publication-quality
PDFs with LilyPond.
- **MusicXML export** — `Score.to_musicxml()` generates MusicXML 4.0 documents
that can be opened in MuseScore, Sibelius, Finale, and any notation software.
Includes proper ties, chords, clef detection, and tempo/time signature metadata.
- **Guitar/bass tablature** — `Part.to_tab()` and `Score.to_tab()` generate ASCII
tablature. Supports guitar (6-string), bass (4-string), drop D, and custom
tunings. Automatically maps notes to the best string/fret positions.
## 0.41.4
- **Fix** — `to_abc()` now ties long notes across barlines instead of emitting
oversized durations that abcjs can't render (e.g. 16-beat notes become four
tied whole notes).
## 0.41.3
- **Fix** — `to_abc()` now skips parts with only drum tones or rests (no pitched
notes), fixing "pitch is undefined" errors in abcjs. Chords are correctly
recognized as pitched content.
## 0.41.2
- **Auto bass clef** — `to_abc()` detects low-register parts (808, bass, timpani)
and assigns `clef=bass` automatically based on average note octave.
## 0.41.1
- **Fix** — `to_abc()` no longer crashes on parts containing drum tones.
## 0.41.0
- **ABC notation export** — `Score.to_abc()` converts scores to ABC notation
strings. Supports multi-voice scores (via `V:` directives), chords, rests,
accidentals, and all standard durations. Pass `html=True` to get a
self-contained HTML page that renders sheet music in the browser via abcjs.
## 0.40.9
- **Mellotron synth** — tape-replay keyboard with wow/flutter, tape saturation,
bandwidth limiting, hiss, and 8-second tape fadeout. Three tape banks via the
`tape` parameter: `"strings"` (default), `"flute"`, and `"choir"`.
- **Analog oscillator synths** — four new waveform generators for fat, alive,
analog-style sounds:
- `Synth.HARD_SYNC` — slave oscillator hard-synced to a master (Prophet-5
leads). `slave_ratio` parameter controls harmonic content.
- `Synth.RING_MOD` — two oscillators multiplied for metallic, bell-like
inharmonic tones. `mod_ratio` parameter.
- `Synth.WAVEFOLD` — west coast wavefolding (Buchla-style). `folds` parameter
sweeps from warm to gnarly.
- `Synth.DRIFT` — analog VCO with pitch drift, jitter, and noise floor.
`shape` parameter (`"saw"`, `"square"`, `"triangle"`, `"pulse"`) and
`drift_amount` for instability level.
- **Synth kwargs passthrough** — `play()`, `save()`, and `_render()` now accept
`**synth_kw` for forwarding parameters to synth wave functions (e.g.
`play(tone, synth=Synth.MELLOTRON, tape="choir")`).
- **14 new instrument presets** — `mellotron`, `mellotron_strings`,
`mellotron_flute`, `mellotron_choir`, `sync_lead`, `sync_lead_bright`,
`ring_mod_bell`, `ring_mod_metallic`, `wavefold_warm`, `wavefold_gnarly`,
`drift_saw`, `drift_square`, `analog_pad`, `analog_bass`.
- **808 bass envelope fix** — changed from `pluck` (zero sustain, wrong for 808)
to `piano` (sharp attack with long decay tail).
## 0.40.8
- **Fix hold() inflating duration** — `Note.beats` was returning the full
duration for held notes (`_hold=True`), causing `Part.total_beats` and
`Score.duration_ms` to overcount. A part with `hold(Sa, WHOLE * 4)` followed
by `add(Pa, QUARTER)` would report 17 beats instead of 1. Now held notes
return 0 beats, matching the renderer which already skipped advancing the
timeline for held notes.
## 0.40.7
- **Expose missing Synth enum entries** — rhodes, wurlitzer, vibraphone,
pipe organ, and choir wave functions were already implemented but not
accessible via the Synth enum. Now available as `Synth.RHODES`,
`Synth.WURLITZER`, `Synth.VIBRAPHONE`, `Synth.PIPE_ORGAN`, `Synth.CHOIR`.
## 0.40.6
- **Saxophone presets cleaned up** — removed lowpass filters and vel_to_filter
from all sax instrument presets (saxophone, alto_sax, tenor_sax, bari_sax).
The saxophone wave function already shapes its own spectrum; the extra
filters were dulling the tone.
## 0.40.5
- **Saxophone synth overhaul** — reed nonlinearity (asymmetric soft clipping),
conical bore formant resonances, breath noise with attack envelope, separate
reed buzz, key click transient, and sub-harmonic warmth. Vibrato dialed back
to subtle, delayed onset.
## 0.40.4
- **Distortion overhaul** — multi-stage clipping (preamp → power amp →
+207
View File
@@ -0,0 +1,207 @@
"""Demo the 5 new synths: Mellotron, Hard Sync, Ring Mod, Wavefold, Drift.
Each synth gets a short musical phrase — not just a scale run — with
reverb and rhythmic variety to show off its character.
"""
from pytheory import Score, Duration, play_score
EIGHTH = Duration.EIGHTH
QUARTER = Duration.QUARTER
HALF = Duration.HALF
DOTTED_Q = Duration.DOTTED_QUARTER
WHOLE = Duration.WHOLE
# ── Mellotron Strings ────────────────────────────────────────────────────────
# Strawberry Fields vibes — slow, haunted, with rests that breathe.
print("=== MELLOTRON STRINGS ===")
s = Score("4/4", bpm=72)
p = s.part("tape", instrument="mellotron_strings",
reverb=0.45, reverb_type="cathedral", reverb_decay=2.0)
p.add("G4", HALF).add("B4", QUARTER).add("D5", QUARTER)
p.add("C5", DOTTED_Q).add("B4", EIGHTH).add("A4", HALF)
p.rest(QUARTER)
p.add("G4", DOTTED_Q).add("F#4", EIGHTH).add("G4", WHOLE)
play_score(s)
# ── Mellotron Flute ──────────────────────────────────────────────────────────
# Lonely, breathy, with space between phrases.
print("\n=== MELLOTRON FLUTE ===")
s = Score("3/4", bpm=84)
p = s.part("flute", instrument="mellotron_flute",
reverb=0.5, reverb_type="taj_mahal", reverb_decay=2.5)
p.add("E5", HALF).add("D5", QUARTER)
p.add("C5", DOTTED_Q).add("B4", EIGHTH).rest(QUARTER)
p.add("A4", HALF).add("G4", QUARTER)
p.add("A4", HALF).rest(QUARTER)
p.add("E5", QUARTER).add("D5", QUARTER).add("C5", QUARTER)
p.add("B4", HALF + QUARTER)
play_score(s)
# ── Mellotron Choir ──────────────────────────────────────────────────────────
# Ghostly pad — slow chords, big reverb.
print("\n=== MELLOTRON CHOIR ===")
s = Score("4/4", bpm=60)
p = s.part("choir", instrument="mellotron_choir",
reverb=0.6, reverb_type="cathedral", reverb_decay=3.0)
p.add("C4", WHOLE)
p.add("E4", HALF).add("G4", HALF)
p.add("A4", DOTTED_Q).add("G4", EIGHTH).add("F4", HALF)
p.add("E4", WHOLE)
play_score(s)
# ── Hard Sync Lead ───────────────────────────────────────────────────────────
# Aggressive, punchy — fast 16ths and syncopation.
print("\n=== HARD SYNC LEAD ===")
s = Score("4/4", bpm=128)
p = s.part("sync", instrument="sync_lead",
reverb=0.25, reverb_type="plate")
p.add("E4", EIGHTH).add("E4", EIGHTH).rest(EIGHTH).add("G4", EIGHTH)
p.add("A4", QUARTER).add("G4", EIGHTH).add("E4", EIGHTH)
p.add("D4", EIGHTH).rest(EIGHTH).add("E4", EIGHTH).add("G4", EIGHTH)
p.add("A4", HALF)
p.rest(QUARTER).add("B4", EIGHTH).add("A4", EIGHTH)
p.add("G4", QUARTER).add("E4", QUARTER).add("D4", HALF)
play_score(s)
# ── Hard Sync Bright ─────────────────────────────────────────────────────────
# Higher slave ratio — more harmonics, screaming lead.
print("\n=== HARD SYNC BRIGHT ===")
s = Score("4/4", bpm=138)
p = s.part("sync2", instrument="sync_lead_bright",
reverb=0.2, reverb_type="plate")
p.add("A4", EIGHTH).add("C5", EIGHTH).add("D5", QUARTER)
p.rest(EIGHTH).add("E5", EIGHTH).add("D5", EIGHTH).add("C5", EIGHTH)
p.add("A4", QUARTER).rest(QUARTER).add("G4", EIGHTH).add("A4", EIGHTH)
p.add("C5", HALF)
play_score(s)
# ── Ring Mod Bell ────────────────────────────────────────────────────────────
# Shimmery, metallic — sparse hits with long reverb tail.
print("\n=== RING MOD BELL ===")
s = Score("4/4", bpm=66)
p = s.part("bell", instrument="ring_mod_bell",
reverb=0.6, reverb_type="cave", reverb_decay=3.0)
p.add("C5", HALF).rest(QUARTER).add("G4", QUARTER)
p.rest(HALF).add("E5", HALF)
p.add("D5", QUARTER).rest(QUARTER).add("C5", HALF)
p.rest(WHOLE)
p.add("G4", QUARTER).add("A4", QUARTER).add("C5", HALF)
play_score(s)
# ── Ring Mod Metallic ────────────────────────────────────────────────────────
# Alien, inharmonic — atonal stabs.
print("\n=== RING MOD METALLIC ===")
s = Score("4/4", bpm=100)
p = s.part("metal", instrument="ring_mod_metallic",
reverb=0.4, reverb_type="parking_garage", reverb_decay=2.0)
p.add("F4", EIGHTH).rest(EIGHTH).add("Ab4", EIGHTH).add("F4", EIGHTH)
p.rest(QUARTER).add("Db5", QUARTER).rest(QUARTER)
p.add("C5", EIGHTH).add("Ab4", EIGHTH).rest(QUARTER).add("F4", HALF)
p.rest(HALF).add("Db5", QUARTER).add("C5", QUARTER)
play_score(s)
# ── Wavefold Warm ────────────────────────────────────────────────────────────
# Gentle folds — round and musical, like a filtered saw with overtones.
print("\n=== WAVEFOLD WARM ===")
s = Score("4/4", bpm=108)
p = s.part("fold", instrument="wavefold_warm",
reverb=0.3, reverb_type="plate")
p.add("A3", QUARTER).add("C4", QUARTER).add("E4", QUARTER).add("A4", QUARTER)
p.add("G4", DOTTED_Q).add("E4", EIGHTH).add("C4", HALF)
p.add("D4", QUARTER).add("F4", QUARTER).add("A4", HALF)
p.add("G4", WHOLE)
play_score(s)
# ── Wavefold Gnarly ──────────────────────────────────────────────────────────
# Cranked folds — buzzy, aggressive, with syncopation.
print("\n=== WAVEFOLD GNARLY ===")
s = Score("4/4", bpm=130)
p = s.part("gnarly", instrument="wavefold_gnarly",
reverb=0.2, reverb_type="spring")
p.add("E3", EIGHTH).add("E3", EIGHTH).rest(EIGHTH).add("G3", EIGHTH)
p.add("A3", EIGHTH).rest(EIGHTH).add("B3", EIGHTH).add("A3", EIGHTH)
p.add("E3", QUARTER).add("G3", EIGHTH).add("A3", EIGHTH).add("B3", QUARTER)
p.rest(QUARTER)
p.add("E4", EIGHTH).add("D4", EIGHTH).add("B3", QUARTER).add("A3", HALF)
play_score(s)
# ── Drift Saw ────────────────────────────────────────────────────────────────
# Warm, alive analog saw — the Minimoog pad.
print("\n=== DRIFT SAW (vintage VCO) ===")
s = Score("4/4", bpm=88)
p = s.part("drift", instrument="drift_saw",
reverb=0.35, reverb_type="taj_mahal", reverb_decay=2.0)
p.add("D4", HALF).add("F4", HALF)
p.add("A4", DOTTED_Q).add("G4", EIGHTH).add("F4", QUARTER).rest(QUARTER)
p.add("D4", QUARTER).add("E4", QUARTER).add("F4", HALF)
p.add("D4", WHOLE)
play_score(s)
# ── Drift Square ─────────────────────────────────────────────────────────────
# Hollow, wobbly — 8-bit with analog soul.
print("\n=== DRIFT SQUARE ===")
s = Score("4/4", bpm=110)
p = s.part("dsq", instrument="drift_square",
reverb=0.25, reverb_type="plate")
p.add("C4", EIGHTH).add("E4", EIGHTH).add("G4", QUARTER).add("E4", QUARTER)
p.rest(QUARTER)
p.add("A4", EIGHTH).add("G4", EIGHTH).add("E4", QUARTER).add("C4", HALF)
p.add("D4", QUARTER).add("F4", EIGHTH).add("G4", EIGHTH).add("A4", HALF)
p.add("G4", WHOLE)
play_score(s)
# ── Analog Pad ───────────────────────────────────────────────────────────────
# Slow, drifting chords — Juno-style lushness.
print("\n=== ANALOG PAD ===")
s = Score("4/4", bpm=70)
p = s.part("pad", instrument="analog_pad",
reverb=0.5, reverb_type="taj_mahal", reverb_decay=3.0)
p.add("A3", WHOLE)
p.add("C4", HALF).add("E4", HALF)
p.add("F4", WHOLE)
p.add("E4", HALF).add("D4", HALF)
p.add("C4", WHOLE)
play_score(s)
# ── Analog Bass ──────────────────────────────────────────────────────────────
# Tight, punchy — Moog bass with filter sweep.
print("\n=== ANALOG BASS ===")
s = Score("4/4", bpm=120)
p = s.part("bass", instrument="analog_bass",
reverb=0.1, reverb_type="plate")
p.add("E2", EIGHTH).add("E2", EIGHTH).rest(EIGHTH).add("G2", EIGHTH)
p.add("A2", QUARTER).rest(QUARTER)
p.add("E2", EIGHTH).rest(EIGHTH).add("B2", EIGHTH).add("A2", EIGHTH)
p.add("G2", QUARTER).add("E2", QUARTER).rest(HALF)
p.add("E2", EIGHTH).add("E2", EIGHTH).add("G2", EIGHTH).add("A2", EIGHTH)
p.add("B2", QUARTER).add("A2", QUARTER).add("E2", HALF)
play_score(s)
print("\nDone!")
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.
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.
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.
+56
View File
@@ -850,6 +850,56 @@ def gen_synth_ukulele():
p.strum(ch, Duration.WHOLE, velocity=72)
render("synth_ukulele", score)
def gen_synth_hard_sync():
score = Score("4/4", bpm=120)
p = score.part("demo", instrument="sync_lead_bright", volume=0.5)
for n in ["C4", "E4", "G4", "C5", "G4", "E4", "C4", "E4"]:
p.add(n, Duration.QUARTER, velocity=90)
render("synth_hard_sync", score)
def gen_synth_ring_mod():
score = Score("4/4", bpm=90)
p = score.part("demo", instrument="ring_mod_bell", volume=0.5)
for n in ["C5", "E5", "G5", "C6", "G5", "E5", "C5", "E5"]:
p.add(n, Duration.QUARTER, velocity=80)
render("synth_ring_mod", score)
def gen_synth_wavefold():
score = Score("4/4", bpm=110)
p = score.part("demo", instrument="wavefold_warm", volume=0.5)
for n in ["C4", "E4", "G4", "C5", "G4", "E4", "C4", "E4"]:
p.add(n, Duration.QUARTER, velocity=85)
render("synth_wavefold", score)
def gen_synth_drift():
score = Score("4/4", bpm=90)
p = score.part("demo", instrument="drift_saw", volume=0.5, reverb=0.35,
reverb_type="taj_mahal")
for n in ["C4", "E4", "G4", "C5", "G4", "E4", "C4", "E4"]:
p.add(n, Duration.HALF, velocity=75)
render("synth_drift", score)
def gen_synth_karplus():
score = Score("4/4", bpm=100)
p = score.part("demo", synth="pluck_synth", envelope="none",
volume=0.5, reverb=0.2)
for n in ["C4", "E4", "G4", "C5", "G4", "E4", "C4", "E4"]:
p.add(n, Duration.QUARTER, velocity=85)
render("synth_karplus", score)
def gen_synth_mellotron():
score = Score("4/4", bpm=80)
p = score.part("demo", instrument="mellotron_flute", volume=0.5)
for n in ["C4", "E4", "G4", "C5"]:
p.add(n, Duration.WHOLE, velocity=75)
render("synth_mellotron", score)
def gen_synth_granular():
score = Score("4/4", bpm=80)
p = score.part("demo", instrument="granular_pad", volume=0.5, reverb=0.4)
@@ -1139,6 +1189,12 @@ GENERATORS = [
gen_synth_banjo,
gen_synth_mandolin,
gen_synth_ukulele,
gen_synth_hard_sync,
gen_synth_ring_mod,
gen_synth_wavefold,
gen_synth_drift,
gen_synth_karplus,
gen_synth_mellotron,
gen_synth_granular,
gen_synth_crotales,
gen_synth_tingsha,
+27 -3
View File
@@ -94,11 +94,11 @@ PyTheory includes 144 pre-built chords (12 roots x 12 qualities):
>>> fb = Fretboard.guitar()
>>> fb.chord("C")
Fingering(e=0, B=1, G=0, D=2, A=3, E=x)
Fingering(E=x, A=3, D=2, G=0, B=1, e=0)
>>> fb.chord("Am")
Fingering(e=0, B=1, G=2, D=2, A=0, E=x)
Fingering(E=x, A=0, D=2, G=2, B=1, e=0)
>>> fb.chord("G7")
Fingering(e=1, B=0, G=0, D=0, A=2, E=3)
Fingering(E=3, A=2, D=0, G=0, B=0, e=1)
You can also build chords directly with ``Chord.from_name()``:
@@ -503,9 +503,16 @@ are standard arranging techniques for spreading chord tones across registers:
>>> cmaj7 = Chord.from_symbol("Cmaj7")
>>> cmaj7.close_voicing()
<Chord C major 7th>
>>> cmaj7.open_voicing()
<Chord C major 7th>
>>> cmaj7.drop2()
<Chord C major 7th>
``open_voicing()`` takes the close voicing and raises every other
non-root tone by an octave, spreading the chord across two octaves.
The result is a wider, more spacious sound — common in orchestral
writing and piano ballads where you want the harmony to breathe.
Chord Extensions
----------------
@@ -596,6 +603,23 @@ music that doesn't follow traditional harmony, this is the tool.
Major and minor triads share the same prime form — they're inversions
of each other in pitch class space.
The **normal form** is the intermediate step — the most compact ascending
arrangement of pitch classes before transposition. It preserves the
actual pitch classes (not transposed to 0), so it tells you which
specific notes are in the set:
.. code-block:: pycon
>>> Chord.from_tones("C", "E", "G").normal_form
(0, 4, 7)
>>> Chord.from_tones("A", "C", "E").normal_form
(9, 0, 4)
Normal form keeps the original pitch classes; prime form transposes to 0
for comparison. Use ``normal_form`` when you care about which notes,
``prime_form`` when you care about the abstract shape.
.. code-block:: pycon
>>> Chord.from_tones("C", "E", "G").forte_number
+40 -31
View File
@@ -18,19 +18,28 @@ positions are just semitone steps along the fingerboard.
Guitars
-------
`Standard guitar tuning <https://en.wikipedia.org/wiki/Guitar_tunings>`_
(high to low)::
`Standard guitar tuning <https://en.wikipedia.org/wiki/Guitar_tunings>`_::
String 1: E4 (highest)
String 2: B3
String 3: G3
String 4: D3
String 5: A2
String 6: E2 (lowest)
String 5: A2
String 4: D3
String 3: G3
String 2: B3
String 1: E4 (highest)
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).
.. note::
Since **v0.43.0**, fingerings and string lists read **low to high**
(lowest-pitched string first) by default — matching how chord
diagrams and tab are conventionally written. To get the pre-0.43
high-to-low order, pass ``high_to_low=True`` to any fretboard
constructor, e.g. ``Fretboard.guitar(high_to_low=True)``. A custom
tuning tuple and manual ``fingering()`` positions are likewise read
in the board's orientation.
.. code-block:: pycon
>>> from pytheory import Fretboard
@@ -192,12 +201,12 @@ on any instrument. It scores each possibility by:
>>> fb = Fretboard.guitar()
>>> f = fb.chord("C")
>>> f
Fingering(e=0, B=1, G=0, D=2, A=3, E=x)
Fingering(E=x, A=3, D=2, G=0, B=1, e=0)
>>> f['A']
3
>>> f[1]
1
3
>>> f.identify()
'C major'
@@ -210,11 +219,11 @@ You can also go from fret positions to chord identification:
.. code-block:: pycon
>>> # "What chord am I playing?"
>>> # "What chord am I playing?" (positions read low to high)
>>> fb = Fretboard.guitar()
>>> f = fb.fingering(0, 0, 0, 2, 2, 0)
>>> f = fb.fingering(0, 2, 2, 0, 0, 0)
>>> f
Fingering(e=0, B=0, G=0, D=2, A=2, E=0)
Fingering(E=0, A=2, D=2, G=0, B=0, e=0)
>>> f.identify()
'E minor'
@@ -223,14 +232,14 @@ 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``::
low E as ``E``. Strings read low to high (lowest first)::
e|--0-- (open — E)
B|--1-- (fret 1 — C)
G|--0-- (open — G)
D|--2-- (fret 2 — E)
E|--x-- (muted — low E)
A|--3-- (fret 3 — C)
E|--x-- (muted)
D|--2-- (fret 2 — E)
G|--0-- (open — G)
B|--1-- (fret 1 — C)
e|--0-- (open — high E)
A value of ``x`` (``None``) means the string is muted (not played).
@@ -243,12 +252,12 @@ For a more visual representation, use ``tab()``:
>>> print(fb.tab("C"))
C major
e|--0--
B|--1--
G|--0--
D|--2--
A|--3--
E|--x--
A|--3--
D|--2--
G|--0--
B|--1--
e|--0--
Generating Full Charts
----------------------
@@ -261,7 +270,7 @@ Generate fingerings for every chord at once:
>>> chart = fb.chart()
>>> chart["C"]
Fingering(e=0, B=1, G=0, D=2, A=3, E=x)
Fingering(E=x, A=3, D=2, G=0, B=1, e=0)
>>> # Works with any instrument
>>> uke_chart = Fretboard.ukulele().chart()
@@ -303,19 +312,19 @@ Any instrument can be modeled with custom string tunings:
>>> from pytheory import Tone, Fretboard
>>> # Baritone ukulele (DGBE — top 4 guitar strings)
>>> # Baritone ukulele (DGBE — top 4 guitar strings, low to high)
>>> bari_uke = Fretboard(tones=[
... Tone.from_string("E4"),
... Tone.from_string("B3"),
... Tone.from_string("G3"),
... Tone.from_string("D3"),
... Tone.from_string("G3"),
... Tone.from_string("B3"),
... Tone.from_string("E4"),
... ])
>>> # Tres cubano (Cuban guitar, 3 doubled courses)
>>> # Tres cubano (Cuban guitar, 3 doubled courses, low to high)
>>> tres = Fretboard(tones=[
... Tone.from_string("E4"),
... Tone.from_string("B3"),
... Tone.from_string("G3"),
... Tone.from_string("B3"),
... Tone.from_string("E4"),
... ])
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.
+404
View File
@@ -0,0 +1,404 @@
Nashville Numbers, Blues Scales, and Tablature
===============================================
Three tools that work together: the Nashville number system for writing
chord charts, blues scales for improvisation, and tablature for seeing
where to put your fingers. This guide covers all three and shows how
they connect.
The Nashville Number System
---------------------------
The `Nashville number system <https://en.wikipedia.org/wiki/Nashville_Number_System>`_
replaces chord names with Arabic numerals (1, 2, 3...) so that a chart
works in **any key**. It's the standard chart format in Nashville
recording studios — a session musician can read a number chart and
transpose on the fly without rewriting anything.
The idea is simple: each number refers to a **scale degree**. In any
major key, 1 is the tonic chord, 4 is the subdominant, 5 is the
dominant, and so on. The chord quality (major, minor, diminished) is
determined by the key — you don't need to write it out.
In C major::
1 = C major 5 = G major
2 = D minor 6 = A minor
3 = E minor 7 = B diminished
4 = F major
In G major::
1 = G major 5 = D major
2 = A minor 6 = E minor
3 = B minor 7 = F# diminished
4 = C major
Same numbers, different key, different chords — but the same harmonic
relationships.
Using Nashville Numbers in PyTheory
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Both :class:`~pytheory.scales.Key` and :class:`~pytheory.scales.TonedScale`
support the ``nashville()`` method:
.. code-block:: pycon
>>> from pytheory import Key
>>> key = Key("C", "major")
>>> [c.identify() for c in key.nashville(1, 4, 5, 1)]
['C major', 'F major', 'G major', 'C major']
>>> # Same progression, different key — just change the Key
>>> key_g = Key("G", "major")
>>> [c.identify() for c in key_g.nashville(1, 4, 5, 1)]
['G major', 'C major', 'D major', 'G major']
Nashville numbers and Roman numerals produce the same result — they're
two notations for the same concept:
.. code-block:: pycon
>>> key = Key("G", "major")
>>> nash = [c.identify() for c in key.nashville(1, 5, 6, 4)]
>>> roman = [c.identify() for c in key.progression("I", "V", "vi", "IV")]
>>> nash == roman
True
Seventh Chords
~~~~~~~~~~~~~~
Suffix ``"7"`` to get seventh chords — essential for jazz and blues
charts:
.. code-block:: pycon
>>> key = Key("C", "major")
>>> [c.identify() for c in key.nashville("17", "47", "57")]
['C major 7th', 'F major 7th', 'G dominant 7th']
Nashville vs. Roman Numerals
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
When should you use which?
- **Nashville numbers** — faster to type, easier to read at a glance,
standard in studio sessions. Use ``key.nashville(1, 4, 5, 1)``.
- **Roman numerals** — encode chord quality (uppercase = major,
lowercase = minor), standard in theory textbooks. Use
``key.progression("I", "IV", "V", "I")``.
Both are fully supported. Use whichever fits your workflow.
Blues Scales
------------
The `blues scale <https://en.wikipedia.org/wiki/Blues_scale>`_ is a
six-note scale built from the minor pentatonic plus one chromatic
passing tone — the **blue note** (flat 5th). That single added note
gives the blues its tension and character.
The blues system in PyTheory includes several related scales:
==================== ===== ==================================
Scale Notes Character
==================== ===== ==================================
minor pentatonic 5 Foundation of rock and blues soloing
major pentatonic 5 Bright, country, pop
blues 6 Minor pentatonic + blue note (b5)
major blues 6 Major pentatonic + blue note (b3)
dominant 7 Mixolydian — dominant 7th sound
minor 7 Dorian-like — minor with natural 6th
==================== ===== ==================================
Building Blues Scales
~~~~~~~~~~~~~~~~~~~~~
Use ``system="blues"`` when creating a :class:`~pytheory.scales.TonedScale`:
.. code-block:: pycon
>>> from pytheory import TonedScale
>>> c = TonedScale(tonic="C4", system="blues")
>>> c["minor pentatonic"].note_names
['C', 'Eb', 'F', 'G', 'Bb', 'C']
>>> c["blues"].note_names
['C', 'Eb', 'F', 'Gb', 'G', 'Bb', 'C']
>>> c["major pentatonic"].note_names
['C', 'D', 'E', 'G', 'A', 'C']
>>> c["major blues"].note_names
['C', 'D', 'Eb', 'E', 'G', 'A', 'C']
The Anatomy of a Blues Scale
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The blues scale in C::
C Eb F Gb G Bb C
1 b3 4 b5 5 b7 8
Root ──┐
├── minor 3rd (3 semitones)
├── perfect 4th (5 semitones)
├── diminished 5th (6 semitones) ← the "blue note"
├── perfect 5th (7 semitones)
├── minor 7th (10 semitones)
└── octave (12 semitones)
The blue note (Gb/F#) sits between the 4th and 5th — a dissonant,
unstable pitch that resolves up or down. It's what makes blues sound
like blues.
The 12-Bar Blues
~~~~~~~~~~~~~~~~
The `12-bar blues <https://en.wikipedia.org/wiki/Twelve-bar_blues>`_ is
the most important chord progression in American music. It uses the
Nashville numbers 1, 4, and 5::
| 1 | 1 | 1 | 1 |
| 4 | 4 | 1 | 1 |
| 5 | 4 | 1 | 5 |
In the key of A:
.. code-block:: pycon
>>> from pytheory import Key
>>> key = Key("A", "major")
>>> bars = key.nashville(1,1,1,1, 4,4,1,1, 5,4,1,5)
>>> [c.identify() for c in 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']
For an authentic blues sound, use dominant 7th chords:
.. code-block:: pycon
>>> bars_7 = key.nashville("17","17","17","17", "47","47","17","17", "57","47","17","57")
>>> [c.identify() for c in bars_7]
['A major 7th', 'A major 7th', 'A major 7th', 'A major 7th', 'D major 7th', 'D major 7th', 'A major 7th', 'A major 7th', 'E dominant 7th', 'D major 7th', 'A major 7th', 'E dominant 7th']
Or use the built-in named progression:
.. code-block:: pycon
>>> key = Key("A", "major")
>>> blues = key.common_progressions()["12-bar blues"]
>>> [c.identify() for c in blues]
['A major', 'A major', 'A major', 'A major', 'D major', 'D major', 'A major', 'A major', 'E major', 'D major', 'A major', 'E major']
Blues Scale on the Fretboard
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Visualize the blues scale on guitar to see the patterns:
.. code-block:: pycon
>>> from pytheory import Fretboard, TonedScale
>>> fb = Fretboard.guitar()
>>> blues = TonedScale(tonic="A4", system="blues")["blues"]
>>> print(fb.scale_diagram(blues, frets=12))
0 1 2 3 4 5 6 7 8 9 10 11 12
E| - | - | - | - | - | A | - | - | C | - | D | Eb| E |
B| - | - | - | D | Eb| E | - | - | - | - | A | - | - |
G| - | - | A | - | - | C | - | D | Eb| E | - | - | - |
D| - | - | - | - | A | - | - | C | - | D | Eb| E | - |
A| A | - | - | C | - | D | Eb| E | - | - | - | - | A |
E| - | - | - | - | - | A | - | - | C | - | D | Eb| E |
The minor pentatonic (same scale without the Eb) is the most-played
scale in rock guitar. Add the blue note and you have the full blues
scale — the same shapes, one extra fret.
Tablature
---------
`Tablature <https://en.wikipedia.org/wiki/Tablature>`_ (tab) shows
**where to put your fingers** rather than what notes to play. Each line
represents a string; numbers indicate fret positions. PyTheory generates
tabs at three levels:
1. **Chord tabs** — single chord fingerings
2. **Part tabs** — full melody/sequence notation
3. **Score tabs** — extract a part from a multi-part score
Chord Tablature
~~~~~~~~~~~~~~~~
Get the tab for any chord on any instrument:
.. code-block:: pycon
>>> from pytheory import Fretboard
>>> fb = Fretboard.guitar()
>>> print(fb.tab("C"))
C major
e|--0--
B|--1--
G|--0--
D|--2--
A|--3--
E|--x--
>>> print(fb.tab("Am"))
A minor
e|--0--
B|--1--
G|--2--
D|--2--
A|--0--
E|--x--
>>> print(fb.tab("E7"))
E dominant 7th
e|--0--
B|--0--
G|--1--
D|--0--
A|--2--
E|--0--
Works with any instrument:
.. code-block:: pycon
>>> uke = Fretboard.ukulele()
>>> print(uke.tab("C"))
C major
A|--3--
E|--0--
C|--0--
G|--0--
Reading Tab Notation
~~~~~~~~~~~~~~~~~~~~~
::
e|--0-- ← open string (don't fret, just pluck)
B|--1-- ← press fret 1
G|--0-- ← open string
D|--2-- ← press fret 2
A|--3-- ← press fret 3
E|--x-- ← muted (don't play this string)
- Each line is a string (highest pitch at top, lowest at bottom)
- Numbers are fret positions (0 = open, 1-24 = fretted)
- ``x`` means the string is muted / not played
- ``|`` marks measure boundaries in sequence tabs
Part Tablature
~~~~~~~~~~~~~~~
Generate tab from a composed part using ``to_tab()``:
.. code-block:: python
from pytheory import Score, Key, Duration
score = Score("4/4", bpm=120)
lead = score.part("lead", synth="saw")
# A simple blues lick
for note in ["A4", "C5", "D5", "Eb5", "E5", "G5", "A5"]:
lead.add(note, Duration.QUARTER)
print(lead.to_tab())
This outputs standard ASCII tab with measure lines, mapping each note
to the most playable string and fret position.
Tuning Options
~~~~~~~~~~~~~~
The ``to_tab()`` method supports multiple tunings:
.. code-block:: python
# Standard guitar (default)
lead.to_tab(tuning="guitar")
# 4-string bass
lead.to_tab(tuning="bass")
# Drop D guitar
lead.to_tab(tuning="drop_d")
# Any Fretboard object — use any of the 25+ instrument presets
from pytheory import Fretboard
lead.to_tab(tuning=Fretboard.mandolin())
lead.to_tab(tuning=Fretboard.banjo())
# Custom tuning as MIDI note numbers (low string first)
lead.to_tab(tuning=[40, 45, 50, 55, 59, 64])
Score Tablature
~~~~~~~~~~~~~~~~
Extract tab from a multi-part score:
.. code-block:: python
score = Score("4/4", bpm=120)
rhythm = score.part("rhythm", synth="saw")
lead = score.part("lead", synth="triangle")
bass = score.part("bass", synth="sine")
# ... compose parts ...
# Tab the lead part
print(score.to_tab("lead"))
# Tab the first non-drum part (if no name given)
print(score.to_tab())
# Bass tab
print(score.to_tab("bass", tuning="bass"))
Putting It All Together
-----------------------
Here's a complete example that uses all three features — Nashville
numbers for the chord progression, the blues scale for the melody, and
tab export to see the fingering:
.. code-block:: python
from pytheory import Key, TonedScale, Fretboard, Score, Duration
# 1. Nashville numbers for the progression
key = Key("A", "major")
chords = key.nashville(1, 1, 1, 1, 4, 4, 1, 1, 5, 4, 1, 5)
# 2. Blues scale for the melody
blues = TonedScale(tonic="A4", system="blues")["blues"]
# 3. Compose a score
score = Score("4/4", bpm=120)
rhythm = score.part("rhythm", synth="saw", envelope="pad")
lead = score.part("lead", synth="triangle", envelope="pluck")
for chord in chords:
rhythm.add(chord, Duration.WHOLE)
for note_name in blues.note_names[:-1]: # walk up the scale
lead.add(f"{note_name}4", Duration.HALF)
# 4. See it as tablature
print(lead.to_tab())
# 5. See the scale on the fretboard
fb = Fretboard.guitar()
print(fb.scale_diagram(blues, frets=12))
Nashville numbers tell you *what chords to play*. The blues scale tells you *what notes to solo with*. Tablature tells you *where to put your fingers*. Together, they're everything you need to play the blues.
+173 -7
View File
@@ -3,19 +3,21 @@ Playback and Export
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.
gives you four ways to get your music out: speakers, WAV files, MIDI
files, and sheet music.
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.
professional instruments, mixing, and mastering. Use **ABC notation
export** when you want sheet music -- rendered in the browser or shared
as plain text. 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 play audio through your speakers, save to WAV, export to
MIDI, or generate sheet music as ABC notation. Everything is synthesized
from waveforms -- no samples or external audio files needed.
.. note::
@@ -44,6 +46,22 @@ Optional parameters for synth, envelope, and temperament:
play(Tone.from_string("C4"), synth=Synth.SAW, envelope=Envelope.PLUCK, t=1_000)
play(Tone.from_string("C4"), temperament="pythagorean", t=1_000)
Synth-specific parameters are passed through as keyword arguments:
.. code-block:: python
# Mellotron with flute tape
play(Tone.from_string("C4"), synth=Synth.MELLOTRON, tape="choir", t=2_000)
# Hard sync with custom slave ratio
play(Tone.from_string("C4"), synth=Synth.HARD_SYNC, slave_ratio=2.5)
# Wavefolding with 4 folds
play(Tone.from_string("C4"), synth=Synth.WAVEFOLD, folds=4.0)
# Drift oscillator with square shape
play(Tone.from_string("C4"), synth=Synth.DRIFT, shape="square")
play_score() -- Full Arrangements
---------------------------------
@@ -155,6 +173,154 @@ Score-based export (with time signature, tempo, and parts):
score.add(chord, Duration.WHOLE)
score.save_midi("progression.mid")
to_abc() -- ABC Notation / Sheet Music
---------------------------------------
ABC notation is a human-readable text format for music that tools can
turn into staff notation and MIDI. It's widely used for folk tunes,
lead sheets, and quick sketches. PyTheory can export any Score as ABC
notation -- and optionally wrap it in an HTML page that renders
sheet music right in the browser using `abcjs <https://www.abcjs.net/>`_.
Basic export:
.. code-block:: python
from pytheory import Score, Duration, Key
score = Score("4/4", bpm=120)
lead = score.part("lead")
for chord in Key("C", "major").progression("I", "V", "vi", "IV"):
lead.add(chord, Duration.WHOLE)
print(score.to_abc(title="Pop Chords", key="C"))
Output:
.. code-block:: text
X:1
T:Pop Chords
M:4/4
Q:1/4=120
L:1/8
K:C
[CEG]8 | [GBd]8 | [Ace]8 | [FAc]8 |
Open sheet music in the browser with ``html=True``:
.. code-block:: python
html = score.to_abc(title="Pop Chords", key="C", html=True)
with open("chords.html", "w") as f:
f.write(html)
import webbrowser
webbrowser.open("chords.html")
This generates a self-contained HTML page with an embedded
``<script>`` tag that loads abcjs from a CDN and renders the notation
as SVG -- no build steps, no dependencies, just open the file.
Multi-part scores automatically get ``V:`` (voice) directives so each
instrument appears on its own staff. Bass parts (average note below C4)
get bass clef automatically. Drum-only parts are skipped. Notes longer
than one measure are split into tied notes across barlines.
Parameters:
- **title** -- Tune title for the ``T:`` header (default ``"Untitled"``).
- **key** -- ABC key signature string (default ``"C"``). Use ``"Am"`` for
A minor, ``"Bb"`` for B-flat major, ``"F#m"`` for F-sharp minor, etc.
- **html** -- If ``True``, return a full HTML document instead of raw ABC
(default ``False``).
to_lilypond() -- LilyPond Export
---------------------------------
`LilyPond <https://lilypond.org/>`_ is the gold standard for
publication-quality music engraving. ``to_lilypond()`` generates
complete LilyPond source files that you can compile to PDF:
.. code-block:: python
score = Score("4/4", bpm=120)
lead = score.part("lead")
for note in ["C4", "D4", "E4", "F4"]:
lead.add(note, Duration.QUARTER)
ly = score.to_lilypond(title="My Score", key="C", mode="major")
with open("score.ly", "w") as f:
f.write(ly)
Then compile with ``lilypond score.ly`` to get a PDF. Multi-part scores
get separate staves in a ``StaffGroup``, bass clef is auto-detected,
and long notes are split with ties across barlines.
Parameters:
- **title** -- Title for the ``\header`` block (default ``"Untitled"``).
- **key** -- Key signature root (default ``"C"``). Use note names like
``"Bb"``, ``"F#"``, ``"Eb"``.
- **mode** -- LilyPond mode string (default ``"major"``). Use ``"minor"``
for minor keys.
to_musicxml() -- MusicXML Export
---------------------------------
MusicXML is the interchange format for notation software. Export your
score and open it in MuseScore, Sibelius, Finale, Dorico, or any
other notation app:
.. code-block:: python
xml = score.to_musicxml(title="My Score")
with open("score.musicxml", "w") as f:
f.write(xml)
The output is a complete MusicXML 4.0 partwise document with proper
time signatures, tempo markings, clef detection, tied notes across
barlines, and chord notation. No external dependencies needed.
to_tab() -- Guitar/Bass Tablature
-----------------------------------
Generate ASCII tablature from any Part or Score:
.. code-block:: python
lead = score.part("lead")
lead.add("E4", Duration.QUARTER)
lead.add("B3", Duration.QUARTER)
lead.add("G3", Duration.QUARTER)
lead.add("D3", Duration.QUARTER)
print(lead.to_tab())
Output::
e|---0---------|
B|------0------|
G|---------0---|
D|------------0|
A|-------------|
E|-------------|
Works on Score too -- it picks the first melodic part automatically:
.. code-block:: python
print(score.to_tab()) # auto-pick part
print(score.to_tab(part_name="bass")) # specific part
print(score.to_tab(tuning="bass")) # 4-string bass tab
print(score.to_tab(tuning="drop_d")) # drop D guitar
Supports ``"guitar"`` (6-string standard), ``"bass"`` (4-string),
``"drop_d"``, or a custom list of MIDI note numbers for any tuning.
play_pattern() -- Drum Patterns
-------------------------------
+27 -1
View File
@@ -269,6 +269,23 @@ easy:
['G major', 'A minor', 'B minor', 'C major', 'D major', 'E minor', 'F# diminished']
>>> key.seventh_chords
['G major 7th', 'A minor 7th', 'B minor 7th', 'C major 7th', 'D dominant 7th', 'E minor 7th', 'F# half-diminished 7th']
Build a seventh chord on any individual degree with ``seventh()``:
.. code-block:: pycon
>>> key.seventh(0) # I7
G major 7th
>>> key.seventh(4) # V7
D dominant 7th
>>> key.seventh(6) # vii7
F# half-diminished 7th
This is the single-degree version of ``seventh_chords`` — useful when
you need one specific chord rather than the full list.
.. code-block:: pycon
>>> Key.detect("C", "E", "G", "A", "D")
C major
@@ -440,7 +457,16 @@ alternative scales to improvise over:
>>> Scale.recommend("C", "Eb", "F", "Gb", "G", "Bb", top=3)
[('C', 'blues', 1.0), ...]
Chromatic scales are deprioritized since they match everything.
How it works: ``recommend()`` tests your notes against every scale in
every key (all 12 tonics times all scale types in the Western system).
Each candidate is scored using ``fitness()`` — the fraction of your notes
that belong to that scale (1.0 = perfect match). Results are ranked by
fitness, with chromatic scales deprioritized since they match everything.
Scales whose length is closer to the number of input notes are preferred
when fitness scores tie.
Returns a list of ``(tonic, scale_name, fitness)`` tuples. Pass ``top=``
to control how many results you get back (default 5).
Parallel Modes
~~~~~~~~~~~~~~
+181 -3
View File
@@ -1,7 +1,7 @@
Synthesizers
============
PyTheory includes 41 built-in waveforms and 10 ADSR envelope presets.
PyTheory includes 56 built-in waveforms and 10 ADSR envelope presets.
Every sound is generated from scratch -- no samples or external audio
files needed.
@@ -249,6 +249,130 @@ produces a natural chorus/vibrato effect built into the waveform itself.
<audio controls style="width:100%;margin:0.3em 0 0.5em"><source src="../_static/audio/synth_pwm_fast.wav" type="audio/wav"></audio>
Analog Synthesis
----------------
These waveforms model the behavior of real analog hardware — the
imperfections, interactions, and nonlinearities that make a room full
of vintage synths sound so much more alive than a room full of VSTs.
Each one is a different approach to the same question: how do you make
a digital oscillator sound like it has a soul?
Hard Sync
~~~~~~~~~
A "slave" oscillator is forced to restart its cycle every time a
"master" oscillator completes one. The abrupt restart creates bright
formant peaks that sweep as the slave ratio changes. This is THE sound
of the Prophet-5, Moog Prodigy, and every screaming analog lead since
1978.
**Use for:** aggressive leads, formant sweeps, cutting solos.
.. code-block:: python
lead = score.part("lead", synth="hard_sync", envelope="pluck")
# Higher slave ratio = more harmonics, brighter
from pytheory import play, Synth, Tone
play(Tone.from_string("C4"), synth=Synth.HARD_SYNC, slave_ratio=2.5)
.. raw:: html
<audio controls style="width:100%;margin:0.3em 0 0.5em"><source src="../_static/audio/synth_hard_sync.wav" type="audio/wav"></audio>
Ring Modulation
~~~~~~~~~~~~~~~
Two oscillators multiplied together, producing sum and difference
frequencies. Unlike FM, ring mod outputs only sidebands — no carrier
or modulator fundamental. The result is metallic, bell-like, and often
inharmonic. Classic Dalek voice, Stockhausen, and every sci-fi
soundtrack.
**Use for:** metallic bells, alien textures, inharmonic percussion.
.. code-block:: python
bells = score.part("bells", instrument="ring_mod_bell",
reverb=0.5, reverb_type="cave")
# Non-integer ratios = more inharmonic
play(Tone.from_string("C4"), synth=Synth.RING_MOD, mod_ratio=2.1)
.. raw:: html
<audio controls style="width:100%;margin:0.3em 0 0.5em"><source src="../_static/audio/synth_ring_mod.wav" type="audio/wav"></audio>
Wavefolding
~~~~~~~~~~~
The heart of west coast synthesis (Buchla, Make Noise, Verbos). A sine
wave is amplified past ±1.0, then "folded" — the overflow bounces back
instead of clipping. Each fold adds new harmonic pairs. At low fold
counts it's warm and round; crank it up and it gets buzzy, gnarly, and
alive.
This sounds completely different from subtractive synthesis — instead of
*removing* harmonics with a filter, you're *generating* them by shaping
the wave. Pairs beautifully with a lowpass filter after the fold.
**Use for:** complex leads, evolving textures, west coast basslines.
.. code-block:: python
# Warm, musical folding
warm = score.part("fold", instrument="wavefold_warm")
# Cranked and aggressive
gnarly = score.part("gnarly", instrument="wavefold_gnarly")
# Direct control over fold amount
play(Tone.from_string("C4"), synth=Synth.WAVEFOLD, folds=3.0)
.. raw:: html
<audio controls style="width:100%;margin:0.3em 0 0.5em"><source src="../_static/audio/synth_wavefold.wav" type="audio/wav"></audio>
Drift Oscillator
~~~~~~~~~~~~~~~~
Real analog oscillators are never perfectly stable. Capacitor charging,
thermal variations, and component tolerances make the pitch wander
slightly. This is what makes a Minimoog sound "fat" and a VST sound
"thin" — the constant micro-motion of imperfect hardware.
The drift oscillator models slow pitch drift (< 1 Hz wander), fast
jitter (per-cycle randomness), a soft analog noise floor, and slightly
rounded waveform edges. It turns any basic shape into something that
breathes.
**Use for:** analog-style pads, warm basses, vintage leads, any voice
that needs to feel "alive."
.. code-block:: python
# Vintage Minimoog-style saw
pad = score.part("pad", instrument="drift_saw",
reverb=0.35, reverb_type="taj_mahal")
# Hollow square with analog wobble
sq = score.part("sq", instrument="drift_square")
# Control the shape and instability directly
play(Tone.from_string("C4"), synth=Synth.DRIFT,
shape="triangle", drift_amount=0.25)
.. raw:: html
<audio controls style="width:100%;margin:0.3em 0 0.5em"><source src="../_static/audio/synth_drift.wav" type="audio/wav"></audio>
Drift amount controls how unstable the oscillator is:
- **0.05** = studio-grade (Sequential, Oberheim)
- **0.15** = classic vintage (Minimoog, ARP) — the default
- **0.30** = barely-holding-it-together (old SH-101)
ADSR Envelopes
--------------
@@ -406,6 +530,10 @@ It sounds genuinely like a real guitar, harp, or koto.
guitar = score.part("guitar", synth="pluck_synth")
harp = score.part("harp", instrument="harp") # uses pluck_synth
.. raw:: html
<audio controls style="width:100%;margin:0.3em 0 0.5em"><source src="../_static/audio/synth_karplus.wav" type="audio/wav"></audio>
Hammond Organ
~~~~~~~~~~~~~
@@ -440,11 +568,11 @@ Dedicated Instrument Synths
--------------------------
Beyond the classic and physical modeling waveforms, PyTheory includes
31 dedicated instrument synths. Each one uses tailored synthesis
36 dedicated instrument synths. Each one uses tailored synthesis
techniques -- additive harmonics, formant shaping, body resonance
modeling, and specialized envelopes -- to capture the character of a
specific acoustic instrument. These are the waveforms that bring the
total count to 41.
total count to 56.
Piano Synth
~~~~~~~~~~~
@@ -495,6 +623,42 @@ picked up by an electrostatic pickup. More nasal, reedy, and biting
<audio controls style="width:100%;margin:0.3em 0 0.5em"><source src="../_static/audio/synth_wurlitzer.wav" type="audio/wav"></audio>
Mellotron
~~~~~~~~~
The original "sampler" — a 1960s keyboard where each key triggers a
strip of magnetic tape with a pre-recorded instrument. The mechanical
tape transport gives it a haunted, lo-fi quality that no digital
emulation fully captures: pitch wobbles from uneven capstan speed,
bandwidth limited to 300 Hz6 kHz (like a worn cassette), soft tape
saturation, and tapes that physically run out after 8 seconds.
The Mellotron defined the sound of *Strawberry Fields Forever*,
*Stairway to Heaven*, and every prog rock record from 19691977.
Three tape banks are available via the ``tape`` parameter:
- ``"strings"`` (default) — the iconic MkII string section
- ``"flute"`` — breathy, haunting solo flute
- ``"choir"`` — ghostly vocal pad
**Use for:** prog rock, haunted textures, vintage orchestral color.
.. code-block:: python
# Use instrument presets (includes reverb)
strings = score.part("strings", instrument="mellotron_strings")
flute = score.part("flute", instrument="mellotron_flute")
choir = score.part("choir", instrument="mellotron_choir")
# Or select the tape directly
from pytheory import play, Synth, Tone
play(Tone.from_string("C4"), synth=Synth.MELLOTRON, tape="flute", t=3000)
.. raw:: html
<audio controls style="width:100%;margin:0.3em 0 0.5em"><source src="../_static/audio/synth_mellotron.wav" type="audio/wav"></audio>
Vibraphone Synth
~~~~~~~~~~~~~~~~
@@ -1127,6 +1291,12 @@ bagpipe, singing_bowl, singing_bowl_ring, tingsha
**Synth**: synth_lead, synth_pad, synth_bass, acid_bass, 808_bass,
granular_pad, granular_texture, vocal, choir
**Mellotron**: mellotron, mellotron_strings, mellotron_flute, mellotron_choir
**Analog**: sync_lead, sync_lead_bright, ring_mod_bell, ring_mod_metallic,
wavefold_warm, wavefold_gnarly, drift_saw, drift_square, analog_pad,
analog_bass
**Percussion**: vibraphone, marimba, xylophone, glockenspiel, tubular_bells,
timpani, crotales
@@ -1169,3 +1339,11 @@ Some practical combos worth memorizing:
long reverb and you're scoring a nature documentary.
- ``saw`` + ``pluck`` = **funk stab.** Short, sharp, bright. The
sound of Nile Rodgers' right hand.
- ``hard_sync`` + ``pluck`` = **prophet lead.** Bright formant peak
that cuts through any mix. The opening riff of every 80s synth solo.
- ``wavefold`` + ``organ`` = **west coast bass.** Warm, harmonically
rich sine-derivative that pairs beautifully with a lowpass after.
- ``drift`` + ``pad`` = **analog pad.** A sawtooth that breathes and
wobbles like a real VCO. Add chorus and reverb for Juno vibes.
- ``mellotron_synth`` + ``organ`` = **prog strings.** Haunted tape
machine. Add cathedral reverb and you're in 1972.
+26
View File
@@ -357,6 +357,32 @@ every tone knows its enharmonic spelling:
>>> Tone.from_string("C4", system="western").enharmonic is None
True
Accidental Properties
~~~~~~~~~~~~~~~~~~~~~
Check whether a tone is natural, sharp, or flat:
.. code-block:: pycon
>>> c = Tone.from_string("C4", system="western")
>>> c.is_natural
True
>>> c.is_sharp
False
>>> cs = Tone.from_string("C#4", system="western")
>>> cs.is_sharp
True
>>> cs.is_natural
False
>>> bb = Tone.from_string("Bb4", system="western")
>>> bb.is_flat
True
Useful for filtering — for example, finding all natural notes in a
scale, or counting accidentals in a melody.
Extended Enharmonics
~~~~~~~~~~~~~~~~~~~~
+1
View File
@@ -118,6 +118,7 @@ What's Inside
guide/scales
guide/chords
guide/fretboard
guide/nashville-blues-tabs
guide/systems
guide/sequencing
guide/synths
+5 -42
View File
@@ -1,17 +1,8 @@
from pytheory import Tone, Fretboard, CHARTS
from pytheory import Fretboard, CHARTS
# Create standard tuning (from high E to low E)
standard_tuning = [
Tone.from_string("E4"), # High E
Tone.from_string("B3"), # B
Tone.from_string("G3"), # G
Tone.from_string("D3"), # D
Tone.from_string("A2"), # A
Tone.from_string("E2"), # Low E
]
# Create fretboard with standard tuning
fretboard = Fretboard(tones=standard_tuning)
# Standard guitar fretboard. Since v0.43.0 fingerings read low to high
# (low E first) by default — exactly how tab is conventionally written.
fretboard = Fretboard.guitar()
# Define flat to sharp note mappings (updated to include all possible flats)
flat_to_sharp = {"Ab": "G#", "Bb": "A#", "Db": "C#", "Eb": "D#", "Gb": "F#"}
@@ -26,34 +17,6 @@ print("Standard Guitar Chord Charts:")
print("-" * 30)
def fingering_to_tab(fingering):
if not fingering:
return ""
# Create 6 strings of dashes, representing the guitar strings
strings = ["-" * 15 for _ in range(6)]
# For each string (starting from high E)
for string_num, fret in enumerate(fingering):
if fret is not None:
# Place the fret number at the correct position
if fret == 0:
strings[string_num] = "0" + strings[string_num][1:]
else:
strings[string_num] = (
"-" * (fret - 1) + str(fret) + strings[string_num][fret:]
)
# Combine strings with newlines, and add string names
tab = "e|" + strings[0] + "\n"
tab += "B|" + strings[1] + "\n"
tab += "G|" + strings[2] + "\n"
tab += "D|" + strings[3] + "\n"
tab += "A|" + strings[4] + "\n"
tab += "E|" + strings[5] + "\n"
return tab
for chord_name in all_chords:
# Store original chord name for lookup
lookup_name = chord_name
@@ -74,7 +37,7 @@ for chord_name in all_chords:
try:
fingering = chord.fingering(fretboard=fretboard)
print(f"\n{display_name}:")
print(fingering_to_tab(fingering))
print(fingering.tab())
except Exception as e:
print(f"{display_name}: Unable to calculate fingering - {str(e)}")
# Add more detailed debug information
+1 -1
View File
@@ -1,6 +1,6 @@
[project]
name = "pytheory"
version = "0.40.4"
version = "0.43.0"
description = "Music Theory for Humans"
readme = "README.md"
license = "MIT"

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