July 24, 2026: Harmonizing Bach chorales with a Transformer (a machine-learning side project)
A short detour from number theory. In my spare time I built ChoraleHarmonizer, a small program that takes a soprano melody and writes the three lower voices (alto, tenor, and bass) in the style of Johann Sebastian Bach’s four-part chorales. Bach left several hundred of these harmonizations, and reconstructing the lower voices from a given melody is a classic music-theory exercise, so there is both ample training material and a clear notion of what a good answer looks like.
The model. At its core is a Transformer,
the same kind of neural network that powers modern language models. It
is trained on the
four-part chorales in the music21 corpus, each transposed
into all twelve keys. Music is turned into text on a sixteenth-note
grid: at each step the sounding pitches are listed, a token
_ holds the previous note, | marks a bar line,
and ^ a fermata (the held notes that close each phrase).
The soprano is woven into the sequence the model predicts, so
that at every position it sees the melody, the metre, and the phrase
endings before committing to the harmony. The network is deliberately
small (about
million parameters) and trained from scratch: fine-tuning a large text
model would buy nothing once its word vocabulary is replaced by musical
tokens.
Why a neural net alone is not enough. A few thousand chorales is a tiny dataset by machine-learning standards, and the raw model, left to its own devices, writes harmony that sounds mostly right but keeps breaking textbook rules, above all parallel fifths and octaves, the cardinal sin of chorale writing. The interesting part of the project is therefore not the network but everything wrapped around it: a layer of hand-coded, classical music theory that steers the generation. This is the “manual processing” that turns a plausible imitator into a passable one.
What that processing does. Four things happen while the harmony is generated, from hard rules to soft preferences:
Constraints. The soprano and all structural symbols are fixed; each lower voice is confined to the range it actually occupies in Bach; and at every fermata all four voices must sound the cadence chord, as they do in over of Bach’s cadences.
Nudges while sampling. As each note is chosen, pitches that would form a parallel fifth or octave with an already-written voice are penalized on the spot (this single check cuts the parallel-fifth rate from to per sixteenth notes, near Bach’s own ), and the inner voices are discouraged from merely re-striking a note they could instead hold.
Best of many. For each phrase the program samples a batch of complete harmonizations and keeps the one that maximizes the model’s own confidence minus a music-theory penalty; fixing the winning phrase before sampling the next explores exponentially many combinations cheaply.
Repair. Finally it locates the single worst remaining rule violation, re-rolls the harmony from just before it, and accepts the result only if the overall score improves.
Rules calibrated against Bach, not a textbook. The rule book has about twenty entries (parallel and hidden fifths, voice crossings and overlaps, augmented seconds, unresolved tritone leaps, doubled thirds, harmonic stagnation, and so on), but the penalties are not set by dogma. Each is fixed by how much more often the raw model breaks the rule than Bach does, measured directly on the corpus; rules that Bach himself bends now and then cost little or nothing, and the penalty mass concentrates on the model’s genuine weaknesses. Even the exceptions are discovered rather than assumed: where two “violations” almost always occur together in Bach’s own writing (a hidden fifth reached by similar motion, say, which he does some forty times more often than chance would predict), they are counted as a single incident instead of punished twice.
A style dial: conservative, balanced, bold. On top
of the rules, two statistics learned from the corpus judge each
candidate harmonization as a whole: a harmonic model of how
each beat’s chord follows the previous one, and a texture model
of how often each voice ought to move. The twist is what they optimize
for. Rather than making the harmony as typical as possible (which would
collapse everything toward a bland, average Bach), each term measures
the distance from Bach’s own median level of surprise, so a
setting that is too predictable is penalized just like one that is too
wild. Shifting that target turns it into a dial: the presets
konservativ, ausgewogen, and
kuehn (conservative, balanced, bold) aim below, at, and
above Bach’s median, so one can ask for anything from cautious textbook
harmony to writing bolder than Bach’s median, all while staying inside
the range of surprise he actually used.
Trying it out. The code is on GitHub, and two commands suffice:
python bach_chorales.py # build the dataset from music21 (once)
python chorale_harmonizer.py # train, then harmonize the test chorales
Adding --presets konservativ,ausgewogen,kuehn writes all
three stylistic variants of each chorale; if a trained model is already
present, the training step is skipped. Ready-made example
outputs sit in the repository’s output/ folder: for
each test chorale, Bach’s original harmonization beside the generated
one (the three style variants where available), each as MIDI and
MusicXML, openable in any notation software, so they can be compared bar
by bar. Every other knob (sampling temperature, search width, the rule
weights) lives in config.py.
Built with Claude, over many iterations. I wrote this project in close collaboration with Claude, Anthropic’s AI assistant, over a long series of iterations: more than a hundred commits that grew the rule catalog, made its weights data-driven, added the phrase-wise search, the repair step, and the style engine, and tried (and sometimes discarded) larger network variants. Even a small, self-contained machine-learning pipeline like this one turns out to need a surprising amount of patient back-and-forth to get right.