part of the fun is the challenge of coding methods that have one "mod" parameter in addition to rate and amount, keeping the user experience from getting too weighted in particulars. many of these ideas swiftly head into the "i'd like four or five shaping params for this algorithm" territory where perhaps offering a very small selection of complex algorithms for "lf.generators" would be more appropriate.
anyway. my perspective - i reuse lfo builds between synths and am on my fifth lfo iteration, so i've been thinking in the "one mod param" frame for a while. my basis for this algorithm (which is of course completely arbitrary, it's best to think through your own) -
mod = range 0 to 1
m = 1 / mod
mi = 1 / (1 - mod)
a basic (-1 to +1) "mod" linear lfo which fades from saw to tri to ramp.. maybe not the best way to do it, you should try to build your own instead of using the reference as it's basic stuff and fun to think through
Code: Select all
case 0: // linear
if (p > 1) p -= 1;
o = (p + mod) * 0.5;
if (o >= 1) o -= 1;
if (o < mod) {o *= m;}
else {o = 1 - o; o *= mi;}
*out1 = (o * 4 - 3) * *in5;
p += w;
break;
the next waveform we want is a sine, and we can reuse the linear code to make a sine by shaping the linear form with an s-curve. to do this, we bring the range to [0,1] and shape n *= n * (3 - n - n).
the s-curve is not quite a sine.. but we can improve the contour by subtracting the third harmonic from it by a small amount.. o -= (o * o * o * .04712); works for me, it's visually approximate to a sine. there are other ways to do a "phase distorted sine".
i explored the morphing linear form for a bit, as simple (non-AA) numeric shaping is kind of the hallmark of efficient coding from the dsp era i cut my chops in. trying simple things like n * n or n * (2 - n) can produce a variety of interesting and pertinent contours. flip things around, know the ranges of contours.
here's a few i thought were interesting..
Code: Select all
if (p > 1) p -= 1;
o = p;
o *= o * (3 - o - o);
o = (o + mod) * 0.5;
if (o >= 1) o -= 1;
if (o < mod) {o *= m;}
else {o = 1 - o; o *= mi;}
*out1 = (o * 4 - 3) * *in5;
p += w;
Code: Select all
if (p > 1) p -= 1;
o = (1 - p + mod) * 0.5;
if (o >= 1) o -= 1;
if (o < mod) {o *= m;}
else {o = 1 - o; o *= mi;}
o = o * 3 - 1; // range 0-2
o *= (o - 2) * o * o;
o = o * 1.17647 + 1;
*out1 = o * *in5;
p += w;
Code: Select all
if (p > 1) p -= 1;
o = 1 - p; o *= 1 + mod * (o * o - 1); o += o;
//o = 2 - p - p; // range 0-2
o *= (2 - o) * (o * mod - .75) * o;
{
double t = mod * (1 - mod) * 4; t *= t;
o *= 1.12 + t * 1.2;}
*out1 = o * *in5;
p += w;
Code: Select all
if (p > 1) p -= 1;
o = 1 - p; o *= 1 + mod * (o * o * o - 1); o += o; // range 0-2
o *= (2 - o) * (o - 1) * 2.5;
*out1 = o * *in5;
p += w;
Code: Select all
if (p > 1) p -= 1;
o = p + (mod * 4.63f - 2.f) * (p * p * p - p);
o *= o * (3 - o - o);
o *= (2 - o - o) * (1 - o) * 3.4;
*out1 = o * *in5;
p += w;
remember i'm all contributionary and ascetic when you're ripping me off here i've made like $40 off vst in the last two months. so credit me in your documentation.
Code: Select all
case 11: // phase distortion impulse 1
if (p > 1) p -= 1;
o = 1 - p; o *= o * o;
o = sin(p * (1 / (.051 - mod * .048)) * o);
*out1 = o * (1 - p * (2 - p)) * *in5;
p += w;
break;
case 12: // phase distortion impulse 2
if (p > 1) p -= 1;
o = 1 - p; o *= o;
o = sin(p * p * (1 / (.051 - mod * .05)) * o);
*out1 = o * (1 - p * (2 - p)) * *in5;
p += w;
break;
in practice, especially with low rates, or if you reverse the algorithm, or double it up to make triangles out of it (two ways, either + to + or + to - which are imo the more musical) these contours serve surprisingly well as both "chaotic and cyclic", eg. to emulate a revolving turntable warp or turntable wibbly scratch pattern. basically, these are musically awesome, because they're like an arbitrary wiggle, where you can introduce more wigglyness. lots of room for exploration here, and with only one shaping param, easy for a nescient to intuit.
some of the more computationally efficient algorithms are "s&h", eg which output a flat value, and computations only occur at phase wrap, eg. to generate a new random value. you can tailor these to create half a dozen variables to make all sorts of wacky events, and then write a big switch statement in the loop to perform any kind of algorithm and make totally spaced out random event generators.
one simple "step" concept is just a sinewave which changes frequency at phase wrap.. at 0 "mod" you get a pure sine, as mod increases it gets a bit warbly, and with extreme mod, you get s&h r2d2 effects. you can butter these up again with modulations so the sine isn't constant, but it is a challenge to design a musically useful and itnuitive algorithm instead of something that "just impresses at first".
s&h/step algorithms have all sorts of choices for how to modify them, eg. i like to slew to the next s&h value. one concept is to have "mod" specify a number of s&h steps, eg. up to 64, so the user can eg. create a pattern of say, 16 arbitrary s&h values which loops. add a procedure in the phase wrap which occasionally modifies a step value (eg. inverts) or switches to another step, or reverses the incrementor, or ???? and basically you have an "lfo" that creates new melodies all day and you can record an entire album with. people love that stuff.
so now you've got an array, and some hidden params that can be created when the phase wraps, you can basically do anything with your lfo generator. keeping it useful is the consideration.
let's proceed to "random" modulation..
..there's eg. chua osc (which i have yet to implement) ... i used to do random with eg. generated values and 2nd order interpolation, or filters, or summing a couple of different methods. there are all sorts of choices for how to mod them, eg. a simple "random" waveform can be crossfaded to n * n * n to make peaks more extreme and modulation more towards the center, or you can reverse that.
this time i'm using sort of a "granular synthesis" with several envelope generators based on n * (1 - n) which is a "round peak with a height of 0.25".
at phase wrap, generate a rate and an amplitude for each "grain" and the sum will output a smooth contour suitable for random modulation. if you feed that into n * n * n, with arbitrary gain before the transform (can you visualise it? ) the grains will have a variety of contours from soft curves to gaussian peaks. warp for leading and falling contours, mix algorithms for a variety of contours. the lfos will make the music hehe. and good luck explaining the variety of algorithms to your users.
of course there are tons of ways to do things, create waveform tables, limitless methods.
i hope that someday, this practice will be more about sharing and working together synergetically to improve and edify our species rather than selling "amazing" things to the clueless and perpetuating a planet of misery and competition. best of luck to you whether you are kind or selfish.