The quirks of programming music theory

Let’s start with a base set of simple, readable classes to cover the information we’ve gleaned thus far.enum class PitchLetter(val integerValue: Int) { A(9), B(11), C(0), D(2), E(4), F(5), G(7)}inline class Accidental(val modifier: Int)data class PitchClass( val pitchLetter: PitchLetter, val accidental: Accidental)inline class Octave(val value: UInt)data class Pitch( val pitchClass: PitchClass, val octave: Octave)Ideally the outcome of this article will be something that provides a simple API surface for transpositions, such as:Pitch#transpose(Interval)What might this proposed Interval class look like?.A naive first attempt could have it be this:// Naive attemptdata class Interval( val distance: Int)The transposition of a PC represented in integer notation can indeed be distilled to a singular number that represents the interval..A PC as a combination of a pitch letter and accidental, however, requires more information..For instance, the intervals from C to D♯, E♭, and F♭♭ are all an integer distance of 3..If the code were to invoke cNatural4.transpose(Interval(3)) how would it know which pitch letter to land on?.Theoretically there are infinite spellings of PCs that map onto the same integer value — this is what is meant by the term enharmonicism..The purpose behind the concept is tonal function; for example, a chord spelled C-E-G-B♭ is interpreted differently from one enharmonically spelled as C-E-G-A♯..The former has a tendency to resolve toward an F major chord, and the latter to a B major chord..This is also why the Interval class actually needs two fields to provide all the data necessary for an unambiguous transposition: one to define the letter distance, and one the integer distance..This is completely in line with how musical intervals are traditionally expressed, too — a minor 2nd, major 3rd, perfect 4th, and so on, each imply a respective letter distance.// Betterdata class Interval( val letterDistance: Int, val integerDistance: Int)cNatural4.transpose(Interval(1, 3)) // D♯4cNatural4.transpose(Interval(2, 3)) // E♭4cNatural4.transpose(Interval(3, 3)) // F♭♭4In order to reach the goal of transposing a Pitch, the majority of its composed fields will first need to understand what it means to be transposed..That is, a Pitch can’t transpose without its PitchClass knowing how to transpose, and that can’t be possible without its PitchLetter knowing how to transpose.From the previous code example we saw that Interval.letterDistance now distinguishes the intended pitch letter output..Transposing in the context of our PitchLetter enum class is simply addition or subtraction of that value within a universe size of the total number of PitchLetters, or 7.. More details