As an exercise, and also to illustrate some points about efficient SynthDef writing, here is a revision to Maths2 that also fixes the bug about scalar linExp
values.
Edit: I’m being picky about it for a couple of reasons – mainly to demonstrate some things that we can think about while writing synths. Sam explained later that this was written very quickly on low battery without access to power, so it wasn’t finished. That’s fine! I hope it’s helpful that I went ahead with some updates.
The main points:
- Multiplication is faster than division: so,
x * SampleDur.ir
is more efficient thanx / SampleRate.ir
. - SC does not automatically fold duplicate UGens down into one. If you write
SampleDur.ir
10 times, there will be 10 instances of SampleDur.- This is even more important for operators. If you write
linExp > 0.5
six times, it will actually do the identical comparison operation six times. It’s much more efficient to save the result in a variable and reuse the variable in multiple places.
- This is even more important for operators. If you write
- The linear interpolation formula
(y - x) * interp + x
has one fewer multiplication than(y * interp) + (x * (1 - interp))
.- In general, it’s worth looking for algebraic reductions: e.g.,
x.neg > 0
is the same asx < 0
, except that the latter renders into a single UGen where the former requires two.
- In general, it’s worth looking for algebraic reductions: e.g.,
Maths2 {
*ar { |rise = 0.1, fall = 0.1, linExp = 0.5, loop = 1, plugged = 0, trig = 0|
var freq = (rise.clip(0.001, 10*60) + fall.clip(0.001, 10*60)).reciprocal;
var width = rise.clip(0.001, 10*60) / (rise.clip(0.001, 10*60) + fall.clip(0.001, 10*60));
// avoid multiple instances of the same UGen,
// if it's known that they're all the same.
// also dividing by SampleRate is slower than multiplying by SampleDur
var sampleDur = SampleDur.ir;
var plugTrig = Trig1.ar(1 - plugged, sampleDur);
var loopTrig = Trig1.ar(loop, sampleDur);
var eof, eor;
var maths, maths2, interp, isExp;
var phasor = Phasor.ar(Silent.ar + loopTrig + plugTrig, 2 * freq * sampleDur, -1, 1, -1);
var phasorTrig, latchTrig, postEnv;
var inTrig, phasor2, postEnv2;
var pluggedIsVariable = plugged.rate != \scalar;
var needMaths = pluggedIsVariable or: { plugged < 1 };
var needMaths2 = pluggedIsVariable or: { plugged >= 1 };
if(needMaths) {
phasorTrig = Trig1.ar(0.5 - phasor, sampleDur) + EnvGen.ar(Env([0, 0, 1, 0], [sampleDur, sampleDur, sampleDur]));
latchTrig = (phasorTrig + (DelayN.ar(loopTrig, 0.01, 0.01))).clip(0, 1);
postEnv = (Latch.ar(K2A.ar(loop), latchTrig) > 0);
phasor = phasor.linlin(-1, 1, width.neg, 1-width);
maths = phasor.bilin(0, width.neg, 1-width, 0, -1, 1);
maths = 1 - maths.abs;
maths = maths * postEnv;
};
if(needMaths2) {
inTrig = Trig1.ar(trig, sampleDur);
phasor2 = Phasor.ar(inTrig, 2 * freq * sampleDur, -1, 1, -1);
postEnv2 = SetResetFF.ar(Delay1.ar(inTrig), Trig1.ar(0.5 - phasor2, sampleDur));
phasor2 = phasor2.linlin(-1, 1, width.neg, 1 - width);
maths2 = phasor2.bilin(0, width.neg, 1 - width, 0, -1, 1);
maths2 = 1 - maths2.abs;
maths2 = maths2 * postEnv2;
};
if(pluggedIsVariable) {
maths = Select.ar(plugged, [maths, maths2]);
} {
if(plugged >= 1) { maths = maths2 };
};
isExp = linExp > 0.5; // don't repeat this 3 times
// could reduce here as well, if linExp is scalar
// but I'm tired now ;-p and these aren't expensive
interp = Select.kr(isExp, [linExp.linlin(0, 0.5, 1, 0), linExp.linlin(0.5, 1, 0, 1)]);
maths = Select.ar(isExp, [maths - 1, maths]);
// maths = (maths ** 8 * interp) + (maths * (1 - interp));
// - * + is more efficient than *, - * +
maths = ((maths ** 8) - maths) * interp + maths;
maths = Select.ar(isExp, [maths + 1, maths]);
if(pluggedIsVariable) {
// again, repeated multiplications...
postEnv = phasor * postEnv;
postEnv2 = phasor2 * postEnv2;
// x.neg > 0, same as x < 0, save one unary op
eof = Select.ar(plugged, [postEnv < 0, postEnv2 < 0]);
eor = Select.ar(plugged, [postEnv > 0, postEnv2 > 0]);
} {
if(plugged < 1) {
postEnv = phasor * postEnv;
eof = postEnv < 0;
eor = postEnv > 0;
} {
postEnv2 = phasor2 * postEnv2;
eof = postEnv2 < 0;
eor = postEnv2 > 0;
};
};
^[maths, eof, eor]
}
}
hjh