# keyToDegree anomaly?

Hi all,

I’m as much fascinated by scales as they confuse me. Now, I have this little piece of code, of which I hoped I would map a range of continuous values to degrees in a scale, i.e. the values passed in mapped and rounded to the next degree, creating a sequence of steps.

``````(
(0, 0.0001..1).collect { |i|
var key = i.linexp(0, 1, 20, 20000).cpsmidi.post;
": ".post;
key.keyToDegree(Scale.major, 12).postln
}.plot
)
``````

Possibly, I’m really not understanding how `keyToDegree`'s supposed to work but I really can’t make head nor tail of the result: For most input values I get one float as output (no steps, not what I hoped). E.g.:

``````// input: output
130.90256153388: 75.95128076694
130.91452047502: 75.957260237511
130.92647941616: 75.963239708082
130.93843835731: 75.969219178653
130.95039729845: 75.975198649224
130.96235623959: 75.981178119794
130.97431518073: 75.987157590365
130.98627412187: 75.993137060936
130.99823306301: 75.999116531507
``````

… but then, suddenly, I have range of different input values, that equally return integer 76:

``````131.01019200416: 76
131.0221509453: 76
131.03410988644: 76
131.04606882758: 76
131.05802776872: 76
131.06998670986: 76
131.081945651: 76
131.09390459215: 76
131.10586353329: 76
131.11782247443: 76
131.12978141557: 76
131.14174035671: 76
131.15369929785: 76
131.165658239: 76
``````

Is that normal? Intended?

Thanks, Stefan

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;
``````
1 Like

Yeah, it’s a bug, from back in the days when “code review” meant “I haven’t tested it thoroughly but it looks OK, so let’s merge” – it looks OK if `indexInBetween` wraps around at the top, but it doesn’t.

It might be better to add a wrapping index-in-between method, though. The `++ 12` does create some load on the garbage collector.

I’ll file an issue.

hjh

2 Likes

Hi @elgiano , thanks for your reply and sorry for my delayed reaction. Also thanks very much for enlightening on the meaning of `keyToDegree` resp. `degreeToKey` - clearing up my confusion on scales quite a bit. As I just had a look again - documentation on `degreeToKey` (in `SimpleNumber`) is pretty verbose, yet your explanation makes musical sense to me.
The example you gave is useful. Thanks! Yes, I think it’s pretty much what I’m looking for. Let’s see how far it gets me…

1 Like