{
  "id": "P057",
  "slug": "scales-and-chords",
  "key": "P057-scales-and-chords",
  "title": "Scales and chords",
  "summary": "Major and minor scales and nine kinds of chords from any root, spelled on the right letters (F major has Bb, not A#), intervals named from their letters and semitones with their simple frequency ratio and where the two notes' overtones meet, and chord progressions moved to another key.",
  "entry": "main.eml",
  "ui": "terminal",
  "readme": "# P057 - Scales and chords\n\nScales and chords from any root, spelled on the right letters - F# major\nhas E#, not F; Bb major seventh has A, not Bbb. The interval between two\nnotes, named from its letters and semitones, with the simple frequency\nratio its semitones stand for and where the two notes' overtones meet.\nChord progressions moved to another key, and a table of how each of the\ntwelve pitches can be spelled.\n\n- `main.eml` - the menu, reading notes and chord symbols, and the messages\n- `notes.eml` - spelled notes: letters and sharps or flats, their pitch,\n  and building a run of notes letter by letter\n- `theory.eml` - the scale and chord kinds, chord symbols, interval names\n  and ratios, and the usual spelling of a transposed root\n\nHow each part works:\n\n- A note is a letter and up to two sharps or flats, so C# and Db are two\n  notes that sound the same. A scale moves one letter and a fixed number of\n  semitones at each step; a chord moves two letters, a third, at each step.\n  The letter fixes the name, and the semitones fix the sharps or flats. When\n  a note would need three, the scale or chord is reported instead of being\n  misspelled.\n- An interval's number comes from the letters it spans, and its quality -\n  perfect, major, minor, augmented, diminished - from how far its semitones\n  are from that number's usual size. So C up to E is a major third and C\n  up to Fb a diminished fourth, both 4 semitones. C up to B# is an\n  augmented seventh of 12 semitones, and C up to Cb a diminished octave.\n- Each span of 0 to 12 semitones stands for a simple ratio of frequencies:\n  3 : 2 for 7, 5 : 4 for 4, and so on. With upper : lower = p : q, every\n  p-th overtone of the lower note has the same frequency as every q-th\n  overtone of the upper one. The corpus case\n  `the-two-notes-shared-every-third-overtone` works this out for 220 and\n  330 Hz, a fifth: every third overtone of the lower tone is shared, the\n  first at 660 Hz. Big numbers in a ratio mean the shared overtones are\n  high and faint, so the two notes blend less.\n- Transposing moves each chord's root by semitones and spells it the\n  usual way - flats, except F# - keeping the chord's kind.\n\nWhat is checked: menu choices 1 to 6; a note as a letter A to G (either\ncase) with up to two # or up to two b; a chord as a note and one of\nnothing, m, dim, aug, 7, maj7, m7, m7b5 and dim7; chords for transposing,\nseparated by spaces or commas; a shift from -11 to 11 semitones. An empty\nanswer cancels.\n\nSessions: `sessions/basic.in` gives:\n- the four scales on C and on F#;\n- the chords Bbmaj7 and F#m7b5;\n- intervals: C to G (3 : 2, overtones meeting at 660 Hz), C to E (5 : 4,\n  at 1100 Hz), C to Fb (a diminished fourth, also 4 semitones), and B to F\n  (a diminished fifth, 45 : 32, which blends less than the simpler ratios);\n- C Am F G7 moved up 2 semitones, to D Bm G A7;\n- the table of spellings.\n\n`sessions/bad-input.in` gives:\n- menu choices 0 and x;\n- roots H and C#b, and an empty root;\n- B#, whose scales need double sharps, and E##, whose scales would need\n  triple sharps;\n- the chords Hm and Cm9, an empty chord, and Fbdim7, which would need a\n  triple flat;\n- an interval cancelled at the upper note, C to C, and C to B#;\n- transposing: nothing; \"C X\"; \"C, G\" with shifts 12 and x, then -5, to\n  G D.\n\nBuilt on the verified corpus case `the-two-notes-shared-every-third-overtone`\n(where the overtones of two tones coincide, and where they narrowly miss).\n",
  "modules": [
    {
      "name": "main.eml",
      "eml": "# P057 scales and chords: scales and chords spelled with the right letter\n# names, the interval between two notes - its name, its ratio and where the\n# two notes' overtones meet - and chord progressions moved to another key.\nimport notes\nimport theory\n\ndef trim(s):\n    0 => i\n    len(s) => j\n    while i < j and s[i] == \" \":\n        i + 1 => i\n    while j > i and s[j - 1] == \" \":\n        j - 1 => j\n    return s[i:j]\n\ndef words(s):\n    [] => out\n    \"\" => word\n    for c in s + \" \":\n        if c == \" \" or c == \",\":\n            if word != \"\":\n                out + [word] => out\n            \"\" => word\n        else:\n            word + c => word\n    return out\n\ndef ask_note(prompt):\n    while True:\n        trim(input(prompt + \"> \")) => answer\n        if answer == \"\":\n            return []\n        notes.parsed(answer) => n\n        if len(n) > 0:\n            return n\n        \"Type a note: a letter A to G, then # or b, up to two, like F#, Bb or C.\" ^0\n\ndef show_scale():\n    ask_note(\"root\") => root\n    if len(root) == 0:\n        \"Cancelled.\" ^0\n        return\n    for kind in theory.scale_kinds():\n        theory.scale(root, kind[1]) => sc\n        notes.name(root) + \" \" + kind[0] => label\n        while len(label) < 20:\n            label + \" \" => label\n        if len(sc) == 0:\n            (label + \"needs triple sharps or flats - try the same pitch spelled another way.\") ^0\n        else:\n            (label + notes.names(sc)) ^0\n\ndef show_chord():\n    while True:\n        trim(input(\"chord (like C, Am, F#m7, Bbmaj7, Cdim7)> \")) => answer\n        if answer == \"\":\n            \"Cancelled.\" ^0\n            return\n        theory.chord_parsed(answer) => c\n        if len(c) > 0:\n            break\n        \"Type a root note and one of: (nothing) m dim aug 7 maj7 m7 m7b5 dim7.\" ^0\n    theory.chord(c[0], c[1][2]) => tones\n    if len(tones) == 0:\n        (answer + \" needs triple sharps or flats.\") ^0\n        return\n    [\"root\", \"third\", \"fifth\", \"seventh\"] => roles\n    \"\" => s\n    for k in [0:len(tones) - 1]:\n        if s != \"\":\n            s + \", \" => s\n        s + notes.name(tones[k]) + \" (\" + roles[k] + \")\" => s\n    (answer + \" is \" + notes.name(c[0]) + \" \" + c[1][1] + \": \" + s + \".\") ^0\n\ndef show_interval():\n    ask_note(\"lower note\") => a\n    if len(a) == 0:\n        \"Cancelled.\" ^0\n        return\n    ask_note(\"upper note\") => b\n    if len(b) == 0:\n        \"Cancelled.\" ^0\n        return\n    theory.interval(a, b) => iv\n    iv[0] => nm\n    iv[1] => semis\n    if a[0] == b[0] and a[1] == b[1]:\n        (\"From \" + notes.name(a) + \" up to \" + notes.name(b) + \": the same note, or an octave.\") ^0\n        return\n    (\"From \" + notes.name(a) + \" up to \" + notes.name(b) + \": \" + nm + \", \" + semitones(semis) + \".\") ^0\n    theory.just_ratio(semis) => r\n    # With frequencies upper : lower = p : q, overtone q * k of the upper note\n    # and overtone p * k of the lower note are the same frequency, so every\n    # p-th overtone of the lower note is shared (the corpus case: 220 and\n    # 330 Hz, 3 : 2, share every third overtone of the lower tone).\n    (\"On a piano, \" + semitones(semis) + \" stand for the simple ratio \" + str(r[0]) + \" : \" + str(r[1]) + \", so every \" + ordinal(r[0]) + \" overtone of the lower note meets every \" + ordinal(r[1]) + \" of the upper one - first at \" + str(r[0]) + \" x 220 = \" + str(r[0] * 220) + \" Hz when the lower note is 220 Hz.\") ^0\n    if r[0] >= 9:\n        \"Overtones that far up are faint, so these two notes blend less than the simpler ratios do.\" ^0\n\ndef semitones(n):\n    if n == 1:\n        return \"1 semitone\"\n    return str(n) + \" semitones\"\n\ndef ordinal(n):\n    n % 10 => last\n    n % 100 => last2\n    if last == 1 and last2 != 11:\n        return str(n) + \"st\"\n    if last == 2 and last2 != 12:\n        return str(n) + \"nd\"\n    if last == 3 and last2 != 13:\n        return str(n) + \"rd\"\n    return str(n) + \"th\"\n\ndef transpose():\n    words(trim(input(\"chords, like C Am F G> \"))) => ws\n    if len(ws) == 0:\n        \"Cancelled.\" ^0\n        return\n    [] => parsed\n    for w in ws:\n        theory.chord_parsed(w) => c\n        if len(c) == 0:\n            (\"Not a chord: \" + w + \".\") ^0\n            return\n        parsed + [c] => parsed\n    while True:\n        trim(input(\"semitones up (-11 to 11)> \")) => answer\n        if answer == \"\":\n            \"Cancelled.\" ^0\n            return\n        answer => digits\n        1 => sign\n        if len(answer) > 1 and answer[0] == \"-\":\n            answer[1:len(answer)] => digits\n            -1 => sign\n        if len(digits) >= 1 and len(digits) <= 2 and digits[0] >= \"0\" and digits[0] <= \"9\" and digits[len(digits) - 1] >= \"0\" and digits[len(digits) - 1] <= \"9\":\n            sign * int(digits) => n\n            if n >= -11 and n <= 11:\n                break\n        \"Type a whole number from -11 to 11.\" ^0\n    \"\" => out\n    for c in parsed:\n        theory.preferred((notes.pitch(c[0]) + n + 12) % 12) => root\n        if out != \"\":\n            out + \" \" => out\n        out + notes.name(root) + c[1][0] => out\n    (\"Moved \" + str(n) + \" semitones: \" + out) ^0\n\ndef table():\n    \"Pitch classes, C = 0, each with its spellings:\" ^0\n    for pc in [0:11]:\n        \"\" => s\n        for letter in [0:6]:\n            notes.spelled(letter, pc) => n\n            if len(n) > 0:\n                if s != \"\":\n                    s + \" = \" => s\n                s + notes.name(n) => s\n        str(pc) => label\n        if len(label) == 1:\n            \" \" + label => label\n        (label + \": \" + s) ^0\n\n\"== Scales and chords ==\" ^0\n\"Notes are spelled: C# and Db sound the same but are written on different letters.\" ^0\nTrue => running\nwhile running:\n    \"\" ^0\n    \"1) scales  2) chord  3) interval  4) transpose  5) note table  6) quit\" ^0\n    trim(input(\"choice> \")) => choice\n    if choice == \"1\":\n        show_scale()\n    elif choice == \"2\":\n        show_chord()\n    elif choice == \"3\":\n        show_interval()\n    elif choice == \"4\":\n        transpose()\n    elif choice == \"5\":\n        table()\n    elif choice == \"6\":\n        False => running\n    else:\n        \"Pick a number from 1 to 6.\" ^0\n\"Bye.\" ^0\n",
      "python": "import notes\nimport theory\n\ndef trim(s):\n    i = 0\n    j = len(s)\n    while i < j and s[i] == \" \":\n        i = i + 1\n    while j > i and s[j - 1] == \" \":\n        j = j - 1\n    return s[i:j]\n\ndef words(s):\n    out = []\n    word = \"\"\n    for c in s + \" \":\n        if c == \" \" or c == \",\":\n            if word != \"\":\n                out = out + [word]\n            word = \"\"\n        else:\n            word = word + c\n    return out\n\ndef ask_note(prompt):\n    while True:\n        answer = trim(input(prompt + \"> \"))\n        if answer == \"\":\n            return []\n        n = notes.parsed(answer)\n        if len(n) > 0:\n            return n\n        print(\"Type a note: a letter A to G, then # or b, up to two, like F#, Bb or C.\")\n\ndef show_scale():\n    root = ask_note(\"root\")\n    if len(root) == 0:\n        print(\"Cancelled.\")\n        return\n    for kind in theory.scale_kinds():\n        sc = theory.scale(root, kind[1])\n        label = notes.name(root) + \" \" + kind[0]\n        while len(label) < 20:\n            label = label + \" \"\n        if len(sc) == 0:\n            print(label + \"needs triple sharps or flats - try the same pitch spelled another way.\")\n        else:\n            print(label + notes.names(sc))\n\ndef show_chord():\n    while True:\n        answer = trim(input(\"chord (like C, Am, F#m7, Bbmaj7, Cdim7)> \"))\n        if answer == \"\":\n            print(\"Cancelled.\")\n            return\n        c = theory.chord_parsed(answer)\n        if len(c) > 0:\n            break\n        print(\"Type a root note and one of: (nothing) m dim aug 7 maj7 m7 m7b5 dim7.\")\n    tones = theory.chord(c[0], c[1][2])\n    if len(tones) == 0:\n        print(answer + \" needs triple sharps or flats.\")\n        return\n    roles = [\"root\", \"third\", \"fifth\", \"seventh\"]\n    s = \"\"\n    for k in range(0, len(tones)):\n        if s != \"\":\n            s = s + \", \"\n        s = s + notes.name(tones[k]) + \" (\" + roles[k] + \")\"\n    print(answer + \" is \" + notes.name(c[0]) + \" \" + c[1][1] + \": \" + s + \".\")\n\ndef show_interval():\n    a = ask_note(\"lower note\")\n    if len(a) == 0:\n        print(\"Cancelled.\")\n        return\n    b = ask_note(\"upper note\")\n    if len(b) == 0:\n        print(\"Cancelled.\")\n        return\n    iv = theory.interval(a, b)\n    nm = iv[0]\n    semis = iv[1]\n    if a[0] == b[0] and a[1] == b[1]:\n        print(\"From \" + notes.name(a) + \" up to \" + notes.name(b) + \": the same note, or an octave.\")\n        return\n    print(\"From \" + notes.name(a) + \" up to \" + notes.name(b) + \": \" + nm + \", \" + semitones(semis) + \".\")\n    r = theory.just_ratio(semis)\n    print(\"On a piano, \" + semitones(semis) + \" stand for the simple ratio \" + str(r[0]) + \" : \" + str(r[1]) + \", so every \" + ordinal(r[0]) + \" overtone of the lower note meets every \" + ordinal(r[1]) + \" of the upper one - first at \" + str(r[0]) + \" x 220 = \" + str(r[0] * 220) + \" Hz when the lower note is 220 Hz.\")\n    if r[0] >= 9:\n        print(\"Overtones that far up are faint, so these two notes blend less than the simpler ratios do.\")\n\ndef semitones(n):\n    if n == 1:\n        return \"1 semitone\"\n    return str(n) + \" semitones\"\n\ndef ordinal(n):\n    last = n % 10\n    last2 = n % 100\n    if last == 1 and last2 != 11:\n        return str(n) + \"st\"\n    if last == 2 and last2 != 12:\n        return str(n) + \"nd\"\n    if last == 3 and last2 != 13:\n        return str(n) + \"rd\"\n    return str(n) + \"th\"\n\ndef transpose():\n    ws = words(trim(input(\"chords, like C Am F G> \")))\n    if len(ws) == 0:\n        print(\"Cancelled.\")\n        return\n    parsed = []\n    for w in ws:\n        c = theory.chord_parsed(w)\n        if len(c) == 0:\n            print(\"Not a chord: \" + w + \".\")\n            return\n        parsed = parsed + [c]\n    while True:\n        answer = trim(input(\"semitones up (-11 to 11)> \"))\n        if answer == \"\":\n            print(\"Cancelled.\")\n            return\n        digits = answer\n        sign = 1\n        if len(answer) > 1 and answer[0] == \"-\":\n            digits = answer[1:len(answer)]\n            sign = -1\n        if len(digits) >= 1 and len(digits) <= 2 and digits[0] >= \"0\" and digits[0] <= \"9\" and digits[len(digits) - 1] >= \"0\" and digits[len(digits) - 1] <= \"9\":\n            n = sign * int(digits)\n            if n >= -11 and n <= 11:\n                break\n        print(\"Type a whole number from -11 to 11.\")\n    out = \"\"\n    for c in parsed:\n        root = theory.preferred((notes.pitch(c[0]) + n + 12) % 12)\n        if out != \"\":\n            out = out + \" \"\n        out = out + notes.name(root) + c[1][0]\n    print(\"Moved \" + str(n) + \" semitones: \" + out)\n\ndef table():\n    print(\"Pitch classes, C = 0, each with its spellings:\")\n    for pc in range(0, 12):\n        s = \"\"\n        for letter in range(0, 7):\n            n = notes.spelled(letter, pc)\n            if len(n) > 0:\n                if s != \"\":\n                    s = s + \" = \"\n                s = s + notes.name(n)\n        label = str(pc)\n        if len(label) == 1:\n            label = \" \" + label\n        print(label + \": \" + s)\n\nprint(\"== Scales and chords ==\")\nprint(\"Notes are spelled: C# and Db sound the same but are written on different letters.\")\nrunning = True\nwhile running:\n    print(\"\")\n    print(\"1) scales  2) chord  3) interval  4) transpose  5) note table  6) quit\")\n    choice = trim(input(\"choice> \"))\n    if choice == \"1\":\n        show_scale()\n    elif choice == \"2\":\n        show_chord()\n    elif choice == \"3\":\n        show_interval()\n    elif choice == \"4\":\n        transpose()\n    elif choice == \"5\":\n        table()\n    elif choice == \"6\":\n        running = False\n    else:\n        print(\"Pick a number from 1 to 6.\")\nprint(\"Bye.\")\n"
    },
    {
      "name": "notes.eml",
      "eml": "# P057 scales and chords - spelled notes. A note is [letter, accidental]:\n# letter 0 to 6 for C D E F G A B, accidental -2 to 2 for double flat to\n# double sharp. Its pitch class (0 to 11, C = 0) is where it sounds; two\n# spellings of one pitch - C# and Db - are different notes on paper.\n\ndef naturals():\n    return [0, 2, 4, 5, 7, 9, 11]\n\ndef pitch(note):\n    return (naturals()[note[0]] + note[1] + 12) % 12\n\ndef name(note):\n    \"CDEFGAB\"[note[0]] => s\n    if note[1] > 0:\n        s + \"#\" * note[1] => s\n    elif note[1] < 0:\n        s + \"b\" * (0 - note[1]) => s\n    return s\n\ndef parsed(s):\n    # \"C\", \"f#\", \"Bb\", \"Ebb\", \"G##\" as a note, or [] when it is not one.\n    if s == \"\" or len(s) > 3:\n        return []\n    -1 => letter\n    for k in [0:6]:\n        if s[0] == \"CDEFGAB\"[k] or s[0] == \"cdefgab\"[k]:\n            k => letter\n    if letter == -1:\n        return []\n    0 => acc\n    for ch in s[1:len(s)]:\n        if ch == \"#\" and acc >= 0:\n            acc + 1 => acc\n        elif ch == \"b\" and acc <= 0:\n            acc - 1 => acc\n        else:\n            return []\n    return [letter, acc]\n\ndef spelled(letter, target):\n    # The note on this letter that sounds at pitch class target, or [] when\n    # it would need more than two sharps or flats.\n    (target - naturals()[letter] + 12) % 12 => d\n    if d > 6:\n        d - 12 => d\n    if d < -2 or d > 2:\n        return []\n    return [letter, d]\n\ndef built(root, steps, letter_steps):\n    # Notes from root, each the given semitones above the last and the given\n    # number of letters further on - so a seven-note scale uses every letter\n    # once and a chord stacks letters a third apart. Returns [] when some\n    # note cannot be written with at most two sharps or flats.\n    [root] => out\n    root[0] => letter\n    pitch(root) => p\n    for k in [0:len(steps) - 1]:\n        (letter + letter_steps[k]) % 7 => letter\n        (p + steps[k]) % 12 => p\n        spelled(letter, p) => n\n        if len(n) == 0:\n            return []\n        out + [n] => out\n    return out\n\ndef names(notes):\n    \"\" => s\n    for n in notes:\n        if s != \"\":\n            s + \" \" => s\n        s + name(n) => s\n    return s\n",
      "python": "def naturals():\n    return [0, 2, 4, 5, 7, 9, 11]\n\ndef pitch(note):\n    return (naturals()[note[0]] + note[1] + 12) % 12\n\ndef name(note):\n    s = \"CDEFGAB\"[note[0]]\n    if note[1] > 0:\n        s = s + \"#\" * note[1]\n    elif note[1] < 0:\n        s = s + \"b\" * (0 - note[1])\n    return s\n\ndef parsed(s):\n    if s == \"\" or len(s) > 3:\n        return []\n    letter = -1\n    for k in range(0, 7):\n        if s[0] == \"CDEFGAB\"[k] or s[0] == \"cdefgab\"[k]:\n            letter = k\n    if letter == -1:\n        return []\n    acc = 0\n    for ch in s[1:len(s)]:\n        if ch == \"#\" and acc >= 0:\n            acc = acc + 1\n        elif ch == \"b\" and acc <= 0:\n            acc = acc - 1\n        else:\n            return []\n    return [letter, acc]\n\ndef spelled(letter, target):\n    d = (target - naturals()[letter] + 12) % 12\n    if d > 6:\n        d = d - 12\n    if d < -2 or d > 2:\n        return []\n    return [letter, d]\n\ndef built(root, steps, letter_steps):\n    out = [root]\n    letter = root[0]\n    p = pitch(root)\n    for k in range(0, len(steps)):\n        letter = (letter + letter_steps[k]) % 7\n        p = (p + steps[k]) % 12\n        n = spelled(letter, p)\n        if len(n) == 0:\n            return []\n        out = out + [n]\n    return out\n\ndef names(notes):\n    s = \"\"\n    for n in notes:\n        if s != \"\":\n            s = s + \" \"\n        s = s + name(n)\n    return s\n"
    },
    {
      "name": "theory.eml",
      "eml": "# P057 scales and chords - scales, chords and intervals, spelled by letter.\nimport notes\n\ndef scale_kinds():\n    # [name, semitone steps between the seven notes]; every step moves one\n    # letter on.\n    return [[\"major\", [2, 2, 1, 2, 2, 2]],\n            [\"natural minor\", [2, 1, 2, 2, 1, 2]],\n            [\"harmonic minor\", [2, 1, 2, 2, 1, 3]],\n            [\"melodic minor\", [2, 1, 2, 2, 2, 2]]]\n\ndef chord_kinds():\n    # [suffix, name, semitones above the root]; each note is a third - two\n    # letters - above the one before.\n    return [[\"\", \"major\", [0, 4, 7]],\n            [\"m\", \"minor\", [0, 3, 7]],\n            [\"dim\", \"diminished\", [0, 3, 6]],\n            [\"aug\", \"augmented\", [0, 4, 8]],\n            [\"7\", \"dominant seventh\", [0, 4, 7, 10]],\n            [\"maj7\", \"major seventh\", [0, 4, 7, 11]],\n            [\"m7\", \"minor seventh\", [0, 3, 7, 10]],\n            [\"m7b5\", \"half-diminished seventh\", [0, 3, 6, 10]],\n            [\"dim7\", \"diminished seventh\", [0, 3, 6, 9]]]\n\ndef scale(root, steps):\n    return notes.built(root, steps, [1, 1, 1, 1, 1, 1])\n\ndef chord(root, tones):\n    [] => steps\n    [] => letters\n    for k in [1:len(tones) - 1]:\n        steps + [tones[k] - tones[k - 1]] => steps\n        letters + [2] => letters\n    return notes.built(root, steps, letters)\n\ndef chord_parsed(s):\n    # A chord symbol - a note, then a suffix - as [root, kind], or [].\n    # The longest root that reads as a note wins, so \"Bbm\" is Bb minor.\n    for size in [3, 2, 1]:\n        if len(s) >= size:\n            notes.parsed(s[0:size]) => root\n            if len(root) > 0:\n                s[size:len(s)] => suffix\n                for kind in chord_kinds():\n                    if kind[0] == suffix:\n                        return [root, kind]\n    return []\n\ndef interval(a, b):\n    # The interval from note a up to note b, as [name, semitones spanned]:\n    # named from the letters it spans (third, fifth ...) and how far the\n    # semitones are from that size's usual count. C to E is a major third\n    # and C to Fb a diminished fourth, though both span 4 semitones; C up to\n    # B# is an augmented seventh, 12 semitones, and C up to Cb a diminished\n    # octave, 11.\n    (b[0] - a[0] + 7) % 7 + 1 => number\n    (notes.pitch(b) - notes.pitch(a) + 12) % 12 => semis\n    [0, 2, 4, 5, 7, 9, 11][number - 1] => reference\n    semis - reference => diff\n    if diff > 6:\n        diff - 12 => diff\n    if diff < -6:\n        diff + 12 => diff\n    reference + diff => span\n    [\"unison\", \"second\", \"third\", \"fourth\", \"fifth\", \"sixth\", \"seventh\"][number - 1] => kind\n    if number == 1 and diff < 0:\n        \"octave\" => kind\n        12 + diff => span\n    if number == 1 or number == 4 or number == 5:\n        if diff == 0:\n            return [\"perfect \" + kind, span]\n        if diff == 1:\n            return [\"augmented \" + kind, span]\n        if diff == -1:\n            return [\"diminished \" + kind, span]\n    else:\n        if diff == 0:\n            return [\"major \" + kind, span]\n        if diff == -1:\n            return [\"minor \" + kind, span]\n        if diff == 1:\n            return [\"augmented \" + kind, span]\n        if diff == -2:\n            return [\"diminished \" + kind, span]\n    return [str(semis) + \" semitones, with no plain name\", semis]\n\ndef just_ratio(semis):\n    # The small whole-number frequency ratio each span of 0 to 12 semitones\n    # stands for in equal temperament, [upper, lower].\n    return [[1, 1], [16, 15], [9, 8], [6, 5], [5, 4], [4, 3], [45, 32],\n            [3, 2], [8, 5], [5, 3], [9, 5], [15, 8], [2, 1]][semis]\n\ndef preferred(pc):\n    # The usual spelling of a pitch class on its own, for transposing chord\n    # roots: sharps for F# only, flats elsewhere.\n    return [[0, 0], [1, -1], [1, 0], [2, -1], [2, 0], [3, 0], [3, 1],\n            [4, 0], [5, -1], [5, 0], [6, -1], [6, 0]][pc]\n",
      "python": "import notes\n\ndef scale_kinds():\n    return [[\"major\", [2, 2, 1, 2, 2, 2]], [\"natural minor\", [2, 1, 2, 2, 1, 2]], [\"harmonic minor\", [2, 1, 2, 2, 1, 3]], [\"melodic minor\", [2, 1, 2, 2, 2, 2]]]\n\ndef chord_kinds():\n    return [[\"\", \"major\", [0, 4, 7]], [\"m\", \"minor\", [0, 3, 7]], [\"dim\", \"diminished\", [0, 3, 6]], [\"aug\", \"augmented\", [0, 4, 8]], [\"7\", \"dominant seventh\", [0, 4, 7, 10]], [\"maj7\", \"major seventh\", [0, 4, 7, 11]], [\"m7\", \"minor seventh\", [0, 3, 7, 10]], [\"m7b5\", \"half-diminished seventh\", [0, 3, 6, 10]], [\"dim7\", \"diminished seventh\", [0, 3, 6, 9]]]\n\ndef scale(root, steps):\n    return notes.built(root, steps, [1, 1, 1, 1, 1, 1])\n\ndef chord(root, tones):\n    steps = []\n    letters = []\n    for k in range(1, len(tones)):\n        steps = steps + [tones[k] - tones[k - 1]]\n        letters = letters + [2]\n    return notes.built(root, steps, letters)\n\ndef chord_parsed(s):\n    for size in [3, 2, 1]:\n        if len(s) >= size:\n            root = notes.parsed(s[0:size])\n            if len(root) > 0:\n                suffix = s[size:len(s)]\n                for kind in chord_kinds():\n                    if kind[0] == suffix:\n                        return [root, kind]\n    return []\n\ndef interval(a, b):\n    number = (b[0] - a[0] + 7) % 7 + 1\n    semis = (notes.pitch(b) - notes.pitch(a) + 12) % 12\n    reference = [0, 2, 4, 5, 7, 9, 11][number - 1]\n    diff = semis - reference\n    if diff > 6:\n        diff = diff - 12\n    if diff < -6:\n        diff = diff + 12\n    span = reference + diff\n    kind = [\"unison\", \"second\", \"third\", \"fourth\", \"fifth\", \"sixth\", \"seventh\"][number - 1]\n    if number == 1 and diff < 0:\n        kind = \"octave\"\n        span = 12 + diff\n    if number == 1 or number == 4 or number == 5:\n        if diff == 0:\n            return [\"perfect \" + kind, span]\n        if diff == 1:\n            return [\"augmented \" + kind, span]\n        if diff == -1:\n            return [\"diminished \" + kind, span]\n    else:\n        if diff == 0:\n            return [\"major \" + kind, span]\n        if diff == -1:\n            return [\"minor \" + kind, span]\n        if diff == 1:\n            return [\"augmented \" + kind, span]\n        if diff == -2:\n            return [\"diminished \" + kind, span]\n    return [str(semis) + \" semitones, with no plain name\", semis]\n\ndef just_ratio(semis):\n    return [[1, 1], [16, 15], [9, 8], [6, 5], [5, 4], [4, 3], [45, 32], [3, 2], [8, 5], [5, 3], [9, 5], [15, 8], [2, 1]][semis]\n\ndef preferred(pc):\n    return [[0, 0], [1, -1], [1, 0], [2, -1], [2, 0], [3, 0], [3, 1], [4, 0], [5, -1], [5, 0], [6, -1], [6, 0]][pc]\n"
    }
  ],
  "sessions": [
    {
      "name": "bad-input",
      "input": "0\nx\n1\nH\nC#b\n\n1\nB#\n1\nE##\n2\nHm\nCm9\n\n2\nFbdim7\n3\nC\n\n3\nC\nC\n3\nC\nB#\n4\n\n4\nC X\n4\nC, G\n12\nx\n-5\n6\n",
      "screen": "== Scales and chords ==\nNotes are spelled: C# and Db sound the same but are written on different letters.\n\n1) scales  2) chord  3) interval  4) transpose  5) note table  6) quit\nchoice> 0\nPick a number from 1 to 6.\n\n1) scales  2) chord  3) interval  4) transpose  5) note table  6) quit\nchoice> x\nPick a number from 1 to 6.\n\n1) scales  2) chord  3) interval  4) transpose  5) note table  6) quit\nchoice> 1\nroot> H\nType a note: a letter A to G, then # or b, up to two, like F#, Bb or C.\nroot> C#b\nType a note: a letter A to G, then # or b, up to two, like F#, Bb or C.\nroot> \nCancelled.\n\n1) scales  2) chord  3) interval  4) transpose  5) note table  6) quit\nchoice> 1\nroot> B#\nB# major            B# C## D## E# F## G## A##\nB# natural minor    B# C## D# E# F## G# A#\nB# harmonic minor   B# C## D# E# F## G# A##\nB# melodic minor    B# C## D# E# F## G## A##\n\n1) scales  2) chord  3) interval  4) transpose  5) note table  6) quit\nchoice> 1\nroot> E##\nE## major           needs triple sharps or flats - try the same pitch spelled another way.\nE## natural minor   needs triple sharps or flats - try the same pitch spelled another way.\nE## harmonic minor  needs triple sharps or flats - try the same pitch spelled another way.\nE## melodic minor   needs triple sharps or flats - try the same pitch spelled another way.\n\n1) scales  2) chord  3) interval  4) transpose  5) note table  6) quit\nchoice> 2\nchord (like C, Am, F#m7, Bbmaj7, Cdim7)> Hm\nType a root note and one of: (nothing) m dim aug 7 maj7 m7 m7b5 dim7.\nchord (like C, Am, F#m7, Bbmaj7, Cdim7)> Cm9\nType a root note and one of: (nothing) m dim aug 7 maj7 m7 m7b5 dim7.\nchord (like C, Am, F#m7, Bbmaj7, Cdim7)> \nCancelled.\n\n1) scales  2) chord  3) interval  4) transpose  5) note table  6) quit\nchoice> 2\nchord (like C, Am, F#m7, Bbmaj7, Cdim7)> Fbdim7\nFbdim7 needs triple sharps or flats.\n\n1) scales  2) chord  3) interval  4) transpose  5) note table  6) quit\nchoice> 3\nlower note> C\nupper note> \nCancelled.\n\n1) scales  2) chord  3) interval  4) transpose  5) note table  6) quit\nchoice> 3\nlower note> C\nupper note> C\nFrom C up to C: the same note, or an octave.\n\n1) scales  2) chord  3) interval  4) transpose  5) note table  6) quit\nchoice> 3\nlower note> C\nupper note> B#\nFrom C up to B#: augmented seventh, 12 semitones.\nOn a piano, 12 semitones stand for the simple ratio 2 : 1, so every 2nd overtone of the lower note meets every 1st of the upper one - first at 2 x 220 = 440 Hz when the lower note is 220 Hz.\n\n1) scales  2) chord  3) interval  4) transpose  5) note table  6) quit\nchoice> 4\nchords, like C Am F G> \nCancelled.\n\n1) scales  2) chord  3) interval  4) transpose  5) note table  6) quit\nchoice> 4\nchords, like C Am F G> C X\nNot a chord: X.\n\n1) scales  2) chord  3) interval  4) transpose  5) note table  6) quit\nchoice> 4\nchords, like C Am F G> C, G\nsemitones up (-11 to 11)> 12\nType a whole number from -11 to 11.\nsemitones up (-11 to 11)> x\nType a whole number from -11 to 11.\nsemitones up (-11 to 11)> -5\nMoved -5 semitones: G D\n\n1) scales  2) chord  3) interval  4) transpose  5) note table  6) quit\nchoice> 6\nBye.\n",
      "interpreter": "equal"
    },
    {
      "name": "basic",
      "input": "1\nC\n1\nF#\n2\nBbmaj7\n2\nF#m7b5\n3\nC\nG\n3\nC\nE\n3\nC\nFb\n3\nB\nF\n4\nC Am F G7\n2\n5\n6\n",
      "screen": "== Scales and chords ==\nNotes are spelled: C# and Db sound the same but are written on different letters.\n\n1) scales  2) chord  3) interval  4) transpose  5) note table  6) quit\nchoice> 1\nroot> C\nC major             C D E F G A B\nC natural minor     C D Eb F G Ab Bb\nC harmonic minor    C D Eb F G Ab B\nC melodic minor     C D Eb F G A B\n\n1) scales  2) chord  3) interval  4) transpose  5) note table  6) quit\nchoice> 1\nroot> F#\nF# major            F# G# A# B C# D# E#\nF# natural minor    F# G# A B C# D E\nF# harmonic minor   F# G# A B C# D E#\nF# melodic minor    F# G# A B C# D# E#\n\n1) scales  2) chord  3) interval  4) transpose  5) note table  6) quit\nchoice> 2\nchord (like C, Am, F#m7, Bbmaj7, Cdim7)> Bbmaj7\nBbmaj7 is Bb major seventh: Bb (root), D (third), F (fifth), A (seventh).\n\n1) scales  2) chord  3) interval  4) transpose  5) note table  6) quit\nchoice> 2\nchord (like C, Am, F#m7, Bbmaj7, Cdim7)> F#m7b5\nF#m7b5 is F# half-diminished seventh: F# (root), A (third), C (fifth), E (seventh).\n\n1) scales  2) chord  3) interval  4) transpose  5) note table  6) quit\nchoice> 3\nlower note> C\nupper note> G\nFrom C up to G: perfect fifth, 7 semitones.\nOn a piano, 7 semitones stand for the simple ratio 3 : 2, so every 3rd overtone of the lower note meets every 2nd of the upper one - first at 3 x 220 = 660 Hz when the lower note is 220 Hz.\n\n1) scales  2) chord  3) interval  4) transpose  5) note table  6) quit\nchoice> 3\nlower note> C\nupper note> E\nFrom C up to E: major third, 4 semitones.\nOn a piano, 4 semitones stand for the simple ratio 5 : 4, so every 5th overtone of the lower note meets every 4th of the upper one - first at 5 x 220 = 1100 Hz when the lower note is 220 Hz.\n\n1) scales  2) chord  3) interval  4) transpose  5) note table  6) quit\nchoice> 3\nlower note> C\nupper note> Fb\nFrom C up to Fb: diminished fourth, 4 semitones.\nOn a piano, 4 semitones stand for the simple ratio 5 : 4, so every 5th overtone of the lower note meets every 4th of the upper one - first at 5 x 220 = 1100 Hz when the lower note is 220 Hz.\n\n1) scales  2) chord  3) interval  4) transpose  5) note table  6) quit\nchoice> 3\nlower note> B\nupper note> F\nFrom B up to F: diminished fifth, 6 semitones.\nOn a piano, 6 semitones stand for the simple ratio 45 : 32, so every 45th overtone of the lower note meets every 32nd of the upper one - first at 45 x 220 = 9900 Hz when the lower note is 220 Hz.\nOvertones that far up are faint, so these two notes blend less than the simpler ratios do.\n\n1) scales  2) chord  3) interval  4) transpose  5) note table  6) quit\nchoice> 4\nchords, like C Am F G> C Am F G7\nsemitones up (-11 to 11)> 2\nMoved 2 semitones: D Bm G A7\n\n1) scales  2) chord  3) interval  4) transpose  5) note table  6) quit\nchoice> 5\nPitch classes, C = 0, each with its spellings:\n 0: C = Dbb = B#\n 1: C# = Db = B##\n 2: C## = D = Ebb\n 3: D# = Eb = Fbb\n 4: D## = E = Fb\n 5: E# = F = Gbb\n 6: E## = F# = Gb\n 7: F## = G = Abb\n 8: G# = Ab\n 9: G## = A = Bbb\n10: Cbb = A# = Bb\n11: Cb = A## = B\n\n1) scales  2) chord  3) interval  4) transpose  5) note table  6) quit\nchoice> 6\nBye.\n",
      "interpreter": "equal"
    }
  ],
  "builtOn": [
    {
      "slug": "the-two-notes-shared-every-third-overtone",
      "caseId": "954-the-two-notes-shared-every-third-overtone",
      "title": "The two notes shared every third overtone"
    }
  ],
  "updated": "2026-10-10"
}
