FAILURE MAP
← Case archive

FA-81176 / Music interval and transposition theory / Open access

Chord symbol to pitch classes: chord tones above B not wrapped · case 01

An A major triad returns [9,13,16] instead of [9,1,4].

Verified by executionVariant 1 · 8 checks per implementationDownload source bundle ↓JSON ↗

ROOT CAUSE

Chord tones are formed by adding intervals to the root without reducing modulo twelve.

VERIFIED REPAIR

Restore the pitch class reduction step so that it reads `(r + i) % 12`.

Unsuccessful approach: Subtracting twelve only above twelve leaves a tone of exactly 12 unreduced.

Case contract

Input a chord symbol: root letter with optional # or b, a quality from "", m, dim, aug, 7, maj7, m7, m7b5, dim7, sus4, mMaj7, 6, and optionally "/" plus a bass note (letter with optional # or b and nothing else). Return pitch classes root-first in template order; for a slash chord the bass moves to the front (removed from its old position if it is a chord tone, otherwise added). Unknown or malformed symbols return None.

Why this case matters

Lead-sheet, accompaniment and guitar-chart tools expand chord symbols into voicings.

1 / The failure

Exit 1
"""Failure Map reference implementation. Python standard library only."""
import json

N = 1
observations = []
def solve(x):
    base = {'C': 0, 'D': 2, 'E': 4, 'F': 5, 'G': 7, 'A': 9, 'B': 11}
    shapes = {'': [0, 4, 7], 'm': [0, 3, 7], 'dim': [0, 3, 6], 'aug': [0, 4, 8], '7': [0, 4, 7, 10], 'maj7': [0, 4, 7, 11], 'm7': [0, 3, 7, 10], 'm7b5': [0, 3, 6, 10], 'dim7': [0, 3, 6, 9], 'sus4': [0, 5, 7], 'mMaj7': [0, 3, 7, 11], '6': [0, 4, 7, 9]}
    def root(s):
        if not s or s[0] not in base:
            return None, s
        if s[1:2] in ('#', 'b'):
            return (base[s[0]] + (1 if s[1] == '#' else -1)) % 12, s[2:]
        return base[s[0]], s[1:]
    if not isinstance(x, str):
        return None
    head, slash, tail = x.partition('/')
    r, quality = root(head)
    if r is None or quality not in shapes:
        return None
    tones = [r + i for i in shapes[quality]]
    if slash:
        b, rest = root(tail)
        if b is None or rest:
            return None
        if b in tones:
            tones.remove(b)
        tones.insert(0, b)
    return tones
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('C', [0, 4, 7]), ('Cm', [0, 3, 7]), ('Bb', [10, 2, 5]), ('Bb7', [10, 2, 5, 8]), ('F#m7b5', [6, 9, 0, 4]), ('Dsus4', [2, 7, 9]), ('C/E', [4, 0, 7]), ('E#', [5, 9, 0])], [('Bbm7', [10, 1, 5, 8]), ('F#m7b5', [6, 9, 0, 4]), ('C/E', [4, 0, 7]), ('C/G', [7, 0, 4]), ('C/Bb', [10, 0, 4, 7]), ('Am/G', [7, 9, 0, 4]), ('Cmaj7/B', [11, 0, 4, 7]), ('AmMaj7', [9, 0, 4, 8])], [('Bb7', [10, 2, 5, 8]), ('F#m7b5', [6, 9, 0, 4]), ('Ebmaj7', [3, 7, 10, 2]), ('F/A', [9, 5, 0]), ('Cmaj7/B', [11, 0, 4, 7]), ('C/E/G', None), ('C/Ebm', None), ('C/H', None)], [('F#m7b5', [6, 9, 0, 4]), ('Gdim7', [7, 10, 1, 4]), ('Am/G', [7, 9, 0, 4]), ('E#', [5, 9, 0]), ('C/H', None), ('H7', None), ('Cmin', None), ('D/F#', [6, 2, 9])], [('Ebmaj7', [3, 7, 10, 2]), ('Aaug', [9, 1, 5]), ('F/A', [9, 5, 0]), ('AmMaj7', [9, 0, 4, 8]), ('D/F#', [6, 2, 9]), ('Ebm/Gb', [6, 3, 10]), ('C/', None), ('Am/', None)]]
for i, (args, expected) in enumerate(fixtures[N-1]):
    check("oracle %d" % i, solve(args), expected)
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
Boundary fixtureActualExpectedOutcome
oracle 0[0, 4, 7][0, 4, 7]Passed
oracle 1[0, 3, 7][0, 3, 7]Passed
oracle 2[10, 14, 17][10, 2, 5]Failed
oracle 3[10, 14, 17, 20][10, 2, 5, 8]Failed
oracle 4[6, 9, 12, 16][6, 9, 0, 4]Failed
oracle 5[2, 7, 9][2, 7, 9]Passed
oracle 6[4, 0, 7][4, 0, 7]Passed
oracle 7[5, 9, 12][5, 9, 0]Failed

