Resettable or Trigable Oscillators?

I need resettable or trigable LFO’s. So far I only found “Phasor” to do the job while running. I prefer not to stop and start synths.

Wouldn’t it make sense to have a “trig” and “resetPos” for oscillators in general?

Cheers, Bernhard

You could take your resetable unipolar linear ramp and plug it into a non-linear function of your desire to create a resetable LFO.

dietcv is recomminding something like this for LFO:

{
    var trig = Impulse.kr(5);
    var trig_phase = TRand.kr(0, 2pi, trig);
    var phase = Phasor.ar(Impulse.kr(2), 10/SampleRate.ir, 0, 2pi, trig_phase).sin;
    SinOsc.ar(100*phase+200)*0.1
}.scope

The answer to your question is, yes, some of them should have a trig reset. However, the only audio-rate osc I know to do this is OscOS3 from OversamplingOscillators:

~signal = Env([0,1,-1,-1/2048],[1,0,1]).asSignal(2048);

~signal.plot;

b = Buffer.loadCollection(s, ~signal, 1);

{
  var trig = Impulse.ar(1000);
  var trig_phase = TRand.ar(0,2,trig);  
  OscOS3.ar(b, -1, 200, trig_phase, trig, 1, 0, 1, 0, 2, 0, 2).dup*0.1
}.scope;

Maybe there are others?

Sam

here are some ideas:

triangle with variable skew

(
var getTriangle = { |phase, skew|
	phase = phase.linlin(0, 1, skew.neg, 1 - skew);
	phase.bilin(0, skew.neg, 1 - skew, 1, 0, 0);
};

{
	var reset = \reset.tr(0);
	var phase = Phasor.ar(reset, 100 * SampleDur.ir);
	var triangle = getTriangle.(phase, \skew.kr(0.5));
	[phase, triangle];
}.plot(0.02);
)

grafik
grafik

sinusoid with variable skew

(
var getTriangle = { |phase, skew|
	phase = phase.linlin(0, 1, skew.neg, 1 - skew);
	phase.bilin(0, skew.neg, 1 - skew, 1, 0, 0);
};

var getSine = { |phase|
	1 - cos(phase * pi) / 2;
};

{
	var reset = \reset.tr(0);
	var phase = Phasor.ar(reset, 100 * SampleDur.ir);
	var triangle = getTriangle.(phase, \skew.kr(0.5));
	var sine = getSine.(triangle);
	[phase, sine];
}.plot(0.02);
)

grafik
grafik

trapezoid with variable skew, shape and width


(
var getTriangle = { |phase, skew|
	phase = phase.linlin(0, 1, skew.neg, 1 - skew);
	phase.bilin(0, skew.neg, 1 - skew, 1, 0, 0);
};

var getTrapezoid = { |phase, duty, shape, skew|
	var offset = phase - (1 - duty);
	var steepness = 1 / (1 - shape);
	var trapezoid = (offset * steepness + (1 - duty)).clip(0, 1);
	var pulse = offset > 0;
	Select.ar(shape |==| 1, [trapezoid, pulse]);
};

{
	var reset = \reset.tr(0);
	var phase = Phasor.ar(reset, 100 * SampleDur.ir);
	var triangle = getTriangle.(phase, \skew.kr(0.5));
	var trapezoid = getTrapezoid.(triangle, \duty.kr(0.25), \shape.kr(0.75));
	[phase, trapezoid];
}.plot(0.02);
)

grafik
grafik

combine them all!!!

