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