SHA-256 / 6626886892dd2f9b90d17292615038db6ce9fa58a4b7848d2c8e4f219d824d33

2 / The unsuccessful fix

Exit 1
"""Failure Map reference implementation. Python standard library only."""
import json

N = 1
observations = []
def solve(x):
    base = {'C': 0, 'D': 2, 'E': 4, 'F': 5, 'G': 7, 'A': 9, 'B': 11}
    shapes = {'': [0, 4, 7], 'm': [0, 3, 7], 'dim': [0, 3, 6], 'aug': [0, 4, 8], '7': [0, 4, 7, 10], 'maj7': [0, 4, 7, 11], 'm7': [0, 3, 7, 10], 'm7b5': [0, 3, 6, 10], 'dim7': [0, 3, 6, 9], 'sus4': [0, 5, 7], 'mMaj7': [0, 3, 7, 11], '6': [0, 4, 7, 9]}
    def root(s):
        if not s or s[0] not in base:
            return None, s
        if s[1:2] in ('#', 'b'):
            return (base[s[0]] + (1 if s[1] == '#' else -1)) % 12, s[2:]
        return base[s[0]], s[1:]
    if not isinstance(x, str):
        return None
    head, slash, tail = x.partition('/')
    r, quality = root(head)
    if r is None or quality not in shapes:
        return None
    tones = [(r + i) - 12 if r + i > 12 else r + i for i in shapes[quality]]
    if slash:
        b, rest = root(tail)
        if b is None or rest:
            return None
        if b in tones:
            tones.remove(b)
        tones.insert(0, b)
    return tones
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('C', [0, 4, 7]), ('Cm', [0, 3, 7]), ('Bb', [10, 2, 5]), ('Bb7', [10, 2, 5, 8]), ('F#m7b5', [6, 9, 0, 4]), ('Dsus4', [2, 7, 9]), ('C/E', [4, 0, 7]), ('E#', [5, 9, 0])], [('Bbm7', [10, 1, 5, 8]), ('F#m7b5', [6, 9, 0, 4]), ('C/E', [4, 0, 7]), ('C/G', [7, 0, 4]), ('C/Bb', [10, 0, 4, 7]), ('Am/G', [7, 9, 0, 4]), ('Cmaj7/B', [11, 0, 4, 7]), ('AmMaj7', [9, 0, 4, 8])], [('Bb7', [10, 2, 5, 8]), ('F#m7b5', [6, 9, 0, 4]), ('Ebmaj7', [3, 7, 10, 2]), ('F/A', [9, 5, 0]), ('Cmaj7/B', [11, 0, 4, 7]), ('C/E/G', None), ('C/Ebm', None), ('C/H', None)], [('F#m7b5', [6, 9, 0, 4]), ('Gdim7', [7, 10, 1, 4]), ('Am/G', [7, 9, 0, 4]), ('E#', [5, 9, 0]), ('C/H', None), ('H7', None), ('Cmin', None), ('D/F#', [6, 2, 9])], [('Ebmaj7', [3, 7, 10, 2]), ('Aaug', [9, 1, 5]), ('F/A', [9, 5, 0]), ('AmMaj7', [9, 0, 4, 8]), ('D/F#', [6, 2, 9]), ('Ebm/Gb', [6, 3, 10]), ('C/', None), ('Am/', None)]]
for i, (args, expected) in enumerate(fixtures[N-1]):
    check("oracle %d" % i, solve(args), expected)
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
Boundary fixtureActualExpectedOutcome
oracle 0[0, 4, 7][0, 4, 7]Passed
oracle 1[0, 3, 7][0, 3, 7]Passed
oracle 2[10, 2, 5][10, 2, 5]Passed
oracle 3[10, 2, 5, 8][10, 2, 5, 8]Passed
oracle 4[6, 9, 12, 4][6, 9, 0, 4]Failed
oracle 5[2, 7, 9][2, 7, 9]Passed
oracle 6[4, 0, 7][4, 0, 7]Passed
oracle 7[5, 9, 12][5, 9, 0]Failed

SHA-256 / 8ef53d5298f31b8a21b6de321fc1fae5687adf8feb48bf4b2aad4c664c785ed4

