FAILURE MAP
← Case archive

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

Spelled transposition by named interval: downward pitch moved upward · case 01

Downward transpositions move the letter down but the pitch up, producing None or wild accidentals (C4 down M3 fails).

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

ROOT CAUSE

The semitone displacement ignores the direction sign while the letter displacement honours it.

VERIFIED REPAIR

Restore the pitch direction step so that it reads `pitch = 12 * octave + base[note[0]] + acc + sign * size`.

Unsuccessful approach: Reducing the signed size modulo twelve fixes nothing for downward moves and also discards compound octaves.

Case contract

Input [note, interval, direction]. note: letter, # or b accidentals, nonnegative octave. interval: quality letter (P, M, m, A, d) and generic number >= 1 with perfect-class numbers 1, 4, 5 (compounds reduce by 7). Direction up or down moves the letter by number-1 steps and the pitch by the interval size; the result is spelled with the moved letter, octave taken from the letter position. Results needing more than two accidentals, or a negative octave, return 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}
    if not isinstance(x, list) or len(x) != 3:
        return None
    note, ivl, direction = x
    if direction not in ('up', 'down'):
        return None
    if not isinstance(note, str) or len(note) < 2 or note[0] not in base:
        return None
    i = 1
    acc = 0
    while i < len(note) and note[i] in '#b':
        acc += 1 if note[i] == '#' else -1
        i += 1
    if not note[i:].isdigit():
        return None
    octave = int(note[i:])
    if not isinstance(ivl, str) or len(ivl) < 2 or not ivl[1:].isdigit():
        return None
    quality = ivl[0]
    number = int(ivl[1:])
    if number < 1:
        return None
    simple = (number - 1) % 7 + 1
    perfect = simple in (1, 4, 5)
    offsets = {'P': 0, 'A': 1, 'd': -1} if perfect else {'M': 0, 'm': -1, 'A': 1, 'd': -2}
    if quality not in offsets:
        return None
    size = [0, 2, 4, 5, 7, 9, 11][simple - 1] + 12 * ((number - 1) // 7) + offsets[quality]
    sign = 1 if direction == 'up' else -1
    idx = 'CDEFGAB'.index(note[0]) + 7 * octave + sign * (number - 1)
    pitch = 12 * octave + base[note[0]] + acc + size
    letter = 'CDEFGAB'[idx % 7]
    new_octave = idx // 7
    new_acc = pitch - (12 * new_octave + base[letter])
    if abs(new_acc) > 2 or new_octave < 0:
        return None
    return letter + ('#' * new_acc if new_acc > 0 else 'b' * -new_acc) + str(new_octave)
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[(['C4', 'M3', 'up'], 'E4'), (['C4', 'M3', 'down'], 'Ab3'), (['B3', 'm2', 'up'], 'C4'), (['E4', 'A4', 'up'], 'A#4'), (['F#4', 'A4', 'up'], 'B#4'), (['C4', 'd5', 'up'], 'Gb4'), (['C4', 'M9', 'up'], 'D5'), (['C4', 'M10', 'down'], 'Ab2')], [(['C4', 'm2', 'down'], 'B3'), (['F#4', 'A4', 'up'], 'B#4'), (['C4', 'd5', 'up'], 'Gb4'), (['C4', 'd7', 'up'], 'Bbb4'), (['C4', 'd3', 'up'], 'Ebb4'), (['Db4', 'd3', 'up'], 'Fbb4'), (['C4', 'M10', 'down'], 'Ab2'), (['C4', 'A1', 'down'], 'Cb4')], [(['C4', 'd3', 'up'], 'Ebb4'), (['Db4', 'd3', 'up'], 'Fbb4'), (['Cb4', 'd3', 'up'], None), (['C4', 'P8', 'up'], 'C5'), (['C4', 'M10', 'down'], 'Ab2'), (['G4', 'P5', 'up'], 'D5'), (['C4', 'd1', 'up'], 'Cb4'), (['C4', 'd1', 'down'], 'C#4')], [(['C4', 'M9', 'up'], 'D5'), (['G4', 'P5', 'up'], 'D5'), (['A4', 'M6', 'up'], 'F#5'), (['E#4', 'A6', 'up'], None), (['C0', 'm2', 'down'], None), (['C4', 'X3', 'up'], None), (['C4', 'A1', 'down'], 'Cb4'), (['C5', 'P15', 'down'], 'C3')], [(['C4', 'M10', 'down'], 'Ab2'), (['C0', 'm2', 'down'], None), (['C4', 'X3', 'up'], None), (['C4', 'M4', 'up'], None), (['C4', 'P3', 'up'], None), (['C4', 'd1', 'down'], 'C#4'), (['C5', 'P15', 'down'], 'C3'), (['D4', 'd4', 'down'], 'A#3')]]
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 0E4E4Passed
oracle 1NoneAb3Failed
oracle 2C4C4Passed
oracle 3A#4A#4Passed
oracle 4B#4B#4Passed
oracle 5Gb4Gb4Passed
oracle 6D5D5Passed
oracle 7NoneAb2Failed

