Updating frequency values of a Glisson Task

Hello, this is my first post here and I am a super rookie. I started this really simple Glisson synth :


(
SynthDef(\GLISSON, { | freq1, freq2, sustain, sloped, envwide, pan |

	var slopetime = (sloped - envwide) * 0.5;

	var sig = SinOsc.ar(Line.kr(freq1, freq2, sustain));

	var env = EnvGen.ar(
		Env([0, 1, 1], [slopetime, envwide], 3),

		timeScale: sustain,
		doneAction: 0);

	var sigX = Pan2.ar(sig, pan);

	OffsetOut.ar(0, sigX * env * 0.1);

}).add;
)

(
Tdef(\GLISS).set(\fmin, 455, \fmax, 655,  \fratio, 0.4 );

Tdef(\GLISS, { |envir|

	var f1, f2, sus ;

	10.do {
		sus = exprand(envir.smin, envir.smax);
		f1 = exprand(envir.fmin, envir.fmax);
		f2 = f1 * (envir.fratio ** 1.0.rand2);

		(	instrument: \GLISSON,
			sloped: 2,
			envwide:0.4,
			freq1: f1,
			freq2: f2,
			pan: 1.0.rand2,
			sustain: 7,

		).play;
		0.01.wait;
	};
}).play;
)

As you can hear, it’s a cluster of 10 sines where each sines randomly rise or fall until a sustained point that produce a random chord (so far sustained just because DoneAction:0).
My question is the following : once I reach this sustained chord I would like to freeze on it as long as I want and then to allow thoses 10 sines to gliss to a next sustained chord value, just by updating manually the Tdef ( with new fmin / fmax or task wait values ).
Conceptually I am quite confuse with this idea as it is the equivalent of having a SynthDef freq envelope with segments that will evolve infinitely…
Anyway I would like to keep this Tdef design and just be able to update the freq values keeping thoses 10 sines, without adding new ones… Possible ?

I hope this is clear :slight_smile:
Thanks a lot.

Hi Clark,

There are many ways you might go about this.
Since you always have 10 synths, I would consider making 10 at the beginning of the Task, storing them in an array and updating them.

To have smooth transitions I would recommend something like this. This uses a function to generate a bunch of controls for you.

SynthDef(\glisser, {
	var retriggerAllEnvs = \reset.tr();
	
	var mkEnvArg = {
		|name, default|
		var from = NamedControl.kr(name ++ 'From', default);
		var to = NamedControl.kr(name ++ 'To', default);
		var dur = NamedControl.kr(name ++ 'Dur', 0);
		var curve = NamedControl.kr(name ++ 'Curve', 0);
		var retrigger = NamedControl.tr(name ++ 'Trig', 0);
		var env = Env([from, from, to], [0, dur], curve, 0);
		EnvGen.kr(env, retrigger + retriggerAllEnvs);
	};
	var freq = mkEnvArg.(\freq, 220).poll;
	
	var someOtherArgument = mkEnvArg.('someOtherArgument', 0)
}).add;

t = Synth(\glisser)

t.set(\freqFrom, 400, \freqTo, 50, \freqDur, 2, \freqCurve, 0, \freqTrig, 1)

t.set(\freqFrom, 50, \freqTo, 150, \freqDur, 10, \freqCurve, 4, \freqTrig, 1)

You can make the setting of an ‘env’ed’ argument simplier with a function…

~envArgSet = {
	|name, from, to, dur=1, curve=0|
	[
		(name ++ 'From').asSymbol,   from,
		(name ++ 'To').asSymbol,     to,
		(name ++ 'Dur').asSymbol,    dur,
		(name ++ 'Curve').asSymbol,  curve,
		(name ++ 'Trig').asSymbol,   1
	]
};


t.set(
	\amp, 1,
	*~envArgSet.(\freq, from: 50, to: 140, dur: 10, curve: 0)
)

