FAILURE MAP
← Case archive

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

Interval quality and number naming: generic number counts letter gaps instead of letters · case 01

C4 to E4 is named AA2 instead of M3, because every generic number comes out one too small.

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

ROOT CAUSE

The generic number is the count of letter steps between the notes rather than the inclusive count of letters.

VERIFIED REPAIR

Restore the generic number step so that it reads `number = steps + 1`.

Unsuccessful approach: Adding the inclusive count only below the octave still names C4 to C5 a seventh and every compound interval one too small.

Case contract

Input [lower, upper] spelled pitches (letter, # or b accidentals, nonnegative octave). The generic number counts letter steps plus 7 per spelled octave plus one; quality compares the semitone span with the major/perfect reference (compounds add 12 per octave). Perfect class for simple numbers 1, 4, 5: P, A, d, AA, dd; others: M, m, A, d, AA, dd. Descending letter order or unrepresentable quality returns None.

Why this case matters

Pitch spelling and interval arithmetic decide the notes that notation, transposition and analysis tools display or play.

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}
    def parse(s):
        if not isinstance(s, str) or len(s) < 2 or s[0] not in base:
            return None
        i = 1
        acc = 0
        while i < len(s) and s[i] in '#b':
            acc += 1 if s[i] == '#' else -1
            i += 1
        if not s[i:].isdigit():
            return None
        octave = int(s[i:])
        return ['CDEFGAB'.index(s[0]) + 7 * octave, 12 * octave + base[s[0]] + acc]
    if not isinstance(x, list) or len(x) != 2:
        return None
    lo, hi = parse(x[0]), parse(x[1])
    if lo is None or hi is None:
        return None
    steps = hi[0] - lo[0]
    semis = hi[1] - lo[1]
    if steps < 0:
        return None
    number = steps
    simple = (number - 1) % 7 + 1
    ref = [0, 2, 4, 5, 7, 9, 11][simple - 1] + 12 * ((number - 1) // 7)
    d = semis - ref
    if simple in (1, 4, 5):
        q = {0: 'P', 1: 'A', -1: 'd', 2: 'AA', -2: 'dd'}.get(d)
    else:
        q = {0: 'M', -1: 'm', 1: 'A', -2: 'd', 2: 'AA', -3: 'dd'}.get(d)
    if q is None:
        return None
    return q + str(number)
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[(['C4', 'E4'], 'M3'), (['C4', 'G4'], 'P5'), (['C4', 'C5'], 'P8'), (['C4', 'G5'], 'P12'), (['D4', 'C4'], None), (['C4', 'B#3'], None), (['Cb4', 'B#3'], None), (['C4', 'Cx4'], None)], [(['C4', 'E4'], 'M3'), (['C4', 'Eb4'], 'm3'), (['C4', 'G4'], 'P5'), (['C4', 'Gb4'], 'd5'), (['C4', 'F#4'], 'A4'), (['C4', 'D5'], 'M9'), (['C4', 'F5'], 'P11'), (['C4', 'Cx4'], None)], [(['C4', 'G4'], 'P5'), (['C4', 'Gb4'], 'd5'), (['C4', 'F#4'], 'A4'), (['C4', 'C5'], 'P8'), (['C4', 'D5'], 'M9'), (['C4', 'C4'], 'P1'), (['C4', 'C6'], 'P15'), (['C4', 'D#6'], 'A16')], [(['C4', 'Gb4'], 'd5'), (['C4', 'C5'], 'P8'), (['C4', 'D5'], 'M9'), (['C4', 'C4'], 'P1'), (['C4', 'C#4'], 'A1'), (['B#3', 'C4'], 'd2'), (['C4', 'G5'], 'P12'), (['Bb2', 'C4'], 'M9')], [(['C4', 'F#4'], 'A4'), (['C4', 'D5'], 'M9'), (['C4', 'C#4'], 'A1'), (['B#3', 'C4'], 'd2'), (['E4', 'F4'], 'm2'), (['C4', 'B4'], 'M7'), (['C4', 'F5'], 'P11'), (['C4', 'A5'], 'M13')]]
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 0AA2M3Failed
oracle 1AA4P5Failed
oracle 2A7P8Failed
oracle 3AA11P12Failed
oracle 4NoneNonePassed
oracle 5NoneNonePassed
oracle 6NoneNonePassed
oracle 7NoneNonePassed