SHA-256 / a5e452722c8505ce2f14a67543884fb97fe9c3914c577e47810f026ed25d01c9

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}
    if not isinstance(x, list) or len(x) != 3:
        return None
    note, ivl, direction = x
    if direction not in ('up', 'down'):
        return None
    if not isinstance(note, str) or len(note) < 2 or note[0] not in base:
        return None
    i = 1
    acc = 0
    while i < len(note) and note[i] in '#b':
        acc += 1 if note[i] == '#' else -1
        i += 1
    if not note[i:].isdigit():
        return None
    octave = int(note[i:])
    if not isinstance(ivl, str) or len(ivl) < 2 or not ivl[1:].isdigit():
        return None
    quality = ivl[0]
    number = int(ivl[1:])
    if number < 1:
        return None
    simple = (number - 1) % 7 + 1
    perfect = simple in (1, 4, 5)
    offsets = {'P': 0, 'A': 1, 'd': -1} if perfect else {'M': 0, 'm': -1, 'A': 1, 'd': -2}
    if quality not in offsets:
        return None
    size = [0, 2, 4, 5, 7, 9, 11][simple - 1] + 12 * ((number - 1) // 7) + offsets[quality]
    sign = 1 if direction == 'up' else -1
    idx = 'CDEFGAB'.index(note[0]) + 7 * octave + sign * (number - 1)
    pitch = 12 * octave + base[note[0]] + acc + sign * size % 12
    letter = 'CDEFGAB'[idx % 7]
    new_octave = idx // 7
    new_acc = pitch - (12 * new_octave + base[letter])
    if abs(new_acc) > 2 or new_octave < 0:
        return None
    return letter + ('#' * new_acc if new_acc > 0 else 'b' * -new_acc) + str(new_octave)
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[(['C4', 'M3', 'up'], 'E4'), (['C4', 'M3', 'down'], 'Ab3'), (['B3', 'm2', 'up'], 'C4'), (['E4', 'A4', 'up'], 'A#4'), (['F#4', 'A4', 'up'], 'B#4'), (['C4', 'd5', 'up'], 'Gb4'), (['C4', 'M9', 'up'], 'D5'), (['C4', 'M10', 'down'], 'Ab2')], [(['C4', 'm2', 'down'], 'B3'), (['F#4', 'A4', 'up'], 'B#4'), (['C4', 'd5', 'up'], 'Gb4'), (['C4', 'd7', 'up'], 'Bbb4'), (['C4', 'd3', 'up'], 'Ebb4'), (['Db4', 'd3', 'up'], 'Fbb4'), (['C4', 'M10', 'down'], 'Ab2'), (['C4', 'A1', 'down'], 'Cb4')], [(['C4', 'd3', 'up'], 'Ebb4'), (['Db4', 'd3', 'up'], 'Fbb4'), (['Cb4', 'd3', 'up'], None), (['C4', 'P8', 'up'], 'C5'), (['C4', 'M10', 'down'], 'Ab2'), (['G4', 'P5', 'up'], 'D5'), (['C4', 'd1', 'up'], 'Cb4'), (['C4', 'd1', 'down'], 'C#4')], [(['C4', 'M9', 'up'], 'D5'), (['G4', 'P5', 'up'], 'D5'), (['A4', 'M6', 'up'], 'F#5'), (['E#4', 'A6', 'up'], None), (['C0', 'm2', 'down'], None), (['C4', 'X3', 'up'], None), (['C4', 'A1', 'down'], 'Cb4'), (['C5', 'P15', 'down'], 'C3')], [(['C4', 'M10', 'down'], 'Ab2'), (['C0', 'm2', 'down'], None), (['C4', 'X3', 'up'], None), (['C4', 'M4', 'up'], None), (['C4', 'P3', 'up'], None), (['C4', 'd1', 'down'], 'C#4'), (['C5', 'P15', 'down'], 'C3'), (['D4', 'd4', 'down'], 'A#3')]]
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 0E4E4Passed
oracle 1NoneAb3Failed
oracle 2C4C4Passed
oracle 3A#4A#4Passed
oracle 4B#4B#4Passed
oracle 5Gb4Gb4Passed
oracle 6NoneD5Failed
oracle 7NoneAb2Failed

