I made this version after I’ve updated the algorithm to prevent moving the B♮ and F♯ from 1100 and 602 cents respectively. This avoids some of the strange jumps between chords. The solution is a bit crude, but I think I can improve on it. I may need to pay attention to some of the other notes, even though those two are the most common ones in the chorale. Next up is the D♮ at 216 cents.
I added some code to list out the cents and ratios of each chord as it passes by. Here are the first few chords in the chorale, and then two from later. The first four numbers are the cent values of the chords. 1100 cents is a B♮, 602 is an F♯, 216 is a D♮, and 1100 is another B♮. The next set are the intervals in the chord stated in ratios and cents. The algorithm favors low integer numbered ratios, and is willing to go pretty far away from 12 TET to find them. For example the second to the last one includes an 8/7, which is far from 12 TET.
([1100, 602, 216, 1100]) [('1/1', 0), ('4/3', 498), ('5/4', 386), ('5/3', 884)]
([1100, 714, 398, 398]) [('3/2', 702), ('5/4', 386), ('6/5', 316), ('1/1', 0)]
([1100, 714, 398, 602]) [('4/3', 498), ('5/4', 386), ('6/5', 316), ('9/8', 204)]
([1100, 216, 1100, 714]) [('5/4', 386), ('5/3', 884), ('5/3', 884), ('5/4', 386)]
...
([699, 930, 348, 117]) [('7/5', 582), ('8/7', 231), ('7/5', 582), ('8/7', 231)]
([602, 918, 420, 216]) [('5/4', 386), ('6/5', 316), ('4/3', 498), ('9/8', 204)]
The goal of this exercise was to create some code that could automatically turn any 12 TET chorale, real or synthetic, into a just intonation chorale, all by itself. The program reads in the MIDI file and searches for a justly tuned chord that will minimize the size of the ratios in the intervals between notes in a chord, while staying reasonably close to the 12 TET note. It tries to minimize those two values, distance and ratio numerator / denominator size, to come up with cent values for the intervals in each chord. I then check to make sure I haven’t moved either of the critical notes, B♮ and F♯.
A lot of the assumptions I made about adaptive tuning were terribly naive. It’s hard. I thought all I had to do was find a chord that contained only low integer ratios by taking each interval of the chord individually. But that ended up with sub-optimal chords, since each interval competed for what the cent value of a given note should be. I ended up re-voicing those that were the most difficult, running some chords through the algorithm to tune the notes in the chord, then re-voicing them back to the way they were. The numpy function roll() provided a means to transform the chords until they found a good tuning. It takes about three and a half minutes to do a 177-chord chorale. I think I can improve that if I remove some of the debugging code.