mirror of
https://github.com/kennethreitz/pytheory.git
synced 2026-06-05 14:50:18 +00:00
51ca98779d
CLI (pytheory command): pytheory tone C4 — frequency, MIDI, overtones pytheory scale C major — notes and intervals pytheory chord C E G — identify, harmony, tension pytheory key C major — full key analysis with diatonic chords pytheory fingering Am — ASCII guitar tab pytheory progression C major I V vi IV — build from Roman numerals pytheory detect C D E G — detect the key Jupyter notebook (examples/tutorial.ipynb): 46-cell interactive tutorial covering tones, scales, modes, keys, chord analysis, progressions, world music systems, guitar fingerings, and building a song from scratch. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
677 lines
20 KiB
Plaintext
677 lines
20 KiB
Plaintext
{
|
|
"cells": [
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"# PyTheory: Music Theory for Humans\n",
|
|
"\n",
|
|
"A hands-on tutorial exploring music theory with Python.\n",
|
|
"\n",
|
|
"PyTheory lets you reason about tones, scales, chords, and progressions\n",
|
|
"using an intuitive, Pythonic API. Whether you're a musician who codes\n",
|
|
"or a coder who plays music, this library gives you the building blocks\n",
|
|
"to explore harmony, composition, and world music systems."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## 1. Getting Started\n",
|
|
"\n",
|
|
"Everything begins with a **Tone** -- the fundamental unit of music.\n",
|
|
"A tone has a name (like `C`, `F#`, or `Bb`), an optional octave number,\n",
|
|
"and a frequency in Hz computed from equal temperament tuning (A4 = 440 Hz)."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"metadata": {},
|
|
"source": [
|
|
"from pytheory import Tone, TonedScale, Key, Chord, Fretboard, CHARTS, Interval\n",
|
|
"from pytheory import analyze_progression\n",
|
|
"from pytheory.scales import PROGRESSIONS"
|
|
],
|
|
"outputs": [],
|
|
"execution_count": null
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"metadata": {},
|
|
"source": [
|
|
"# Create tones with octave numbers (scientific pitch notation)\n",
|
|
"middle_c = Tone.from_string(\"C4\")\n",
|
|
"concert_a = Tone.from_string(\"A4\")\n",
|
|
"\n",
|
|
"print(f\"Middle C: {middle_c} -> {middle_c.frequency:.2f} Hz\")\n",
|
|
"print(f\"Concert A: {concert_a} -> {concert_a.frequency:.2f} Hz\")\n",
|
|
"print(f\"MIDI note: {middle_c.midi}\")\n",
|
|
"print(f\"Is natural? {middle_c.is_natural}\")"
|
|
],
|
|
"outputs": [],
|
|
"execution_count": null
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"metadata": {},
|
|
"source": [
|
|
"# Create tones from frequencies or MIDI numbers\n",
|
|
"from_hz = Tone.from_frequency(440.0)\n",
|
|
"from_midi = Tone.from_midi(60)\n",
|
|
"\n",
|
|
"print(f\"440 Hz -> {from_hz}\")\n",
|
|
"print(f\"MIDI 60 -> {from_midi}\")"
|
|
],
|
|
"outputs": [],
|
|
"execution_count": null
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"metadata": {},
|
|
"source": [
|
|
"# Explore the harmonic series -- the physics behind consonance\n",
|
|
"c3 = Tone.from_string(\"C3\")\n",
|
|
"harmonics = c3.overtones(8)\n",
|
|
"print(f\"Harmonic series of {c3} ({c3.frequency:.1f} Hz):\")\n",
|
|
"for i, hz in enumerate(harmonics, 1):\n",
|
|
" print(f\" Harmonic {i}: {hz:.1f} Hz\")"
|
|
],
|
|
"outputs": [],
|
|
"execution_count": null
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## 2. Tone Arithmetic\n",
|
|
"\n",
|
|
"Tones support arithmetic operations. Adding an integer to a tone raises it\n",
|
|
"by that many **semitones** (half steps). Subtracting two tones gives the\n",
|
|
"semitone distance between them. You can also compare tones by pitch."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"metadata": {},
|
|
"source": [
|
|
"c4 = Tone.from_string(\"C4\")\n",
|
|
"\n",
|
|
"# Add semitones: C + 4 semitones = E (a major third)\n",
|
|
"e4 = c4 + 4\n",
|
|
"g4 = c4 + Interval.PERFECT_FIFTH\n",
|
|
"print(f\"{c4} + 4 semitones = {e4}\")\n",
|
|
"print(f\"{c4} + perfect 5th = {g4}\")\n",
|
|
"\n",
|
|
"# Subtract to find interval distance\n",
|
|
"distance = g4 - c4\n",
|
|
"print(f\"\\nDistance from {c4} to {g4}: {distance} semitones\")"
|
|
],
|
|
"outputs": [],
|
|
"execution_count": null
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"metadata": {},
|
|
"source": [
|
|
"# Name the interval between two tones\n",
|
|
"print(f\"{c4} -> {e4}: {c4.interval_to(e4)}\")\n",
|
|
"print(f\"{c4} -> {g4}: {c4.interval_to(g4)}\")\n",
|
|
"\n",
|
|
"c5 = Tone.from_string(\"C5\")\n",
|
|
"print(f\"{c4} -> {c5}: {c4.interval_to(c5)}\")\n",
|
|
"\n",
|
|
"# Compare tones by pitch\n",
|
|
"print(f\"\\n{c4} < {g4}? {c4 < g4}\")\n",
|
|
"print(f\"{c4} == {c4}? {c4 == c4}\")"
|
|
],
|
|
"outputs": [],
|
|
"execution_count": null
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"metadata": {},
|
|
"source": [
|
|
"# The circle of fifths -- the backbone of Western harmony\n",
|
|
"c = Tone.from_string(\"C4\")\n",
|
|
"fifths = c.circle_of_fifths()\n",
|
|
"print(\"Circle of fifths from C:\")\n",
|
|
"print(\" -> \".join(str(t) for t in fifths))"
|
|
],
|
|
"outputs": [],
|
|
"execution_count": null
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## 3. Scales and Modes\n",
|
|
"\n",
|
|
"A **scale** is a collection of tones arranged in ascending order.\n",
|
|
"The `TonedScale` class provides access to dozens of scales from a given tonic.\n",
|
|
"\n",
|
|
"**Modes** are rotations of the same set of intervals. The seven modes of the\n",
|
|
"major scale each have a distinct character:\n",
|
|
"\n",
|
|
"| Mode | Character |\n",
|
|
"|------------|--------------------|\n",
|
|
"| Ionian | Bright, happy |\n",
|
|
"| Dorian | Jazzy, soulful |\n",
|
|
"| Phrygian | Spanish, dark |\n",
|
|
"| Lydian | Dreamy, floating |\n",
|
|
"| Mixolydian | Bluesy, rock |\n",
|
|
"| Aeolian | Sad, natural minor |\n",
|
|
"| Locrian | Tense, unstable |"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"metadata": {},
|
|
"source": [
|
|
"# Build a scale from a tonic\n",
|
|
"ts = TonedScale(tonic=\"C4\")\n",
|
|
"\n",
|
|
"# See all available scale names\n",
|
|
"print(\"Available scales:\")\n",
|
|
"for name in ts.scales:\n",
|
|
" print(f\" {name}\")"
|
|
],
|
|
"outputs": [],
|
|
"execution_count": null
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"metadata": {},
|
|
"source": [
|
|
"# Get a specific scale and iterate its tones\n",
|
|
"c_major = ts[\"major\"]\n",
|
|
"print(f\"C major: {c_major.note_names}\")\n",
|
|
"\n",
|
|
"c_minor = ts[\"minor\"]\n",
|
|
"print(f\"C minor: {c_minor.note_names}\")\n",
|
|
"\n",
|
|
"# Check if a note belongs to the scale\n",
|
|
"print(f\"\\nIs F# in C major? {'F#' in c_major}\")\n",
|
|
"print(f\"Is G in C major? {'G' in c_major}\")"
|
|
],
|
|
"outputs": [],
|
|
"execution_count": null
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"metadata": {},
|
|
"source": "from pytheory.scales import Scale\n\n# Transpose a scale\nd_major = c_major.transpose(2)\nprint(f\"D major (C major transposed up 2): {d_major.note_names}\")\n\n# Detect a scale from a set of notes\nresult = Scale.detect(\"A\", \"B\", \"C#\", \"D\", \"E\", \"F#\", \"G#\")\nprint(f\"\\nDetected scale: {result}\")",
|
|
"outputs": [],
|
|
"execution_count": null
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## 4. The Key Class\n",
|
|
"\n",
|
|
"A **Key** is the most convenient entry point for working with harmony.\n",
|
|
"It wraps a tonic and mode, giving you instant access to scales, diatonic\n",
|
|
"chords, key signatures, and related keys."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"metadata": {},
|
|
"source": [
|
|
"key = Key(\"C\", \"major\")\n",
|
|
"\n",
|
|
"print(f\"Key: {key}\")\n",
|
|
"print(f\"Notes: {key.note_names}\")\n",
|
|
"print(f\"Signature: {key.signature}\")"
|
|
],
|
|
"outputs": [],
|
|
"execution_count": null
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"metadata": {},
|
|
"source": [
|
|
"# Diatonic triads -- the seven chords built from the scale\n",
|
|
"print(\"Diatonic triads in C major:\")\n",
|
|
"for i, name in enumerate(key.chords, 1):\n",
|
|
" print(f\" {i}. {name}\")"
|
|
],
|
|
"outputs": [],
|
|
"execution_count": null
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"metadata": {},
|
|
"source": [
|
|
"# Seventh chords add richness and color\n",
|
|
"print(\"Diatonic seventh chords in C major:\")\n",
|
|
"for i, name in enumerate(key.seventh_chords, 1):\n",
|
|
" print(f\" {i}. {name}\")"
|
|
],
|
|
"outputs": [],
|
|
"execution_count": null
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"metadata": {},
|
|
"source": [
|
|
"# Related keys\n",
|
|
"print(f\"Relative minor of C major: {key.relative}\")\n",
|
|
"print(f\"Parallel minor of C major: {key.parallel}\")\n",
|
|
"\n",
|
|
"# Key signatures for sharp and flat keys\n",
|
|
"for tonic in [\"G\", \"D\", \"F\", \"Bb\"]:\n",
|
|
" k = Key(tonic, \"major\")\n",
|
|
" sig = k.signature\n",
|
|
" print(f\"{k}: {sig['sharps']} sharps, {sig['flats']} flats -> {sig['accidentals']}\")"
|
|
],
|
|
"outputs": [],
|
|
"execution_count": null
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## 5. Chord Analysis\n",
|
|
"\n",
|
|
"Chords can be created from note names, intervals, chord symbols, or MIDI.\n",
|
|
"PyTheory can identify chord quality, measure tension and consonance,\n",
|
|
"and compute optimal voice leading between chords."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"metadata": {},
|
|
"source": [
|
|
"# Multiple ways to create chords\n",
|
|
"c_major_chord = Chord.from_tones(\"C\", \"E\", \"G\")\n",
|
|
"g7 = Chord.from_intervals(\"G\", 4, 7, 10)\n",
|
|
"am = Chord.from_name(\"Am\")\n",
|
|
"\n",
|
|
"print(f\"{c_major_chord} (intervals: {c_major_chord.intervals})\")\n",
|
|
"print(f\"{g7} (intervals: {g7.intervals})\")\n",
|
|
"print(f\"{am} (intervals: {am.intervals})\")"
|
|
],
|
|
"outputs": [],
|
|
"execution_count": null
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"metadata": {},
|
|
"source": [
|
|
"# Analyze harmonic tension\n",
|
|
"# The dominant 7th chord is the most tension-filled chord in tonal music\n",
|
|
"print(\"Tension analysis:\")\n",
|
|
"for chord in [c_major_chord, am, g7]:\n",
|
|
" t = chord.tension\n",
|
|
" print(f\" {chord.identify():20s} -> score={t['score']:.2f}, \"\n",
|
|
" f\"tritones={t['tritones']}, dominant={t['has_dominant_function']}\")"
|
|
],
|
|
"outputs": [],
|
|
"execution_count": null
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"metadata": {},
|
|
"source": [
|
|
"# Consonance vs dissonance (psychoacoustic measures)\n",
|
|
"print(f\"{'Chord':20s} {'Harmony':>10s} {'Dissonance':>12s}\")\n",
|
|
"print(\"-\" * 44)\n",
|
|
"for chord in [c_major_chord, am, g7]:\n",
|
|
" print(f\"{chord.identify():20s} {chord.harmony:10.4f} {chord.dissonance:12.4f}\")"
|
|
],
|
|
"outputs": [],
|
|
"execution_count": null
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"metadata": {},
|
|
"source": [
|
|
"# Voice leading: how individual notes move between chords\n",
|
|
"f_major = Chord.from_tones(\"F\", \"A\", \"C\")\n",
|
|
"vl = c_major_chord.voice_leading(f_major)\n",
|
|
"\n",
|
|
"print(f\"Voice leading: {c_major_chord.identify()} -> {f_major.identify()}\")\n",
|
|
"for src, dst, movement in vl:\n",
|
|
" direction = \"up\" if movement > 0 else \"down\" if movement < 0 else \"stays\"\n",
|
|
" print(f\" {src} -> {dst} ({movement:+d} semitones, {direction})\")"
|
|
],
|
|
"outputs": [],
|
|
"execution_count": null
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"metadata": {},
|
|
"source": [
|
|
"# Inversions rearrange chord voicings\n",
|
|
"print(f\"Root position: {[t.full_name for t in c_major_chord.tones]}\")\n",
|
|
"print(f\"1st inversion: {[t.full_name for t in c_major_chord.inversion(1).tones]}\")\n",
|
|
"print(f\"2nd inversion: {[t.full_name for t in c_major_chord.inversion(2).tones]}\")"
|
|
],
|
|
"outputs": [],
|
|
"execution_count": null
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## 6. Chord Progressions\n",
|
|
"\n",
|
|
"Chord progressions are the harmonic backbone of songs. PyTheory supports\n",
|
|
"both **Roman numeral** analysis (classical/jazz) and the **Nashville number\n",
|
|
"system** (studio shorthand). It also ships with common progressions built in."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"metadata": {},
|
|
"source": [
|
|
"key = Key(\"G\", \"major\")\n",
|
|
"\n",
|
|
"# Build a progression from Roman numerals\n",
|
|
"prog = key.progression(\"I\", \"V\", \"vi\", \"IV\")\n",
|
|
"print(\"I - V - vi - IV in G major (the 'four chord song'):\")\n",
|
|
"for chord in prog:\n",
|
|
" print(f\" {chord.identify()}\")"
|
|
],
|
|
"outputs": [],
|
|
"execution_count": null
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"metadata": {},
|
|
"source": [
|
|
"# Nashville number system -- same thing, Arabic numerals\n",
|
|
"nashville = key.nashville(1, 5, 6, 4)\n",
|
|
"print(\"Nashville 1-5-6-4 in G major:\")\n",
|
|
"for chord in nashville:\n",
|
|
" print(f\" {chord.identify()}\")"
|
|
],
|
|
"outputs": [],
|
|
"execution_count": null
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"metadata": {},
|
|
"source": [
|
|
"# Browse the built-in progression library\n",
|
|
"print(\"Built-in progressions:\")\n",
|
|
"for name, numerals in PROGRESSIONS.items():\n",
|
|
" print(f\" {name:25s} -> {' '.join(numerals)}\")"
|
|
],
|
|
"outputs": [],
|
|
"execution_count": null
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"metadata": {},
|
|
"source": [
|
|
"# Analyze an existing chord progression\n",
|
|
"chords = [Chord.from_name(\"C\"), Chord.from_name(\"Am\"),\n",
|
|
" Chord.from_name(\"F\"), Chord.from_name(\"G\")]\n",
|
|
"numerals = analyze_progression(chords, key=\"C\")\n",
|
|
"print(\"Progression analysis in C:\")\n",
|
|
"for chord, numeral in zip(chords, numerals):\n",
|
|
" print(f\" {chord.identify():15s} -> {numeral}\")"
|
|
],
|
|
"outputs": [],
|
|
"execution_count": null
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## 7. World Music Systems\n",
|
|
"\n",
|
|
"Music theory extends far beyond Western harmony. PyTheory includes scale\n",
|
|
"systems from several traditions:\n",
|
|
"\n",
|
|
"- **Indian** (raga/thaat) -- 10 parent scales covering all of Hindustani music\n",
|
|
"- **Arabic** (maqam) -- modal systems with characteristic augmented seconds\n",
|
|
"- **Japanese** -- pentatonic scales used in koto, shamisen, and folk music\n",
|
|
"- **Blues** -- the scales that built American popular music\n",
|
|
"- **Gamelan** -- Javanese/Balinese tuning systems (12-TET approximations)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"metadata": {},
|
|
"source": [
|
|
"from pytheory import SYSTEMS\n",
|
|
"\n",
|
|
"# Indian thaat system\n",
|
|
"indian = TonedScale(tonic=\"C4\", system=SYSTEMS[\"indian\"])\n",
|
|
"print(\"Indian thaats from C:\")\n",
|
|
"for name in indian.scales:\n",
|
|
" scale = indian[name]\n",
|
|
" print(f\" {name:12s} -> {scale.note_names}\")"
|
|
],
|
|
"outputs": [],
|
|
"execution_count": null
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"metadata": {},
|
|
"source": [
|
|
"# Arabic maqam -- the Hijaz scale has a distinctive augmented 2nd\n",
|
|
"arabic = TonedScale(tonic=\"D4\", system=SYSTEMS[\"arabic\"])\n",
|
|
"hijaz = arabic[\"hijaz\"]\n",
|
|
"print(f\"Maqam Hijaz from D: {hijaz.note_names}\")\n",
|
|
"\n",
|
|
"# Japanese hirajoshi -- hauntingly beautiful pentatonic\n",
|
|
"japanese = TonedScale(tonic=\"A4\", system=SYSTEMS[\"japanese\"])\n",
|
|
"hirajoshi = japanese[\"hirajoshi\"]\n",
|
|
"print(f\"Hirajoshi from A: {hirajoshi.note_names}\")"
|
|
],
|
|
"outputs": [],
|
|
"execution_count": null
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"metadata": {},
|
|
"source": [
|
|
"# Blues scales -- the foundation of rock, jazz, and R&B\n",
|
|
"blues = TonedScale(tonic=\"A4\", system=SYSTEMS[\"blues\"])\n",
|
|
"print(\"Blues scales from A:\")\n",
|
|
"for name in blues.scales:\n",
|
|
" scale = blues[name]\n",
|
|
" print(f\" {name:20s} -> {scale.note_names}\")\n",
|
|
"\n",
|
|
"# Gamelan -- approximations of non-Western tuning\n",
|
|
"gamelan = TonedScale(tonic=\"C4\", system=SYSTEMS[\"gamelan\"])\n",
|
|
"slendro = gamelan[\"slendro\"]\n",
|
|
"print(f\"\\nGamelan slendro from C: {slendro.note_names}\")"
|
|
],
|
|
"outputs": [],
|
|
"execution_count": null
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## 8. Guitar and Instruments\n",
|
|
"\n",
|
|
"The `Fretboard` class models stringed instruments. You can look up\n",
|
|
"chord fingerings, render tab diagrams, apply a capo, and visualize\n",
|
|
"scale patterns across the neck."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"metadata": {},
|
|
"source": [
|
|
"# Standard guitar fretboard\n",
|
|
"guitar = Fretboard.guitar()\n",
|
|
"print(f\"Standard tuning: {guitar}\")\n",
|
|
"\n",
|
|
"# Look up chord fingerings from the chart\n",
|
|
"c_chart = CHARTS[\"western\"][\"C\"]\n",
|
|
"print(f\"\\n{c_chart.tab(fretboard=guitar)}\")"
|
|
],
|
|
"outputs": [],
|
|
"execution_count": null
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"metadata": {},
|
|
"source": [
|
|
"# Show several common chord shapes\n",
|
|
"for chord_name in [\"G\", \"Am\", \"Em\", \"D\"]:\n",
|
|
" chart = CHARTS[\"western\"][chord_name]\n",
|
|
" print(chart.tab(fretboard=guitar))\n",
|
|
" print()"
|
|
],
|
|
"outputs": [],
|
|
"execution_count": null
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"metadata": {},
|
|
"source": [
|
|
"# Apply a capo -- raises all strings by N semitones\n",
|
|
"capo2 = Fretboard.guitar(capo=2)\n",
|
|
"print(f\"Capo on fret 2: {capo2}\")\n",
|
|
"print(\"Playing 'G shape' with capo 2 = A major voicing\")"
|
|
],
|
|
"outputs": [],
|
|
"execution_count": null
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"metadata": {},
|
|
"source": [
|
|
"# Scale diagram -- see where notes fall on the neck\n",
|
|
"c_major_scale = TonedScale(tonic=\"C4\")[\"major\"]\n",
|
|
"diagram = guitar.scale_diagram(c_major_scale, frets=12)\n",
|
|
"print(\"C major scale on guitar:\")\n",
|
|
"print(diagram)"
|
|
],
|
|
"outputs": [],
|
|
"execution_count": null
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## 9. Building a Song\n",
|
|
"\n",
|
|
"Let's put it all together: pick a key, explore its chords, build a\n",
|
|
"progression, and analyze the harmonic movement."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"metadata": {},
|
|
"source": [
|
|
"# Step 1: Choose a key\n",
|
|
"song_key = Key(\"E\", \"minor\")\n",
|
|
"print(f\"Key: {song_key}\")\n",
|
|
"print(f\"Notes: {song_key.note_names}\")\n",
|
|
"print(f\"Relative major: {song_key.relative}\")\n",
|
|
"print(f\"Signature: {song_key.signature}\")"
|
|
],
|
|
"outputs": [],
|
|
"execution_count": null
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"metadata": {},
|
|
"source": [
|
|
"# Step 2: See what chords are available\n",
|
|
"print(\"Diatonic chords in E minor:\")\n",
|
|
"for i, name in enumerate(song_key.chords, 1):\n",
|
|
" print(f\" {i}. {name}\")\n",
|
|
"\n",
|
|
"print(\"\\nBorrowed chords from E major:\")\n",
|
|
"for name in song_key.borrowed_chords[:4]:\n",
|
|
" print(f\" {name}\")"
|
|
],
|
|
"outputs": [],
|
|
"execution_count": null
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"metadata": {},
|
|
"source": [
|
|
"# Step 3: Build a progression\n",
|
|
"# i - VI - III - VII is a classic minor key progression\n",
|
|
"prog = song_key.progression(\"i\", \"VI\", \"III\", \"VII\")\n",
|
|
"\n",
|
|
"print(\"Progression: i - VI - III - VII\")\n",
|
|
"for chord in prog:\n",
|
|
" name = chord.identify()\n",
|
|
" numeral = chord.analyze(\"E\", \"minor\")\n",
|
|
" t = chord.tension\n",
|
|
" print(f\" {name:18s} [{numeral:5s}] tension={t['score']:.2f}\")"
|
|
],
|
|
"outputs": [],
|
|
"execution_count": null
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"metadata": {},
|
|
"source": [
|
|
"# Step 4: Analyze voice leading through the progression\n",
|
|
"print(\"Voice leading through the progression:\\n\")\n",
|
|
"for i in range(len(prog) - 1):\n",
|
|
" src = prog[i]\n",
|
|
" dst = prog[i + 1]\n",
|
|
" vl = src.voice_leading(dst)\n",
|
|
" total = sum(abs(m) for _, _, m in vl)\n",
|
|
" print(f\"{src.identify()} -> {dst.identify()} (total movement: {total} semitones)\")\n",
|
|
" for s, d, m in vl:\n",
|
|
" print(f\" {s} -> {d} ({m:+d})\")\n",
|
|
" print()"
|
|
],
|
|
"outputs": [],
|
|
"execution_count": null
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"metadata": {},
|
|
"source": [
|
|
"# Step 5: Show the chords on guitar\n",
|
|
"guitar = Fretboard.guitar()\n",
|
|
"chord_names = [\"Em\", \"C\", \"G\", \"D\"]\n",
|
|
"\n",
|
|
"print(\"Guitar charts for the progression:\\n\")\n",
|
|
"for name in chord_names:\n",
|
|
" chart = CHARTS[\"western\"][name]\n",
|
|
" print(chart.tab(fretboard=guitar))\n",
|
|
" print()"
|
|
],
|
|
"outputs": [],
|
|
"execution_count": null
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"metadata": {},
|
|
"source": [
|
|
"# Bonus: Detect the key from a set of notes\n",
|
|
"detected = Key.detect(\"E\", \"G\", \"A\", \"B\", \"D\")\n",
|
|
"print(f\"Key detected from [E, G, A, B, D]: {detected}\")\n",
|
|
"\n",
|
|
"# Secondary dominant -- adds harmonic color\n",
|
|
"v_of_v = song_key.secondary_dominant(5)\n",
|
|
"print(f\"\\nSecondary dominant V/V in E minor: {v_of_v.identify()}\")\n",
|
|
"print(f\"Tension score: {v_of_v.tension['score']:.2f}\")"
|
|
],
|
|
"outputs": [],
|
|
"execution_count": null
|
|
}
|
|
],
|
|
"metadata": {
|
|
"kernelspec": {
|
|
"display_name": "Python 3",
|
|
"language": "python",
|
|
"name": "python3"
|
|
},
|
|
"language_info": {
|
|
"name": "python",
|
|
"version": "3.12.0"
|
|
}
|
|
},
|
|
"nbformat": 4,
|
|
"nbformat_minor": 5
|
|
} |