SHA-256 / 8ae297837b6d3ed66a67ad699a4117fa2a8bd15c2a18cb1956e4b3c31fa1ab34

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}
    if not isinstance(x, list) or len(x) != 3:
        return None
    note, ivl, direction = x
    if direction not in ('up', 'down'):
        return None
    if not isinstance(note, str) or len(note) < 2 or note[0] not in base:
        return None
    i = 1
    acc = 0
    while i < len(note) and note[i] in '#b':
        acc += 1 if note[i] == '#' else -1
        i += 1
    if not note[i:].isdigit():
        return None
    octave = int(note[i:])
    if not isinstance(ivl, str) or len(ivl) < 2 or not ivl[1:].isdigit():
        return None
    quality = ivl[0]
    number = int(ivl[1:])
    if number < 1:
        return None
    simple = (number - 1) % 7 + 1
    perfect = simple in (1, 4, 5)
    offsets = {'P': 0, 'A': 1, 'd': -1} if perfect else {'M': 0, 'm': -1, 'A': 1, 'd': -2}
    if quality not in offsets:
        return None
    size = [0, 2, 4, 5, 7, 9, 11][simple - 1] + 12 * ((number - 1) // 7) + offsets[quality]
    sign = 1 if direction == 'up' else -1
    idx = 'CDEFGAB'.index(note[0]) + 7 * octave + sign * (number - 1)
    pitch = 12 * octave + base[note[0]] + acc + sign * size
    letter = 'CDEFGAB'[idx % 7]
    new_octave = idx // 7
    new_acc = pitch - (12 * new_octave + base[letter])
    if abs(new_acc) > 2 or new_octave < 0:
        return None
    return letter + ('#' * new_acc if new_acc > 0 else 'b' * -new_acc) + str(new_octave)
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[(['C4', 'M3', 'up'], 'E4'), (['C4', 'M3', 'down'], 'Ab3'), (['B3', 'm2', 'up'], 'C4'), (['E4', 'A4', 'up'], 'A#4'), (['F#4', 'A4', 'up'], 'B#4'), (['C4', 'd5', 'up'], 'Gb4'), (['C4', 'M9', 'up'], 'D5'), (['C4', 'M10', 'down'], 'Ab2')], [(['C4', 'm2', 'down'], 'B3'), (['F#4', 'A4', 'up'], 'B#4'), (['C4', 'd5', 'up'], 'Gb4'), (['C4', 'd7', 'up'], 'Bbb4'), (['C4', 'd3', 'up'], 'Ebb4'), (['Db4', 'd3', 'up'], 'Fbb4'), (['C4', 'M10', 'down'], 'Ab2'), (['C4', 'A1', 'down'], 'Cb4')], [(['C4', 'd3', 'up'], 'Ebb4'), (['Db4', 'd3', 'up'], 'Fbb4'), (['Cb4', 'd3', 'up'], None), (['C4', 'P8', 'up'], 'C5'), (['C4', 'M10', 'down'], 'Ab2'), (['G4', 'P5', 'up'], 'D5'), (['C4', 'd1', 'up'], 'Cb4'), (['C4', 'd1', 'down'], 'C#4')], [(['C4', 'M9', 'up'], 'D5'), (['G4', 'P5', 'up'], 'D5'), (['A4', 'M6', 'up'], 'F#5'), (['E#4', 'A6', 'up'], None), (['C0', 'm2', 'down'], None), (['C4', 'X3', 'up'], None), (['C4', 'A1', 'down'], 'Cb4'), (['C5', 'P15', 'down'], 'C3')], [(['C4', 'M10', 'down'], 'Ab2'), (['C0', 'm2', 'down'], None), (['C4', 'X3', 'up'], None), (['C4', 'M4', 'up'], None), (['C4', 'P3', 'up'], None), (['C4', 'd1', 'down'], 'C#4'), (['C5', 'P15', 'down'], 'C3'), (['D4', 'd4', 'down'], 'A#3')]]
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 0E4E4Passed
oracle 1Ab3Ab3Passed
oracle 2C4C4Passed
oracle 3A#4A#4Passed
oracle 4B#4B#4Passed
oracle 5Gb4Gb4Passed
oracle 6D5D5Passed
oracle 7Ab2Ab2Passed

SHA-256 / 2a4b809a4d37833c4d44d82d6765290f60ebe74df12b37e675f71e8f264de9b8

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

Case digest / 971d2038011083bf39f1e5151d5a81ad6c6741ef33705420b3d57ce1358aa616