πΌ Chord progression analysis via holonomy β detect modulations, modal interchange, and cycle violations in harmony.
Holonomy-harmony proves that harmonic movement = cycle consistency. A chord progression has zero holonomy when it returns to its tonal center. When it doesn't, you've detected a modulation. This is constraint theory applied to music theory: the circle of fifths is a topological space, and chord progressions trace paths through it.
Music theory has always had an implicit spatial structure β the circle of fifths, the line of fifths, voice-leading spaces. But the connection between harmonic motion and topological holonomy (the "did you end up where you started?" invariant) hasn't been made explicit in a tool. This library makes that connection executable: every chord progression gets a holonomy number, and that number tells you exactly how "far from home" the harmony wandered.
Holonomy measures whether a closed loop through a space returns you to the same orientation you started with. On the circle of fifths, each chord transition moves you clockwise (dominant direction) or counter-clockwise (subdominant direction). If you sum all those movements and get zero, the progression is tonally consistent β it returned home. If the sum is non-zero, you modulated.
Winding number counts how many full rotations around the circle of fifths the progression makes. A I-IV-V-I progression winds zero times. Coltrane's Giant Steps winds multiple times due to its major-third cycles.
Stability score (0β1) measures how "safe" a progression is: 1.0 = entirely diatonic, zero holonomy. 0.0 = highly chromatic with multiple modulations.
pip install holonomy-harmonyfrom holonomy_harmony import analyze_progression, PROGRESSIONS
# Analyze the Pachelbel Canon progression in D major
symbols, tonic, mode = PROGRESSIONS["pachelbel_canon"]
result = analyze_progression(symbols, key_tonic=tonic, mode=mode)
print(f"Holonomy: {result.holonomy.holonomy}") # -5
print(f"Winding: {result.holonomy.winding_number}") # 0.0833
print(f"Type: {result.holonomy.progression_type}") # ProgressionType.MODULATION
print(f"Stability: {result.stability_score}") # 0.35
# Analyze Giant Steps β much more adventurous
symbols, tonic, mode = PROGRESSIONS["giant_steps"]
result = analyze_progression(symbols, key_tonic=tonic, mode=mode)
print(f"Type: {result.holonomy.progression_type}") # ProgressionType.CHROMATIC
print(f"Stability: {result.stability_score}") # lowerOutput:
Holonomy: -5
Winding: 0.08333333333333333
Type: ProgressionType.MODULATION
Stability: 0.35
Type: ProgressionType.CHROMATIC_MEDIANT
Stability: 0.377
from holonomy_harmony import analyze_progression
result = analyze_progression(
symbols=["I", "vi", "IV", "V"], # Roman numerals
key_tonic=0, # C
mode="major", # major or minor
wrap=False, # treat as closed cycle?
)
# result.chords -> List[Chord]
# result.holonomy -> HolonomyResult
# result.graph -> TonalGraph
# result.modulations -> List[(index, description)]
# result.modal_interchanges -> List[(index, description)]
# result.stability_score -> float (0.0-1.0)from holonomy_harmony import compute_holonomy, winding_number, classify_progression
roots = [0, 7, 9, 5, 0] # C, G, A, F, C
h = compute_holonomy(roots, wrap=True)
print(h.holonomy) # net circle-of-fifths displacement
print(h.winding_number) # full rotations
print(h.max_deviation) # furthest wander from tonic
print(h.is_consistent())# True if holonomy == 0
print(winding_number(roots)) # shortcut
print(classify_progression(roots)) # ProgressionType enumfrom holonomy_harmony import parse_roman
chord = parse_roman("V7/vi", key_tonic=0, mode="major")
# Chord(root=10, quality='7', function='V7/vi',
# is_secondary_dominant=True, implied_key=(9, 'major'))from holonomy_harmony import TonalGraph
g = TonalGraph()
g.build_from_progression([0, 7, 9, 5, 0])
print(g) # <TonalGraph nodes=12 edges=4 total_weight=4.0>
print(g.adjacency_matrix()) # 12Γ12 transition matrix
print(g.transition_probability(0, 7)) # P(G|C)20 famous progressions included:
from holonomy_harmony import PROGRESSIONS
for name in PROGRESSIONS:
symbols, tonic, mode = PROGRESSIONS[name]
print(f"{name}: {' '.join(symbols)}")Includes: pachelbel_canon, blues_12_bar, giant_steps, chopin_em_prelude, axis_progression, autumn_leaves, coltrane_changes, rhythm_changes, hey_jude, creep, take_five, and more.
βββββββββββββββ βββββββββββββββββ ββββββββββββββββ
β analyzer.pyββββ>β cycle_checker ββββ>β tonal_graph β
β β β β β β
β parse_roman β β compute_ β β TonalGraph β
β analyze_ β β holonomy β β Transition β
β progression β β winding_ β β Direction β
β detect_ β β number β β adjacency β
β modulations β β classify_ β β matrix β
β score_ β β progression β β β
β stability β β HolonomyResultβ β β
βββββββββββββββ βββββββββββββββββ ββββββββββββββββ
Input: Roman numerals β Chord objects β pitch-class roots
Process: roots β circle-of-fifths steps β holonomy signature
Output: HolonomyResult + stability score + modulation list
- User Guide β Complete usage documentation
- Developer Guide β Contributing and internals
- Examples β Working code examples
- spline-midi-smooth β Spline interpolation for MIDI automation
- plato-room-musician β PLATO rooms β MIDI music
- tensor-midi β INT8-saturated MIDI for neural synthesis
- Python 3.10+
- No external dependencies (pure Python)
pip install holonomy-harmonyOr from source:
git clone https://github.com/SuperInstance/holonomy-harmony.git
cd holonomy-harmony
pip install -e .Apache License 2.0