SHA-256 / b5c8998a658aeccb4c444aed4756972e49cd84402495c58633dad43b19e90ed9

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}
    def parse(s):
        if not isinstance(s, str) or len(s) < 2 or s[0] not in base:
            return None
        i = 1
        acc = 0
        while i < len(s) and s[i] in '#b':
            acc += 1 if s[i] == '#' else -1
            i += 1
        if not s[i:].isdigit():
            return None
        octave = int(s[i:])
        return ['CDEFGAB'.index(s[0]) + 7 * octave, 12 * octave + base[s[0]] + acc]
    if not isinstance(x, list) or len(x) != 2:
        return None
    lo, hi = parse(x[0]), parse(x[1])
    if lo is None or hi is None:
        return None
    steps = hi[0] - lo[0]
    semis = hi[1] - lo[1]
    if steps < 0:
        return None
    number = steps + 1 if steps < 7 else steps
    simple = (number - 1) % 7 + 1
    ref = [0, 2, 4, 5, 7, 9, 11][simple - 1] + 12 * ((number - 1) // 7)
    d = semis - ref
    if simple in (1, 4, 5):
        q = {0: 'P', 1: 'A', -1: 'd', 2: 'AA', -2: 'dd'}.get(d)
    else:
        q = {0: 'M', -1: 'm', 1: 'A', -2: 'd', 2: 'AA', -3: 'dd'}.get(d)
    if q is None:
        return None
    return q + str(number)
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[(['C4', 'E4'], 'M3'), (['C4', 'G4'], 'P5'), (['C4', 'C5'], 'P8'), (['C4', 'G5'], 'P12'), (['D4', 'C4'], None), (['C4', 'B#3'], None), (['Cb4', 'B#3'], None), (['C4', 'Cx4'], None)], [(['C4', 'E4'], 'M3'), (['C4', 'Eb4'], 'm3'), (['C4', 'G4'], 'P5'), (['C4', 'Gb4'], 'd5'), (['C4', 'F#4'], 'A4'), (['C4', 'D5'], 'M9'), (['C4', 'F5'], 'P11'), (['C4', 'Cx4'], None)], [(['C4', 'G4'], 'P5'), (['C4', 'Gb4'], 'd5'), (['C4', 'F#4'], 'A4'), (['C4', 'C5'], 'P8'), (['C4', 'D5'], 'M9'), (['C4', 'C4'], 'P1'), (['C4', 'C6'], 'P15'), (['C4', 'D#6'], 'A16')], [(['C4', 'Gb4'], 'd5'), (['C4', 'C5'], 'P8'), (['C4', 'D5'], 'M9'), (['C4', 'C4'], 'P1'), (['C4', 'C#4'], 'A1'), (['B#3', 'C4'], 'd2'), (['C4', 'G5'], 'P12'), (['Bb2', 'C4'], 'M9')], [(['C4', 'F#4'], 'A4'), (['C4', 'D5'], 'M9'), (['C4', 'C#4'], 'A1'), (['B#3', 'C4'], 'd2'), (['E4', 'F4'], 'm2'), (['C4', 'B4'], 'M7'), (['C4', 'F5'], 'P11'), (['C4', 'A5'], 'M13')]]
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 0M3M3Passed
oracle 1P5P5Passed
oracle 2A7P8Failed
oracle 3AA11P12Failed
oracle 4NoneNonePassed
oracle 5NoneNonePassed
oracle 6NoneNonePassed
oracle 7NoneNonePassed

SHA-256 / 330435766426599facf031c466325a584d99b3673675fbb0a0b17251615bdab3

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}
    def parse(s):
        if not isinstance(s, str) or len(s) < 2 or s[0] not in base:
            return None
        i = 1
        acc = 0
        while i < len(s) and s[i] in '#b':
            acc += 1 if s[i] == '#' else -1
            i += 1
        if not s[i:].isdigit():
            return None
        octave = int(s[i:])
        return ['CDEFGAB'.index(s[0]) + 7 * octave, 12 * octave + base[s[0]] + acc]
    if not isinstance(x, list) or len(x) != 2:
        return None
    lo, hi = parse(x[0]), parse(x[1])
    if lo is None or hi is None:
        return None
    steps = hi[0] - lo[0]
    semis = hi[1] - lo[1]
    if steps < 0:
        return None
    number = steps + 1
    simple = (number - 1) % 7 + 1
    ref = [0, 2, 4, 5, 7, 9, 11][simple - 1] + 12 * ((number - 1) // 7)
    d = semis - ref
    if simple in (1, 4, 5):
        q = {0: 'P', 1: 'A', -1: 'd', 2: 'AA', -2: 'dd'}.get(d)
    else:
        q = {0: 'M', -1: 'm', 1: 'A', -2: 'd', 2: 'AA', -3: 'dd'}.get(d)
    if q is None:
        return None
    return q + str(number)
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[(['C4', 'E4'], 'M3'), (['C4', 'G4'], 'P5'), (['C4', 'C5'], 'P8'), (['C4', 'G5'], 'P12'), (['D4', 'C4'], None), (['C4', 'B#3'], None), (['Cb4', 'B#3'], None), (['C4', 'Cx4'], None)], [(['C4', 'E4'], 'M3'), (['C4', 'Eb4'], 'm3'), (['C4', 'G4'], 'P5'), (['C4', 'Gb4'], 'd5'), (['C4', 'F#4'], 'A4'), (['C4', 'D5'], 'M9'), (['C4', 'F5'], 'P11'), (['C4', 'Cx4'], None)], [(['C4', 'G4'], 'P5'), (['C4', 'Gb4'], 'd5'), (['C4', 'F#4'], 'A4'), (['C4', 'C5'], 'P8'), (['C4', 'D5'], 'M9'), (['C4', 'C4'], 'P1'), (['C4', 'C6'], 'P15'), (['C4', 'D#6'], 'A16')], [(['C4', 'Gb4'], 'd5'), (['C4', 'C5'], 'P8'), (['C4', 'D5'], 'M9'), (['C4', 'C4'], 'P1'), (['C4', 'C#4'], 'A1'), (['B#3', 'C4'], 'd2'), (['C4', 'G5'], 'P12'), (['Bb2', 'C4'], 'M9')], [(['C4', 'F#4'], 'A4'), (['C4', 'D5'], 'M9'), (['C4', 'C#4'], 'A1'), (['B#3', 'C4'], 'd2'), (['E4', 'F4'], 'm2'), (['C4', 'B4'], 'M7'), (['C4', 'F5'], 'P11'), (['C4', 'A5'], 'M13')]]
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 0M3M3Passed
oracle 1P5P5Passed
oracle 2P8P8Passed
oracle 3P12P12Passed
oracle 4NoneNonePassed
oracle 5NoneNonePassed
oracle 6NoneNonePassed
oracle 7NoneNonePassed

SHA-256 / a47f4ba9155d61369aa7058865eaae4797d68a63be602c70444f092cacc7b549

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:49:57.993376+00:00.

Case digest / 82307613a88b0f7f5372fdd677dbd59c80636cc994ced33bc3f9bd898b876bb8