mirror of
https://github.com/kennethreitz/pytheory.git
synced 2026-06-05 06:46:14 +00:00
Add 12 example scripts showcasing pytheory features
- circle_of_fifths.py — visualize keys around the circle - chord_identifier.py — identify chords from notes and fingerings - key_explorer.py — explore keys, signatures, progressions, borrowed chords - temperament_comparison.py — compare equal, Pythagorean, and meantone - chord_tension.py — analyze tension, consonance, and voice leading - world_scales.py — scales from 6 musical traditions - fretboard_explorer.py — instruments, tunings, capo transposition - midi_converter.py — MIDI ↔ note ↔ frequency reference - progression_writer.py — famous progressions, Nashville numbers, random generation - interval_trainer.py — interval names, songs, and consonance ranking - overtone_series.py — harmonics and why chords sound good - key_detection.py — detect keys from melodies and chord progressions Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,46 @@
|
||||
"""Identify chords from notes or guitar fingerings."""
|
||||
|
||||
from pytheory import Chord, Fretboard
|
||||
|
||||
print("=== Chord Identification from Notes ===")
|
||||
print()
|
||||
|
||||
test_chords = [
|
||||
("C", "E", "G"),
|
||||
("A", "C", "E"),
|
||||
("G", "B", "D", "F"),
|
||||
("D", "F#", "A"),
|
||||
("Bb", "D", "F"),
|
||||
("E", "G#", "B"),
|
||||
("C", "Eb", "Gb"),
|
||||
("C", "G"),
|
||||
("C", "F", "G"),
|
||||
("C", "D", "G"),
|
||||
]
|
||||
|
||||
for notes in test_chords:
|
||||
chord = Chord.from_tones(*notes)
|
||||
name = chord.identify() or "Unknown"
|
||||
print(f" {', '.join(notes):20s} → {name}")
|
||||
|
||||
print()
|
||||
print("=== Chord Identification from Guitar Fingerings ===")
|
||||
print()
|
||||
|
||||
fb = Fretboard.guitar()
|
||||
|
||||
# Common guitar chord shapes
|
||||
shapes = [
|
||||
("Open C", (0, 1, 0, 2, 3, 0)),
|
||||
("Open G", (3, 0, 0, 0, 2, 3)),
|
||||
("Open D", (2, 3, 2, 0, 0, 0)),
|
||||
("Open Am", (0, 1, 2, 2, 0, 0)),
|
||||
("Open Em", (0, 0, 0, 2, 2, 0)),
|
||||
("Barre F", (1, 1, 2, 3, 3, 1)),
|
||||
("Power E5", (0, 0, 0, 0, 2, 0)),
|
||||
]
|
||||
|
||||
for label, positions in shapes:
|
||||
f = fb.fingering(*positions)
|
||||
name = f.identify() or "Unknown"
|
||||
print(f" {label:12s} {f} → {name}")
|
||||
@@ -0,0 +1,52 @@
|
||||
"""Analyze harmonic tension and resolution across chords."""
|
||||
|
||||
from pytheory import Chord
|
||||
|
||||
print("Chord Tension Analysis")
|
||||
print("=" * 70)
|
||||
print()
|
||||
print(f"{'Chord':>20s} {'Tension':>8s} {'Harmony':>8s} {'Dissonance':>11s} {'Notes'}")
|
||||
print(f"{'─' * 20} {'─' * 8} {'─' * 8} {'─' * 11} {'─' * 15}")
|
||||
|
||||
chords = [
|
||||
# Stable chords
|
||||
"C", "Am",
|
||||
# Moderate tension
|
||||
"Dm7", "Cmaj7",
|
||||
# High tension
|
||||
"G7", "Bdim",
|
||||
# Extended
|
||||
"Am7", "Cmaj9",
|
||||
]
|
||||
|
||||
for name in chords:
|
||||
chord = Chord.from_name(name)
|
||||
t = chord.tension
|
||||
tones = " ".join(tone.name for tone in chord.tones)
|
||||
print(
|
||||
f"{name:>20s} {t['score']:>8.2f} {chord.harmony:>8.4f}"
|
||||
f" {chord.dissonance:>11.4f} {tones}"
|
||||
)
|
||||
|
||||
# Show the V7 → I resolution
|
||||
print()
|
||||
print("─" * 70)
|
||||
print()
|
||||
print("The V7 → I resolution (the strongest pull in tonal music):")
|
||||
print()
|
||||
|
||||
g7 = Chord.from_name("G7")
|
||||
c = Chord.from_name("C")
|
||||
|
||||
print(f" G7 (dominant): tension={g7.tension['score']:.2f} "
|
||||
f"tritones={g7.tension['tritones']} "
|
||||
f"dominant_function={g7.tension['has_dominant_function']}")
|
||||
print(f" C (tonic): tension={c.tension['score']:.2f} "
|
||||
f"tritones={c.tension['tritones']} "
|
||||
f"dominant_function={c.tension['has_dominant_function']}")
|
||||
|
||||
print()
|
||||
print("Voice leading (G7 → C):")
|
||||
for src, dst, motion in g7.voice_leading(c):
|
||||
direction = "↑" if motion > 0 else "↓" if motion < 0 else "="
|
||||
print(f" {src.name:3s} → {dst.name:3s} ({direction} {abs(motion)} semitones)")
|
||||
@@ -0,0 +1,34 @@
|
||||
"""Visualize the circle of fifths with key signatures."""
|
||||
|
||||
from pytheory import Tone, Key
|
||||
|
||||
c = Tone.from_string("C4", system="western")
|
||||
|
||||
print("╔══════════════════════════════════════════════╗")
|
||||
print("║ THE CIRCLE OF FIFTHS ║")
|
||||
print("╠══════════════════════════════════════════════╣")
|
||||
print("║ Key Sig Accidentals ║")
|
||||
print("╠══════════════════════════════════════════════╣")
|
||||
|
||||
for tone in c.circle_of_fifths():
|
||||
key = Key(tone.name, "major")
|
||||
sig = key.signature
|
||||
relative = key.relative
|
||||
|
||||
if sig["sharps"]:
|
||||
mark = f'{sig["sharps"]}#'
|
||||
elif sig["flats"]:
|
||||
mark = f'{sig["flats"]}b'
|
||||
else:
|
||||
mark = "--"
|
||||
|
||||
accidentals = ", ".join(sig["accidentals"]) if sig["accidentals"] else "none"
|
||||
print(f"║ {tone.name:3s} {mark:3s} {accidentals:20s} rel: {relative.tonic_name} {relative.mode:5s} ║")
|
||||
|
||||
print("╚══════════════════════════════════════════════╝")
|
||||
|
||||
# Show that 12 fifths returns to the start
|
||||
print()
|
||||
print("Proof: 12 perfect fifths cycle through all 12 tones")
|
||||
names = [t.name for t in c.circle_of_fifths()]
|
||||
print(f" {' → '.join(names)} → {names[0]}")
|
||||
@@ -0,0 +1,74 @@
|
||||
"""Explore instruments, tunings, and chord fingerings."""
|
||||
|
||||
from pytheory import Fretboard, CHARTS
|
||||
|
||||
# ── Compare Instruments ─────────────────────────────────────────────────
|
||||
|
||||
print("Instrument Tunings")
|
||||
print("=" * 55)
|
||||
|
||||
instruments = [
|
||||
("Guitar (standard)", Fretboard.guitar()),
|
||||
("Guitar (drop D)", Fretboard.guitar("drop d")),
|
||||
("Guitar (open G)", Fretboard.guitar("open g")),
|
||||
("Guitar (DADGAD)", Fretboard.guitar("dadgad")),
|
||||
("Bass", Fretboard.bass()),
|
||||
("Ukulele", Fretboard.ukulele()),
|
||||
("Mandolin", Fretboard.mandolin()),
|
||||
("Violin", Fretboard.violin()),
|
||||
("Banjo", Fretboard.banjo()),
|
||||
("Bouzouki (Irish)", Fretboard.bouzouki()),
|
||||
]
|
||||
|
||||
for name, fb in instruments:
|
||||
tuning = " ".join(t.full_name for t in fb.tones)
|
||||
print(f" {name:22s} {tuning}")
|
||||
|
||||
# ── Guitar Chord Chart ──────────────────────────────────────────────────
|
||||
|
||||
print()
|
||||
print("Guitar Chord Chart (standard tuning)")
|
||||
print("=" * 55)
|
||||
|
||||
fb = Fretboard.guitar()
|
||||
chart = CHARTS["western"]
|
||||
|
||||
for chord_name in ["C", "G", "D", "Am", "Em", "F", "A", "E", "Dm", "G7", "C7", "Am7"]:
|
||||
f = chart[chord_name].fingering(fretboard=fb)
|
||||
print(f" {chord_name:5s} {f}")
|
||||
|
||||
# ── Capo Magic ──────────────────────────────────────────────────────────
|
||||
|
||||
print()
|
||||
print("Capo Transposition")
|
||||
print("=" * 55)
|
||||
print(" Playing open chord shapes with a capo changes the key:")
|
||||
print()
|
||||
|
||||
open_shapes = ["C", "G", "D", "Am", "Em"]
|
||||
|
||||
for capo_fret in range(1, 6):
|
||||
fb_capo = Fretboard.guitar(capo=capo_fret)
|
||||
results = []
|
||||
for shape in open_shapes:
|
||||
f = chart[shape].fingering(fretboard=fb_capo)
|
||||
actual = f.identify() or "?"
|
||||
results.append(f"{shape}→{actual.split()[0]}")
|
||||
print(f" Capo {capo_fret}: {', '.join(results)}")
|
||||
|
||||
# ── Same Chord on Different Instruments ─────────────────────────────────
|
||||
|
||||
print()
|
||||
print("C Major on Different Instruments")
|
||||
print("=" * 55)
|
||||
|
||||
c_chord = chart["C"]
|
||||
for name, fb in [("Guitar", Fretboard.guitar()),
|
||||
("Ukulele", Fretboard.ukulele()),
|
||||
("Mandolin", Fretboard.mandolin()),
|
||||
("Banjo", Fretboard.banjo())]:
|
||||
try:
|
||||
f = c_chord.fingering(fretboard=fb)
|
||||
print(f" {name:12s} {f}")
|
||||
except Exception:
|
||||
print(f" {name:12s} (not available for this tuning)")
|
||||
@@ -0,0 +1,93 @@
|
||||
"""Learn intervals — names, sounds, and relationships."""
|
||||
|
||||
from pytheory import Tone, Chord, Interval
|
||||
|
||||
c4 = Tone.from_string("C4", system="western")
|
||||
|
||||
# ── Interval Reference ──────────────────────────────────────────────────
|
||||
|
||||
print("Interval Reference (from C4)")
|
||||
print("=" * 70)
|
||||
print()
|
||||
print(f"{'Semitones':>10s} {'Note':>5s} {'Interval Name':>18s} {'Sound / Song'}")
|
||||
print(f"{'─' * 10} {'─' * 5} {'─' * 18} {'─' * 30}")
|
||||
|
||||
songs = {
|
||||
0: "Same note",
|
||||
1: "Jaws",
|
||||
2: "Happy Birthday",
|
||||
3: "Greensleeves",
|
||||
4: "Here Comes the Sun",
|
||||
5: "Here Comes the Bride",
|
||||
6: "The Simpsons",
|
||||
7: "Star Wars (main theme)",
|
||||
8: "Love Story",
|
||||
9: "My Bonnie Lies Over the Ocean",
|
||||
10: "Somewhere (West Side Story)",
|
||||
11: "Take On Me (chorus)",
|
||||
12: "Somewhere Over the Rainbow",
|
||||
}
|
||||
|
||||
for semitones in range(13):
|
||||
tone = c4 + semitones
|
||||
name = c4.interval_to(tone)
|
||||
song = songs.get(semitones, "")
|
||||
print(f"{semitones:>10d} {tone.name:>5s} {name:>18s} {song}")
|
||||
|
||||
# ── Interval Constants ──────────────────────────────────────────────────
|
||||
|
||||
print()
|
||||
print("Interval Constants (pytheory.Interval)")
|
||||
print("=" * 40)
|
||||
|
||||
constants = [
|
||||
("UNISON", Interval.UNISON),
|
||||
("MINOR_SECOND", Interval.MINOR_SECOND),
|
||||
("MAJOR_SECOND", Interval.MAJOR_SECOND),
|
||||
("MINOR_THIRD", Interval.MINOR_THIRD),
|
||||
("MAJOR_THIRD", Interval.MAJOR_THIRD),
|
||||
("PERFECT_FOURTH", Interval.PERFECT_FOURTH),
|
||||
("TRITONE", Interval.TRITONE),
|
||||
("PERFECT_FIFTH", Interval.PERFECT_FIFTH),
|
||||
("MINOR_SIXTH", Interval.MINOR_SIXTH),
|
||||
("MAJOR_SIXTH", Interval.MAJOR_SIXTH),
|
||||
("MINOR_SEVENTH", Interval.MINOR_SEVENTH),
|
||||
("MAJOR_SEVENTH", Interval.MAJOR_SEVENTH),
|
||||
("OCTAVE", Interval.OCTAVE),
|
||||
]
|
||||
|
||||
for name, value in constants:
|
||||
print(f" Interval.{name:16s} = {value}")
|
||||
|
||||
# ── Compound Intervals ─────────────────────────────────────────────────
|
||||
|
||||
print()
|
||||
print("Compound Intervals (beyond one octave)")
|
||||
print("=" * 50)
|
||||
|
||||
for semitones in [13, 14, 15, 16, 19, 24]:
|
||||
tone = c4 + semitones
|
||||
name = c4.interval_to(tone)
|
||||
print(f" {semitones:2d} semitones {tone.full_name:5s} {name}")
|
||||
|
||||
# ── Consonance Ranking ──────────────────────────────────────────────────
|
||||
|
||||
print()
|
||||
print("Intervals Ranked by Consonance")
|
||||
print("=" * 50)
|
||||
|
||||
intervals = []
|
||||
for semitones in range(1, 13):
|
||||
tone = c4 + semitones
|
||||
dyad = Chord.from_tones("C", tone.name)
|
||||
name = c4.interval_to(tone)
|
||||
intervals.append((dyad.harmony, dyad.dissonance, semitones, name))
|
||||
|
||||
# Sort by harmony score (descending)
|
||||
intervals.sort(key=lambda x: x[0], reverse=True)
|
||||
|
||||
print(f"{'Rank':>5s} {'Interval':>18s} {'Harmony':>8s} {'Dissonance':>11s}")
|
||||
print(f"{'─' * 5} {'─' * 18} {'─' * 8} {'─' * 11}")
|
||||
|
||||
for rank, (harmony, dissonance, _, name) in enumerate(intervals, 1):
|
||||
print(f"{rank:>5d} {name:>18s} {harmony:>8.4f} {dissonance:>11.4f}")
|
||||
@@ -0,0 +1,64 @@
|
||||
"""Detect the key of a melody or chord progression."""
|
||||
|
||||
from pytheory import Key, Chord
|
||||
|
||||
print("Key Detection")
|
||||
print("=" * 55)
|
||||
print()
|
||||
|
||||
# ── Detect from Melody Notes ────────────────────────────────────────────
|
||||
|
||||
melodies = [
|
||||
("Twinkle Twinkle", ["C", "G", "A", "F", "E", "D"]),
|
||||
("Happy Birthday", ["G", "A", "B", "C", "D", "F#"]),
|
||||
("Yesterday", ["F", "E", "D", "C", "Bb", "A", "G"]),
|
||||
("Minor melody", ["A", "B", "C", "D", "E", "F", "G"]),
|
||||
("Blues lick", ["E", "G", "A", "B", "D"]),
|
||||
("Chromatic fragment", ["C", "C#", "D", "D#", "E"]),
|
||||
]
|
||||
|
||||
print("Detecting key from melody notes:")
|
||||
print()
|
||||
for label, notes in melodies:
|
||||
key = Key.detect(*notes)
|
||||
print(f" {label:22s} {', '.join(notes):30s} → {key}")
|
||||
|
||||
# ── Detect from Chord Progression ──────────────────────────────────────
|
||||
|
||||
print()
|
||||
print("Detecting key from chord tones:")
|
||||
print()
|
||||
|
||||
progressions = [
|
||||
("I-IV-V", [("C", "E", "G"), ("F", "A", "C"), ("G", "B", "D")]),
|
||||
("Pop in G", [("G", "B", "D"), ("D", "F#", "A"), ("E", "G", "B"), ("C", "E", "G")]),
|
||||
("Jazz ii-V-I", [("D", "F", "A"), ("G", "B", "D", "F"), ("C", "E", "G", "B")]),
|
||||
]
|
||||
|
||||
for label, chord_tones in progressions:
|
||||
# Collect all unique note names
|
||||
all_notes = set()
|
||||
for tones in chord_tones:
|
||||
all_notes.update(tones)
|
||||
|
||||
key = Key.detect(*all_notes)
|
||||
chord_names = [Chord.from_tones(*t).identify() for t in chord_tones]
|
||||
print(f" {label:15s} {' → '.join(chord_names):40s} → {key}")
|
||||
|
||||
# ── All 24 Keys ─────────────────────────────────────────────────────────
|
||||
|
||||
print()
|
||||
print("All 24 Major and Minor Keys")
|
||||
print("=" * 55)
|
||||
print()
|
||||
|
||||
for key in Key.all_keys():
|
||||
sig = key.signature
|
||||
acc = ", ".join(sig["accidentals"]) if sig["accidentals"] else "none"
|
||||
rel = key.relative
|
||||
print(
|
||||
f" {str(key):12s} "
|
||||
f"{sig['sharps']}# {sig['flats']}b "
|
||||
f"({acc:15s}) "
|
||||
f"rel: {rel}"
|
||||
)
|
||||
@@ -0,0 +1,58 @@
|
||||
"""Explore a key — its chords, progressions, and relationships."""
|
||||
|
||||
from pytheory import Key
|
||||
|
||||
def explore_key(tonic, mode="major"):
|
||||
key = Key(tonic, mode)
|
||||
sig = key.signature
|
||||
acc = ", ".join(sig["accidentals"]) or "none"
|
||||
|
||||
print(f"{'=' * 60}")
|
||||
print(f" {key}")
|
||||
print(f"{'=' * 60}")
|
||||
print()
|
||||
print(f" Scale: {' '.join(key.note_names)}")
|
||||
print(f" Signature: {sig['sharps']} sharps, {sig['flats']} flats ({acc})")
|
||||
print(f" Relative: {key.relative}")
|
||||
print(f" Parallel: {key.parallel}")
|
||||
print()
|
||||
|
||||
# Diatonic triads
|
||||
print(" Diatonic Triads:")
|
||||
for chord in key.scale.harmonize():
|
||||
numeral = chord.analyze(tonic, mode) or "?"
|
||||
print(f" {numeral:6s} {chord.identify()}")
|
||||
print()
|
||||
|
||||
# Seventh chords
|
||||
print(" Seventh Chords:")
|
||||
for name in key.seventh_chords:
|
||||
print(f" {name}")
|
||||
print()
|
||||
|
||||
# Common progressions
|
||||
print(" Common Progressions:")
|
||||
progressions = {
|
||||
"Pop": ("I", "V", "vi", "IV"),
|
||||
"Blues": ("I", "IV", "V"),
|
||||
"50s": ("I", "vi", "IV", "V"),
|
||||
"Jazz": ("ii", "V", "I"),
|
||||
}
|
||||
for label, numerals in progressions.items():
|
||||
chords = key.progression(*numerals)
|
||||
names = [c.identify() for c in chords]
|
||||
print(f" {label:8s} {' → '.join(numerals):20s} {' → '.join(names)}")
|
||||
print()
|
||||
|
||||
# Borrowed chords
|
||||
borrowed = key.borrowed_chords
|
||||
if borrowed:
|
||||
print(f" Borrowed from {key.parallel}:")
|
||||
for chord in borrowed[:4]:
|
||||
print(f" {chord.identify()}")
|
||||
print()
|
||||
|
||||
|
||||
# Explore several keys
|
||||
for tonic, mode in [("C", "major"), ("G", "major"), ("A", "minor"), ("E", "major")]:
|
||||
explore_key(tonic, mode)
|
||||
@@ -0,0 +1,35 @@
|
||||
"""Convert between MIDI note numbers, frequencies, and note names."""
|
||||
|
||||
from pytheory import Tone
|
||||
|
||||
print("MIDI ↔ Note ↔ Frequency Reference")
|
||||
print("=" * 50)
|
||||
print()
|
||||
print(f"{'MIDI':>5s} {'Note':>5s} {'Freq (Hz)':>10s} {'Octave':>6s}")
|
||||
print(f"{'─' * 5} {'─' * 5} {'─' * 10} {'─' * 6}")
|
||||
|
||||
# Show all notes from C2 to C7
|
||||
for midi in range(36, 97):
|
||||
tone = Tone.from_midi(midi)
|
||||
freq = tone.frequency
|
||||
print(f"{midi:>5d} {tone.full_name:>5s} {freq:>10.2f} {tone.octave:>6d}")
|
||||
|
||||
# Useful reference points
|
||||
print()
|
||||
print("Key Reference Points:")
|
||||
print(f" Lowest piano note: A0 = MIDI {Tone.from_string('A0', system='western').midi}")
|
||||
print(f" Middle C: C4 = MIDI {Tone.from_string('C4', system='western').midi}")
|
||||
print(f" Concert A: A4 = MIDI {Tone.from_string('A4', system='western').midi}")
|
||||
print(f" Highest piano note: C8 = MIDI {Tone.from_string('C8', system='western').midi}")
|
||||
|
||||
# Round-trip demo
|
||||
print()
|
||||
print("Round-trip conversions:")
|
||||
for start in ["C4", "A4", "F#3", "Bb5"]:
|
||||
tone = Tone.from_string(start, system="western")
|
||||
midi = tone.midi
|
||||
freq = tone.frequency
|
||||
from_midi = Tone.from_midi(midi)
|
||||
from_freq = Tone.from_frequency(freq)
|
||||
print(f" {start:4s} → MIDI {midi} → {from_midi.full_name:4s} | "
|
||||
f"{start:4s} → {freq:.2f} Hz → {from_freq.full_name}")
|
||||
@@ -0,0 +1,68 @@
|
||||
"""Explore the overtone series — nature's chord."""
|
||||
|
||||
from pytheory import Tone, Chord
|
||||
|
||||
a4 = Tone.from_string("A4", system="western")
|
||||
|
||||
print("The Overtone Series")
|
||||
print("=" * 65)
|
||||
print()
|
||||
print("When you play a note, you're actually hearing many frequencies")
|
||||
print("at once. The fundamental plus its integer multiples:")
|
||||
print()
|
||||
print(f"{'Harmonic':>9s} {'Frequency':>10s} {'Nearest Note':>13s} {'Interval from Root'}")
|
||||
print(f"{'─' * 9} {'─' * 10} {'─' * 13} {'─' * 25}")
|
||||
|
||||
overtones = a4.overtones(16)
|
||||
|
||||
for i, hz in enumerate(overtones, 1):
|
||||
nearest = Tone.from_frequency(hz)
|
||||
if i == 1:
|
||||
interval = "Fundamental"
|
||||
else:
|
||||
interval = a4.interval_to(nearest)
|
||||
print(f"{i:>9d} {hz:>10.1f} {nearest.full_name:>13s} {interval}")
|
||||
|
||||
# ── Why Chords Sound Good ───────────────────────────────────────────────
|
||||
|
||||
print()
|
||||
print("Why the Major Triad Sounds 'Natural'")
|
||||
print("=" * 65)
|
||||
print()
|
||||
print("The first 6 harmonics contain: root, octave, 5th, 2nd octave, 3rd, 5th")
|
||||
print("That's a major triad! The major chord is literally embedded in physics.")
|
||||
print()
|
||||
|
||||
c4 = Tone.from_string("C4", system="western")
|
||||
harmonics = c4.overtones(6)
|
||||
harmonic_names = [Tone.from_frequency(hz).name for hz in harmonics]
|
||||
unique = []
|
||||
for n in harmonic_names:
|
||||
if n not in unique:
|
||||
unique.append(n)
|
||||
print(f" First 6 harmonics of C: {', '.join(harmonic_names)}")
|
||||
print(f" Unique pitch classes: {', '.join(unique)}")
|
||||
print(f" C major triad: C, E, G")
|
||||
print()
|
||||
|
||||
# ── Shared Overtones = Consonance ───────────────────────────────────────
|
||||
|
||||
print("Shared Overtones Between Intervals")
|
||||
print("=" * 65)
|
||||
print()
|
||||
print("The more overtones two notes share, the more consonant they sound.")
|
||||
print()
|
||||
|
||||
root = Tone.from_string("C4", system="western")
|
||||
root_overtones = set(round(h, 1) for h in root.overtones(12))
|
||||
|
||||
for semitones, label in [(7, "Perfect 5th (C→G)"),
|
||||
(4, "Major 3rd (C→E)"),
|
||||
(5, "Perfect 4th (C→F)"),
|
||||
(3, "Minor 3rd (C→Eb)"),
|
||||
(6, "Tritone (C→F#)"),
|
||||
(1, "Minor 2nd (C→C#)")]:
|
||||
other = root + semitones
|
||||
other_overtones = set(round(h, 1) for h in other.overtones(12))
|
||||
shared = root_overtones & other_overtones
|
||||
print(f" {label:25s} {len(shared):2d} shared overtones (of first 12)")
|
||||
@@ -0,0 +1,81 @@
|
||||
"""Build and analyze chord progressions in any key."""
|
||||
|
||||
from pytheory import Key, Chord
|
||||
|
||||
def show_progression(key, numerals, label=""):
|
||||
chords = key.progression(*numerals)
|
||||
if label:
|
||||
print(f" {label}")
|
||||
print(f" Key: {key}")
|
||||
print(f" Progression: {' – '.join(numerals)}")
|
||||
print()
|
||||
for numeral, chord in zip(numerals, chords):
|
||||
t = chord.tension
|
||||
print(
|
||||
f" {numeral:6s} {chord.identify():20s} "
|
||||
f"tension={t['score']:.2f} "
|
||||
f"{'*** DOMINANT ***' if t['has_dominant_function'] else ''}"
|
||||
)
|
||||
print()
|
||||
|
||||
|
||||
# ── Famous Progressions ─────────────────────────────────────────────────
|
||||
|
||||
print("Famous Chord Progressions")
|
||||
print("=" * 65)
|
||||
print()
|
||||
|
||||
key_c = Key("C", "major")
|
||||
|
||||
show_progression(key_c, ("I", "V", "vi", "IV"),
|
||||
"The Pop Progression (Let It Be, No Woman No Cry, Someone Like You)")
|
||||
|
||||
show_progression(key_c, ("I", "vi", "IV", "V"),
|
||||
"The 50s Progression (Stand By Me, Every Breath You Take)")
|
||||
|
||||
show_progression(key_c, ("ii", "V", "I"),
|
||||
"Jazz ii–V–I (the backbone of jazz harmony)")
|
||||
|
||||
show_progression(key_c, ("I", "IV", "V", "I"),
|
||||
"The Three-Chord Trick (blues, rock, country)")
|
||||
|
||||
# ── Same Progression in Different Keys ──────────────────────────────────
|
||||
|
||||
print("─" * 65)
|
||||
print()
|
||||
print("I – V – vi – IV in every key:")
|
||||
print()
|
||||
|
||||
for tonic in ["C", "G", "D", "A", "E", "F", "Bb", "Eb"]:
|
||||
key = Key(tonic, "major")
|
||||
chords = key.progression("I", "V", "vi", "IV")
|
||||
names = [c.identify() for c in chords]
|
||||
print(f" {tonic} major: {' → '.join(names)}")
|
||||
|
||||
# ── Nashville Number System ─────────────────────────────────────────────
|
||||
|
||||
print()
|
||||
print("─" * 65)
|
||||
print()
|
||||
print("Nashville Number System:")
|
||||
print(" (Same thing as Roman numerals, but with integers)")
|
||||
print()
|
||||
|
||||
key_g = Key("G", "major")
|
||||
chords = key_g.nashville(1, 5, 6, 4)
|
||||
names = [c.identify() for c in chords]
|
||||
print(f" G major: 1 – 5 – 6 – 4 → {' → '.join(names)}")
|
||||
|
||||
# ── Random Progression Generator ────────────────────────────────────────
|
||||
|
||||
print()
|
||||
print("─" * 65)
|
||||
print()
|
||||
print("Random 8-bar progressions:")
|
||||
print()
|
||||
|
||||
for _ in range(3):
|
||||
key = Key("C", "major")
|
||||
chords = key.random_progression(8)
|
||||
names = [c.identify().split()[0] for c in chords] # Just root names
|
||||
print(f" | {' | '.join(names)} |")
|
||||
@@ -0,0 +1,49 @@
|
||||
"""Compare equal, Pythagorean, and meantone temperaments."""
|
||||
|
||||
import math
|
||||
from pytheory import Tone
|
||||
|
||||
a4 = Tone.from_string("A4", system="western")
|
||||
|
||||
print("Temperament Comparison")
|
||||
print("=" * 75)
|
||||
print()
|
||||
print(f"{'Note':>5s} {'Equal (Hz)':>12s} {'Pythag (Hz)':>12s} {'Meantone (Hz)':>14s} {'P diff':>8s} {'M diff':>8s}")
|
||||
print(f"{'─' * 5} {'─' * 12} {'─' * 12} {'─' * 14} {'─' * 8} {'─' * 8}")
|
||||
|
||||
for semitones in range(13):
|
||||
tone = a4 + semitones
|
||||
|
||||
equal = tone.pitch(temperament="equal")
|
||||
pyth = tone.pitch(temperament="pythagorean")
|
||||
mean = tone.pitch(temperament="meantone")
|
||||
|
||||
# Difference in cents (1 cent = 1/100 of a semitone)
|
||||
pyth_cents = 1200 * math.log2(pyth / equal) if pyth > 0 else 0
|
||||
mean_cents = 1200 * math.log2(mean / equal) if mean > 0 else 0
|
||||
|
||||
print(
|
||||
f"{tone.name:>5s} {equal:>12.3f} {pyth:>12.3f} {mean:>14.3f}"
|
||||
f" {pyth_cents:>+7.1f}¢ {mean_cents:>+7.1f}¢"
|
||||
)
|
||||
|
||||
print()
|
||||
print("Key intervals to listen for:")
|
||||
print()
|
||||
|
||||
intervals = [
|
||||
(4, "Major 3rd", "Meantone is pure (5:4), equal is sharp, Pythagorean sharper still"),
|
||||
(7, "Perfect 5th", "Pythagorean is pure (3:2), equal is slightly flat, meantone flatter"),
|
||||
(6, "Tritone", "The 'devil's interval' — all three temperaments handle it differently"),
|
||||
]
|
||||
|
||||
for semitones, name, note in intervals:
|
||||
tone = a4 + semitones
|
||||
equal = tone.pitch(temperament="equal")
|
||||
pyth = tone.pitch(temperament="pythagorean")
|
||||
mean = tone.pitch(temperament="meantone")
|
||||
|
||||
print(f" {name} ({a4.name}→{tone.name}):")
|
||||
print(f" Equal: {equal:.3f} Hz | Pythagorean: {pyth:.3f} Hz | Meantone: {mean:.3f} Hz")
|
||||
print(f" {note}")
|
||||
print()
|
||||
@@ -0,0 +1,65 @@
|
||||
"""Explore scales from six musical traditions around the world."""
|
||||
|
||||
from pytheory import TonedScale
|
||||
|
||||
systems = [
|
||||
("western", "C4", [
|
||||
("major", "The foundation of Western tonal music"),
|
||||
("minor", "Natural minor — dark and introspective"),
|
||||
("harmonic minor", "Raised 7th — classical, Middle Eastern flavor"),
|
||||
("dorian", "Jazz, funk, soul (So What, Scarborough Fair)"),
|
||||
("mixolydian", "Blues, rock (Norwegian Wood, Sweet Home Alabama)"),
|
||||
("phrygian", "Flamenco, metal (White Rabbit)"),
|
||||
("lydian", "Dreamy, floating (The Simpsons theme)"),
|
||||
]),
|
||||
("indian", "Sa4", [
|
||||
("bilawal", "Equivalent to Western major scale"),
|
||||
("bhairav", "Morning raga — devotional, meditative"),
|
||||
("kafi", "Equivalent to Dorian mode — romantic, earthy"),
|
||||
("bhairavi", "Equivalent to Phrygian — melancholic, devotional"),
|
||||
("kalyan", "Equivalent to Lydian — serene, uplifting"),
|
||||
]),
|
||||
("arabic", "Do4", [
|
||||
("ajam", "Equivalent to Western major scale"),
|
||||
("hijaz", "The quintessential 'Middle Eastern' sound"),
|
||||
("bayati", "Contemplative, spiritual — most common maqam"),
|
||||
("rast", "Bright, festive — the 'mother' of maqamat"),
|
||||
("nahawand", "Equivalent to Western minor — melancholic"),
|
||||
]),
|
||||
("japanese", "C4", [
|
||||
("hirajoshi", "Haunting pentatonic — koto music"),
|
||||
("miyako-bushi", "Urban folk — shamisen music"),
|
||||
("yo", "Bright pentatonic — folk songs, festival music"),
|
||||
("in", "Dark pentatonic — court music, Buddhist chant"),
|
||||
("ritsu", "Elegant pentatonic — gagaku court music"),
|
||||
]),
|
||||
("blues", "C4", [
|
||||
("blues", "The 6-note blues scale with the 'blue note'"),
|
||||
("minor pentatonic", "The backbone of rock guitar solos"),
|
||||
("major pentatonic", "Bright, open — country, folk, pop"),
|
||||
]),
|
||||
("gamelan", "C4", [
|
||||
("slendro", "5-note near-equal division — metallic, shimmering"),
|
||||
("pelog", "7-note unequal — mysterious, otherworldly"),
|
||||
]),
|
||||
]
|
||||
|
||||
for system_name, tonic, scales in systems:
|
||||
print(f"{'═' * 65}")
|
||||
print(f" {system_name.upper()}")
|
||||
print(f"{'═' * 65}")
|
||||
|
||||
ts = TonedScale(tonic=tonic, system=system_name)
|
||||
|
||||
for scale_name, description in scales:
|
||||
try:
|
||||
scale = ts[scale_name]
|
||||
notes = " ".join(scale.note_names)
|
||||
print(f" {scale_name:20s} {notes}")
|
||||
print(f" {'':20s} {description}")
|
||||
print()
|
||||
except (KeyError, IndexError):
|
||||
print(f" {scale_name:20s} (not available)")
|
||||
print()
|
||||
|
||||
print(f"{'═' * 65}")
|
||||
Reference in New Issue
Block a user