Universal modulator

EDIT .add is now added to the synthdef, sorry I have been looking for a way to create a relatively simple universal modulator synth which is able to produce many different shapes over a user specified amount of time. In some cases the synthdef below is overkill, e.g. when producing a simple lfo modulation, but I figured that the advantages of this unified approach outweighs the added cpu load (which is low).

I chose to specify the lfo modulation as a ratio (lfo.midiratio). There are case where one might prefer an added lfo rather than a multiplied ratio. In these case a separate but similar modulation synth could be used or the current one expanded with a Select.kr choosing between env * lfo.midiratio and env + lfo.

I should also the mention that the total duration of the shape is specified by the dur argument unless (atk + rel) > dur in which case the total duration is (atk + rel).

I am curious if other people are using a similar approach?

I came up with this:

(
SynthDef(\mod, {|dur=3, ampLo=0, ampHi=0, ampAtk=0, ampRel=0, rateLo=5, rateHi=5, rateAtk=0, rateRel=0, lo=0, hi=0, atk=0.5, rel=0.5, out, phase=0, mul=1, add=0, curve=0, round=0|
	var ampEnv = Env([ampLo, ampHi, ampHi, ampLo], [ampAtk, (dur-ampAtk-ampRel).max(0), ampRel], curve).kr(0);
	var rateEnv = Env([rateLo, rateHi, rateHi, rateLo], [rateAtk, (dur-rateAtk-rateRel).max(0), rateRel], curve).kr(0);
	var lfo = SinOsc.kr(rateEnv, phase, ampEnv);
	var env = Env([lo, hi, hi, lo], [atk, (dur-atk-rel).max(0), rel], curve).kr(2) * lfo.midiratio.round(round);
	Out.kr(out, env * mul + add)
}).add;
)

This can create a lot of different shapes. Here are a few different ones. Each plot takes 3 seconds (sorry!).

b = Bus.control(s);

 // trapez
(
Synth(\mod, [\out, b, \hi, 1]);
b.plot(3)
)

// triangle

(
Synth(\mod, [\out, b, \hi, 1, \atk, 1.5, \rel, 1.5]);
b.plot(3)
)

// ramp

(
Synth(\mod, [\out, b, \hi, 1, \atk, 3]);
b.plot(3)
)

// lfo modulation of basevalue 100

(
Synth(\mod, [\out, b, \lo, 100, \hi, 100, \atk, 0, \rel, 0, \ampHi, 1, \rateHi, 5]);
b.plot(3)
)

// envelope with amp modulation

(
Synth(\mod, [\out, b, \hi, 10, \ampHi, 2, \ampAtk, 0.5, \ampRel, 0.5, \rateLo, 10, \rateHi, 10]);
b.plot(3)
)

// amp modulation

(
Synth(\mod, [\out, b, \lo, 2, \hi, 2, \ampLo, 0, \ampHi, 10, \ampAtk, 3, \ampRel, 0, \rateHi, 10]);
b.plot(3)
)

// rate modulation

(
Synth(\mod, [\out, b, \lo, 2, \hi, 2, \ampHi, 1, \rateLo, 2, \rateHi, 20, \rateAtk, 3]);
b.plot(3)
)

// envelope with amp and rate modulation
 
(
Synth(\mod, [\out, b, \hi, 10, \atk, 1, \rel, 0.5, \ampLo, 1, \ampHi, 10, \ampAtk, 3, \rateLo, 1, \rateHi, 20, \rateAtk, 3]);
b.plot(3)
)

// using round

(
Synth(\mod, [\out, b, \hi, 100, \ampHi, 3, \rateHi, 5, \round, 0.1]);
b.plot(3)
)

Here are some sound examples using a non-gated synth. If used one a gated synth the user would have to keep track of old mappings or use several control busses to keep modulation of multiple arguments separate.

(
SynthDef(\synth, {
	var sig = 2.collect{Saw.ar(\freq.kr(110) * LFNoise1.kr(1).bipolar(\det.kr(0.2)).midiratio * \vib.kr(0).midiratio)};
	var env = Env.perc(\atk.kr(0.1), \rel.kr(3), 1, \curve.kr(2)).kr();
	sig = RLPF.ar(sig, \lp.kr(1000), \rq.kr(1).max(0.1));
	sig = RHPF.ar(sig, \hp.kr(60), \rq.kr(1).max(0.1));
	sig = Balance2.ar(sig[0], sig[1], \pan.kr(0), \amp.kr(1) * 0.3);
	Out.ar(\out.kr(0), sig * env)
}).add
)

