Giant Steps Are Small Steps: The Group Theory, Geometry and Fourier Structure of Coltrane’s Changes

For engineers, data scientists and musicians who want a runnable, quantitative model of why Coltrane’s most famous progression is hard to play and easy to hear.

“Giant Steps” has a reputation as the final exam of jazz harmony. The tonal center jumps by a major third every bar or two, the chords arrive every two beats, and the tempo leaves almost no time to think. Most explanations stop at that description: the keys are far apart, so the tune is hard.

I think that description is half right, and the wrong half is the interesting part. Measured in scales, the keys of Giant Steps are far apart: B major and G major share only 3 of their 7 notes. Measured in chords, they are almost neighbors. A B major triad becomes a G major triad by moving two voices one semitone each, a total of 2 semitones. The “normal” move from C major to G major costs 3. The giant steps are small steps in the space where chords live, and large steps in the space where scales live. That gap is, I will argue, the mathematical signature of the tune: it is why it sounds coherent to a listener and punishing to an improviser.

This piece builds that argument from first principles. We treat the 12 pitch classes as the cyclic group Z₁₂, show that the three key centers form a subgroup coset (an augmented triad), measure voice-leading distance with an optimal-assignment algorithm, look at the tune through the discrete Fourier transform of pitch-class sets, compute a winding number that splits the form into a descending loop and an ascending loop, and close with a proposed metric for improviser load. Every number is computed by code you can run, and every figure is generated by that code.

The record, and the changes as data

The master take was cut at Atlantic Studios in New York during the May 4 and 5, 1959 sessions and released on the album Giant Steps in 1960, with Coltrane on tenor, Tommy Flanagan on piano, Paul Chambers on bass and Art Taylor on drums (Wikipedia, composition; Wikipedia, album). Sources disagree on which of the two days produced the master: Wikipedia lists May 5, while the Discogs CD credits place the Flanagan tracks on May 4. Two earlier attempts from March 26, 1959, with Cedar Walton and Lex Humphries, survive as alternate versions. Coltrane brought the tune in without rehearsal, and Flanagan’s halting, start-stop solo on the master is one of the most discussed piano solos in jazz.

The best quantitative anchor I found is the Jazzomat Research Project at the University of Music Franz Liszt Weimar, which transcribed Coltrane’s first solo note by note. Their entry reports 1,127 notes over 178 bars, 11 choruses, a mean tempo of 290.3 beats per minute and an event density of 7.64 notes per second across 147.5 seconds. Those numbers are internally consistent: 178 bars × 4 beats × 60 / 290.3 ≈ 147.1 seconds. I use 290.3 bpm as the reference tempo for everything below.

The form is 16 bars of 4/4, 26 chords, 64 beats. Here is one chorus, matching the chord list in the Jazzomat transcription (they spell B as Cb):

To make the harmony computable, each chord becomes a root, a quality and a duration in beats, and each chord is assigned the key it functions in: a major seventh is I, a dominant seventh is V, a minor seventh is ii. The first block of code is the data model the rest of the article builds on. Every later block assumes the earlier ones have run, so the blocks concatenate into one script.

import numpy as np
from itertools import permutations

PC = {"C": 0, "C#": 1, "Db": 1, "D": 2, "D#": 3, "Eb": 3, "E": 4, "F": 5,
      "F#": 6, "Gb": 6, "G": 7, "G#": 8, "Ab": 8, "A": 9, "A#": 10, "Bb": 10, "B": 11}
NAME = ["C", "Db", "D", "Eb", "E", "F", "Gb", "G", "Ab", "A", "Bb", "B"]
QUALITY = {"maj7": (0, 4, 7, 11), "m7": (0, 3, 7, 10), "7": (0, 4, 7, 10)}

# One 16-bar chorus of Giant Steps as (root, quality, beats) in 4/4
GIANT_STEPS = [
    ("B", "maj7", 2), ("D", "7", 2), ("G", "maj7", 2), ("Bb", "7", 2),
    ("Eb", "maj7", 4), ("A", "m7", 2), ("D", "7", 2), ("G", "maj7", 2),
    ("Bb", "7", 2), ("Eb", "maj7", 2), ("F#", "7", 2), ("B", "maj7", 4),
    ("F", "m7", 2), ("Bb", "7", 2), ("Eb", "maj7", 4), ("A", "m7", 2),
    ("D", "7", 2), ("G", "maj7", 4), ("C#", "m7", 2), ("F#", "7", 2),
    ("B", "maj7", 4), ("F", "m7", 2), ("Bb", "7", 2), ("Eb", "maj7", 4),
    ("C#", "m7", 2), ("F#", "7", 2),
]

