Control a synth parameter within touchosc inside a routine

Hi, newbie here,

What would be the best way to control the amplitude of a synth, which is sequenced within a routine with touchosc ?

I’m trying to achieve this with the code below, but it doesn’t work as i would expect.

Thank you for your help
here is the code

//Create the synth

(
SynthDef(\mixedklanks ,{
	arg out = 0, freqs = #[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], rings = #[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], atk = 5, sus = 8, rel = 5, pan = 0, amp = 0.5, gate = 0;
	var sustain = 6, transition = 10, overlap = 5;
	var period = transition*2+sustain/overlap;
	var e = EnvGen.kr(Env.linen(atk, sus, rel, 1, 4), gate:gate, doneAction: Done.freeSelf);
	var i = WhiteNoise.ar(0.0012);
	var z = Klank.ar(`[freqs, nil, rings], i);
	Out.ar(out, Pan2.ar(z*e*amp, pan));
}).add;
)




//Create controllers
//button1 -> start/stop routine
(
OSCdef.new(key: \toggle1,
	func:
	{
		arg msg;
		msg.postln;
		(msg[1] > 0.5).if({
			r = Routine{
				var sustain = 6, transition = 10, overlap = 5;
				var period = transition*2+sustain/overlap;
				0.5.wait;            // wait for the synthdef to be sent to the server
				inf.do {
					~synth = Synth(\mixedklanks, [
						\gate, 1,
						\atk, 10,
						\sus, 6,
						\rel, 10,
						\pan, 1.0.rand2,
						\freqs, {((#[0,2,4,5,7,9,11].choose + #[24,36,48,60,72,84].choose + 5).midicps)}.dup(12),
						\rings, {0.1.rrand(3)}.dup(12)
					]);
					period.wait;
				}
			}.play;
		}, {
			r.stop;
		});
	},
	path: '/1/toggle1'
);

//fader1 -> control amp
OSCdef.new(
	\fader1,
	{
		arg msg, time, addr, port;
		msg.postln;
		~synth.set(\amp, msg[1]);
	},
	'/1/fader1'
);
)

A number of things come to mind:

  1. You should take these two lines out of the SynthDef Ugen graph because they don’t do anything.

  2. Maybe you can have a pattern or the synthdef itself do the sequencing instead of a routine. That might make things easier. For routine:

I understand why you did this, but it’s probably better to initialize the function outside the OSCdef. You should also use s.bind when you send the synth to the server. It also won’t take 0.5 seconds to send the synth to the server and there’s a better way to make synths that are varied each time:

(
~makeSynth = {
	var freqs = {([[0,2,4,5,7,9,11].choose, [24,36,48,60,72,84].choose].sum + 5).midicps}!12;
	var rings = {rrand(0.1, 3)}!12;
	var pan = 1.0.rand2;
	Synth(\mixedklanks, [
		\gate, 1,
		\atk, 10,
		\sus, 6,
		\rel, 10,
		\pan, pan,
		\freqs, freqs,
		\rings, rings
	]
	)
}
) //a routine that will make a synth with the values you wanted to be randomized each time it is called

s.bind(~makeSynth); //s.bind is important to remember

You could then make a routine that continuously starts and stops new synths using s.bind. You should always use s.bind to send synths to the server from a routine. Pattern sequencing handles this automatically, but routine sequencing does not.

(
var dura = 5, gap = 2;
fork{
	s.bind(~synth = ~makeSynth);
	loop{
		dura.wait;
		s.bind(~synth.set(\gate, 0));
		gap.wait;
		s.bind(~synth = ~makeSynth);
	}
}
)

Then you make your OSCdef. You can just have it set the gate of the last synth that was spawned in the looping routine to 0, since the variable is assigned the newest synth on each iteration and the preceding ones are freed automatically.

If you release the gate control in the gap period, it won’t matter because you’ll just be setting the already 0 gate argument to 0 and it’ll continue to fade out. You could also store the synths in an array instead to iterate over each synth that’s currently playing if you want more control.

At this point, all you have to do is make another OSCdef that sets the amplitude argument of your synth (or the array of synths if you go that route).

You could also play the synths on a separate audio bus, make a synth that takes this bus as its input and has an amplitude argument you can set with an OSCdef, and then sends the attenuated signal to the main output bus.

A simpler approach would be to output all the synths to a bus, route that bus into a new synth, which just applies the amplitude change, before sending it to the speakers. This is like a ‘mix bus’ in a DAW.
You should use a group to store all the initial synths.

Another option is to use the fact you can set multiple arguments of nodes when they are inside a group, by directing the set message to the group. Personally I am not a fan of this as there is no way to guarantee newly created bode will match the value of the group.

Not to be pedantic, but I’m not sure how this is different…

Perhaps control buses? https://doc.sccode.org/Classes/Bus.html They’re nice for “many reader” arrangements, i.e.

var amp = Bus.control(s, 2);
{	/* a writer */
	Out.kr(amp.index, SinOsc.kr(1 / [13, 17]).range(0, 1) / 7)
}.play;
{	/* some readers */
	inf.do {
		{
			var osc = SinOsc.ar({ 5.rrand(23) } ! 2 * 17, 0);
			var env = Env.sine(7, 3 / 7).kr(2);
			osc * env * amp.kr
		}.play;
		(7 / 5).wait
	}
}.fork

or:

var amp = Bus.control(s, 2);
{	/* a writer */
	inf.do {
		amp.setn({ (1 / 7).rand } ! 2);
		(7 / 3).wait
	}
}.fork;
{	/* some readers */
	inf.do {
		{
			var osc = SinOsc.ar({ 7.rrand(23) } ! 2 * 17, 0);
			var env = Env.sine(7, 3 / 7).kr(2);
			osc * env * amp.kr.lag(5 / 3)
		}.play;
		(7 / 5).wait
	}
}.fork

thanks everyone , the bus option seems to be a good way to do this :slightly_smiling_face:

I think this can be made much easier.

Because you are using Env.linen you do not need a gate, this means you don’t need to do this assignment ~synth = Synth(\mixedklanks..., you can just forget about the made synth because it will free itself.

Therefore spiting the controls into separate synths is much easier.

Conceptually you have one button that resumes and pauses a process … this is a Task.
Then you have a fader that sets the amp on a mix bus.

This can be achieve like this…

(s.waitForBoot {

	// declare synths
	SynthDef(\mixedKlanks, {
		var numParts = 12;
		var input = WhiteNoise.ar(0.0012);
		var env = Env.linen( \atk.kr(5), \sus.kr(8), \rel.kr(5), 1, 4 ).kr(doneAction: Done.freeSelf);
		var klank = Klank.ar(
			`[ \freqs.kr(0.dup(numParts)),  nil,  \rings.kr(0.dup(numParts)) ], 
			input	
		);
		Out.ar(\out.kr, Pan2.ar(klank * env, \pan.kr(0)));
	}).add; 
	
	SynthDef(\mixedKlanksMixer, {
		Out.ar(\out.kr(0), In.ar(\in.kr(0), 2) * \amp.kr(0.1));
	}).add;
	
	// make shared resources
	s.sync;
	~mixedKlanksBus = Bus.audio(s, 2);
	~mixedKlanksGroup = Group(s);
	
	s.sync;
	
	// mixer
	~mixedKlanksMixer = Synth.after(~mixedKlanksGroup, \mixedKlanksMixer, [\in, ~mixedKlanksBus]);

	// player
	~klankPlayer = Task({
		var sustain = 6, transition = 10, overlap = 5;
		var period = transition * 2 + sustain / overlap;
		
		var steps = Scale.major.degrees;
		var octaves = [24, 36, 48, 60, 72, 84];
		var root = 5;
		
		var gen_freq = { (steps.choose + octaves.choose + root).midicps };
		var gen_rings = { 0.1.rrand(3) };
		
		inf.do{ 
			s.bind{
				Synth.head(~mixedKlanksGroup, \mixedKlanks, [
					\out, ~mixedKlanksBus,
					\atk, 10, \sus, 6, \rel, 10, \pan, 1.0.rand2,
					\freqs, gen_freq.dup(12), \rings, gen_rings.dup(12)
				])
			};
			period.wait;
		} 
	});
		
	// now simply hook everything up.
	OSCdef.new(
		key: \toggle1,
		func: {|msg| (msg[1] > 0.5).if( { ~klankPlayer.resume }, { ~klankPlayer.pause } ) },
		path: '/1/toggle1'
	);
	OSCdef.new(
		key: \fader1,
		func: {|msg| ~mixedKlanksMixer.set(\amp, msg[1].clip(0, 1)) },
		path: '/1/fader1'
	);
});

// testing
~klankPlayer.resume
~mixedKlanksMixer.set(\amp, 0.4)
~klankPlayer.pause
s.plotTree

Adding other mixing capabilities to this is now much easier, for example you could add some filtering or compression by just extending the synth \mixedKlanksMixer, I think this is perhaps close to what you intended?

2 Likes

This is a very nice solution, and very clear, thank you Jordan

Just in case your interested, I wrote a library for TouchOSC once which allows you to write

o = OSCMapper(\myLayout);

// in a ndef
Ndef(\mySine, {SinOsc.ar!2 * o['/1/fader1'].asNdef}).play;

// in language
o['/1/fader1'].value;

// in pattern
(
p = Pbind(
    \instrument, \default,
    \dur, 0.5,
    \degree, Pxrand((0..10), inf),
    \amp, o['/1/fader1'].asPdefn,
).play;
)
1 Like