(
var getTriangle = { |phase, skew|
	phase = phase.linlin(0, 1, skew.neg, 1 - skew);
	phase.bilin(0, skew.neg, 1 - skew, 1, 0, 0);
};

var getSine = { |phase|
	1 - cos(phase * pi) / 2;
};

var getTrapezoid = { |phase, duty, shape, skew|
	var offset = phase - (1 - duty);
	var steepness = 1 / (1 - shape);
	var trapezoid = (offset * steepness + (1 - duty)).clip(0, 1);
	var pulse = offset > 0;
	Select.ar(shape |==| 1, [trapezoid, pulse]);
};

var getMultiLFO = { |phase, skew, width, shape, mix|
	var triangle = getTriangle.(phase, skew);
	var trapezoid = getTrapezoid.(triangle, width, shape);
	var sine = getSine.(trapezoid);
	trapezoid * (1 - mix) + (sine * mix);
};

{
	var reset = \reset.tr(0);
	var phase = Phasor.ar(reset, 100 * SampleDur.ir);
	var lfo = getMultiLFO.(phase, \skew.kr(0.5), \width.kr(0.25), \shape.kr(0.75), \mix.kr(1));
	[phase, lfo];
}.plot(0.02);
)

grafik
grafik
grafik

2 Likes

More options:

SinOsc can be used if you set the freq to 0 and control the phase directly with Phasor.

Also, if you first make your own LFO wavetable shapes (see Wavetable | SuperCollider 3.13.0 Help ), you can use Osc, VOsc and VOsc3 ugens and control the phase directly, again with Phasor.

You can also use Env as a retriggerable LFO:

(
{
	var freq = 10;
	var curves =  [-10, -5, 0, 5, 10];
	var trig = Impulse.kr (4);  // retrigger
	var gate = 1 - trig;
	curves.collect({arg curve, i;
		Env([0, 0, -1, 0, 1, 0, 0], [0, 0.25, 0.25, 0.25, 0.25, 0],
			curve: [curve, curve.neg, curve, curve.neg, curve],
			releaseNode: 5, loopNode: 0)
		.kr(gate: gate, timeScale: freq.reciprocal);
	})
}.plot(1);
)
//////////
(
{
	var freq = 10;
	var trig = Impulse.kr (4);  // retrigger
	var gate = 1 - trig;
	var proportions =  [0.5, 0.7, 0.9, 1];
	proportions.collect({arg prop, i;
		Env([0, 0, 1, 0, 0], [0, prop, 1 - prop, 0], releaseNode: 3, loopNode: 0)
		.kr(gate: gate, timeScale: freq.reciprocal);
	})
}.plot(1);
)

Im actually not a big fan of setting up complexity by hardcoding a fixed amount of segments via envelopes or using wavetables, where wavetables are the better option IMO. I think its better to create complexity by combining basic shapes to a compound structure for a variety of possibilites. When deriving your LFOs from stateless functions you get just a few meaningful control params to create a bunch of different shapes and can modulate them via LFOs.

by far the most versatile LFO IMO:

morph between S-shapes and J-shapes

(
var getTriangle = { |phase, skew|
	phase = phase.linlin(0, 1, skew.neg, 1 - skew);
	phase.bilin(0, skew.neg, 1 - skew, 1, 0, 0);
};

var scurve = { |x, curve|
	var v1 = x - (curve * x);
	var v2 = curve - (2 * curve * x.abs) + 1;
    v1 / v2;
};

var sigmoidBipolar = { |x, shape|
	var shapeBipolar = shape * 2 - 1;
	var xBipolar = x * 2 - 1;
	scurve.(xBipolar, shapeBipolar) * 0.5 + 0.5;
};

var sigmoidUnipolar = { |x, shape|
	var shapeBipolar = shape * 2 - 1;
	scurve.(x, shapeBipolar);
};

var sigmoidBlended = { |x, shape, mix|
	var unipolar = sigmoidUnipolar.(x, shape);
	var bipolar = sigmoidBipolar.(x, shape);
	unipolar * (1 - mix) + (bipolar * mix);
};

{
	var phase = Phasor.ar(0, 100 * SampleDur.ir);
	var triangle = getTriangle.(phase, \skew.kr(0.05));
	sigmoidBlended.(triangle, \shape.kr(0.10), \mix.kr(0.2));
}.plot(0.02);
)

grafik
grafik
grafik
grafik

… and driven by the trapezoid…

