You are right you can’t use an if
on the server, but you can’t use case statements either. Just a little thing here…
var result, softclippermain, clipper;
Try not to put all the variables at the top like this, its much better to declare them as you go down (where possible). Also, this result variable is unnecessary, functions always return the result of the last expression.
result = Select.ar(x > 0, [
clipper.(x, upperLim, upperSkew, floor: 0, ceil: upperLim ),
clipper.(x, lowerLim, lowerSkew, floor: 0, ceil: lowerLim ).neg + lowerLim
]);
result;
It’s not a bad way of shaping a signal, but it is terribly slow for full runtime use. Here are two versions, one that is essentially a pseudo UGen and another that can be used to generate a buffer. The UGen one is a little messy, I haven’t found a nicer way of write functions that work at both control and audio rate.
(
~softclipperUgen = {
|x, upperLim, lowerLim, slope, xOffFactor, upperSkew, lowerSkew|
var rate = if(x.rate == \audio, \ar, \kr);
var zero = DC.perform(rate, 0); // useful to force a number -> ugen
// the actual softclip
var softclippermain = { |scaledIn, limit|
var s = slope * scaledIn;
var c = s - (s.pow(3) / 3);
((3/2 * limit * c)) / 2 + (limit / 2)
};
// if input is out of range, return floor or ceil
var clipper = { |in, limit, skew, floor, ceil|
var xOff = slope.reciprocal * slope.pow(xOffFactor);
var scaledIn = in + (xOff * in.neg.sign) * skew;
// 0 is normal, 1 is ceil, 2 is floor -- this is a subtle line of code
var index =
((scaledIn >= slope.reciprocal) * 1) +
((scaledIn <= slope.reciprocal.neg) * 2);
Select.perform(rate, zero + index,
[softclippermain.(scaledIn, limit), ceil, floor]
);
};
Select.perform(rate, x > zero, [
clipper.(x, upperLim, upperSkew, floor: zero, ceil: zero + upperLim ) ,
lowerLim + clipper.(x, lowerLim, lowerSkew, floor: zero, ceil: zero + lowerLim ).neg
] );
};
~softclipper = { |x, upperLim, lowerLim, slope, xOffFactor, upperSkew, lowerSkew|
var softclippermain = { |scaledIn, limit|
var s = slope * scaledIn;
var c = s - (s.pow(3) / 3);
(3/2 * limit * c).half + limit.half
};
var clipper = { |in, limit, skew, floor, ceil|
var xOff = slope.reciprocal * slope.pow(xOffFactor);
var scaledIn = in + (xOff * in.neg.sign) * skew;
case (
{ scaledIn >= slope.reciprocal }, { ceil },
{ scaledIn <= slope.reciprocal.neg }, { floor },
{ softclippermain.(scaledIn, limit) }
)
};
if(x > 0,
{ clipper.(x, upperLim, upperSkew, floor: 0, ceil: upperLim ) },
{ lowerLim + clipper.(x, lowerLim, lowerSkew, floor: 0, ceil: lowerLim ).neg }
);
};
)
(
// uses the function live, you can alter all the argument with ugens
s.waitForBoot {
{
var a = LFSaw.ar(1);
[ a, ~softclipperUgen.(a.range(-2,2), 1, -1, 1, 1, 1, 1 ) ]
}.scope;
}
)
(
// freezes these arguments and stores them in a buffer.
s.waitForBoot {
~b = Buffer.alloc(s, 1024);
~t = Signal.fill(513, { |n|
~softclipper.( n.linlin(0, 512, -2.0, 2.0), 1, -1, 2, 1, 1, 1 )
});
s.sync;
~b.sendCollection(~t.asWavetableNoWrap);
// Shaper reads from the buffer
{
var a = LFSaw.kr(1);
[ a, Shaper.kr(~b, a)]
}.scope
}
)