I'm not 100% sure what your goals are since I see a conflict between using the tone net, which just goes off in all directions with more and more sharps/flats, and your goal of limiting/manipulating the number of sharps and flats. In conventional music the key signatures are (usually) selected so as to avoid double-flats/sharps and the use of chromatic notes that are enharmonic with natural notes (e.g. E# or F♭) so I'd just setup a rule-base to replace those keys with their enharmonic equivalents.
However, since you're using the tone net, I have to assume that you want to do something that is capable of representing
some aspects of the tone net without just remapping enharmonic notes.
Maybe you need something where, the note F occurs as the 3rd of the key of D♭
major and the note E# appears as the 3rd of the key of C# major.
Since I can't tell how far away from the selected root you are going to get, I'm going to start with some general considerations, and then narrow it down to a specific set of requirements, that are hopefully relevant.
General
First, explicitly represent intervals as (major-3rd, perfect-5th)
pairs, i.e. a major 3rd is (1,0)
a perfect fourth is (0,-1)
, minor third is (-1,1)
etc. These directly represent the motions in the tone lattice. If I were concerned about octaves, I'd append them as a third field to the tuple.
Second, I would define a function that takes a root-note, and an interval, and returns the note name. This can be achieved with a restricted lookup table:
A+(1,0)=C#
, A+(-1,0)=F
, A+(0,1)=E
, A+(0,-1)=D
B+(1,0)=D#
,...
- same for C
- ...
Note that from the table for the natural notes we can get the results for chromatic notes, D#+(1,0)=[D+(1,0)]#=F##
. Note that a sharp-flat combination cancels: D♭+(1,0)=[D+(1,0)]♭=[F#]♭=F.
This function is important in translating from the (M3,P5)
representation in the model into the names displayed in the presentation layer. Also note movement in the lattice is associative (and commutative) so that A+(-1,2)=A+(-1,0)+(0,1)+(0,1)=[[F]+(0,1)]+(0,1)=C+(0,1)=G
so you only need to pre-compute the table for the shifts (1,0),(-1,0),(0,1),(0,-1)
and can compute the rest with appropriate logic. So far, this is just the mechanics to produce the infinite tone net, i.e.
just doing this will, for large intervals, produce notes with many sharps/flats.
To fold the tone-net back in on itself, I'd use to pair representation of intervals, and try to reduce those. First off, I'd consider the octave reductions:(0,+/-12)=>(0,0)
or (+/-3,0)=>(0,0)
, and set up logic to see if a given interval can be "simplified" by applying these octave reductions. One would need to define "simplfied" -- off the top of my head, I'm not sure if the Euclidean (sum-of-squares) or Manhattan (sum-of-absolute-values) (or something else?) metric is more useful. Basically, one would have a list of reduction rules, and then you'd need to see if applying the reduction rules makes the interval smaller (more simple).
If these octave equivalent's don't do everything you need, I'd look into other intervals that can produce notes that can be enharmonically reduced. For example, the diagram in your question has a yellow D♭ a fifth above a F# -- a note that would usually be written C#. This can be reproduced by a rule that allows for the substitution (0,7)=>(-1,-2)
.
Specific
We can see how this general approach would apply by considering the differently colored areas in your figure in your question. Rooted at C this gives:
A E B F# (+1,-1) (+1, 0) (+1,+1) (+1,+2)
F C G D ( 0,-1) ( 0, 0) ( 0,+1) ( 0,+2)
D♭ A♭ E♭ B♭ (-1,-1) (-1, 0) (-1,-1) (-1,+2)
so the idea is that we're saying "When rooted at C use these notes, when rooted somewhere else use the notes in the same relative positions as above".
Then you can look at the set of intervals that this set of notes corresponds to (right hand side above) and use this as a template for generating the note names via the logic outlined above when the template is rooted at a different note.
To limit consideration to this block we need two reduction rules: (0,4)=>(1,0)
, identify the C-G-D-A-E E with the E a major third above C, and (3,0)=>(0,0)
i.e. a chain of 3 major thirds spans an octave (more accurately, it's instead of going up two M3rds, go down 1 M3rd). Then given a pair (M3,P5)
we can use the following logic:
<pre>
while P5>3 : P5-=4, M3+=1
while P5<-1 : P5+=4, M3-=1
while M3>1 : M3-=3
while M3<-1 : M3+=3
</pre>
once we've used this to reduce the interval, we can then use the note-name logic to compute the note name given the root note that we have chosen.
Finally, this particular block of notes around a given root has a special significance in that it exactly describes quarter comma meantone tuning, and thus is particularly relevant for describing common practice music, which was primarily performed in tunings that descended from quarter comma meantone.