The Bach Generator That Stopped Repairing
I write a program that generates Bach's instrumental music. Organ fugues, the unaccompanied cello suites, the Chaconne. It emits MIDI, not notation. The first architecture was, in hindsight, too naive. Generate the notes first, then fix the rule violations afterward. I thought that would work.
It did work. Parallel fifths disappeared. Voice crossings were resolved. Leaps got reined in. The tests went green.
But when I played it back, it wasn't music.
There was a strange sensation: the more I fixed, the less it sounded like music. However many repairs I stacked on, what poured out was a storm of dissonance — unlistenable, like a child mashing an organ at random. Watching it come out batch after batch, I understood there was no finish line down this road. Eventually I threw the whole engine out. I threw out the very idea of repair. This is a record of that pivot.
Counterpoint in three minutes
Let me set up the minimum vocabulary first. Most engineers reading this have probably never had much to do with counterpoint. You can skip this section, but reading it makes the second half go down in one gulp.
Counterpoint is the craft of sounding several independent melodies at once and still making them agree. It isn't a tune with backing chords. Rather than a main melody plus harmony, every line stands on its own as a melody, and they don't fall apart when layered together. Each line is called a voice. A fugue stacks three, four of them.
There are two axes. Horizontal — each line, on its own, has to be a singable melody. Vertical — the notes sounding together at any instant have to ring without mud. Composing is the work of satisfying the vertical and the horizontal at the same time, continuously.
Think of several people talking at once. Each is saying something coherent (horizontal), and they aren't all chanting the same words in unison (vertical independence). The moment everyone collapses onto one voice, it stops being a conversation and becomes a chant.
Consonance and dissonance
The distance between two notes is an interval. Depending on the distance, the sonority is stable or tense. Every score from here on is playable; press play and check it with your ears.
First, the perfect consonances — the perfect fifth and octave.
Why "perfect"?
"Perfect" isn't a quality judgment. C up to G is a perfect fifth; C up to the C above it is a perfect octave — both blend so strongly that the two notes nearly fuse into one. A third or sixth comes in two sizes, major and minor, so its color shifts; the fifth and octave have no such wobble — there's essentially one of each. Hence "perfect." And that strong blend is exactly why their parallels get forbidden later.
Next, the imperfect consonances — thirds, sixths, and tenths.
Seconds, sevenths, and the tritone are dissonances; they create tension.
Motion
When two voices move, the relationship is one of three. Parallel (same direction), contrary (opposite directions), or oblique (one holds while the other moves). A voice's independence comes from not always moving in parallel.
Beats
One more thing. Beats have a hierarchy: the head of each bar is strong, everything else is weak. The dissonance from a moment ago may pass through on a weak beat, but land it on a strong beat and it reads as a structural error. Where you can put tension depends on where you are in the bar.
That gives us voices, consonance and dissonance, motion, and the strong and weak beats. That's enough tooling. Now the real question: why can't repair make music?
Learn, generate, fix
The engine I discarded — the one I called the "legacy generation system" in the commit log — was, for who I was back then, decent work. It had three stages.
First, learning. I built a corpus of 448 real Bach works as JSON, then extracted Markov transition tables and 5-gram vocabulary tables from it. Statistics of the form "this figure tends to be followed by that one."
Then, generation. Lay down notes while consulting the vocabulary tables. Voice motion went through a constraint solver — a three-layer model called ConstraintState: obligations to satisfy, invariants never to break, and a per-note gravity, with a FeasibilityEstimator to judge "is this still solvable from here." A fairly elaborate machine.
Then, scoring. Hand the result to a scorer in a sibling project, bach-mcp, which graded it by closeness to the real corpus. Too low, regenerate. Not an automatic loop, though — I drove it by hand, eyeballing the score.
In short: learn Bach from data, imitate to generate, then fix what looks wrong. Not so different from the naive instinct behind today's generative AI. The trouble was packed entirely into "fix."
The repairs pile up
Generated notes always carry rule violations. Each time, I added another repair pass. Skim the terms — what I want you to see is the pile itself, left behind in the commit log.
- Erase parallel fifths and octaves (
parallel repair) - Fix leaps — big jumps in a line (
leap resolution module) - Fix repeated notes (
repeated-note repair) - Fix clashes between neighboring voices (
tritone repair,cross-relation fix) - Stop the outer voices from landing on a bare perfect interval (
hidden-perfect outer-voice rule) - Untangle voice collisions across the whole timeline (
harmonic-timeline-aware collision resolution) - Rebuild the voice-leading in the coda — the closing bars (
coda voice-leading search)
Each one is correct on its own. Parallel fifths are forbidden in counterpoint; they should be fixed. But the more repairs I added, the more new problems surfaced. Nudge a note to kill a parallel fifth, and the nudge creates a leap. Fix the leap and you get a repeated note. Fix the repeated note and now it's a parallel octave against another voice. Whack-a-mole.
And the fatal part: repair can erase a rule violation, but it cannot make music.
Repair breaks the music
Why can't local repair produce music? It's faster to look at concrete cases, checking each one by ear.
Parallel fifths are forbidden not for moral reasons. To borrow the conversation again: the instant two people move in lockstep a perfect fifth apart, the two voices melt into one.
Through the lens of repair, this looks like a "shift one note and it's gone" problem. And indeed it goes away. But the note you shifted lives inside the logic of a melody. A line that connected smoothly to its neighbors now snaps to suit the repair. The violation is gone, but the line is dead.
Hidden fifths are nastier. Even if there's no fifth at this exact instant, when the outer voices arrive at a perfect fifth from the same direction, the ear hears the ghost of a parallel fifth.
Here's where a key fact bites: only parallels of perfect intervals are forbidden. Parallel thirds and sixths — tenths included — are in fact a staple of Bach's writing, two voices riding nicely alongside each other.
So a "remove the parallels" repair is too blunt. Perfect and imperfect intervals are treated in opposite ways, which means you have to decide what to keep and what to kill before you place the note.
A suspension can't be made after the fact
The limits of repair show clearest in the suspension.
A suspension holds a note that was consonant on the previous beat into the next beat, where it becomes a dissonance, and then resolves down by step. Preparation — suspension — resolution, in three beats. It must be prepared as a consonance before it turns dissonant.
This is the catch. Repair only acts after it finds a dissonance that is currently sounding. But the preparation of a suspension has to be in place on the beat before the dissonance sounds. By the time repair spots the violation, it's already too late. Rewind time to rewrite the previous beat as a consonance, and now the previous beat's consistency falls apart.
Place a dissonance with no preparation and it's just mud, not a suspension.
Listen to a chain of Bach suspensions and you hear the dissonances generating a rhythm of tension and release. Post-generation fixes can never reach this. You have to build it with the time axis intact, at the design stage.
The constraint of surviving inversion
There's one more case local repair can never reach. Invertible counterpoint.
Here, to invert means to take the lower voice, lift it a whole octave, and let the two voices trade places. Swap them and the intervals change. Say the lower note is C and the upper is G — a perfect fifth. Lift the C above the G and now it reads G up to C: a perfect fourth. The fifth turned into a fourth. A third turns into a sixth the same way. The two interval numbers always add up to nine.
You can watch it happen with the toggle below. Flip between "original" and "inverted": line A on top holds still while line B (blue) below rises an octave, and every third turns into a sixth.
And here's the trap. Between two voices on their own, the perfect fourth counts as a dissonance — even though the perfect fifth is a consonance. So a fifth that was perfectly stable flips into a dissonant fourth the instant you swap the voices.
The perfect-fourth puzzle
G up to the C above it is a perfect fourth. It sounds settled to the ear, yet between two voices alone it's treated as a dissonance — an old rule of counterpoint. The reason: the lower G reads as the foundation, leaving the upper C feeling unsupported. Add a third voice underneath and the fourth becomes consonant again. So it only causes trouble in exactly this case — swapping two voices top for bottom.
To satisfy this, you have to design the two lines from the start so each works whether it's on top or on the bottom. It isn't the kind of property you can approach by repairing after generation. In Bach's fugues, subject and countersubject satisfy this constraint as a matter of course, which is exactly why the development can swap them top-for-bottom without falling apart.
Voice crossing has the same smell. The old engine treated crossings as something to "detect and fix." But Bach crosses voices deliberately, to sharpen the independence of the lines rather than blur it. Repair fixes uniformly the very thing that must not be fixed uniformly.
Place it right the first time
The conclusion was simple. Stop generating-then-fixing. Choose the right note at the moment you place it.
The new engine — I named it the "composer subsystem" in the commits — builds MIDI directly from a harmonic plan and per-voice intents. At its center is candidate_search. When placing a note at a position, it evaluates the candidate pitches one by one. Does it create a parallel perfect interval? Cause a voice crossing? Is the leap appropriate? Is the dissonance prepared and resolved? Is it valid as a passing tone? Does it carry the fugal answer? Each rule scores the candidate, and the highest-scoring one is chosen.
It isn't a matter of fixing violations after they appear. A candidate that would be a violation simply never gets chosen. The repair passes became unnecessary. The only thing that runs after generation is the validator — final checks of structure, the horizontal flow, and the vertical sonority. It's not a device that fixes; it's a device that confirms the placement was right.
With the new engine standing, I cut off the retreat.
- First I moved the 448-work Bach reference corpus, scorer and all, into bach-mcp. The generation engine doesn't depend on this data — the real Bach is consulted only at the final verification.
- I switched all ten forms across the C API, the CLI, and the WASM build over to the new
composer. - And I deleted the legacy engine wholesale. The counterpoint, fugue, forms, ornament, instrument, transform, and solo-string subsystems, along with the Generator that tied them together.
Verification after the deletion: 920 ctest cases, the WASM build plus 217 JS tests, 146 Python tests — all green. For how much code I threw away, nothing broke. The pile of repairs was never the essence.
Confirm the rules by ear
I wanted the rules candidate_search looks at to be something you could hear, not just read. So I built a counterpoint course alongside the site. Every playable score in this article is lifted straight from that course.
The course teaches the grammar through Bach's own examples. Take the fugal answer. Transpose the subject directly to the dominant and you get a "real answer"; adjust part of it to preserve the tonal logic and you get a "tonal answer." Which one to choose is not a local matter but a question of the whole piece's tonal design.
What's the dominant?
The dominant is the key a fifth above the home key. If C major is home, the dominant is G major. In a fugue the subject first appears in the home key, and the answer replies in the dominant — that call-and-response between home and dominant is the backbone of the whole form.
Once the subject enters, it needs a countersubject riding alongside it. And when the subject entries overlap, you get a stretto.
Then the cadence. Not only the perfect cadence, but the deceptive cadence that betrays expectation, and the Picardy third that closes a minor-key piece on a major chord. A cadence is harmonic grammar — and this too can't be written unless it's decided before the notes are placed.
The course's table of contents is also, exactly, the list of rules candidate_search looks at — intervals and consonance, motion and forbidden parallels, dissonance treatment, melodic writing, tonal grammar, fugal devices, and form-specific constraints. The article's constraints map one-to-one onto the course's chapters. If the reasoning piques your curiosity, the fastest path is to follow the counterpoint course from the start. Every score plays right in the browser.
What I learned
I started out thinking "Bach is logic." I was wrong.
More precisely: it is logic, but you cannot apply the logic after the fact. Remove a parallel fifth, fix a leap, resolve a dissonance — each is doable one at a time. But music is not a heap of such local correctnesses. A suspension must be prepared before it sounds; invertible counterpoint must hold up when top and bottom are swapped; an answer must sit inside the tonal design of the whole piece. None of them can be reached by "place it, then fix it."
Don't generate then fix. Place it right from the start. It took throwing away an entire engine to understand something this obvious. And every time I play a generated piece, I sit a while with the greatness of Bach — a man who wrote all of this without fixing a single note after the fact.