Strange offset behaviour when plotting some .kr uGens

And as a more general point - control rate saves a little bit of CPU because control rate signals have a lower sample rate. If your audio sample rate is S and your blocksize is 64, then the sample rate for control signals will be S\64.

The way this is handled in SuperCollider depends on the UGen. In the control rate UGen itself, it will generate a new sample at the beginning of every audio block (which by default is 64 samples long). This sample will remain the same for the entirety of that block.

When an audio rate UGen receives a control rate sample on one of its inputs it will convert it to an audio rate signal. To do this it will do one of the following:

For continuous signals (e.g. freq on a SinOsc)

  • If the new sample (which remember lasts for a whole block) is the same as the last sample it wonā€™t do anything.
  • If the new sample is different, then it will create a linear ramp over the length of the audio block where at the start of the block the sample value is the last value, and at the end of the block itā€™s the new value. This is called interpolation.

Iā€™m not sure what happens when you feed a control rate into an audio rate Ugen that takes a gate (I know what I think should happen, but that may not be the same thing of course).

Important Note - No such conversion happens when you feed a control rate signal into a control rate UGen (e.g. if you feed the Impulse.kr into an EnvGen.kr).

So in the case of Impulse.kr(0) - this will generate a 1 at the beginning of the first control block. Youā€™ll get:

  1. Block One - 64 samples of 1
  2. Block Two - 64 samples of 0.

If you feed this into an EnvGen.kr you will get the expected result. The envelope will start at sample 1. Everything will work the way that you expect it to.

There are situations where you need values to be sample accurate, rather than block rate (FM synthesis is a good example). In these cases you should use audio rate signals, and avoid issues with conversion from control rate signals.

All of this incidentally has nothing to do with SuperCollider, itā€™s just inherent to digital audio. SuperCollider handles the relationship between control rates and audio rate about as well as it can be handled (given that control rate signals have literally 1/64th of the information that an audio rate signal does). The reason that control rate signals use less CPU is because they contain less information (the CPU is generating 1 sample every 64 samples, rather than 64). Youā€™re making a tradeoff between accuracy and CPU power. Most of the time that tradeoff makes sense because you canā€™t hear it - when you can hear it, you should use audio rate signals instead.

youā€™re looking at a converted signal and there is a loss of accuracy with the conversion

Comparing the control rate plot with an audio rate one, I canā€™t see any loss of accuracy, only an offset.

If the new sample is different, then it will create a linear ramp over the length of the audio block where at the start of the block the sample value is the last value, and at the end of the block itā€™s the new value. This is called interpolation.

Yes, this is the issue. Letā€™s say there are blocks in the following ranges: audio samples 0-63, 64-127, etc. The 0-63 seems to interpolate towards the first control sample, meaning it has nothing to interpolate from. Of course this is measurably not the case, so there is some form of ā€˜control sample 0ā€™ at play. The only issues are that the initialisation of uGens is unpredictable, and that this is not shown when plotting pure kr output.

If you convert the control signal to audio there is no guarantee that it will remain aligned to the block.

Is this to do with initial offset, or some kind of progressive loss of accuracy?

your solution gives sample accuracy when you convert THIS control signal to audio.

By ā€˜my solutionā€™, I just mean using that general process to find the right combination of supplement uGens and then use them. So for any control rate signal, the supplementary +(x*Trig1.kr(1,0))+(y*Impulse.kr(0)) would have carefully calculated values for x and y.

post a real example of a ugen where youā€™re worried about this

Iā€™m not sure if this is what youā€™re asking for, but hereā€™s an example of why I needed to figure out what was going on. The example is simplified and missing a lot of context.
The LFSaws trigger triggers, which in turn trigger an envelope to make a click sound. I only want the click when the saw goes from being a non-positive number to a positive number, but the envelope is triggered by control sample 0. Using Impulse to negate the first trigger doesnā€™t work as Impulse happens on control sample 1, so there is an audible hiss at the beginning.

    var saw1 = LFSaw.kr(4, -0.4);
    var saw2 = LFSaw.kr(4, 0.9);
    var trig1 = Trig1.kr(saw1, 0);
    var trig2 = Trig1.kr(saw2, 0);
    var impulse = Impulse.kr(0) * 10;
    var trigSum = trig1 + trig2 - impulse;
    var env = EnvGen.kr(Env([0, 0.1, 0], [0.01, 0.04]), trigSum);
    var out = WhiteNoise.ar(env);
    Out.ar(0, [out, out])
}.play

