/Remi Corson

My tests said 90%. They were measuring the wrong thing.

AnyChord shipped this month. You give it any song on YouTube, it works out the chords, and it scrolls them under your hands in time with the recording. The bar sounding now is lit, the next one is already on screen, and tapping a beat sends the video there, which is how you learn a bridge without hearing the first verse nine times.

The AnyChord home screen, searching for a song by artist and titleThe chord grid scrolling bar by bar in time with the YouTube video, with the current bar lit

The app is the easy half. An embedded player and a scrolling grid is about a week of work. A grid that says C where the record plays Am is worthless however smoothly it scrolls, and if you read the reviews of the apps already doing this, that is what people complain about. It is never the scrolling.

So I did not start with the app. I started with the recogniser, because the only question that mattered was how often it is right, and that question has a number.

This is the story of that number, and of how long it lied to me.

Scoring something that has no answer key

Real music has no ground truth. Nobody ships a record with a bar-by-bar chart of what it actually is, and where charts exist they are someone's transcription, which is an opinion.

So I made my own. Some Might Play, the app I shipped two weeks ago, contains a synthesiser that renders a progression from roman numerals: chosen key, chosen tempo, chosen drum style. I had it render a set of progressions and write the answers next to them. Audio on one side, the chart that produced it on the other.

The reasoning was simple. If the recogniser cannot read audio I generated myself, where I know every chord and every downbeat, it has no business being pointed at a record.

It got to 89.7% of bars correct, and four of the five test files came out sixteen bars from sixteen. The fifth had every chord right with the grid starting one bar late, which is a phase error rather than a hearing error.

What the number taught me on the way up

Getting there took a series of changes, and the useful part is that the measurements disagreed with my instincts almost every time.

A silence template ate entire songs. I had included a flat template so the recogniser could say "nothing here". A flat template scores well against any chroma that drums have smeared flat, so the louder the kit, the more the recogniser heard silence. Two of the five files returned nothing at all. Deleting that one template took them from 0% to over 90%.

Beat tracking errors are not symmetric. A tracker that runs at double the real tempo costs nothing: two beats inside one chord both get that chord, and you are still right. A tracker that runs at half costs everything: one beat straddling two chords can only be right about one of them. So I asked for a fast prior and halved the answer afterwards. 56.6% to 85.3%, from a change that is three lines and no cleverness.

Weighting the third was the obvious move and the wrong one. The third is what separates major from minor, so surely it should count for more. I raised its weight from 1.0 to 1.3 and accuracy fell to 52%. At 1.6 it fell to 23%. The third is the weakest evidence in a strummed chord, buried under the root and the fifth, so betting on it means betting on noise.

A misaligned bar does not come out undecided. It comes out confident and wrong. I had been stepping through bars by a fixed number of seconds from the first beat. A window straddling a D and an A reads as F#m, which shares notes with both and is a perfectly plausible answer. Whole songs collapsed onto one chord that way. Bars are counted in beats now, and the phase is searched rather than assumed.

Pool evidence by summing, never by averaging. Four beats in a bar are four observations. Averaging their log-likelihoods left the gap between competing chords smaller than the price my Viterbi pass charges for changing chord, so the answer stopped changing at all. One song came out as a single chord for three minutes. Summing fixed it, and made the result stable across every smoothing value I tried.

None of those are music problems. They are the same measurement problems you hit anywhere: a class that wins by default, an error that is cheap in one direction and fatal in the other, a feature you were sure mattered, and a pooling operator quietly cancelling your signal.

Then I watched it on a real record

C read as Am. Over and over.

It is the obvious confusion once you see it. C is C E G, Am is A C E. They share two notes out of three. What separates them is the note underneath, and my analyser was not listening to the bass at all.

So I wrote a bass-aware pass: a second chroma hearing only the bottom two octaves, voting for chords whose root matches what the bass is playing. Exactly the fix the failure calls for.

Measured against my test set, it was worth nothing. Neutral up to a weight of 0.10, and actively destructive above it, taking 89.7% down to 67.5%.

I nearly deleted it.

The test never fails the way reality fails

Then I went and counted. In the generated set, the C-for-Am mistake happens exactly zero times.

Two of the files contain both C and Am. Every error in them is a boundary error or a phase error. Not one is a relative-chord confusion. My synthesiser plays clean triads with a clear root, so the one ambiguity that dominates real recordings is the one piece of reality my test bench cannot produce.

Which means the 89.7% was never a measure of the thing that was broken. It was real, it was reproducible, it went up when I improved the engine, and it was blind in precisely the place where the engine actually failed. A test that never fails the way reality fails cannot tell you whether your fix for reality works. It will keep reporting ninety percent while you stare at a screen full of wrong chords.

Generating my own answers took the engine from nothing to ninety, which is worth a great deal. It also took it as far as it can go. The next step is labelled real audio, songs whose grid is known bar by bar, and there is no shortcut to that.

The bass pass is still in the code, switched off, because the idea is sound and the evidence is not. Shipping it on the strength of a number I no longer trust would have been the worse mistake.

The one I have not solved

Two kinds of record want two different models, and nothing in the engine can yet tell them apart on its own.

A strummed or arpeggiated record wants triads, with a penalty on the root-and-fifth templates so they do not swallow everything. A distorted record played in power chords wants no penalty at all. Set the penalty to zero and Smells Like Teen Spirit comes out as F Db Bb Db, which is the actual riff, while the generated set falls to 70.5% because every G becomes G5.

I tried choosing automatically, by scoring both readings against how much of the sound each explains. It picks power chords every single time, because a template that ignores the third is never punished for ignoring it. Deciding it properly needs a measure of how much third is actually present, or a model trained on both kinds of record. For now it is a parameter, and the default serves clean material.

So the app tells you it might be wrong

All of which leads to the design decision I am most comfortable with.

The FAQ on the site asks "are the chords always right?" and the answer starts with the word No. Automatic listening returns an estimate, and it is most often wrong on riffs with no third in them. That is not modesty, it is the engine's actual failure mode, written down where people can read it before they pay.

Chord diagrams in playing order, spelled to match the key of the songCorrecting a chord: hold a beat and change it, remove it, or mark a rest

So you get the last word. Hold a beat, change the chord, remove it, pull the next one forward, mark a rest. Your corrections stay with the song and can be undone. A recogniser that admits it guesses and lets you fix the guess is more useful than one that pretends, and it is the only version I could ship while knowing what I know about the number.

Two apps, opposite answers

Two weeks ago I wrote that Some Might Play contains no networking code at all, and that this was deliberate.

AnyChord cannot make that claim, and it is worth saying plainly. iOS gives an app no access to the audio of an embedded player, so the recognition runs on a server: it takes a video id, fetches the audio, returns beats and chords as JSON, and caches the result so a song is analysed once for everybody. That is why the first analysis of a song takes three or four minutes and every one after that is instant, for you and for everyone else.

What it still does not do is know who you are. No account, no email, no advertising, no analytics, no tracking. The server is there because the physics of the problem put it there, not because there is a funnel behind it.

Where it is

anychord.app, iPhone and iPad, English, French, Spanish and German. Four new songs a month for free, plus everything you have already analysed, for good, along with the grid, the diagrams, the tuner and the metronome. Premium removes the limit at 3.99 euros a month.

If you play, the thing I want to hear about is the songs it gets wrong. Those are worth more to me than compliments right now, because a labelled list of records where the grid is off is exactly the thing that would take the engine past ninety.