About the Algorithms: Chords

By Adam Wagner, written on Feb 27, 2026

Related Items

About
Learn about the me and my motivation for this project.
Chord Reference
Explore references and tools for understanding chords.

This post assumes some familiarity with code and algorithms, but I do recognize this site is dedicated to music and not code. I've tried to keep things light on code, but did think it might be interesting to those with understanding of both music and code; if that's not you and/or you have questions about this, feel free to reach out!

Motivation and Design

A goal I always have in mind when designing an application or algorithm is accuracy. With the exception of lossy algorithms (like most compression), or more recently LLMs, accuracy is rarely the problem of a well written algorithm; it's usually from us error-prone humans. And while I can write tests for my algorithms, and use fancy typesystems to ensure I don't have bugs, this is more difficult to do with a pile of data.

If I can limit the amount of data entry I do in my software, I should have less errors. Put another way, the more I can derive from first pricipals, the more I can trust the output. This is the theme of the processess and algorithms for chords here. There are no fundamentally difficult (time-wise) problems here (for chords, specifically) - everything is relatively easy to compute - but I am attempting to minimize the chances that I've got errors in the ouput of the site.

Because of this, there are no databases here, just around 60 lines of code that give the core definitions of the chords that help drive the entire site. Everything else about chords are derived (with much more than just 60 lines) from this and the few other manually entered pieces of music 'data' we need (scales and guitar tunings are the other main places non-generated data shows up).

Those 60 lines are just defining values of the following `Chord` type:

data Chord = Chord
    { preferredSymbol   :: Text             -- short type symbol like "m" for minor
    , description       :: Text             -- long name like "minor" or "diminished"
    , intervals         :: [Interval]       -- interval stack defining chord, eg: [Major3, Minor3]
    , additionalSymbols :: [Text]           -- other names used to define the chord
    , metaTypeTag       :: ChordMetaType    -- Broad categorization of chord for grouping purposes

    -- These two are not defined manually, but only tracked so we can associate inversions to their
    -- base chord types - more on this below in the 'inversions' section.
    , inversion         :: Maybe Int        -- If this is an inversion, which one is it (first, second, etc)?
    , derivedFrom       :: Maybe Chord      -- Main chord inversion was derived from
    }

data ChordMetaType = Triad | Seventh | Ninth | Eleventh | Other

And the definitions look something like this:

knownChordTypes =
    [ fromSemitones Triad "" "Major" [0, 4, 7] ["Maj", "M"]
    , fromSemitones Triad "5" "Powerchord" [0, 7] []
    , fromSemitones Triad "aug" "Augmented" [0, 4, 8] ["+", "#5"]
    ...

I've got a helper function `fromSemitones` that allows me to specify the semitone locations of each note, relative to the root, rather than interval stacks, as that can be easier to think about sometimes. The beauty of this approach is that the only things I can't really test easily (that could be wrong) are the names, and possibly the semitones, which would make the notes wrong.. this would be bad, but short of deriving the names from what intervals are used, there's not much more I can trim away - and I can't really derive the names that well, as chord names are not 100% systematic in nature.

Fortunate for us though, most of the rest of music is systematic, and derivable. All of the other aspects you see on the chord detail pages are derived - you could say I've taught the code how to construct chords given a root note and a list of Intervals, for instance. Here's a brief list of the remaining features and a summary of how this is accomplished.

Scale Degree Formula

This is another instance of music 'data', but to a very small degree. For this, I convert the inteval list back to semitones, then apply a simple map of the degree name for that semitone number:

0  -> "1"
1  -> "♭2"
2  -> "2"
3  -> "♭3"
...

From here, I just concatenate these together to give us our classic: "1-3-5" style of formula.

Notes

Notes are also pretty simple, once again, I use the intervals on my chord type, along with the root note I'm given to jump interval at a time from one note to the next till I've found all notes required for this chord. The process for this is almost identical to interval-stack method given on the chord detail pages. Here's an example of this for an A Minor chord.

A1Bb♭2B2C♭3C#3D4D#♭5E5m3M3

Inversions

Inversions are a relatively new addition to the chords listed here, and, in following with the theme, are all derived from the defined chords. I've just taught the code how to figure the inversions belonging to a chord - which is helpful, because there are over 200 inversions of these roughly 60 chords defined manually.

To do this, I have a `chordInversion` function that called on every defined chord to produce a list of inversions of for a chord. This `chordInversions` function returns the existing chord along with any inversions generated. These generated chords fill in values for the `inversion` and `derivedFrom` fields listed above allowing us to reference back to the source chord.

knownChordTypesWithInversions :: [Chord]
knownChordTypesWithInversions = addChordInversions knownChordTypes

-- | Update list of chords with inversions of each chord
addChordInversions :: [Chord] -> [Chord]
addChordInversions = concatMap go
    where go x = x : chordInversions x

I don't have a nice diagram for how the `chordInversions` function works yet, but it functions something like this: We take our defined intervals for the chord, add the interval required to get back to the root, then add the original defined intervals again. At this point we have a repeating cycle of intervals, and from here we can take slices of intervals to form each inversion. Something like this (using a minor chord as an example):

| Original Intervals   | Bridge Interval | Original Intervals |

[ Minor3,     Major3,    Perfect4,         Minor3,     Major3 ]

| No inversion: m3+M3 |
            | First inversion: M3+P4 |
                        | Second inversion: P4+m3 |

Everything else we have about the inversion is just computed from which inversion it is (first, second, etc), including the new lowest note.

Similar Chords

Finding similar chords is a little more computationally expensive - I'm currently just brute forcing the chord against every other chord, and showing those with a small number of differences. I could optimize this at some point, but since I'm not considering inversions, there are fewer than 1000 comparisons happening, so it runs quick-enough to be low on my list of things to improve.

Associated Scales

An associated scale is just a scale that this chord is allowed to be played in without borrowing any notes. Like similar chords, this isn't too interesting, as it is basically a brute force search.

Conclusion

This concludes a basic overview of chord functionality. Whether interesting or just brute force, the advantage to all of this, is that as soon as I add a new chord, or a new concept (like inversions), all of this just shows up for free, reducing the possibility for error, and allowing me to improve how things are explained, what things are discussed about chords without needing to repeat this manually for all chords.

I hope this was an interesting, or at least illuminating, read on how all chords are dealt with on behind the scenes here on the site. Feel free to reach out if you have questions or comments about my approach.

Related Items

About
Learn about the me and my motivation for this project.
Chord Reference
Explore references and tools for understanding chords.