3 / The verified repair

Exit 0
"""Failure Map reference implementation. Python standard library only."""
import json

N = 1
observations = []
def solve(x):
    base = {'C': 0, 'D': 2, 'E': 4, 'F': 5, 'G': 7, 'A': 9, 'B': 11}
    shapes = {'': [0, 4, 7], 'm': [0, 3, 7], 'dim': [0, 3, 6], 'aug': [0, 4, 8], '7': [0, 4, 7, 10], 'maj7': [0, 4, 7, 11], 'm7': [0, 3, 7, 10], 'm7b5': [0, 3, 6, 10], 'dim7': [0, 3, 6, 9], 'sus4': [0, 5, 7], 'mMaj7': [0, 3, 7, 11], '6': [0, 4, 7, 9]}
    def root(s):
        if not s or s[0] not in base:
            return None, s
        if s[1:2] in ('#', 'b'):
            return (base[s[0]] + (1 if s[1] == '#' else -1)) % 12, s[2:]
        return base[s[0]], s[1:]
    if not isinstance(x, str):
        return None
    head, slash, tail = x.partition('/')
    r, quality = root(head)
    if r is None or quality not in shapes:
        return None
    tones = [(r + i) % 12 for i in shapes[quality]]
    if slash:
        b, rest = root(tail)
        if b is None or rest:
            return None
        if b in tones:
            tones.remove(b)
        tones.insert(0, b)
    return tones
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('C', [0, 4, 7]), ('Cm', [0, 3, 7]), ('Bb', [10, 2, 5]), ('Bb7', [10, 2, 5, 8]), ('F#m7b5', [6, 9, 0, 4]), ('Dsus4', [2, 7, 9]), ('C/E', [4, 0, 7]), ('E#', [5, 9, 0])], [('Bbm7', [10, 1, 5, 8]), ('F#m7b5', [6, 9, 0, 4]), ('C/E', [4, 0, 7]), ('C/G', [7, 0, 4]), ('C/Bb', [10, 0, 4, 7]), ('Am/G', [7, 9, 0, 4]), ('Cmaj7/B', [11, 0, 4, 7]), ('AmMaj7', [9, 0, 4, 8])], [('Bb7', [10, 2, 5, 8]), ('F#m7b5', [6, 9, 0, 4]), ('Ebmaj7', [3, 7, 10, 2]), ('F/A', [9, 5, 0]), ('Cmaj7/B', [11, 0, 4, 7]), ('C/E/G', None), ('C/Ebm', None), ('C/H', None)], [('F#m7b5', [6, 9, 0, 4]), ('Gdim7', [7, 10, 1, 4]), ('Am/G', [7, 9, 0, 4]), ('E#', [5, 9, 0]), ('C/H', None), ('H7', None), ('Cmin', None), ('D/F#', [6, 2, 9])], [('Ebmaj7', [3, 7, 10, 2]), ('Aaug', [9, 1, 5]), ('F/A', [9, 5, 0]), ('AmMaj7', [9, 0, 4, 8]), ('D/F#', [6, 2, 9]), ('Ebm/Gb', [6, 3, 10]), ('C/', None), ('Am/', None)]]
for i, (args, expected) in enumerate(fixtures[N-1]):
    check("oracle %d" % i, solve(args), expected)
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
Boundary fixtureActualExpectedOutcome
oracle 0[0, 4, 7][0, 4, 7]Passed
oracle 1[0, 3, 7][0, 3, 7]Passed
oracle 2[10, 2, 5][10, 2, 5]Passed
oracle 3[10, 2, 5, 8][10, 2, 5, 8]Passed
oracle 4[6, 9, 0, 4][6, 9, 0, 4]Passed
oracle 5[2, 7, 9][2, 7, 9]Passed
oracle 6[4, 0, 7][4, 0, 7]Passed
oracle 7[5, 9, 0][5, 9, 0]Passed

SHA-256 / 5e164b446710421eebb4aa98357cb724236cf022bbcff4c5a964a8f4c3ed2666

Verification & scope

A deterministic bounded teaching model with a stipulated toy contract; it is not a complete music notation or theory engine. This reproducer isolates one failure mechanism. Results cover the supplied fixtures. Variants within a family share a test contract and should remain grouped when constructing evaluation splits. Related mechanisms with a shared evaluation_group must also remain together; these controlled models are not independent production incidents.

Observations recorded using Python 3.12.14 at 2026-09-29T14:50:00.596672+00:00.

Case digest / db00cb3d8bcae4cfbb2f790996123c2a5f2804b9d7048a3c88a649f51e9cda80