DegreeToKey inconsistently produces inf

Hi all,

When I run the following two bits of code, one after the other, about 5-10% of the time, the frequency calculated by DegreeToKey is inf instead of the expected value. With slightly different versions, I’ve also gotten nan or really large numbers like 3.68935e+19. In these cases, the bad value is also produced for any negative index that corresponds to an octave below the root (e.g. index = -10, -15, etc). The problem does not occur for positive index values.

What’s most disturbing about this problem is the inconsistency. If I re-evaluate these two chunks (allocating a new buffer and overwriting the language reference), the code will usually work fine — but not always.

This vaguely feels like a rounding/precision error to me, but it’s just a guess. I also noticed that if I load the buffer and a bad value is produced, this behavior is consistent so long as I continue using the most recently-allocated buffer. If I load the buffer and the sound behaves normally, this is also consistent as long as I don’t mess with the buffer. So it seems like the rounding error (if that is what’s happening) occurs during the buffer writing process.

SinOsc doesn’t seem bothered by wacky freq values, so I think the following code is pretty safe, but as you can imagine, it blows the lid off lots of other UGens.

Can anyone reproduce and/or explain this behavior? Is DegreeToKey not meant to receive negative indices? I’m on SC 3.11.2, macOS 10.14.6.

Eli

s.boot;

~scale = Buffer.loadCollection(s, [0,3,5,7,10]);

(
{
	var sig, freq;
	freq = DegreeToKey.kr(~scale, -5, add:84).midicps;
	freq.poll;
	sig = SinOsc.ar(freq, mul:0.25!2);
}.play;
)

I just tried your code a whole bunch of times, and never got the behaviour you describe, also trying many different values above and below zero for DegreeToKey's in argument. I’m also running 3.11.2, but on Windows 10 (version 21H1). I even tried it with an empty collection for the scale (Buffer of size 0), thinking that might make it unhappy, but didn’t get any bad sound or nan/inf values.

Are you on macOS or Linux?

Glen.

SC 3.11.2, macOS 10.14.6.

Oh yeah, as you said… :wink:

Also – just wanted to upload a quick video of this issue.

The source code shows divisions by tableSize – if, somehow, this were incorrectly 0, that would explain bad values. But I can’t explain how a 5-element buffer would have size 0.

hjh

I couldn’t reproduce this on linux in my local build of some more or less recent version of 3.11.2.

Would this be a useful experiment?

(
~scale = Buffer.loadCollection(s, [0,3,5,7,10], action: {{
	var sig,
	freq = DegreeToKey.kr(~scale, -5, add:84);
	freq.poll;
	sig = SinOsc.ar(freq, mul:0.25!2);
}.play});
)

to rule out the possibility that the loadCollection somehow didn’t finish before the synth is built.

Oops I didn’t really think this through. I guess the first time ~scale will not be assigned yet when the action is run.

An alternative experiment would involve using s.sync between the loadCollection and the synth building.
(the chance of loadCollection not finishing for such a small collection probably is very small anway)

I can reproduce this (MacOS 10.14.6, SC 3.11.2). I’ve tried various reproducers and as said, it doesn’t happen consistently. The code below doesn’t always fail, but when it does, it does so consistently as long as the loop continues, which probably means it’s not related to either SynthDef building, or collection loading/buffer writing, but rather the allocation of the buffer. But it seems strange, and I can’t decipher what’s going on in the DegreeToKey source code.

(
SynthDef(\dtk, {
	var sig, freq;
	freq = DegreeToKey.kr(\scale.kr, -5, add:84).midicps;
	freq.poll;
	sig = SinOsc.ar(freq, mul:0.25!2);
}).add;
)

(
//Try to evaluate this a few times, with Cmd-Period in between
var scale = Buffer.alloc(s, 5);
{		
	loop {
		var x;
		scale.loadCollection([0,3,5,7,10]);
		(s.latency + 0.1).wait;
		x = Synth(\dtk, [\scale, scale]);
		0.5.wait;
		x.free;
		"-----".postln;
    }
}.fork
)
diff --git a/server/plugins/OscUGens.cpp b/server/plugins/OscUGens.cpp
index 58fd90897..6f2ff6343 100644
--- a/server/plugins/OscUGens.cpp
+++ b/server/plugins/OscUGens.cpp
@@ -399,7 +399,7 @@ void DegreeToKey_next_1(DegreeToKey* unit, int inNumSamples) {
         val = unit->mPrevKey;
     } else if (index < 0) {
         unit->mPrevIndex = index;
-        key = tableSize + index % tableSize;
+        key = (tableSize + index % tableSize) % tableSize;
         oct = (index + 1) / tableSize - 1;
         val = unit->mPrevKey = table[key] + octave * oct;
     } else if (index > maxindex) {
@@ -431,7 +431,7 @@ void DegreeToKey_next_k(DegreeToKey* unit, int inNumSamples) {
         val = unit->mPrevKey;
     } else if (index < 0) {
         unit->mPrevIndex = index;
-        key = tableSize + index % tableSize;
+        key = (tableSize + index % tableSize) % tableSize;
         oct = (index + 1) / tableSize - 1;
         val = unit->mPrevKey = table[key] + octave * oct;
     } else if (index > maxindex) {
@@ -465,7 +465,7 @@ void DegreeToKey_next_a(DegreeToKey* unit, int inNumSamples) {
         inNumSamples, int32 index = (int32)floor(ZXP(in));
         if (index == previndex) { ZXP(out) = prevkey; } else if (index < 0) {
             previndex = index;
-            key = tableSize + index % tableSize;
+            key = (tableSize + index % tableSize) % tableSize;
             oct = (index + 1) / tableSize - 1;
             ZXP(out) = prevkey = table[key] + octave * oct;
         } else if (index > maxindex) {

@nathan is this a commit that was made before or after my original post? Is it a cause or solution to the issue?

It’s a patch that should fix the issue. The mistake is that tableSize + index % tableSize is an incorrect formula for the modulo operator on negative numbers. When index is a negative multiple of tableSize, it returns tableSize, which is an out of bounds index for the table array.

Or perhaps key = sc_mod(index, tableSize); … from include/plugin_interface/SC_InlineBinaryOp.h.

hjh