(
var getTriangle = { |phase, skew|
	phase = phase.linlin(0, 1, skew.neg, 1 - skew);
	phase.bilin(0, skew.neg, 1 - skew, 1, 0, 0);
};

var scurve = { |x, curve|
	var v1 = x - (curve * x);
	var v2 = curve - (2 * curve * x.abs) + 1;
    v1 / v2;
};

var sigmoidBipolar = { |x, shape|
	var shapeBipolar = shape * 2 - 1;
	var xBipolar = x * 2 - 1;
	scurve.(xBipolar, shapeBipolar) * 0.5 + 0.5;
};

var sigmoidUnipolar = { |x, shape|
	var shapeBipolar = shape * 2 - 1;
	scurve.(x, shapeBipolar);
};

var sigmoidBlended = { |x, shape, mix|
	var unipolar = sigmoidUnipolar.(x, shape);
	var bipolar = sigmoidBipolar.(x, shape);
	unipolar * (1 - mix) + (bipolar * mix);
};

var getTrapezoid = { |phase, duty, shape|
	var offset = phase - (1 - duty);
	var steepness = 1 / (1 - shape);
	var trapezoid = (offset * steepness + (1 - duty)).clip(0, 1);
	var pulse = offset > 0;
	Select.ar(shape |==| 1, [trapezoid, pulse]);
};

var rand = Array.fill(5, { rrand(0.0, 1.0) });

{
	var phase = Phasor.ar(0, 100 * SampleDur.ir);
	var triangle = getTriangle.(phase, rand[0]);
	var trapezoid = getTrapezoid.(triangle, rand[1], rand[2]);
	sigmoidBlended.(trapezoid, rand[3], rand[4]);
}.plot(0.02);
)


with nice cadence in the end haha

1 Like

My default position is usually conservative when it comes to changing the class library, but for this catalog of Phasor-driven oscillator techniques, I’m gonna say these would be a great addition to the class library :exploding_head: – perhaps as a user-extensible library of functions rather than as classes (because often, people take hard classes as a set of limitations as well as a set of possibilities, but the point of these techniques is to push the limitations much farther toward the horizon than the basic idea of “warp a phasor” would seem capable of).

Or at least a help file or wiki page that’s linked in prominently from SC documentation somewhere.

Really great stuff. Also useful for implementing oscillator sync.

hjh

Thanks :slight_smile: I can write an introduction of using ramp signals as a source of time with their possibilities to derive triggers, durations and slopes from them, subdivide them with fractional precision and to build basic unit shapers with them for creating windows and lfos.

1 Like

Instead of using the tunable sigmoid (which is a very useful function), i have shared above to morph between s- and j-shapes, you could also use quintic easing functions (here i use easeIn, easeOut, easeInOut and double-quintic seat). This is more heavy on code (not necessarily on CPU) but it has the advantage that shape values of 1 or 0 do not result in infinity.

“exponential” to linear morph

(
var coreQuintic = { |x|
	x * x * x * x * x;
};

var outQuintic = { |x|
	1 - coreQuintic.(1 - x);
};

var quinticOutToLinear = { |x, shape|
	var mix = shape * 2;
	var easeOut = outQuintic.(x);
	easeOut * (1 - mix) + (x * mix);
};

var linearToQuinticIn = { |x, shape|
	var mix = (shape - 0.5) * 2;
	var easeIn = coreQuintic.(x);
	x * (1 - mix) + (easeIn * mix);
};

var expToLinearMorph = { |x, shape|
	Select.ar(shape > 0.5, [
		quinticOutToLinear.(x, shape),
		linearToQuinticIn.(x, shape)
	]);
};

{
	var phase = Phasor.ar(0, 50 * SampleDur.ir);
	expToLinearMorph.(phase, \shape.kr(0));
}.plot(0.02);
)

“sigmoid” to linear morph