// The un-modulated synth //

Synth(\synth);

// rate and amp modulation of lopass filter

(
s.makeBundle(nil, {
	x = Synth(\synth);
	Synth(\mod, [\out, b, \lo, 150, \hi, 700, \atk, 1, \rel, 1, \ampLo, 1, \ampHi, 10, \ampAtk, 2, \ampRel, 2, \rateLo, 0.1, \rateHi, 20, \rateAtk, 2, \rateRel, 2, \curve, 4]);
	x.set(\lp, b.asMap)
})
)

// rate and amp modulation of hipass filter

(
s.makeBundle(nil, {
	x = Synth(\synth);
	Synth(\mod, [\out, b, \lo, 60, \hi, 800, \ampLo, 20, \ampHi, 2, \ampRel, 3, \rateLo, 2, \rateHi, 20, \rateAtk, 1, \rateRel, 2]);
	x.set(\hp, b.asMap)
})
)

// pan left to right over duration of note, progressively more vibrato

(
c = Bus.control(s); // this example uses 2 control busses so we define an extra one
s.makeBundle(nil, {
	x = Synth(\synth);
	Synth(\mod, [\out, c, \hi, 1, \add, -1, \atk, 0, \ampHi, 24, \ampAtk, 3, \curve, 4]);
	Synth(\mod, [\out, b, \lo, -1, \hi, 1, \atk, 3]);
	x.set(\pan, b.asMap, \vib, c.asMap)
})
)

// tremolo

(
s.makeBundle(nil, {
	x = Synth(\synth);
	Synth(\mod, [\out, b, \hi, 1, \atk, 0, \rel, 0, \ampHi, 24, \mul, 0.25]);
	x.set(\amp, b.asMap)
})
)

// detune, the sound is quite different each time due to the use of LFNoise in the synthdef

(
s.makeBundle(nil, {
	x = Synth(\synth);
	Synth(\mod, [\out, b, \lo, 0.2, \hi, 12, \atk, 3, \curve, 2]);
	x.set(\det, b.asMap)
})
)

// modulation of frequency

(
s.makeBundle(nil, {
	x = Synth(\synth, [\rel, 2]);
	Synth(\mod, [\out, b, \lo, 110, \hi, 220, \dur, 2, \ampHi, 3, \round, 2]);
	x.set(\freq, b.asMap)
})
)

b.free;
c.free;
1 Like

ive been also working on something similiar recently:

(
var modulators = { |modFreq|
	var sin = SinOsc.ar(modFreq);
	var saw = LFSaw.ar(modFreq);
	var lato = LatoocarfianC.ar(
		freq: modFreq,
		a: LFNoise2.kr(2, 1.5, 1.5),
		b: LFNoise2.kr(2, 1.5, 1.5),
		c: LFNoise2.kr(2, 0.5, 1.5),
		d: LFNoise2.kr(2, 0.5, 1.5)
	);
	var heno = HenonC.ar(
		freq: modFreq,
		a: LFNoise2.kr(1, 0.2, 1.2),
		b: LFNoise2.kr(1, 0.15, 0.15),
	);
	var gendy = Gendy3.ar(
		ampdist: 6,
		durdist: 3,
		adparam: 0.9,
		ddparam: 0.9,
		freq: modFreq,
		ampscale: 1
	);
	(sin: sin, saw: saw, lato: lato, heno: heno, gendy: gendy);
};

var modulatorMatrix = { |modSelect, modFreq, min, max, curve, lag|
	var numModulators = modSelect.size;
	var matrix = numModulators.collect { |i|
		var mod = modulators.(modFreq[i])[modSelect[i]];
		mod.lincurve(-1, 1, min[i], max[i], curve[i]).lag(lag[i]);
	};
	matrix.product;
};

{
	var modMatrix = modulatorMatrix.(
		modSelect: [\lato, \gendy],
		modFreq: [5, 0.1],
		min: [1, 0.1],
		max: [5, 1],
		curve: [4.0, 4.0],
		lag: [0.2, 0.2],
	);
	var freq = \freq.kr(220) * modMatrix;
	var sig = Saw.ar(freq);

	sig !2 * 0.1;

}.play;
)

i think the interface for modMatrix could be better.

1 Like