Thanks a lot Jordan for this super smooth control and also to make me discover the NamedControl concept (that’s great !). But I have a lot of trouble to implement it inside my original Task : this task is important to me as I would like to update on the fly the array of pitches and also the value.wait at the end of the task.
Would you mind to show me how to write your strategy inside my original task, and in this case how to update the value after lauching the 10 sines ? It’s not that I am lazy eh :slight_smile: But there is something that I missed for sure

Thanks a lot

Okay my approach was wrong here. Much better to use VarLag when you want to continually change the same synth instance.

1

Synth def should have something like this… note the control names are important for the next few steps.

SynthDef(\glisser, {
	var laggedCtl = {|name, def=0|
		VarLag.kr( 
			in: NamedControl.kr(name, def), 
			time: NamedControl.kr(name ++ 'Lag', 1), 
			curvature: NamedControl.kr(name ++ 'Curve', 0), 
			start: def
		)
	};
	var freq = laggedCtl.(\freq, 220);
	var snd = SinOsc.ar(freq) * 0.1;
	var env = Env.adsr(2).kr(doneAction:2, gate: \gate.kr(1));
	Out.ar(0, snd!2 * env); 
}).add;

2

Task owns the synths and frees them at the end. It looks through the \laggedArgs keys to find which keys in should be setting - they must all have a min, max, lag and curve suffix defined.

Tdef(\player, {
	|envir|
	var ownedSynths = 10.collect{ Synth(\glisser) };
	var getE = { |name, postfix| envir[(name ++ postfix).asSymbol] };
	var setFromEnvir = { |name| [ 
		(name).asSymbol,            exprand(getE.(name, 'Min'), getE.(name, 'Max')),
		(name ++ 'Lag').asSymbol,   getE.(name, 'Lag'), 
		(name ++ 'Curve').asSymbol, getE.(name, 'Curve')
	]};
	s.sync;
	while {envir[\shouldLoop]}
	{
		ownedSynths.do{|synth|
			envir[\laggedArgs].do{ |argname|
				synth.set( *setFromEnvir.(argname).postln )
			};
			(envir.waitTime ? 0.1).wait
		};
	};	
	
	ownedSynths.do(_.set(\gate,0))
});

3

Set up Tdef

( // make sure all this is set before you play the tdef
Tdef(\player).set(\laggedArgs, [\freq]);
Tdef(\player).set(\freqMin, 800, \freqMax, 1100, \freqLag, 20, \freqCurve, 2);
Tdef(\player).set(\waitTime, 1);
Tdef(\player).set(\shouldLoop, true);
)

4

Change values on the fly…

Tdef(\player).play

Tdef(\player).set(\freqMin, 800, \freqMax, 1100, \freqLag, 2, \freqCurve, 2);
Tdef(\player).set(\freqMin, 200, \freqMax, 400, \freqLag, 12, \freqCurve, -6);
Tdef(\player).set(\waitTime, 0.1)


Tdef(\player).set(\shouldLoop, false) // end and release synths

As one block of code

