This addresses how to get the note name for the root. The key think is to think about the problem in terms of the circle of fifths.
Setup an array of note names like this:
F C G D A E B
0 1 2 3 4 5 6
Note that the intervals for the movement of the root are given by
P5=+1 P4=-1
M2=+2 m7=-2
M6=+3 m3=-3
M3=+4 m6=-6
aug4=+6 dim5=-6
note that, for example, iii means root movement by a major third (M3 above), whereas it is ♭iii (or ♭III) to indicate movement by a minor third.
And the notes with sharps/flats live in the "periodic extension" of your table of note names
[... ♭♭'s] F♭ Cb G♭ D♭ A♭ E♭ B♭ | F C G D A E B | F♯ C♯ G♯ D♯ A♯ E♯ B♯ [♯♯'s ...]
-7 -6 -5 -4 -3 -2 -1 | 0 1 2 3 4 5 6 | 7 8 9 10 11 12 13
so you can map intervallic movements from a given starting point into movements along this spine of fifths (this answer covers a similar idea for naming notes on the tonnetz lattice)
The steps involved in deriving the name of the root would be:
- Get the index of your starting note.
To go from a note name to an index, you start with the note letter, this gives you a starting index in the range [0-6]; then for each sharp, add 7; for each flat, subtract 7.
Add in whichever intervallic shifts you require, as index shifts
Derive the resulting note name
The letter can be derived by looking at the resulting-index mod 7; the number of accidentals is determined by (resulting-index-resulting-index%7)/7
, i.e. how many blocks to the left or right of the central block you ended up in. Then you need to tack on that many sharps, if positive, or flats, if negative.
In python we'd have (as unoptimized code)
def notename2index(name):
lookup={'F':0, 'C':1, 'G':2, 'D':3, 'A':4, 'E':5, 'B':6}
i = lookup[name[0]] # get the starting index by looking up the note letter
if name[-1] == '#':
# shift 7 steps to the right for each # sign
i = i+7*(len(name)-1) # first character is note letter
if name[-1] == 'b':
# shift 7 steps to the left for each b sign
i = i-7*(len(name)-1)
return i
def index2notename(i):
(block, phase)=divmod(i,7)
name='FCGDAEB'[phase] # pick out the note name
if block < 0:
# add flats if we're to the left of the 'central' block
name = name+'b'*(-block)
elif block > 0:
# add sharps if we are to the right
name = name+'#'*block
else:
pass # name is just the letter
return name