Skip to content

Repository files navigation

Chord Harmonizer

A small tool that picks chords for a melody written in numbered notation (Jianpu).

I built this because a lot of church songbooks, especially older Chinese hymnals, only print the melody. There are no chord labels, so if you want to accompany on piano or guitar you have to work the chords out yourself. This tool does that: you give it the melody, and it predicts one chord for each bar.

Example

The input is Jianpu text. The first line gives the key, then the notes follow, with bars separated by |. Notes are the digits 1 to 7, and 0 is a rest.

1=C major
1 3 5 3 | 4 4 6 4 | 5 5 7 5 | 6 5 3 1

The output is one chord per bar:

C | F | G7 | C

demo.ipynb has more examples, including one where the model does well and one where it doesn't. The notebook is already run, so you can just read it on GitHub — or run it yourself:

git clone https://github.com/Shuhan6017/Chord_Generator
cd Chord_Generator
pip install jupyter
jupyter notebook demo.ipynb

Nothing else to install — the pipeline itself is pure Python standard library. Once it's open, edit the "Try your own melody" cell near the end with your own Jianpu and re-run it.

How it works

The model is a simple Markov chain trained on a small set of hymns. For each bar, it scores every chord in the key on two things:

  1. How well the chord fits the melody notes in that bar (how many of them are chord tones).
  2. How likely this chord is to come after the previous bar's chord. This part is learned: the training step just counts, across all the training songs, how often each chord moves to each other chord.

It adds the two scores and keeps the best chord. After every bar has a chord, a second pass decides if some chords should become 7th chords, for example a V going to I usually becomes V7. The rules for that are tuned for hymn style.

Both steps work in scale degrees (I, ii, V, and so on) instead of absolute chords, so a song in G major and a song in C major can train the same table.

Using it

There is no app to run. You call the function directly:

from markov import train_counts, to_logprob, jianpu_to_chords_markov
from evaluate import load_songs

logp = to_logprob(train_counts(load_songs()))
jianpu_to_chords_markov("1=C major\n1 3 5 3 | 4 4 6 4 | 5 5 7 5 | 6 5 3 1", logp)
# -> ['C', 'F', 'G7', 'C']

Project files

File What it does
markov.py The active engine. Trains the transition table and picks the chords.
jianpu.py Parses the Jianpu text into bars of notes plus the key and mode.
chord_graph.py Builds the seven diatonic chords for a key.
harmonize_jianpu.py Turns a chord into a symbol like C, Am, or G7.
harmonizer.py The old rule-based engine. The Markov engine still borrows two of its scoring functions, and evaluate.py uses it as a comparison baseline.
evaluate.py Compares an engine's output against the reference chords in dataset/.
tune_markov.py Searches for good hyperparameter values using leave-one-song-out cross validation.
demo.ipynb Worked examples, already run.
harmony_engine.py, midi_export.py Leftovers from an earlier version that generated progressions from scratch with no melody. Nothing uses them now. Kept for reference.

Dataset

Each file in dataset/ is one song: its Jianpu melody plus a reference chord for every bar.

{
  "name": "...",
  "jianpu": "1=C major\n1 3 5 3 | 4 4 6 4 | ...",
  "expected": ["C", "F", "G", "C"]
}

The reference chords have to be diatonic to the song's key. Training maps each chord to a scale degree, and it raises an error if a chord doesn't fit the key.

Evaluation and tuning

python evaluate.py            # compare both engines against the reference chords
python evaluate.py -v         # also print a bar-by-bar diff per song
python tune_markov.py         # search alpha and lam with leave-one-song-out CV

One thing I learned doing this: harmonization is not a classification problem. A melody usually has more than one valid harmonization, so a mismatch with the reference chord is not automatically wrong. That's why evaluate.py reports "agreement with a reference" instead of accuracy, and also scores an always-play-the-tonic baseline so there's a floor to compare against.

Tests

pytest

Each test file also runs on its own with plain python test_markov.py style commands, no pytest needed.

Requirements

  • Python 3.10+
  • Nothing else for the main pipeline. pytest if you want to run the tests that way, jupyter if you want to re-run the notebook, and music21 only if you touch the legacy midi_export.py.

Limitations

  • Only diatonic chords. No secondary dominants, borrowed chords, or anything chromatic. For example, the Amazing Grace reference progression uses G7 and A7 in the key of G major, and this engine simply cannot produce those.
  • The diminished chord is always skipped, even when it would fit the melody best.
  • One key per song. If the piece modulates, it won't follow.
  • The Markov chain only looks one chord back, and the training set is small (36 songs right now), so the learned probabilities are noisy. A longer memory isn't worth it until there's a lot more data.
  • One chord per bar. Faster harmonic rhythm inside a bar can't be represented.

About

Predicts chords for a melody written in Jianpu (numbered musical notation), using a Markov chain trained on Chinese hymns.

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages