Scales and chords
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.
Every screen below was recorded under CPython. When this page was built, the EML interpreter replayed each session from the same input and printed the same bytes.
About
Scales and chords from any root, spelled on the right letters - F# major has E#, not F; Bb major seventh has A, not Bbb. The interval between two notes, named from its letters and semitones, with the simple frequency ratio its semitones stand for and where the two notes' overtones meet. Chord progressions moved to another key, and a table of how each of the twelve pitches can be spelled.
main.eml- the menu, reading notes and chord symbols, and the messagesnotes.eml- spelled notes: letters and sharps or flats, their pitch, and building a run of notes letter by lettertheory.eml- the scale and chord kinds, chord symbols, interval names and ratios, and the usual spelling of a transposed root
How each part works:
- A note is a letter and up to two sharps or flats, so C# and Db are two notes that sound the same. A scale moves one letter and a fixed number of semitones at each step; a chord moves two letters, a third, at each step. The letter fixes the name, and the semitones fix the sharps or flats. When a note would need three, the scale or chord is reported instead of being misspelled.
- An interval's number comes from the letters it spans, and its quality - perfect, major, minor, augmented, diminished - from how far its semitones are from that number's usual size. So C up to E is a major third and C up to Fb a diminished fourth, both 4 semitones. C up to B# is an augmented seventh of 12 semitones, and C up to Cb a diminished octave.
- Each span of 0 to 12 semitones stands for a simple ratio of frequencies: 3 : 2 for 7, 5 : 4 for 4, and so on. With upper : lower = p : q, every p-th overtone of the lower note has the same frequency as every q-th overtone of the upper one. The corpus case
the-two-notes-shared-every-third-overtoneworks this out for 220 and 330 Hz, a fifth: every third overtone of the lower tone is shared, the first at 660 Hz. Big numbers in a ratio mean the shared overtones are high and faint, so the two notes blend less. - Transposing moves each chord's root by semitones and spells it the usual way - flats, except F# - keeping the chord's kind.
What is checked: menu choices 1 to 6; a note as a letter A to G (either case) with up to two # or up to two b; a chord as a note and one of nothing, m, dim, aug, 7, maj7, m7, m7b5 and dim7; chords for transposing, separated by spaces or commas; a shift from -11 to 11 semitones. An empty answer cancels.
Sessions: sessions/basic.in gives: - the four scales on C and on F#; - the chords Bbmaj7 and F#m7b5; - intervals: C to G (3 : 2, overtones meeting at 660 Hz), C to E (5 : 4, at 1100 Hz), C to Fb (a diminished fourth, also 4 semitones), and B to F (a diminished fifth, 45 : 32, which blends less than the simpler ratios); - C Am F G7 moved up 2 semitones, to D Bm G A7; - the table of spellings.
sessions/bad-input.in gives: - menu choices 0 and x; - roots H and C#b, and an empty root; - B#, whose scales need double sharps, and E##, whose scales would need triple sharps; - the chords Hm and Cm9, an empty chord, and Fbdim7, which would need a triple flat; - an interval cancelled at the upper note, C to C, and C to B#; - transposing: nothing; "C X"; "C, G" with shifts 12 and x, then -5, to G D.
Built on the verified corpus case the-two-notes-shared-every-third-overtone (where the overtones of two tones coincide, and where they narrowly miss).
Recorded sessions
What the screen shows while someone uses the program. Each typed line appears after its prompt, the way a terminal shows it.
bad-input
interpreter: byte-equal== Scales and chords ==
Notes are spelled: C# and Db sound the same but are written on different letters.
1) scales 2) chord 3) interval 4) transpose 5) note table 6) quit
choice> 0
Pick a number from 1 to 6.
1) scales 2) chord 3) interval 4) transpose 5) note table 6) quit
choice> x
Pick a number from 1 to 6.
1) scales 2) chord 3) interval 4) transpose 5) note table 6) quit
choice> 1
root> H
Type a note: a letter A to G, then # or b, up to two, like F#, Bb or C.
root> C#b
Type a note: a letter A to G, then # or b, up to two, like F#, Bb or C.
root>
Cancelled.
1) scales 2) chord 3) interval 4) transpose 5) note table 6) quit
choice> 1
root> B#
B# major B# C## D## E# F## G## A##
B# natural minor B# C## D# E# F## G# A#
B# harmonic minor B# C## D# E# F## G# A##
B# melodic minor B# C## D# E# F## G## A##
1) scales 2) chord 3) interval 4) transpose 5) note table 6) quit
choice> 1
root> E##
E## major needs triple sharps or flats - try the same pitch spelled another way.
E## natural minor needs triple sharps or flats - try the same pitch spelled another way.
E## harmonic minor needs triple sharps or flats - try the same pitch spelled another way.
E## melodic minor needs triple sharps or flats - try the same pitch spelled another way.
1) scales 2) chord 3) interval 4) transpose 5) note table 6) quit
choice> 2
chord (like C, Am, F#m7, Bbmaj7, Cdim7)> Hm
Type a root note and one of: (nothing) m dim aug 7 maj7 m7 m7b5 dim7.
chord (like C, Am, F#m7, Bbmaj7, Cdim7)> Cm9
Type a root note and one of: (nothing) m dim aug 7 maj7 m7 m7b5 dim7.
chord (like C, Am, F#m7, Bbmaj7, Cdim7)>
Cancelled.
1) scales 2) chord 3) interval 4) transpose 5) note table 6) quit
choice> 2
chord (like C, Am, F#m7, Bbmaj7, Cdim7)> Fbdim7
Fbdim7 needs triple sharps or flats.
1) scales 2) chord 3) interval 4) transpose 5) note table 6) quit
choice> 3
lower note> C
upper note>
Cancelled.
1) scales 2) chord 3) interval 4) transpose 5) note table 6) quit
choice> 3
lower note> C
upper note> C
From C up to C: the same note, or an octave.
1) scales 2) chord 3) interval 4) transpose 5) note table 6) quit
choice> 3
lower note> C
upper note> B#
From C up to B#: augmented seventh, 12 semitones.
On 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.
1) scales 2) chord 3) interval 4) transpose 5) note table 6) quit
choice> 4
chords, like C Am F G>
Cancelled.
1) scales 2) chord 3) interval 4) transpose 5) note table 6) quit
choice> 4
chords, like C Am F G> C X
Not a chord: X.
1) scales 2) chord 3) interval 4) transpose 5) note table 6) quit
choice> 4
chords, like C Am F G> C, G
semitones up (-11 to 11)> 12
Type a whole number from -11 to 11.
semitones up (-11 to 11)> x
Type a whole number from -11 to 11.
semitones up (-11 to 11)> -5
Moved -5 semitones: G D
1) scales 2) chord 3) interval 4) transpose 5) note table 6) quit
choice> 6
Bye.
What was typed (35 lines)
0
x
1
H
C#b
1
B#
1
E##
2
Hm
Cm9
2
Fbdim7
3
C
3
C
C
3
C
B#
4
4
C X
4
C, G
12
x
-5
6
basic
interpreter: byte-equal== Scales and chords ==
Notes are spelled: C# and Db sound the same but are written on different letters.
1) scales 2) chord 3) interval 4) transpose 5) note table 6) quit
choice> 1
root> C
C major C D E F G A B
C natural minor C D Eb F G Ab Bb
C harmonic minor C D Eb F G Ab B
C melodic minor C D Eb F G A B
1) scales 2) chord 3) interval 4) transpose 5) note table 6) quit
choice> 1
root> F#
F# major F# G# A# B C# D# E#
F# natural minor F# G# A B C# D E
F# harmonic minor F# G# A B C# D E#
F# melodic minor F# G# A B C# D# E#
1) scales 2) chord 3) interval 4) transpose 5) note table 6) quit
choice> 2
chord (like C, Am, F#m7, Bbmaj7, Cdim7)> Bbmaj7
Bbmaj7 is Bb major seventh: Bb (root), D (third), F (fifth), A (seventh).
1) scales 2) chord 3) interval 4) transpose 5) note table 6) quit
choice> 2
chord (like C, Am, F#m7, Bbmaj7, Cdim7)> F#m7b5
F#m7b5 is F# half-diminished seventh: F# (root), A (third), C (fifth), E (seventh).
1) scales 2) chord 3) interval 4) transpose 5) note table 6) quit
choice> 3
lower note> C
upper note> G
From C up to G: perfect fifth, 7 semitones.
On 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.
1) scales 2) chord 3) interval 4) transpose 5) note table 6) quit
choice> 3
lower note> C
upper note> E
From C up to E: major third, 4 semitones.
On 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.
1) scales 2) chord 3) interval 4) transpose 5) note table 6) quit
choice> 3
lower note> C
upper note> Fb
From C up to Fb: diminished fourth, 4 semitones.
On 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.
1) scales 2) chord 3) interval 4) transpose 5) note table 6) quit
choice> 3
lower note> B
upper note> F
From B up to F: diminished fifth, 6 semitones.
On 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.
Overtones that far up are faint, so these two notes blend less than the simpler ratios do.
1) scales 2) chord 3) interval 4) transpose 5) note table 6) quit
choice> 4
chords, like C Am F G> C Am F G7
semitones up (-11 to 11)> 2
Moved 2 semitones: D Bm G A7
1) scales 2) chord 3) interval 4) transpose 5) note table 6) quit
choice> 5
Pitch classes, C = 0, each with its spellings:
0: C = Dbb = B#
1: C# = Db = B##
2: C## = D = Ebb
3: D# = Eb = Fbb
4: D## = E = Fb
5: E# = F = Gbb
6: E## = F# = Gb
7: F## = G = Abb
8: G# = Ab
9: G## = A = Bbb
10: Cbb = A# = Bb
11: Cb = A## = B
1) scales 2) chord 3) interval 4) transpose 5) note table 6) quit
choice> 6
Bye.
What was typed (25 lines)
1
C
1
F#
2
Bbmaj7
2
F#m7b5
3
C
G
3
C
E
3
C
Fb
3
B
F
4
C Am F G7
2
5
6
Modules
The program as written, entry module first. Each module transpiles to its own Python file, which is what eml project run executes.
main.eml(entry)
eml# P057 scales and chords: scales and chords spelled with the right letter
# names, the interval between two notes - its name, its ratio and where the
# two notes' overtones meet - and chord progressions moved to another key.
import notes
import theory
def trim(s):
0 => i
len(s) => j
while i < j and s[i] == " ":
i + 1 => i
while j > i and s[j - 1] == " ":
j - 1 => j
return s[i:j]
def words(s):
[] => out
"" => word
for c in s + " ":
if c == " " or c == ",":
if word != "":
out + [word] => out
"" => word
else:
word + c => word
return out
def ask_note(prompt):
while True:
trim(input(prompt + "> ")) => answer
if answer == "":
return []
notes.parsed(answer) => n
if len(n) > 0:
return n
"Type a note: a letter A to G, then # or b, up to two, like F#, Bb or C." ^0
def show_scale():
ask_note("root") => root
if len(root) == 0:
"Cancelled." ^0
return
for kind in theory.scale_kinds():
theory.scale(root, kind[1]) => sc
notes.name(root) + " " + kind[0] => label
while len(label) < 20:
label + " " => label
if len(sc) == 0:
(label + "needs triple sharps or flats - try the same pitch spelled another way.") ^0
else:
(label + notes.names(sc)) ^0
def show_chord():
while True:
trim(input("chord (like C, Am, F#m7, Bbmaj7, Cdim7)> ")) => answer
if answer == "":
"Cancelled." ^0
return
theory.chord_parsed(answer) => c
if len(c) > 0:
break
"Type a root note and one of: (nothing) m dim aug 7 maj7 m7 m7b5 dim7." ^0
theory.chord(c[0], c[1][2]) => tones
if len(tones) == 0:
(answer + " needs triple sharps or flats.") ^0
return
["root", "third", "fifth", "seventh"] => roles
"" => s
for k in [0:len(tones) - 1]:
if s != "":
s + ", " => s
s + notes.name(tones[k]) + " (" + roles[k] + ")" => s
(answer + " is " + notes.name(c[0]) + " " + c[1][1] + ": " + s + ".") ^0
def show_interval():
ask_note("lower note") => a
if len(a) == 0:
"Cancelled." ^0
return
ask_note("upper note") => b
if len(b) == 0:
"Cancelled." ^0
return
theory.interval(a, b) => iv
iv[0] => nm
iv[1] => semis
if a[0] == b[0] and a[1] == b[1]:
("From " + notes.name(a) + " up to " + notes.name(b) + ": the same note, or an octave.") ^0
return
("From " + notes.name(a) + " up to " + notes.name(b) + ": " + nm + ", " + semitones(semis) + ".") ^0
theory.just_ratio(semis) => r
# With frequencies upper : lower = p : q, overtone q * k of the upper note
# and overtone p * k of the lower note are the same frequency, so every
# p-th overtone of the lower note is shared (the corpus case: 220 and
# 330 Hz, 3 : 2, share every third overtone of the lower tone).
("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
if r[0] >= 9:
"Overtones that far up are faint, so these two notes blend less than the simpler ratios do." ^0
def semitones(n):
if n == 1:
return "1 semitone"
return str(n) + " semitones"
def ordinal(n):
n % 10 => last
n % 100 => last2
if last == 1 and last2 != 11:
return str(n) + "st"
if last == 2 and last2 != 12:
return str(n) + "nd"
if last == 3 and last2 != 13:
return str(n) + "rd"
return str(n) + "th"
def transpose():
words(trim(input("chords, like C Am F G> "))) => ws
if len(ws) == 0:
"Cancelled." ^0
return
[] => parsed
for w in ws:
theory.chord_parsed(w) => c
if len(c) == 0:
("Not a chord: " + w + ".") ^0
return
parsed + [c] => parsed
while True:
trim(input("semitones up (-11 to 11)> ")) => answer
if answer == "":
"Cancelled." ^0
return
answer => digits
1 => sign
if len(answer) > 1 and answer[0] == "-":
answer[1:len(answer)] => digits
-1 => sign
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":
sign * int(digits) => n
if n >= -11 and n <= 11:
break
"Type a whole number from -11 to 11." ^0
"" => out
for c in parsed:
theory.preferred((notes.pitch(c[0]) + n + 12) % 12) => root
if out != "":
out + " " => out
out + notes.name(root) + c[1][0] => out
("Moved " + str(n) + " semitones: " + out) ^0
def table():
"Pitch classes, C = 0, each with its spellings:" ^0
for pc in [0:11]:
"" => s
for letter in [0:6]:
notes.spelled(letter, pc) => n
if len(n) > 0:
if s != "":
s + " = " => s
s + notes.name(n) => s
str(pc) => label
if len(label) == 1:
" " + label => label
(label + ": " + s) ^0
"== Scales and chords ==" ^0
"Notes are spelled: C# and Db sound the same but are written on different letters." ^0
True => running
while running:
"" ^0
"1) scales 2) chord 3) interval 4) transpose 5) note table 6) quit" ^0
trim(input("choice> ")) => choice
if choice == "1":
show_scale()
elif choice == "2":
show_chord()
elif choice == "3":
show_interval()
elif choice == "4":
transpose()
elif choice == "5":
table()
elif choice == "6":
False => running
else:
"Pick a number from 1 to 6." ^0
"Bye." ^0
Python projection (main.py)
import notes
import theory
def trim(s):
i = 0
j = len(s)
while i < j and s[i] == " ":
i = i + 1
while j > i and s[j - 1] == " ":
j = j - 1
return s[i:j]
def words(s):
out = []
word = ""
for c in s + " ":
if c == " " or c == ",":
if word != "":
out = out + [word]
word = ""
else:
word = word + c
return out
def ask_note(prompt):
while True:
answer = trim(input(prompt + "> "))
if answer == "":
return []
n = notes.parsed(answer)
if len(n) > 0:
return n
print("Type a note: a letter A to G, then # or b, up to two, like F#, Bb or C.")
def show_scale():
root = ask_note("root")
if len(root) == 0:
print("Cancelled.")
return
for kind in theory.scale_kinds():
sc = theory.scale(root, kind[1])
label = notes.name(root) + " " + kind[0]
while len(label) < 20:
label = label + " "
if len(sc) == 0:
print(label + "needs triple sharps or flats - try the same pitch spelled another way.")
else:
print(label + notes.names(sc))
def show_chord():
while True:
answer = trim(input("chord (like C, Am, F#m7, Bbmaj7, Cdim7)> "))
if answer == "":
print("Cancelled.")
return
c = theory.chord_parsed(answer)
if len(c) > 0:
break
print("Type a root note and one of: (nothing) m dim aug 7 maj7 m7 m7b5 dim7.")
tones = theory.chord(c[0], c[1][2])
if len(tones) == 0:
print(answer + " needs triple sharps or flats.")
return
roles = ["root", "third", "fifth", "seventh"]
s = ""
for k in range(0, len(tones)):
if s != "":
s = s + ", "
s = s + notes.name(tones[k]) + " (" + roles[k] + ")"
print(answer + " is " + notes.name(c[0]) + " " + c[1][1] + ": " + s + ".")
def show_interval():
a = ask_note("lower note")
if len(a) == 0:
print("Cancelled.")
return
b = ask_note("upper note")
if len(b) == 0:
print("Cancelled.")
return
iv = theory.interval(a, b)
nm = iv[0]
semis = iv[1]
if a[0] == b[0] and a[1] == b[1]:
print("From " + notes.name(a) + " up to " + notes.name(b) + ": the same note, or an octave.")
return
print("From " + notes.name(a) + " up to " + notes.name(b) + ": " + nm + ", " + semitones(semis) + ".")
r = theory.just_ratio(semis)
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.")
if r[0] >= 9:
print("Overtones that far up are faint, so these two notes blend less than the simpler ratios do.")
def semitones(n):
if n == 1:
return "1 semitone"
return str(n) + " semitones"
def ordinal(n):
last = n % 10
last2 = n % 100
if last == 1 and last2 != 11:
return str(n) + "st"
if last == 2 and last2 != 12:
return str(n) + "nd"
if last == 3 and last2 != 13:
return str(n) + "rd"
return str(n) + "th"
def transpose():
ws = words(trim(input("chords, like C Am F G> ")))
if len(ws) == 0:
print("Cancelled.")
return
parsed = []
for w in ws:
c = theory.chord_parsed(w)
if len(c) == 0:
print("Not a chord: " + w + ".")
return
parsed = parsed + [c]
while True:
answer = trim(input("semitones up (-11 to 11)> "))
if answer == "":
print("Cancelled.")
return
digits = answer
sign = 1
if len(answer) > 1 and answer[0] == "-":
digits = answer[1:len(answer)]
sign = -1
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)
if n >= -11 and n <= 11:
break
print("Type a whole number from -11 to 11.")
out = ""
for c in parsed:
root = theory.preferred((notes.pitch(c[0]) + n + 12) % 12)
if out != "":
out = out + " "
out = out + notes.name(root) + c[1][0]
print("Moved " + str(n) + " semitones: " + out)
def table():
print("Pitch classes, C = 0, each with its spellings:")
for pc in range(0, 12):
s = ""
for letter in range(0, 7):
n = notes.spelled(letter, pc)
if len(n) > 0:
if s != "":
s = s + " = "
s = s + notes.name(n)
label = str(pc)
if len(label) == 1:
label = " " + label
print(label + ": " + s)
print("== Scales and chords ==")
print("Notes are spelled: C# and Db sound the same but are written on different letters.")
running = True
while running:
print("")
print("1) scales 2) chord 3) interval 4) transpose 5) note table 6) quit")
choice = trim(input("choice> "))
if choice == "1":
show_scale()
elif choice == "2":
show_chord()
elif choice == "3":
show_interval()
elif choice == "4":
transpose()
elif choice == "5":
table()
elif choice == "6":
running = False
else:
print("Pick a number from 1 to 6.")
print("Bye.")
notes.eml
eml# P057 scales and chords - spelled notes. A note is [letter, accidental]:
# letter 0 to 6 for C D E F G A B, accidental -2 to 2 for double flat to
# double sharp. Its pitch class (0 to 11, C = 0) is where it sounds; two
# spellings of one pitch - C# and Db - are different notes on paper.
def naturals():
return [0, 2, 4, 5, 7, 9, 11]
def pitch(note):
return (naturals()[note[0]] + note[1] + 12) % 12
def name(note):
"CDEFGAB"[note[0]] => s
if note[1] > 0:
s + "#" * note[1] => s
elif note[1] < 0:
s + "b" * (0 - note[1]) => s
return s
def parsed(s):
# "C", "f#", "Bb", "Ebb", "G##" as a note, or [] when it is not one.
if s == "" or len(s) > 3:
return []
-1 => letter
for k in [0:6]:
if s[0] == "CDEFGAB"[k] or s[0] == "cdefgab"[k]:
k => letter
if letter == -1:
return []
0 => acc
for ch in s[1:len(s)]:
if ch == "#" and acc >= 0:
acc + 1 => acc
elif ch == "b" and acc <= 0:
acc - 1 => acc
else:
return []
return [letter, acc]
def spelled(letter, target):
# The note on this letter that sounds at pitch class target, or [] when
# it would need more than two sharps or flats.
(target - naturals()[letter] + 12) % 12 => d
if d > 6:
d - 12 => d
if d < -2 or d > 2:
return []
return [letter, d]
def built(root, steps, letter_steps):
# Notes from root, each the given semitones above the last and the given
# number of letters further on - so a seven-note scale uses every letter
# once and a chord stacks letters a third apart. Returns [] when some
# note cannot be written with at most two sharps or flats.
[root] => out
root[0] => letter
pitch(root) => p
for k in [0:len(steps) - 1]:
(letter + letter_steps[k]) % 7 => letter
(p + steps[k]) % 12 => p
spelled(letter, p) => n
if len(n) == 0:
return []
out + [n] => out
return out
def names(notes):
"" => s
for n in notes:
if s != "":
s + " " => s
s + name(n) => s
return s
Python projection (notes.py)
def naturals():
return [0, 2, 4, 5, 7, 9, 11]
def pitch(note):
return (naturals()[note[0]] + note[1] + 12) % 12
def name(note):
s = "CDEFGAB"[note[0]]
if note[1] > 0:
s = s + "#" * note[1]
elif note[1] < 0:
s = s + "b" * (0 - note[1])
return s
def parsed(s):
if s == "" or len(s) > 3:
return []
letter = -1
for k in range(0, 7):
if s[0] == "CDEFGAB"[k] or s[0] == "cdefgab"[k]:
letter = k
if letter == -1:
return []
acc = 0
for ch in s[1:len(s)]:
if ch == "#" and acc >= 0:
acc = acc + 1
elif ch == "b" and acc <= 0:
acc = acc - 1
else:
return []
return [letter, acc]
def spelled(letter, target):
d = (target - naturals()[letter] + 12) % 12
if d > 6:
d = d - 12
if d < -2 or d > 2:
return []
return [letter, d]
def built(root, steps, letter_steps):
out = [root]
letter = root[0]
p = pitch(root)
for k in range(0, len(steps)):
letter = (letter + letter_steps[k]) % 7
p = (p + steps[k]) % 12
n = spelled(letter, p)
if len(n) == 0:
return []
out = out + [n]
return out
def names(notes):
s = ""
for n in notes:
if s != "":
s = s + " "
s = s + name(n)
return s
theory.eml
eml# P057 scales and chords - scales, chords and intervals, spelled by letter.
import notes
def scale_kinds():
# [name, semitone steps between the seven notes]; every step moves one
# letter on.
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]]]
def chord_kinds():
# [suffix, name, semitones above the root]; each note is a third - two
# letters - above the one before.
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]]]
def scale(root, steps):
return notes.built(root, steps, [1, 1, 1, 1, 1, 1])
def chord(root, tones):
[] => steps
[] => letters
for k in [1:len(tones) - 1]:
steps + [tones[k] - tones[k - 1]] => steps
letters + [2] => letters
return notes.built(root, steps, letters)
def chord_parsed(s):
# A chord symbol - a note, then a suffix - as [root, kind], or [].
# The longest root that reads as a note wins, so "Bbm" is Bb minor.
for size in [3, 2, 1]:
if len(s) >= size:
notes.parsed(s[0:size]) => root
if len(root) > 0:
s[size:len(s)] => suffix
for kind in chord_kinds():
if kind[0] == suffix:
return [root, kind]
return []
def interval(a, b):
# The interval from note a up to note b, as [name, semitones spanned]:
# named from the letters it spans (third, fifth ...) and how far the
# semitones are from that size's usual count. C to E is a major third
# and C to Fb a diminished fourth, though both span 4 semitones; C up to
# B# is an augmented seventh, 12 semitones, and C up to Cb a diminished
# octave, 11.
(b[0] - a[0] + 7) % 7 + 1 => number
(notes.pitch(b) - notes.pitch(a) + 12) % 12 => semis
[0, 2, 4, 5, 7, 9, 11][number - 1] => reference
semis - reference => diff
if diff > 6:
diff - 12 => diff
if diff < -6:
diff + 12 => diff
reference + diff => span
["unison", "second", "third", "fourth", "fifth", "sixth", "seventh"][number - 1] => kind
if number == 1 and diff < 0:
"octave" => kind
12 + diff => span
if number == 1 or number == 4 or number == 5:
if diff == 0:
return ["perfect " + kind, span]
if diff == 1:
return ["augmented " + kind, span]
if diff == -1:
return ["diminished " + kind, span]
else:
if diff == 0:
return ["major " + kind, span]
if diff == -1:
return ["minor " + kind, span]
if diff == 1:
return ["augmented " + kind, span]
if diff == -2:
return ["diminished " + kind, span]
return [str(semis) + " semitones, with no plain name", semis]
def just_ratio(semis):
# The small whole-number frequency ratio each span of 0 to 12 semitones
# stands for in equal temperament, [upper, lower].
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]
def preferred(pc):
# The usual spelling of a pitch class on its own, for transposing chord
# roots: sharps for F# only, flats elsewhere.
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]
Python projection (theory.py)
import notes
def scale_kinds():
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]]]
def chord_kinds():
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]]]
def scale(root, steps):
return notes.built(root, steps, [1, 1, 1, 1, 1, 1])
def chord(root, tones):
steps = []
letters = []
for k in range(1, len(tones)):
steps = steps + [tones[k] - tones[k - 1]]
letters = letters + [2]
return notes.built(root, steps, letters)
def chord_parsed(s):
for size in [3, 2, 1]:
if len(s) >= size:
root = notes.parsed(s[0:size])
if len(root) > 0:
suffix = s[size:len(s)]
for kind in chord_kinds():
if kind[0] == suffix:
return [root, kind]
return []
def interval(a, b):
number = (b[0] - a[0] + 7) % 7 + 1
semis = (notes.pitch(b) - notes.pitch(a) + 12) % 12
reference = [0, 2, 4, 5, 7, 9, 11][number - 1]
diff = semis - reference
if diff > 6:
diff = diff - 12
if diff < -6:
diff = diff + 12
span = reference + diff
kind = ["unison", "second", "third", "fourth", "fifth", "sixth", "seventh"][number - 1]
if number == 1 and diff < 0:
kind = "octave"
span = 12 + diff
if number == 1 or number == 4 or number == 5:
if diff == 0:
return ["perfect " + kind, span]
if diff == 1:
return ["augmented " + kind, span]
if diff == -1:
return ["diminished " + kind, span]
else:
if diff == 0:
return ["major " + kind, span]
if diff == -1:
return ["minor " + kind, span]
if diff == 1:
return ["augmented " + kind, span]
if diff == -2:
return ["diminished " + kind, span]
return [str(semis) + " semitones, with no plain name", semis]
def just_ratio(semis):
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]
def preferred(pc):
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]