So, to sum up, Iā€™ve got a solution to the problems I was having.
It would be nice if control rate .plot would include what I call ā€˜control sample 0ā€™, and if control rate uGens would initialise slightly differently. I think it would make things cleaner.

First things first - unless Iā€™m very mistaken, plot doesnā€™t show you a control rate directly. Iā€™m fairly certain that it converts a control rate to an audio rate, and then plots it. There will ALWAYS be a change in the signal when you convert a control rate to audio rate. So your assumptions about what is happening with the control rate is based upon a fault reading.

That said - there is no offset with Impulse. Look:

({
  Impulse.kr(0, 1);
}.plot)

({
  Impulse.ar(0, 1);
}.plot)

However, impulse.kr extends over a longer time period because kr has a sample rate of 689 samples per second. What it would look like if you could plot control rate signals (and there is a way, itā€™s just tricky for this particular example and I donā€™t have time) is a 1 for for 0.001 seconds, followed by a 0. However here we see it as a triangle because itā€™s being converted to audio (and SuperCollider interpolates signals, which is what we want it to do).

I donā€™t think your example is doing what you think it is. However, there may be a bug with EnvGen (unrelated to what you think youā€™re looking at it here) which Iā€™m going to investigate.

Using Trig1.kr(1,0) instead of Impulse.kr(0) solves the EnvGen problem as predicted though.

Your plot examples display the same output for me, except with .kr being 1/64th of the gradient of course. But wrapped in K2A.ar, the .kr example no longer matches. Does that match your expectations?

Interesting, SinOsc uses a different interpolation method to K2A:

{
    var latchK = Latch.kr(1, Impulse.kr(0));
    var latchA = K2A.ar(latchK);
    var sinK = SinOsc.ar(latchK * 400);
    var sinA = SinOsc.ar(latchA * 440);
    [sinK, sinA]
}.plot;

EDIT: Sorry, scratch that, SinOsc uses no interpolationā€¦

{
    var freqK = LFPulse.kr(s.sampleRate / s.options.blockSize / 4) * 1000 + 5;
    var freqA = K2A.ar(freqK);
    var sinK = SinOsc.ar(freqK);
    var sinA = SinOsc.ar(freqA);
    [sinK, sinA]
}.plot

Using Trig1.kr(1,0) instead of Impulse.kr(0) solves the EnvGen problem as predicted though.

Iā€™m kind of confused as to what you think the EnvGen problem is. The issue Iā€™m talking about is that for reasons that Iā€™m pretty sure donā€™t have anything to do with Impulse/Trig (unless thereā€™s some weird denormal thing that Iā€™m missing), just mean that you can get EnvGen to behave incorrectly. Your example seems to trigger it, Iā€™m not sure why. EnvGen is frankly a mess, so Iā€™m not that surprised.

Plot examples are not to be relied upon when using control signals. Youā€™re literally not seeing what SuperCollider sees. Not sure how else to explain this.

On interpolation. Your example has nothing to do with interpolation. This is to do with the difference between control rate and audio rate. The period between samples for control rate is 0.01 seconds. Look at the X axis of the graph. If you require sample rate accuracy for onsets then you need to use audio rate. Typically at low frequencies it doesnā€™t really matter (as you canā€™t hear it).

If you still donā€™t get what Iā€™m talking about then I suggest finding a book on the basics of Digital Signal Processing and reading about this.

What would you expect EnvGenā€™s output to look like?

I get everything youā€™re saying, apart from why you keep trying to explain basic interpolation and plot output. Itā€™s clear you think Iā€™m missing something there but Iā€™m pretty sure Iā€™m not.

The reason that I think youā€™re missing something is that the example above that you think is to do with interpolation has nothing to do with interpolation. Itā€™s to do with sample rate - you seem to expect that you should get exactly the same results with control rate and audio rate - and thatā€™s simply not realistic.

The reason I keep going back to plot output is because you keep citing plot output of control signals as if itā€™s meaningful, as Iā€™m trying to explain to you it rarely is.

Okay Iā€™ve actually managed to create an example of the problem. Impulse.kr has what I think is a bug. It seems that impulse.kr(0) generates [0,1,0,0,0,0] which I donā€™t think is right:

(
{
  var sig1 = EnvGen.kr(Env.perc(0.01, 0.04), 1) * SinOsc.ar(440);
  var sig2 = EnvGen.kr(Env.perc(0.01, 0.04), Impulse.kr(0)) * SinOsc.ar(440);
  [sig1,sig2]
}.plot)

Actually the above is wrong - but it doesnā€™t matter. There is a bug here and I think I know how to handle it.