From 9da3ac8b28836630f518ee5b1cce06dc285ac31a Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Sun, 22 Mar 2026 20:27:18 -0400 Subject: [PATCH] Add 12 example scripts showcasing pytheory features MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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) --- examples/chord_identifier.py | 46 +++++++++++++++ examples/chord_tension.py | 52 +++++++++++++++++ examples/circle_of_fifths.py | 34 +++++++++++ examples/fretboard_explorer.py | 74 ++++++++++++++++++++++++ examples/interval_trainer.py | 93 ++++++++++++++++++++++++++++++ examples/key_detection.py | 64 ++++++++++++++++++++ examples/key_explorer.py | 58 +++++++++++++++++++ examples/midi_converter.py | 35 +++++++++++ examples/overtone_series.py | 68 ++++++++++++++++++++++ examples/progression_writer.py | 81 ++++++++++++++++++++++++++ examples/temperament_comparison.py | 49 ++++++++++++++++ examples/world_scales.py | 65 +++++++++++++++++++++ 12 files changed, 719 insertions(+) create mode 100644 examples/chord_identifier.py create mode 100644 examples/chord_tension.py create mode 100644 examples/circle_of_fifths.py create mode 100644 examples/fretboard_explorer.py create mode 100644 examples/interval_trainer.py create mode 100644 examples/key_detection.py create mode 100644 examples/key_explorer.py create mode 100644 examples/midi_converter.py create mode 100644 examples/overtone_series.py create mode 100644 examples/progression_writer.py create mode 100644 examples/temperament_comparison.py create mode 100644 examples/world_scales.py diff --git a/examples/chord_identifier.py b/examples/chord_identifier.py new file mode 100644 index 0000000..9457d83 --- /dev/null +++ b/examples/chord_identifier.py @@ -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}") diff --git a/examples/chord_tension.py b/examples/chord_tension.py new file mode 100644 index 0000000..c4d6020 --- /dev/null +++ b/examples/chord_tension.py @@ -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)") diff --git a/examples/circle_of_fifths.py b/examples/circle_of_fifths.py new file mode 100644 index 0000000..5b61b77 --- /dev/null +++ b/examples/circle_of_fifths.py @@ -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]}") diff --git a/examples/fretboard_explorer.py b/examples/fretboard_explorer.py new file mode 100644 index 0000000..1d2f031 --- /dev/null +++ b/examples/fretboard_explorer.py @@ -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)") diff --git a/examples/interval_trainer.py b/examples/interval_trainer.py new file mode 100644 index 0000000..89a15a6 --- /dev/null +++ b/examples/interval_trainer.py @@ -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}") diff --git a/examples/key_detection.py b/examples/key_detection.py new file mode 100644 index 0000000..2c3907c --- /dev/null +++ b/examples/key_detection.py @@ -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}" + ) diff --git a/examples/key_explorer.py b/examples/key_explorer.py new file mode 100644 index 0000000..7e36693 --- /dev/null +++ b/examples/key_explorer.py @@ -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) diff --git a/examples/midi_converter.py b/examples/midi_converter.py new file mode 100644 index 0000000..640a47b --- /dev/null +++ b/examples/midi_converter.py @@ -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}") diff --git a/examples/overtone_series.py b/examples/overtone_series.py new file mode 100644 index 0000000..60201a8 --- /dev/null +++ b/examples/overtone_series.py @@ -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)") diff --git a/examples/progression_writer.py b/examples/progression_writer.py new file mode 100644 index 0000000..9c16889 --- /dev/null +++ b/examples/progression_writer.py @@ -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)} |") diff --git a/examples/temperament_comparison.py b/examples/temperament_comparison.py new file mode 100644 index 0000000..0fca532 --- /dev/null +++ b/examples/temperament_comparison.py @@ -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() diff --git a/examples/world_scales.py b/examples/world_scales.py new file mode 100644 index 0000000..e43814e --- /dev/null +++ b/examples/world_scales.py @@ -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}")