def chord_pcs(root, quality):
    r = PC[root]
    return tuple(sorted((r + i) % 12 for i in QUALITY[quality]))

def key_of(root, quality):
    """Functional key: maj7 is I, dominant 7 is V, minor 7 is ii."""
    offset = {"maj7": 0, "7": 5, "m7": 10}[quality]
    return (PC[root] + offset) % 12

def key_regions(prog):
    """Collapse consecutive chords that share a key into [key, start_beat, beats]."""
    regions, t = [], 0
    for root, q, beats in prog:
        k = key_of(root, q)
        if regions and regions[-1][0] == k:
            regions[-1][2] += beats
        else:
            regions.append([k, t, beats])
        t += beats
    return regions

if __name__ == "__main__":
    regs = key_regions(GIANT_STEPS)
    print("chords:", len(GIANT_STEPS), "beats:", sum(b for *_, b in GIANT_STEPS))
    print("key path:", " ".join(NAME[k] for k, *_ in regs))
    print("key changes per chorus:", len(regs) - 1)

Running it prints 26 chords, 64 beats, the key path B G Eb G Eb B Eb G B Eb B, and 10 key changes per chorus. At 290.3 bpm one chorus lasts 64 × 60 / 290.3 ≈ 13.23 seconds, so the soloist meets a new chord every 0.51 seconds on average and a new key every 1.32 seconds. That is the raw difficulty everyone talks about. The rest of the piece asks what kind of difficulty it is.

The framing model: one coset of Z₁₂

The organizing idea of this piece is that Giant Steps never leaves a single coset of a subgroup of Z₁₂. Everything else follows from that.

In equal temperament, octave-equivalent pitch classes form the cyclic group Z₁₂: the integers 0 to 11 under addition mod 12, with C = 0, C# = 1 and so on up to B = 11. Transposing by n semitones is the map Tₙ(x) = x + n. The element 4 (a major third) generates a subgroup of order 3, because three major thirds make an octave. That subgroup has 12 / 3 = 4 cosets, and each coset is an augmented triad.

The three tonics of the tune, Eb, G and B, are exactly the coset 3 + H. Every key change in the chorus is a move of +4 or −4, so the tonal center performs a walk on a three-node cycle and can never reach the other nine keys. Compare that with ordinary jazz harmony, which moves by fifths. The element 7 generates all of Z₁₂ because gcd(7, 12) = 1, so a cycle of fifths eventually visits every key. Coltrane swapped a generator of the whole group for a generator of a closed loop of just three keys.

The circle of fifths is the same group with the elements reordered by the map φ(x) = 7x mod 12. Because 7 × 7 = 49 ≡ 1, φ is its own inverse, so it is an automorphism of Z₁₂. It sends 4 to 28 ≡ 4, which means it maps H onto itself and carries augmented triads to augmented triads. That is why the tonic triangle stays equilateral in both pictures: on the circle of fifths, B, G and Eb sit at positions 5, 1 and 9, four fifths apart. Four fifths is two steps short of the maximum possible distance of six, the tritone. On the fifths metric musicians use to judge how related two keys are, every move in the tune is a long one.

Figure 1. The tonics B, G and Eb on the chromatic circle (left) and the circle of fifths (right). Multiplying by 7 maps the major-third subgroup to itself, so the triangle stays equilateral.
def fifths_position(pc):
    """Circle-of-fifths position: multiply by 7 in Z12 (7 is its own inverse mod 12)."""
    return (7 * pc) % 12

tonics = {PC["B"], PC["G"], PC["Eb"]}
H = {0, 4, 8}
print("one coset of <4>:", tonics == {(3 + h) % 12 for h in H})                 # True
print("fifths positions:", {NAME[k]: fifths_position(k) for k in sorted(tonics)})  # Eb 9, G 1, B 5

David Demsey made the musical version of this point in 1991: when three key areas are equidistant, nothing in the cycle itself privileges one of them, and the home key is only settled by how the piece ends (Coltrane changes, citing Demsey). The group picture says the same thing in one line. The cycle has a rotational symmetry of order 3 (T₄ permutes the three keys), and a symmetric object cannot single out a preferred element. The Fourier section below turns that symmetry into a number.

Giant steps are small steps: two distances that disagree

The major-third key change is the cheapest move between two major triads and one of the most expensive moves between two major scales. That disagreement is the core of the tune.

