What could be wrong with the "range" method?

Dear community,

I often use the “range” method in contexts like this:

0.5.range(7, 80) ;

Today, it doesn’t work anymore. I get an error message saying that .range doesn’t exist as a float method : ERROR: Message ‘range’ not understood. I don’t see why this sudden change. Could it be that this method has been defined in an extension that I’ve accidentally uninstalled (I’ve uninstalled some that I never used, but I can’t remember which ones?

Thanks for your help, I’ve got a lot of programs that use it (and it worked !?) and I’d rather not have to rewrite everything…

I tried it, and got the same error. I don’t have many Quarks installed, and searching range in the Help Browser shows that it shouldn’t work with Floats.

So I guess it is the other way around: you used to have an extension that allowed to .range numbers, which you uninstalled? Something Math related?

In case you’d have to rewrite stuff, I’d advocate switching to:
0.5.linlin(0, 1, 7, 80);
(or linexp, explin, lincurve, etc)

I don’t know where Float:range comes from… but you can add it yourself by making a class file with the following

+Float {
   range { |min, max| ^this.linlin(0, 1, min, max) }
}

You’ll have to include this class file manually.

Thank you very much for your quick reply, Dindoleon et jordan.
I’ve never modified any of the existing classes. Is there a way to add to Float this new method without modifying the Float.sc file? My idea is to keep modifications of this type in a specific place that doesn’t depend on the installation of a new version of supercoliider. Do I need to create a personal quark?

No, just add a file with the extension .sc to the Extension folder in the Open user support directory, accessible from the File menu.

The + before the Class means you are adding a new method. When adding new methods, the class source code does not need to be in the same place as the new file.

Be careful about overriding methods; they require a bit more care. But you don’t need to worry about that now.

+Float {
   // NEW METHODS//
}

It works ! Thank you :pray:

1 Like

This operation isn’t fully defined, is it? I’m assuming that the intent is to calculate the position between 7 and 80 corresponding to 0.5’s position in some other range, but… which input range?

“Obviously” it’s 0.5 within 0 to 1, with the result 43.5… except it could just as obviously be 0.5 within -1 to 1, giving you 61.75. Both 0 to 1 and -1 to 1 are normalized ranges, and there’s nothing in the code to distinguish between them or any other range.

So it’s kinda logical, then, that aFloat.range throws an error – because it doesn’t have all the information needed to do the job. linlin explicitly specifies both ranges, so it’s a better choice here. (Ok, you might stipulate 0 to 1, as ControlSpec does, and explicitly document that.)

.range works on UGens because most UGens are tagged \unipolar (such as Impulse or LFPulse IIRC) or \bipolar (LFSaw). But LFSaw.kr(0.1) + 1 no longer has a range that can be described as unipolar normal or bipolar normal, so .range won’t work on that addition result.

IOW .range is a convenience method that works in some cases, but it isn’t generally applicable to all cases (linlin is the general method).

hjh

I’ve used range with UGens in the SD all my life, but never to do something in the lang. I don’t know why I have never considered that maybe because it is undefined. And that’s it.

EDIT: sometimes, it would be good to remember that we have different languages in sclang. Patterns and SynthDefs are such cases.

An interesting idea, btw, might be to make “range” a property of a UGen instance, and then let math-op UGens try to track the range – + and - are easy, * and / would have to do low = min(a.low * b.low, a.low * b.high) and so on. So then you could have:

  • LFTri.kr(rate): low -1, high +1
  • * 0.5: low -0.5, high 0.5
  • + 5: low -4.5, high 5.5

… and then range/exprange could be supported accurately for many math-op cases (though I’m sure there’s some case where it would break – e.g. take care with the trigonometry ops, atan2’s output can never exceed ±pi for instance, though it may be smaller depending on the inputs).

hjh

hi

This reminds me of some other discussions here about micro-optimizations.
The UGen type would be the requirement, precisely as other existing optimizations.

Again, looking for safe ones: constants, deterministic UGens, stateless UGens, etc):

“Pure” signals or constants:

LFTri.kr * 0.5                 // MulAdd with output range [-0.5,0.5]
LFTri.kr.range(0,1)           //  MulAdd output range [0,1]
SinOsc.ar * 0.1 + 0.5         // MulAdd   range [0.4,0.6]

SinOsc.ar.range(0, 1).range(2, 3)   // -> MulAdd (could be merged)

// Could skip  MulAdd  since range is  [0,1]:
DC.kr(1).range(0,1)

Without going to far off topic.

If I remember correctly…
These specific optimisations could be done, but in general, optimising large arithmetic structures isn’t trivial. The issue is that the rate of the argument (kr, ar, tr, demand, scalar, or even DC which is halfway between kr/ar and scalar) influences the result’s rate and isn’t commutative. a * b * c can have a different rate to c * b * a. If the values are stored in variables and used multiple times (a non-linear type system), which they often are, then this becomes even more complex.

Thank you for your contributions to this thread, it’s always very rewarding to read you.

My use of range in sclang is most often like this:

MIDIdef.cc(\wagner_n52, {|val| var ctlin; 
	ctlin = val.linlin(0, 127, 0, 1);
	x.set(\amp, ctlin, \freqTrig, ctlin.range(10, 20))
	//etc...
}, ccNum: 10);

It allows you to write a little more concisely than with .linlin. In the method that worked (before it stopped working…), the range was always 0 to 1.
By adding the code proposed by jordan to the extensions, I won’t need to correct everything, it’s great. (So that 0 and 1 are also interpreted as floats in the range, I’ve also added it to Integer.)

And I’m going to be even more concise with:

+Float{
    mdr { |min, max, inMin = 0, inMax = 127|
        ^this.linlin(inMin, inMax, min, max)
    }
}

:wink:

This is a bit strange because if you are composing functions (same type, in case the signal is [-1,+1], but could be something else, DC Signal, Any Stream, etc), you already know they can only compose because of the type. It would not work otherwise. Even if the same function receives different types, the function has to be correct for that particular context. One can’t use untyped calculi in real life, only on paper.

(Maybe that’s what you said? Ok, but then I could not see what this argument goes against

I think its often times a good practice to be as explicit as possible and to avoid shortcuts and syntactic sugar. Therefore i would reccomend to use .linlin, .linexp, etc. instead of .range and .exprange (.linexp beeing a topic on its own, i never use it). If its just a conversion from bipolar to unipolar or unipolar to bipolar its also possible to use unipolar * 2 - 1 or bipolar * 0.5 + 0.5. Im most of the times just using these conversions and for example for lfos which is often a usecase for range conversions, sig + (mod * moddepth) or sig * (2 ** (mod * moddepth)) instead of mapping the range to min a max values which results in two params to control instead of just one param moddepth.

3 Likes

@dietcv Agree. That’s precisely that with Signal.

But another thing is to use it in the Sclang language. Not safe at all.

I agree ! And supercollider is full of concise solutions. But sometimes we don’t know them…

1 Like