s.waitForBoot {
	SynthDef(\glisser, {
		var laggedCtl = {|name, def=0|
			VarLag.kr( 
				in: NamedControl.kr(name, def), 
				time: NamedControl.kr(name ++ 'Lag', 1), 
				curvature: NamedControl.kr(name ++ 'Curve', 0), 
				start: def
			)
		};
		var freq = laggedCtl.(\freq, 220);
		var snd = SinOsc.ar(freq) * 0.1;
		var env = Env.adsr(2).kr(doneAction:2, gate: \gate.kr(1));
		Out.ar(0, snd!2 * env); 
	}).add;
	
	Tdef(\player, {
		|envir|
		var ownedSynths = 10.collect{ Synth(\glisser) };
		var getE = { |name, postfix| envir[(name ++ postfix).asSymbol] };
		var setFromEnvir = { |name| [ 
			(name).asSymbol,            exprand(getE.(name, 'Min'), getE.(name, 'Max')),
			(name ++ 'Lag').asSymbol,   getE.(name, 'Lag'), 
			(name ++ 'Curve').asSymbol, getE.(name, 'Curve')
		]};
		
		s.sync;
		
		while {envir[\shouldLoop]}
		{
			ownedSynths.do{|synth|
				envir[\laggedArgs].do{ |argname|
					synth.set( *setFromEnvir.(argname).postln )
				};
				(envir.waitTime ? 0.1).wait
			};
		};	
		
		ownedSynths.do(_.set(\gate,0))
	});
	
	// make sure all this is set before you play the tdef
	Tdef(\player).set(\laggedArgs, [\freq]);
	Tdef(\player).set(\freqMin, 800, \freqMax, 1100, \freqLag, 20, \freqCurve, 2);
	Tdef(\player).set(\waitTime, 1);
	Tdef(\player).set(\shouldLoop, true);
	
	Tdef(\player).play;
};


Tdef(\player).set(\freqMin, 800, \freqMax, 1100, \freqLag, 2, \freqCurve, 2);
Tdef(\player).set(\freqMin, 200, \freqMax, 400, \freqLag, 12, \freqCurve, -6);
Tdef(\player).set(\waitTime, 0.1)


Tdef(\player).set(\shouldLoop, false)

Thank you very much but it does not behave like I was expecting, probably because of the strange loop beating that make the sound not natural - you can hear that is not the same audio result as my first example when applying the same values - also maybe because it’s not the same to drop 10 sines from silence with a timing shift ( ie 0.1 wait; ) in this original example that to drop 10 sines at the same time and then shift their glissando, also there is strange behavior when playing with it, especially with short lag time, is it the loop ?
Is there a way to avoid looping ? Thanks a lot

This is how you could only updated on a change., Fixes the short lag issue

Tdef(\player, {
		|envir|
		var ownedSynths = 10.collect{ Synth(\glisser) };
		var getE = { |name, postfix| envir[(name ++ postfix).asSymbol] };
		var setFromEnvir = { |name| [ 
			(name).asSymbol,            exprand(getE.(name, 'Min'), getE.(name, 'Max')),
			(name ++ 'Lag').asSymbol,   getE.(name, 'Lag'), 
			(name ++ 'Curve').asSymbol, getE.(name, 'Curve')
		]};
		var prevEnvir = envir.copy;
		s.sync;
		
		while {envir[\shouldLoop]}
		{
			(prevEnvir != envir).if({ // only update if changed
				prevEnvir = envir.copy;
				ownedSynths.do{|synth|
					envir[\laggedArgs].do{ |argname|
						synth.set( *setFromEnvir.(argname) )
					};
					envir.waitTime.wait
				};
			});
			0.1.wait; // could probably do something nicer with CondVar
		};	
		
		ownedSynths.do(_.set(\gate,0))
	});

Your right this doesn’t emulate your sound exactly, instead it gives you a framework sequence mulitple changing synth controls through a Tdef interface.

This would all be much easier with a function call, or even a pattern, but you said you wanted tdefs that you can retrigger, this is the only way I can think to do it. I don’t know if some has a been way, but i think this new version (above) should get you where you want.

Thanks a lot Jordan, it responds well better indeed.
I have three questions :

Do you know why the first drop never plays the good frequency values ? It only does after playing the synth once and then updating the first .set ( and for sure I am carefull with the " // make sure all this is set before you play the tdef " ).

I would like to be able to update on the “fly” using also pitches array, instead of using random fmin fmax ie to gliss from an array of 10 midi pitches to another array of 10 pitches, possible to implement that inside the current code ?

What would be the starting point for doing the same idea using patterns ? I can try to code it myself, I am now familiar with Pseg ( that would be the stuff no ? ) but how updating values on the fly without any frequency jump ?

thanks again