Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Chorale Optimizer

An interactive four-part chorale harmonizer. Given a soprano melody and a chord progression, the program generates alto, tenor, and bass voices that obey classical voice-leading rules, then renders the result as a playable score in the browser.

What it does

Given a soprano line (as MIDI note numbers) and a sequence of chord symbols, the harmonizer fills in the three lower voices using a beam search followed by iterative rule-enforcement passes. The output respects standard SATB constraints:

  • Voice ranges (soprano 60–79, alto 55–74, tenor 48–67, bass 43–60)
  • No voice crossing or overlapping
  • Chord tone coverage at every beat
  • Correct bass note for root position, first inversion, and second inversion chords
  • Doubling rules: root doubled in root-position triads, soprano doubled in first inversion, bass/fifth doubled in second inversion, leading tone and seventh never doubled
  • No parallel fifths or octaves, no hidden fifths
  • No consecutive unisons between any pair of voices
  • Seventh resolution (descends by step), leading-tone resolution (rises by half-step)
  • Chromatic tone resolution for secondary dominants, secondary leading-tone chords, and modal mixture chords
  • Avoidance of augmented second melodic intervals

The result is shown in a text table in the GUI, with any out-of-chord tones flagged. Clicking "Show Score" opens a browser page with a rendered grand-staff score (via VexFlow) and a playable audio transport.

The optimizer makes a best effort but does not guarantee perfect voice leading in all cases. Some progressions may still contain minor rule violations.

Files

  • harmonizer.py: all music logic: chord definitions, key signatures, beam search (_beam_search_chunk), voice snapping, and the full suite of rule-enforcement fixup passes called by optimize()
  • main.py: Tkinter GUI: beat editor, key selector, example loader, result display, and the "Show Score" button
  • score_svg.py: HTML/VexFlow score renderer: converts MIDI pitches to VexFlow note strings with correct accidentals for the active key signature, then serves a self-contained HTML page over a local HTTP server and opens it in the browser; includes a Web Audio synthesizer for playback
  • test_harmony.py: unittest suite covering coverage, voice ranges, ordering, spacing, bass correctness, doubling rules, parallel motion, resolution, and a library of named progressions tested end-to-end

Requirements

No third-party packages are required. The GUI requires Tkinter (included with standard Python on macOS and Windows; on Linux install python3-tk). The score viewer requires a browser with JavaScript enabled.

Usage

python main.py

On startup the app loads the first built-in example. Use the Load example dropdown to switch between the 22 included progressions, or edit the beat table directly:

  • Key: select any of the 30 supported major and minor keys
  • Beat table: each row has a chord symbol and a soprano MIDI note; use + Add Beat / – Remove Beat to resize
  • ▶ Harmonize: runs the optimizer and displays the four-voice result
  • 🎼 Show Score: opens the score in a browser tab with Play/Stop controls and a tempo slider

Chord symbols

The chord vocabulary covers diatonic triads and seventh chords in all inversions, secondary dominants and secondary leading-tone chords, modal mixture/borrowed chords, Neapolitan and augmented-sixth chords, and fully diminished seventh chords in all enharmonic spellings. A few examples:

Symbol Meaning (in C)
I, I6, I64 Tonic triad, root / first / second inversion
V7, V65, V43, V42 Dominant seventh, all inversions
viio6 Leading-tone triad, first inversion
V/V, V7/V Secondary dominant of V (D major / D dominant 7th)
viio7/V Secondary leading-tone dim7 of V
iv, bVI, bVII Modal mixture (borrowed from parallel minor)
N6 Neapolitan sixth
Ger65 German augmented sixth
viiodim7 Fully diminished seventh

How the optimizer works

optimize() in harmonizer.py runs in two stages:

Beam search splits the progression at phrase boundaries (cadential I64, half cadences) and runs a forward beam search over each segment with width 40, scoring candidates on voice-leading smoothness, doubling quality, spacing, parallel motion penalties, and resolution tendencies.

Rule-enforcement loop runs up to six iterations of twelve sequential fixup passes over the full progression until the voices stop changing:

  1. _ensure_coverage: moves an inner voice to supply any missing chord tone
  2. _fix_common_tones: retains common tones between adjacent chords where possible
  3. _fix_aug2: eliminates augmented-second melodic leaps
  4. _fix_parallels: removes parallel fifths and octaves
  5. _fix_hidden_fifths: removes hidden (direct) fifths in outer voices
  6. _fix_consec_unisons: prevents consecutive unisons between voice pairs
  7. _fix_crossing_and_spacing: corrects voice crossing and spacing violations
  8. _fix_third_doubling: avoids doubling the third in major triads
  9. _fix_doubling: enforces root/soprano/bass doubling conventions
  10. _fix_seventh_resolution: resolves chord sevenths downward by step
  11. _fix_chromatic_resolution: resolves leading tones and mixture tones in secondary chords
  12. _enforce_ordering: final pass to ensure S ≥ A ≥ T ≥ B

Running the tests

python -m unittest test_harmony -v

The suite runs 81 tests covering individual rule classes and 17 named progressions checked against the full rule set end-to-end.

About

Interactive four-part chorale harmonizer using beam search and iterative constraint-enforcement over classical voice-leading rules.

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages