Hi!
Here i try to explain keyToDegree, hope it helps. It looks like there is indeed an anomaly with that 76, which I write about below.
‘key’ and ‘degree’
in keyToDegree
:
-
key is a number of semitones from the root of the scale (not a midi note).
-
Degree is the corresponding index in the degrees array (a degree number, like a major third (5 semitones) is the third degree of the major scale, or degree number 2).
Scale.major.degrees
// -> [ 0, 2, 4, 5, 7, 9, 11 ]
0.keyToDegree(Scale.major) // 0.0
5.keyToDegree(Scale.major) // 3.0 (5 semitones = fourth degree = degree number 3)
-5.keyToDegree(Scale.major) // -3.0 ?? third-to-last degree in the array
// see Scale.major.degrees.wrapAt(-3) = 7 (7 semitones, the fifth)
So, by looking in the degrees
array you get the number of semitones, and that’s what degreeToKey
does.
// Scale.major.degrees[degreeNumber] = numberOfSemitones
degreeNumber.degreeToKey(scale) = numberOfSemitones
keyToDegree
does the inverse: given how many semitones from the root, it tells you which degree of the scale it is.
// Scale.major.degrees.indexInBetween(numberOfSemitones) = degreeNumber
numberOfSemitones.keyToDegree(scale) = degreeNumber
Now, like .indexInBetween
, it also gives you the “fractional” index: that is, if you ask for an interval that is not in the scale, it will tell you how far it is from the closest degree:
// a tritone is halfway between the fourth and the fifth
6.keyToDegree(Scale.major) // -> 3.5
By the way, if you want to round to the closest degree, you can always do that:
6.keyToDegree(Scale.major).round // -> 4: rounds up to the fifth in this case
the anomaly
That fixed 76 looked a bit suspicious… so i looked a bit into it. Long story short:
it’s a problem with the last degree of the scale. Your 131
breaks down to 10 octaves and 11 semitones. Since the degrees
array doesn’t contain the octave (in this case degree number 7 = 12 semitones), the approximation doesn’t work for anything between the last degree and the octave.
It’s the same if you try with 11:
11.3.keyToDegree(Scale.major) // 6
11.5.keyToDegree(Scale.major) // 6
11.999.keyToDegree(Scale.major) // 6
So I think this should be fixed in SuperCollider. This works of course:
(Scale.major.degrees ++ 12).indexInBetween(11.9) // 6.9
And since the documentation mentions that the scale is considered octave-repeating, I wouldn’t see any harm in doing that when calculating keyToDegree
.
extra: autotune freq
I have a feeling you wanted to, given a frequency, get the midinote of a tuned note in a scale. Here is a way:
var freq = 610;
var rootMidi = 60;
var scale = Scale.major;
var midiInterval = (freq / rootMidi.midicps).ratiomidi;
var closestDegree = midiInterval.keyToDegree(scale).round;
var tunedInterval = closestDegree.degreeToKey(scale);
var autotunedMidi = rootMidi + tunedInterval;
var autotunedFreq = autotunedMidi.midicps;
"% Hz -> tuned to midi % (% Hz)".format(freq, autotunedMidi, autotunedFreq).postln;