To make “cheap” precise I use voice-leading distance in the sense of Dmitri Tymoczko’s chord geometry (Tymoczko, Science, 2006). Treat two chords X and Y of the same size as sets of pitch classes. Assign each note of X to a note of Y, let every voice move by the shortest path around the octave, and add up the semitones. The voice-leading distance is the smallest total over all assignments.

Here n is the number of notes in each chord, σ ranges over the n! ways to pair notes, and |a|₁₂ is the distance around the pitch-class circle. For the scale side I use the simplest count that matters to an improviser: the number of pitch classes in the old major scale that are absent from the new one, d_scale(k₁, k₂) = |S(k₁) \ S(k₂)|.

Worked example. B major (B, D#, F#) to G major (G, B, D): B stays, D# drops a semitone to D, F# rises a semitone to G. Total d_VL = 2. The textbook dominant motion C major (C, E, G) to G major (G, B, D) keeps G, moves C down to B (1) and E down to D (2), for d_VL = 3. Meanwhile B major and G major share only B, E and F#, so 4 of 7 scale tones change, against 1 of 7 for C to G. The fifth move is a big step for the hands and a tiny step for the scale. The major-third move is the opposite.

Figure 2. For each interval between two keys: semitones of motion needed to connect the tonic triads (teal) and scale tones that change (coral). The major third is the only interval where the triad moves by 2.
MAJOR_SCALE = (0, 2, 4, 5, 7, 9, 11)

def vl_distance(a, b):
    """Smallest total voice-leading (taxicab, octave-equivalent) between equal-size chords."""
    return min(sum(min((y - x) % 12, (x - y) % 12) for x, y in zip(a, perm))
               for perm in permutations(b))

def scale(key):
    return {(key + s) % 12 for s in MAJOR_SCALE}

def scale_distance(k1, k2):
    """Pitch classes that must change when moving between two major scales."""
    return len(scale(k1) - scale(k2))

def triad(root):
    return tuple((root + i) % 12 for i in (0, 4, 7))

def maj7(root):
    return tuple((root + i) % 12 for i in (0, 4, 7, 11))

for i, name in zip(range(1, 7), ["m2", "M2", "m3", "M3", "P4/P5", "TT"]):
    t, s, d = vl_distance(triad(0), triad(i)), vl_distance(maj7(0), maj7(i)), scale_distance(0, i)
    print(f"{name:>6}  triad {t}  maj7 {s}  scale {d}  leverage {d / t:.2f}")

The loop prints the whole picture. Triad voice-leading costs 3, 6, 3, 2, 3 and 6 semitones for the minor second through the tritone; scale changes are 5, 2, 3, 4, 1 and 5 tones. Brute force over permutations is fine here because a four-note chord has only 24 pairings.

I’ll name the ratio. As a proposed measure, call modulation leverage λ = d_scale / d_VL: how many scale tones change per semitone of chord motion. For triads, λ is 0.33 for a fifth, 1.0 for a minor third, 1.67 for a minor second and 2.0 for a major third, the maximum. The major third is the unique interval that moves a major triad by 2 semitones, and it buys 4 changed scale tones with that motion. In neo-Riemannian terms this is Richard Cohn’s hexatonic relation: major triads a major third apart sit two parsimonious moves apart (a P and an L transformation), each moving one voice by a semitone (Cohn, Audacious Euphony, Oxford University Press, 2012; see Jason Yust’s review in Music Theory Online).

The result is cleanest at the triad level, and I want to be precise about where it weakens. With major seventh chords, the major third costs 4 semitones of voice motion, which ties the fifth, the minor third, the minor second and the tritone; only the whole step costs more (6). So with sevenths, the major-third move is no longer uniquely smooth. It is as smooth as a fifth while changing four times as many scale tones. That is still the asymmetry the argument needs, though with sevenths the major third only ties for smoothest.

The Fourier view: a tune with almost no fifths in it

Measured by the discrete Fourier transform of its pitch-class content, Giant Steps keeps about 10% of the “fifths energy” of a single major scale and puts its weight on the major-third component instead.

The tool comes from mathematical music theory: Ian Quinn’s work on Fourier balances and Emmanuel Amiot’s textbook Music Through Fourier Space (Springer, 2016; see Jason Yust’s review). Represent any pitch-class collection as a weight vector x(0), …, x(11) and take its 12-point DFT.

F(0) is total weight. The magnitude |F(k)| for k from 1 to 6 measures how strongly the collection leans toward the evenly spaced structure with period 12 / k. The labels theorists use are: f₁ chromatic clustering, f₂ quartal, f₃ the augmented triad (the major-third cycle), f₄ the diminished seventh (the minor-third cycle), f₅ the diatonic or fifths component, f₆ the whole-tone scale. A C major scale has |f₅| = 2 + √3 ≈ 3.732 out of a total weight of 7, about 0.533 once normalized. That is the fingerprint of tonality.

Now the tonic set. For {Eb, G, B} = {3, 7, 11}, the DFT is exactly 3 at k = 0, 3 and 6 and exactly 0 everywhere else. The fifths component vanishes because the set is invariant under T₄, and any T₄-invariant vector can only have energy at multiples of 3. That is the symmetry argument from the framing section, now with a number attached: the key centers of Giant Steps contain zero fifths-direction information about which one is home.

The tonic set is an abstraction, so I also computed the DFT of what a listener actually hears: every chord tone in the chorus, weighted by how many beats it sounds, normalized so F(0) = 1. For a controlled comparison I used the first eight bars of Tune Up (Em7 A7 Dmaj7, Dm7 G7 Cmaj7), and the same eight bars rewritten with the Coltrane substitution, both as given on Wikipedia’s Coltrane changes page.

Figure 3. Fourier magnitudes of the duration-weighted pitch-class profile. Tune Up (grey) is dominated by f5. The Coltrane substitution (teal) drains it. Giant Steps (coral) moves the energy to f3, the major-third cycle.
def dft_mag(weights):
    """|F(k)| for k = 0..6 of a length-12 pitch-class weight vector."""
    x = np.asarray(weights, dtype=float)
    n = np.arange(12)
    return np.array([abs(np.sum(x * np.exp(-2j * np.pi * k * n / 12))) for k in range(7)])

def indicator(pcs):
    v = np.zeros(12)
    v[list(pcs)] = 1
    return v

def profile(prog):
    """Duration-weighted pitch-class profile of all chord tones, normalized to sum 1."""
    v = np.zeros(12)
    for root, q, beats in prog:
        for p in chord_pcs(root, q):
            v[p] += beats
    return v / v.sum()

TUNE_UP_8 = [("E", "m7", 4), ("A", "7", 4), ("D", "maj7", 8),
             ("D", "m7", 4), ("G", "7", 4), ("C", "maj7", 8)]
COLTRANE_TUNE_UP_8 = [("E", "m7", 2), ("F", "7", 2), ("Bb", "maj7", 2), ("Db", "7", 2),
                      ("Gb", "maj7", 2), ("A", "7", 2), ("D", "maj7", 4),
                      ("D", "m7", 2), ("Eb", "7", 2), ("Ab", "maj7", 2), ("B", "7", 2),
                      ("E", "maj7", 2), ("G", "7", 2), ("C", "maj7", 4)]

np.set_printoptions(precision=3, suppress=True)
print("tonic set:", dft_mag(indicator({3, 7, 11})))      # [3 0 0 3 0 0 3]
for name, prog in [("Giant Steps", GIANT_STEPS), ("Tune Up 8", TUNE_UP_8),
                   ("Coltrane Tune Up 8", COLTRANE_TUNE_UP_8)]:
    print(f"{name:20s}", dft_mag(profile(prog)))

The results, as normalized magnitudes. Tune Up’s first eight bars have |f₅| = 0.434, about 81% of a pure major scale’s 0.533: two adjacent keys, very tonal. Applying the Coltrane substitution to the same eight bars drops |f₅| to 0.117, a 73% reduction, with no change to where the phrase starts or lands. Giant Steps drops it further to 0.055, roughly 10% of the major-scale value, and puts its largest component at |f₃| = 0.291.

One detail surprised me. The substituted Tune Up has |f₃| = 0 exactly, even though it is built entirely from major-third cycles. The reason is phase. The first half cycles through D, Bb and Gb ({2, 6, 10}) and the second half through C, Ab and E ({0, 4, 8}). The f₃ coefficient of {0, 4, 8} is +3 and that of {2, 6, 10} is −3, because the two augmented triads sit half a period apart on the f₃ circle. With equal time in each, they cancel. Giant Steps lives inside one coset for the whole chorus, so its f₃ survives. In Fourier terms, that is the difference between a tune that uses Coltrane changes and a tune that is built from one: the substitution erases the key, and Giant Steps replaces it with a different, symmetric center of gravity.

Winding number: one loop down, one loop back

The key path of Giant Steps winds once around the augmented triad in bars 1 to 7, unwinds once in bars 8 to 16, and has total winding number zero. In just intonation that structure would show up as an audible pitch drift of one diesis, 41.06 cents, and then cancel.

Since the tonal center lives on a three-node cycle, every key change is a step of +1 (up a major third) or −1 (down a major third). The cycle is a loop, so the natural invariant of a closed walk on it is the winding number: the signed number of full trips around.

The key path from the first code block is B G Eb G Eb B, then Eb G B Eb B. The steps are −1, −1, +1, −1, −1 for the first five moves, which sum to −3 and land back on B in bar 7: one full loop downward, w = −1. The remaining moves are +1, +1, +1, +1, −1, which sum to +3: one full loop upward, w = +1. Total: zero. This matches the standard description of the form, a descending cycle in the first half and ascending ii-V-I motion in the second, but the winding number states it as a single conserved quantity. Each half of the tune is a complete trip around the triangle, run in opposite directions.

The winding number matters musically because in any tuning other than 12-tone equal temperament, the triangle does not close. A pure major third has frequency ratio 5:4, which is 1200 × log₂(5/4) ≈ 386.31 cents, about 13.69 cents narrower than the equal-tempered 400. Three pure thirds fall short of an octave by the lesser diesis.

Run a thought experiment. Suppose an ensemble tuned each new tonic as a pure 5:4 from the previous one. Every downward step leaves the tonic 13.69 cents higher than its equal-tempered position, and every upward step leaves it 13.69 cents lower. The drift after any number of moves is −13.69 × (sum of steps) cents, and after a full loop it is exactly one diesis. Figure 4 plots both views on the same time axis.

Figure 4. Top: the tonic position unwrapped onto a line, in units of major thirds. Bars 1 to 7 descend one full loop and bars 8 to 16 climb one full loop back. Bottom: the drift a pure-third tuning would accumulate against equal temperament, peaking at +41.06 cents when B returns in bar 7.
def third_steps(keys):
    """+1 for a move up a major third, -1 for down. Fails loudly if a move leaves the cycle."""
    steps = []
    for a, b in zip(keys, keys[1:]):
        d = (b - a) % 12
        assert d in (4, 8), f"{NAME[a]}->{NAME[b]} is not a major-third move"
        steps.append(+1 if d == 4 else -1)
    return steps

DIESIS_CENTS = 1200 * np.log2(128 / 125)

def just_drift_cents(steps):
    """Tonic drift vs 12-TET if every key change were tuned as a pure 5:4 third."""
    pure, tet = 1200 * np.log2(5 / 4), 400.0
    return np.cumsum([s * (pure - tet) for s in steps])

keys = [k for k, *_ in key_regions(GIANT_STEPS)]
steps = third_steps(keys)
print("steps:", steps)                                         # [-1,-1,1,-1,-1, 1,1,1,1,-1]
print("winding A, B:", sum(steps[:5]) / 3, sum(steps[5:]) / 3)  # -1.0  1.0
print("diesis:", round(DIESIS_CENTS, 2))                      # 41.06
print("drift:", np.round(just_drift_cents(steps), 2))

The drift sequence is 13.69, 27.37, 13.69, 27.37 and 41.06 cents through the first half, then back down through 27.37, 13.69, 0 and −13.69 before ending at 0. A 41-cent error is a little under half a semitone and would sound plainly out of tune against a fixed-pitch piano.

I want to be careful about what this does and does not say. No one plays Giant Steps in just intonation, and horn players adjust intonation continuously by ear rather than chaining pure intervals. The point is structural. The tune’s harmonic logic depends on three major thirds adding up to exactly one octave, which is true only in equal temperament. The form also happens to be built so that the error a pure-interval tuning would accumulate is undone within the same chorus.

The improviser’s load: what actually changes per second

At the recorded tempo, Giant Steps asks the soloist to replace about 3 scale tones every second while the chords underneath move only about 21% more than in a conventional ii-V-I tune. The difficulty is a memory problem more than a voice-leading problem.

I propose two figures for comparing progressions, and both are my own constructions. The first is the scale-change rate: the total scale distance across all key changes in a chorus, divided by the chorus length. The second is the load ratio: the same total, divided by the total seventh-chord voice-leading across all chord changes. The scale-change rate says how fast the improviser’s note pool turns over. The load ratio says how much of that turnover is hidden from the ear by smooth chord motion.

Here k_j are the successive key regions, c_i the successive chords (both wrapping to the start of the next chorus), and T_chorus is beats × 60 / bpm. For Giant Steps at 290.3 bpm, T = 64 × 60 / 290.3 ≈ 13.23 seconds. All ten key changes are major thirds, each changing 4 scale tones, so the total is 40 and R_scale = 40 / 13.23 ≈ 3.02 tones per second, or 40 / 64 = 0.625 per beat.

def load_metrics(prog, bpm):
    """Proposed improviser-load figures for one chorus at a given tempo."""
    beats = sum(b for *_, b in prog)
    seconds = beats * 60 / bpm
    regs = key_regions(prog)
    keys = [k for k, *_ in regs] + [regs[0][0]]          # wrap to the next chorus
    scale_changes = sum(scale_distance(a, b) for a, b in zip(keys, keys[1:]))
    chords = [chord_pcs(r, q) for r, q, _ in prog]
    chords.append(chords[0])
    vl = sum(vl_distance(a, b) for a, b in zip(chords, chords[1:]))
    return {"chords_per_s": len(prog) / seconds,
            "scale_tones_per_beat": scale_changes / beats,
            "scale_tones_per_s": scale_changes / seconds,
            "vl_per_change": vl / len(prog),
            "load_ratio": scale_changes / vl}

for name, prog in [("Giant Steps", GIANT_STEPS), ("Tune Up 8", TUNE_UP_8),
                   ("Coltrane Tune Up 8", COLTRANE_TUNE_UP_8)]:
    print(name, {k: round(v, 3) for k, v in load_metrics(prog, bpm=290.3).items()})

Two cautions on the table. Tune Up is not normally played at 290 bpm, so the per-second column only normalizes all three to the same clock; the per-beat column is the tempo-free comparison. And the substituted Tune Up is the skeleton from Wikipedia, not the full chart of Coltrane’s “Countdown”.

The comparison shows the asymmetry from the voice-leading section in numbers. Against plain Tune Up, Giant Steps changes scale tones 5 times faster per beat, while the average chord-to-chord voice-leading grows only from 2.67 to 3.23 semitones. The ear gets a smooth surface, and the hands get a new note pool every 1.32 seconds. The substituted Tune Up is denser still per beat, 0.875, because each half crams a full loop into four bars. Giant Steps spreads its two loops over sixteen bars.

The Jazzomat event density makes this concrete for Coltrane himself. At 7.64 notes per second, his first solo spends about 7.64 × 13.23 / 26 ≈ 3.9 notes on each chord and about 10.1 notes in each key region. Four notes per chord is exactly what two beats of straight eighth notes hold. That is the arithmetic behind the four-note cells, such as the 1–2–3–5 pattern, that teachers associate with this solo: one cell per chord fills the time with no remainder. It is consistent with the view, reported from Frank Kofsky’s analysis, that Coltrane prepared melodic patterns for these changes in advance (Wikipedia, citing Kofsky 1998). The arithmetic does not prove that he played them on every chord; that needs a note-level check against the transcription.

Coltrane changes as an operator

The Coltrane substitution is a function from progressions to progressions: it keeps the start and end of a ii-V-I and inserts one full descending loop around an augmented triad in between. Written that way, it generalizes, and the generalization shows why Coltrane chose the major third.

Some history first, kept short. Coltrane’s third-cycle substitutions appear on record before Giant Steps, including “Three Little Words” on Bags & Trane and “Limehouse Blues” with Cannonball Adderley. The bridge of Rodgers and Hart’s “Have You Met Miss Jones?” (1937) already modulates by major thirds, and it is a commonly cited model. Lewis Porter documents that Coltrane studied Nicolas Slonimsky’s Thesaurus of Scales and Melodic Patterns (1947). In a 2018 interview Quincy Jones went further and said the tune came from an example in that book. That is one person’s recollection, so I report it as his claim. All of this is summarized, with citations, on Wikipedia’s Coltrane changes page and the composition page.

The standard substitution over a four-bar ii-V-I in C turns Dm7 | G7 | C | C into Dm7 Eb7 | Ab B7 | E G7 | C. Each new major chord is preceded by its own dominant, and the tonics run C, Ab, E, C: down a major third three times. In the language of the previous section, the inserted path has winding number −1. The original ii-V-I and the substituted version start and end at the same place. The substitution adds one lap around the triangle. The first seven bars of Giant Steps are the same object started on B.

def cycle_sub(tonic, step=4, beats=2):
    """ii, then V-I pairs that loop down by `step` semitones until they reach the tonic again.
    step=4 is the Coltrane substitution; step=3 gives a four-key minor-third version."""
    t = PC[tonic]
    n = 12 // step                                          # keys in the cycle
    out = [(NAME[(t + 2) % 12], "m7", beats)]
    for j in range(1, n + 1):
        target = (t - j * step) % 12
        out.append((NAME[(target + 7) % 12], "7", beats))
        out.append((NAME[target], "maj7", 2 * beats if target == t else beats))
    return out

def label(prog):
    return " ".join(r + ("" if q == "maj7" else q) for r, q, _ in prog)

print(label(cycle_sub("C")))           # Dm7 Eb7 Ab B7 E G7 C
print(label(cycle_sub("D")))           # Em7 F7 Bb Db7 Gb A7 D  (the Tune Up rewrite above)
print(label(cycle_sub("C", step=3)))   # Dm7 E7 A Db7 Gb Bb7 Eb G7 C

The function reproduces the textbook substitution for C and the substituted first half of Tune Up for D, which is a useful check that the data model is right. The step argument is where it gets interesting. Any interval that divides 12 defines a symmetric cycle of keys, and each such cycle has |f₅| = 0 for the same symmetry reason as before: none of them privileges a home key. So every one of these cycles erases the fifths component, and they differ only in cost and length.

The major third is the only symmetric key cycle that is both short enough to learn as a loop (three keys) and smoother at the triad level than the ordinary circle of fifths (2 semitones against 3). The minor-third cycle is the obvious alternative, and the code generates it. It costs one more key to memorize, gives up the voice-leading advantage, and needs 8 chords per loop (four V-I pairs) instead of 6. If you were designing a progression that sounds as connected as possible while rotating the note pool as fast as possible, the table says you would land where Coltrane did.

Where it breaks

Everything above models the chord chart rather than the recording, and several modeling choices could move the numbers.

Pitch-class sets throw away register, voicing, rhythm, articulation and sound. Flanagan’s comping, Chambers’s bass line (the loping line Coltrane said gave the tune its name) and Taylor’s ride cymbal are all invisible here. So is the melody, which is mostly long notes and does a lot of the work of making the changes audible. A listener hears a performance, and a pitch-class profile is a heavily compressed summary of one.

The key labels are a modeling choice. I assigned every dominant seventh to the key it resolves to and every minor seventh to the key it is ii of. That is the standard reading, but it is not the only one. An analyst who hears each dominant as its own momentary key, or reads the whole tune in Eb, as the Jazzomat metadata does, would get a different key path and different load figures. The winding-number result is robust to this because it depends only on the tonic arrivals, but the per-beat scale-change rate is not.

The voice-leading metric is one of several reasonable ones. I used the taxicab (sum of semitones) distance between chords as abstract pitch-class sets. Tymoczko’s framework allows Euclidean and other norms, and with a different norm the four-way tie among seventh chords could break in either direction. Real pianists also use rootless and upper-structure voicings, which are not the four-note sets in my table.

The scale distance assumes each key is a major scale. Over dominant chords many players use altered, diminished or melodic-minor material, which changes the actual note pool. The real turnover rate could be higher or lower than 3.02 tones per second, depending on the player.

The comparisons are illustrations. Three progressions, one of them an eight-bar excerpt and one of them a textbook rewrite, cannot establish that modulation leverage or the load ratio predict difficulty. Those are proposed measures. Validating them would need difficulty ratings or error rates from real improvisers across a much larger set of tunes.

Finally, the claim in my opening that the tune is easy to hear is a hypothesis built on the voice-leading numbers; I have not run a listening test. The just-intonation drift is a thought experiment about tuning systems, and nobody performs the piece that way.

The path forward

The toolkit here is about 300 lines of NumPy and Matplotlib, figures included, and the most useful next step is to point it at real note data rather than chord charts.

The obvious target is the transcription itself. The Jazzomat Research Project publishes Coltrane’s Giant Steps solos as MIDI files and in the Weimar Jazz Database, alongside hundreds of other transcribed solos. A natural first test of the load argument is whether improvisers’ note choices track the rotating scale, and how quickly. The function below measures the share of notes that fall inside the major scale of the key active at each note’s onset. It takes plain (beat, MIDI pitch) pairs, so it works with whatever MIDI reader you prefer. I checked it on synthetic data rather than on the Jazzomat file.

def scale_hit_rate(notes, prog):
    """notes: (beat_onset, midi_pitch) pairs, beat 0 = start of a chorus, choruses repeat.
    Returns the share of notes inside the major scale of the key active at their onset."""
    bounds = np.cumsum([0] + [b for *_, b in prog])          # chord start beats
    chorus = bounds[-1]
    hits = 0
    for beat, pitch in notes:
        i = np.searchsorted(bounds, beat % chorus, side="right") - 1
        root, q, _ = prog[i]
        hits += (pitch % 12) in scale(key_of(root, q))
    return hits / len(notes)

# Synthetic check: a 1-2-3-5 cell in eighth notes on every chord, for two choruses
CELL = {"maj7": (0, 2, 4, 7), "7": (0, 2, 4, 7), "m7": (0, 2, 3, 7)}
demo, t = [], 0.0
for _ in range(2):
    for root, q, beats in GIANT_STEPS:
        for j in range(int(beats * 2)):
            demo.append((t + 0.5 * j, 60 + PC[root] + CELL[q][j % 4]))
        t += beats
print("1-2-3-5 cells:", scale_hit_rate(demo, GIANT_STEPS))                      # 1.0
print("a semitone off:", round(scale_hit_rate([(b, p + 1) for b, p in demo], GIANT_STEPS), 3))  # 0.25

The 1–2–3–5 cells land in the active scale 100% of the time, and the same cells shifted up a semitone land there 25% of the time, which is the sanity check the function needs. To run it on the real solo, the one piece of work is aligning note onsets to beats. The Weimar database annotates beats, so the alignment exists; a fixed 290.3 bpm grid will drift, because a live rhythm section does not hold a perfect tempo. Two questions I would ask of the real data: does the hit rate dip in the first eighth note after each key change (anticipation or lag), and is Coltrane’s hit rate on the master take higher than on the slower March alternates?

Beyond this tune, there are four things worth doing this quarter if you work on music, audio or symbolic sequence modeling.

First, run load_metrics and dft_mag(profile(…)) over a corpus of standards and see where Giant Steps, “Countdown”, “26–2” and “Have You Met Miss Jones?” fall in the joint distribution of |f₅| and scale-change rate. If the load ratio is any good as a difficulty proxy, it should separate tunes that musicians call hard from tunes they call fast.

Second, use cycle_sub as a reharmonization engine. Applied to every ii-V-I in a chart, it produces a family of Coltrane-style rewrites, and the minor-third variant is one keyword away. It is a small, interpretable baseline for generative harmony work, and it is easy to test, because every output can be checked against the winding-number and voice-leading functions above.

Third, for teaching: order practice material by modulation leverage rather than by tempo. A student who can hear a 2-semitone triad move but loses track of a 4-tone scale change needs scale drills before a slower metronome.

Fourth, if you build models of musical structure, the lesson generalizes. Giant Steps is a sequence whose surface is smooth under one metric and discontinuous under another, and the interesting behavior lives in the gap between the two. Measuring a system with two distances, and asking where they disagree, is a habit worth keeping well outside jazz.

This piece first appeared on Substack, along with the full companion script that reproduces every number and figure: brianjamescurry.substack.com

About the Author

Brian Curry is a Kansas City-based machine learning and AI strategist, marketing economist, and applied researcher. He leads Vector1, an independent research studio working on cognitive AI, causal inference, marketing economics, and knowledge engineering, with a focus on simulation-driven intelligence systems.

Over more than twenty years, Brian has led data science and analytics initiatives at enterprises ranging from $4B to $125B in revenue, including Koch Industries, Tractor Supply, Vail Resorts, Hallmark, Garmin, and AT&T. His work applies economics and causal inference to connect marketing investment to structural outcomes, operational decisions to economic value, and AI adoption to durable competitive advantage.

He is also the founder of KC AI Lab, a 1,500-member AI and ML education community in the Midwest. He lives in Kansas City and is an active part of the Kansas City community.

Research and Open-Source Projects

The modeling approach in this paper draws on PyCausalSim and Papilon.

Causal Inference and Economics

  • PyCausalSim: Simulation-driven causal discovery and evaluation, using counterfactual scenarios to isolate structural drivers.
  • Papilon: Complex-system simulation and optimization, with applications in media mix modeling and adaptive architectures.
  • Daedalus: Causal analytics for money-flow mapping and macroeconomic tracking.
  • The Autonomy Economy: A macroeconomic framework for the combined effects of rapid productivity growth and structural price deflation in an AI-automated economy.

Forecasting and Applied Modeling

  • Regimecast: Time-series regime detection, structural breaks, and economic forecasting.
  • CarryNet: Financial modeling for commodity carry trade analysis.
  • Foodmesh-ml: Machine learning, optimization, and simulation for distributed food systems.

Cognitive AI and Semantic Architecture

  • Memory-Node Encapsulation (MNE): A cognitive AI architecture and data structure for artificial episodic memory (NeoCortex-M).
  • MeaningFlow: Semantic content modeling, embeddings, clustering, and coverage gap analysis.

Contact: brian@vector1.ai | GitHub @Bodhi8 | Substack

The text of this article may be shared with attribution under Creative Commons Attribution 4.0 International (CC BY 4.0). Code and models referenced here are available separately to paid subscribers.

Sources

Related research