(
var coreQuintic = { |x|
	x * x * x * x * x;
};

var inOutQuintic = { |x|
	Select.ar(x > 0.5, [
		coreQuintic.(2 * x) / 2,
		1 - (coreQuintic.(2 * (1 - x)) / 2)
	]);
};

var seatQuintic = { |x|
	Select.ar(x > 0.5, [
		(1 + coreQuintic.((x * 2) - 1)) / 2,
		(1 - coreQuintic.(1 - (x * 2))) / 2
	]);
};

var quinticInOutToLinear = { |x, shape|
	var mix = shape * 2;
	var easeInOut = inOutQuintic.(x);
	easeInOut * (1 - mix) + (x * mix);
};

var linearToSeat = { |x, shape|
	var mix = (shape - 0.5) * 2;
	var seat = seatQuintic.(x);
	x * (1 - mix) + (seat * mix);
};

var sigmoidToLinearMorph = { |x, shape|
	Select.ar(shape > 0.5, [
		quinticInOutToLinear.(x, shape),
		linearToSeat.(x, shape)
	]);
};

{
	var phase = Phasor.ar(0, 50 * SampleDur.ir);
	sigmoidToLinearMorph.(phase, \shape.kr(1));
}.plot(0.02);
)

combined to “sigmoid” to “exponential” morph

(
var coreQuintic = { |x|
	x * x * x * x * x;
};

var outQuintic = { |x|
	1 - coreQuintic.(1 - x);
};

var inOutQuintic = { |x|
	Select.ar(x > 0.5, [
		coreQuintic.(2 * x) / 2,
		1 - (coreQuintic.(2 * (1 - x)) / 2)
	]);
};

var seatQuintic = { |x|
	Select.ar(x > 0.5, [
		(1 + coreQuintic.((x * 2) - 1)) / 2,
		(1 - coreQuintic.(1 - (x * 2))) / 2
	]);
};

var quinticOutToLinear = { |x, shape|
	var mix = shape * 2;
	var easeOut = outQuintic.(x);
	easeOut * (1 - mix) + (x * mix);
};

var linearToQuinticIn = { |x, shape|
	var mix = (shape - 0.5) * 2;
	var easeIn = coreQuintic.(x);
	x * (1 - mix) + (easeIn * mix);
};

var quinticInOutToLinear = { |x, shape|
	var mix = shape * 2;
	var easeInOut = inOutQuintic.(x);
	easeInOut * (1 - mix) + (x * mix);
};

var linearToSeat = { |x, shape|
	var mix = (shape - 0.5) * 2;
	var seat = seatQuintic.(x);
	x * (1 - mix) + (seat * mix);
};

var expToLinearMorph = { |x, shape|
	Select.ar(shape > 0.5, [
		quinticOutToLinear.(x, shape),
		linearToQuinticIn.(x, shape)
	]);
};

var sigmoidToLinearMorph = { |x, shape|
	Select.ar(shape > 0.5, [
		quinticInOutToLinear.(x, shape),
		linearToSeat.(x, shape)
	]);
};

var sigmoidToExp = { |x, shape, mix|
	var sigmoid = sigmoidToLinearMorph.(x, shape);
	var exponential = expToLinearMorph.(x, shape);
	exponential * (1 - mix) + (sigmoid * mix);
};

{
	var phase = Phasor.ar(0, 50 * SampleDur.ir);
	sigmoidToExp.(phase, \shape.kr(1), \mix.kr(0.5));
}.plot(0.02);
)

hey, maybe you find this useful. Check out my latest post in this thread for anti-aliased osc sync, which could be combined with arbitrary unit shapers for a bunch of different shapes.

basical means to solve this:

  • using an envelope to trigger the sound
  • *the envelope can be created with EnvGen.kr, and triggered by another UGen, ranging from LFDNoise, to TRand, to a ChaosGen class ugen

according to community standards, it’s beter to do something like HenonC.ar, although if you are using jitlib, you can do something like ChaosGen.allsubclasses.choose.ar, which is something i personally tend to like even though it’s against to the community standards - but if you using JitLib correctly then that’s noso much the case, to a whole bunch of other things

alternatively:

  • you can create a class for your sound definition (modularizing things is always great, and if you start to archive all the sounds and all the projects you create into a singe thing, then great)
  • you can trigger your class with something ranging from:
    • a routine
    • to a task
    • to a pbind
    • to an oscdef
    • to a mididef