Cookbook¶
You know what you want to do; you just want the code. Each recipe below is ready to paste into a Python session — grab one, run it, and adapt it. The composition recipes build on a shared set of imports, shown at the top of that section.
Harmony and Analysis¶
Analyze a Song¶
Take the chord progression from “Let It Be” (C G Am F) and analyze it in the key of C major:
>>> 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 is built from the I, IV, and V chords. Here it is in the key of A:
>>> 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 Key class builds diatonic chords for any
key and lets you pull progressions by Roman numeral or Nashville number:
>>> 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']
Voice Leading Between Chords¶
Find the smoothest path from one chord to the next — each voice moves the minimum distance:
>>> 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:
>>> 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 away — they share the same tritone interval:
>>> 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!)
Borrowed Chords and Secondary Dominants¶
Add color by borrowing from the parallel key or building secondary dominants that approach other scale degrees:
>>> 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'
Scales and Keys¶
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):
>>> 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']
Key Signatures and Detection¶
View the accidentals in any key, or detect the key from a set of notes:
>>> 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). Both come back
as Key objects, so you can keep working with
them:
>>> from pytheory import Key
>>> c = Key("C", "major")
>>> c.relative
<Key A minor>
>>> c.parallel
<Key C minor>
>>> c.relative.chords[:4]
['A minor', 'B diminished', 'C major', 'D minor']
World Scales¶
Explore scales from Indian, Arabic, and Japanese traditions:
>>> 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']
Those are 12-TET approximations — what a piano can play. For the real
thing, reach for the dedicated Maqam (Arabic,
true quarter tones) and Raga (Indian, shruti
just intonation) classes:
>>> from pytheory import Maqam, Raga
>>> rast = Maqam.get("rast")
>>> rast.degree_names() # ↓ marks the quarter-flat notes
['Do', 'Re', 'Mi↓', 'Fa', 'Sol', 'La', 'Si↓']
>>> rast.note_names("C") # nearest 12-TET names
['C', 'D', 'E', 'F', 'G', 'A', 'Bb']
>>> bhairav = Raga.get("bhairav")
>>> bhairav.note_names("C")
['C', 'C#', 'E', 'F', 'G', 'G#', 'B']
>>> bhairav.aroha_swaras() # ascending line
['S', 'r', 'G', 'm', 'P', 'd', 'N', "S'"]
Both classes carry the real tuning: rast.maqam_table("C") lists each
degree’s just ratio and how many cents it sits off the piano, and
rast.play(tonic="C4") / bhairav.play(sa="C4") sound them out with
the quarter tones and shrutis intact. See Musical Systems for the full
catalogue of maqamat, ragas, and tuning systems.
Guitar¶
Guitar Chord Chart¶
Generate fingerings for guitar and ukulele with
Fretboard:
>>> from pytheory import Fretboard
>>> fb = Fretboard.guitar()
>>> fb.chord("C")
Fingering(E=x, A=3, D=2, G=0, B=1, e=0)
>>> fb.chord("G")
Fingering(E=3, A=2, D=0, G=0, B=0, e=3)
>>> fb.chord("Am")
Fingering(E=x, A=0, D=2, G=2, B=1, e=0)
>>> fb.chord("D")
Fingering(E=x, A=x, D=0, G=2, B=3, e=2)
>>> uke = Fretboard.ukulele()
>>> uke.chord("C")
Fingering(G=0, C=0, E=0, A=3)
>>> uke.chord("G")
Fingering(G=0, C=2, E=3, A=2)
Visualize a Scale on Guitar¶
See where the notes fall across the fretboard — E minor pentatonic, the most-played scale in rock:
>>> 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 |
A| A | - | B | - | - | D | - | E | - | - | G | - | A |
D| D | - | E | - | - | G | - | A | - | B | - | - | D |
G| G | - | A | - | B | - | - | D | - | E | - | - | G |
B| B | - | - | D | - | E | - | - | G | - | A | - | B |
E| E | - | - | G | - | A | - | B | - | - | D | - | E |
Tones and Physics¶
Explore an Interval¶
Start from A4 (440 Hz) and walk through intervals, checking names and frequency ratios:
>>> 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 is the backbone of Western harmony — each step adds one sharp or flat:
>>> 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']
The Overtone Series¶
Every musical tone contains a stack of harmonics — the physics behind why intervals sound consonant:
>>> 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:
>>> 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
Composition Recipes¶
These recipes go beyond theory into actual music-making. They all share the same handful of imports:
from pytheory import Score, Pattern, Duration, Chord, Key
from pytheory.play import play_score
Acid House Track¶
303-style acid with sidechain pump:
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)
Dub Reggae with Delay Madness¶
Sparse notes into infinite echo:
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)
Jazz Ballad with Humanize¶
The difference between a robot and a musician:
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)
Song with Sections¶
Define once, arrange freely:
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")
Lead Sheet with Lyrics¶
Attach words to a vocal line with the lyric= argument — one syllable
per sounding note — and they flow straight into the notation. Here a
melody becomes an ABC lead sheet you can paste into any ABC viewer:
>>> from pytheory import Score, Duration
>>> score = Score("4/4", bpm=120)
>>> vox = score.part("vocal", synth="sine", envelope="pad")
>>> words = [("Hap-", "C5"), ("py", "C5"), ("birth-", "D5"),
... ("day", "C5"), ("to", "F5"), ("you", "E5")]
>>> for syllable, note in words:
... vox.add(note, Duration.QUARTER, lyric=syllable)
>>> print(score.to_abc(title="Birthday", key="C"))
X:1
T:Birthday
M:4/4
Q:1/4=120
L:1/8
K:C
c2 c2 d2 c2 | f2 e2 |
w: Hap\- py birth\- day | to you
The same lyrics render to LilyPond (score.to_lilypond(...)) and
MusicXML (score.to_musicxml(...)) too, so you can open the lead
sheet in MuseScore, Sibelius, or Finale.
Shape the performance with articulation= — "staccato",
"accent", "marcato", "tenuto", or "fermata". Each one
changes how the note is played and prints the matching mark in the
score:
lead = score.part("lead", synth="triangle")
lead.add("C5", Duration.QUARTER, articulation="staccato")
lead.add("E5", Duration.QUARTER, articulation="tenuto")
lead.add("G5", Duration.HALF, articulation="fermata")
See Playback and Export and Effects for the full performance toolkit.
Export Everything to MIDI¶
The whole point — sketch fast, finish in your DAW:
# 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)
# ...and read MIDI back in — same-onset notes regroup into chords
from pytheory import Score
sketch = Score.from_midi("track.mid", synth="sine", envelope="pluck")
Already have a MIDI file you want to understand? The CLI detects the key
and prints a Roman-numeral chord timeline (add --json to pipe it
elsewhere):
$ pytheory analyze song.mid
Listening Recipes¶
These start from a recording instead of code — see Listening — Microphone In for the full guide.
Voice Memo → Sheet Music¶
You hummed something into your phone and want it on a staff. The whole pipeline — recording to engraved PDF:
# 1. Transcribe it — .m4a voice memos load directly
from pytheory import Score
score = Score.from_wav(
"hum.m4a",
quantize=0.25, # snap to sixteenths for a clean chart
fmin=100, # floor above vocal fry — kills the creaky
fmax=350, # sub-bass blips at note starts and ends
) # tempo is estimated; pass bpm= to pin it
# 2. Sanity-check what it heard
print(f"estimated tempo: {score.bpm}")
for n in score.parts["melody"].notes:
print(n.tone or "rest", n.beats)
# 3. Engrave it — three formats, pick the tool you live in
with open("hum.ly", "w") as f: # LilyPond → engraved PDF
f.write(score.to_lilypond(title="My Hum", key="G", mode="major"))
with open("hum.musicxml", "w") as f: # opens in MuseScore/Sibelius/Finale
f.write(score.to_musicxml(title="My Hum"))
with open("hum.abc", "w") as f: # plain-text, easy to paste and share
f.write(score.to_abc(title="My Hum", key="G"))
# ...and keep the MIDI for your DAW while you're at it
score.save_midi("hum.mid")
# 4. Compile to PDF (brew install lilypond)
lilypond --pdf hum.ly
A real hum is messy — breath noise, fry, scoops between notes. If
the transcription has stray notes, the fix is almost always the
pitch range: print the detected notes, find the artifacts (very low,
very quiet, at phrase edges), and tighten fmin/fmax around
the actual melody. Two or three runs gets it clean.
What Chords Is This Song Playing?¶
You’re trying to learn a song by ear. Let the chromagram do the
listening — it reads the chord progression (including 7ths, sus
chords, and inversions like C/E) straight off the audio:
from pytheory.audio import load_wav, detect_chords, estimate_tempo
samples, sr = load_wav("song.m4a")
bpm = estimate_tempo(samples, sr) or 120
for start, dur, symbol in detect_chords(samples, sr, bpm=bpm):
print(f"beat {start:5g} {symbol:8s} ({dur:g} beats)")
Or get the whole arrangement — chords plus melody, bassline, drums, and the key — as an editable Score, and hear PyTheory’s cover of it:
from pytheory import Score
from pytheory.play import play_score
score = Score.from_wav("song.m4a", split=True, quantize=0.5)
print(score.detected_key) # e.g. <Key C major>
play_score(score) # instant cover version
Playing Live¶
Jam in Sync with Ableton Link¶
Someone in the room is running Ableton Live (or an iOS drum app, or
anything else that speaks Ableton Link).
Join their session — tempo, beat grid, and transport sync
automatically, peer-to-peer (requires pip install "pytheory[link]"):
from pytheory.live import LiveEngine
engine = LiveEngine()
engine.channel(1, instrument="electric_piano", reverb=0.3)
engine.channel(2, instrument="synth_bass", lowpass=800)
engine.drums("house", volume=0.5) # locks to the shared beat grid
engine.enable_link() # find the session on the network
engine.start() # play your MIDI keyboard in time
Or as one command, with the TUI dashboard:
$ pytheory live --link
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.