How to better write Ryoji Ikeda/Alva Noto Style Synth

How to better write Ryoji Ikeda/Alva Noto Style Synth

Greetings

How can I better write the following piece of code?

(
~synth16={
	~soundA={[SinOsc.ar([48,49],mul:EnvGen.ar(Env.perc,LFDClipNoise.ar(11))), SinOsc.ar([48,49],mul:EnvGen.ar(Env.perc,LFDNoise0.ar(11)))]*0.3};
	~soundB={CombC.ar(FreeVerb.ar([Saw.ar([48*16],mul:EnvGen.ar(Env.perc,LFDClipNoise.ar(11))), Saw.ar([48*16,49*16],mul:EnvGen.ar(Env.perc,LFDNoise0.ar(11)))]))*0.3};
	~soundC={CombC.ar(FreeVerb.ar([Pulse.ar([48*32],mul:EnvGen.ar(Env.perc,LFDClipNoise.ar(11))), Pulse.ar([48*32],mul:EnvGen.ar(Env.perc,LFDNoise0.ar(11)))]))*0.3};
	~out=~soundA+~soundB+~soundC;
	~out;
};
~synth16.play;
)

vielen danke
T.

1 Like

Use variables (var), not ‘globals’ because they don’t quite do what the name suggests. This is actually a bigger issue than it might seem at first.

Why is ~soundA a function?

B & C follow a similar pattern of Comb, reverb and mul, that pattern should be a function.

Do not copy and paste code, avoid duplication where possible by using functions.

The purpose of doing this is to make the code readable, so you know what is going on quickly. That way, you can reuse bits later in other projects. If you’re doing this on stage and the code is thrown away, does it really matter what it looks like? Probably not.

Also, you posted something similar a few months ago, whilst the first few points were specific about the structure, the others are valid here. Basically, if you have to scroll horizontally, something might be wrong, if the code has more that 3 indents, something might be wrong. There’s exceptions to everything, but that’s generally good advice.

does this look any better?

(
var synth16={
	var soundA={[SinOsc.ar([48,49],mul:EnvGen.ar(Env.perc,LFDClipNoise.ar(11))), SinOsc.ar([48,49],mul:EnvGen.ar(Env.perc,LFDNoise0.ar(11)))]*0.3};
	var soundB={[Saw.ar([48*16],mul:EnvGen.ar(Env.perc,LFDClipNoise.ar(11))), Saw.ar([48*16,49*16],mul:EnvGen.ar(Env.perc,LFDNoise0.ar(11)))]*0.3};
	var soundC={[Pulse.ar([48*32],mul:EnvGen.ar(Env.perc,LFDClipNoise.ar(11))), Pulse.ar([48*32],mul:EnvGen.ar(Env.perc,LFDNoise0.ar(11)))]*0.3};
	var out=soundA+soundB+soundC;
	out=FreeVerb.ar(out)+out;
	out=CombC.ar(out)+out;
	out=out*0.3;
	out;
};
synth16.play;
)

I also tried to improve the identation, but i feel it kind of sucks

(
var synth16={
	
	var soundA={
		[SinOsc.ar(
			freq:[48,49],
			mul:EnvGen.ar(Env.perc,LFDClipNoise.ar(11))), 
		SinOsc.ar(
			freq:[48,49],
			mul:EnvGen.ar(Env.perc,LFDNoise0.ar(11)))]*0.3};
	
	var soundB={
		[Saw.ar(
			freq:[48*16],
			mul:EnvGen.ar(Env.perc,LFDClipNoise.ar(11))), 
		Saw.ar(
			freq:[48*16,49*16],
			mul:EnvGen.ar(Env.perc,LFDNoise0.ar(11)))]*0.3};
	
	var soundC={
		[Pulse.ar(
			freq:[48*32],
			mul:EnvGen.ar(Env.perc,LFDClipNoise.ar(11))), 
		Pulse.ar(
			freq:[48*32],
			mul:EnvGen.ar(Env.perc,LFDNoise0.ar(11)))]
		*0.3};
	
	var out=soundA+soundB+soundC;
	
	out=FreeVerb.ar(out)+out;
	out=CombC.ar(out)+out;
	out=out*0.3;
	
	out;
};
synth16.play;
)

I would probably write it like this:

(
var synth16 = {
	var freq = [48, 49];
	var freq2 = 48 * 32;
	var gate1 = LFDClipNoise.ar(11);
	var gate2 = LFDNoise0.ar(11);
	var mul1 = Env.perc.ar(0, gate1);
	var mul2 = Env.perc.ar(0, gate2);
	
var soundA = { 
		[
			SinOsc.ar(freq, mul:mul1), 
			SinOsc.ar(freq, mul:mul2)
		] * 0.3 		
	};
	
	var soundB = {
		[
			Saw.ar(freq * 16, mul:mul1),
			Saw.ar(freq * 16, mul:mul2)
		] * 0.3
	};
	
	var soundC = {
		[
			Pulse.ar(freq2, mul:mul1), 
			Pulse.ar(freq2, mul:mul2)
		] * 0.3
	};
	
	var out = soundA + soundB + soundC;
	
	out = FreeVerb.ar(out) + out;
	out = CombC.ar(out) + out;
	out = out * 0.3;
	out;
};
synth16.play;
)

or

(
var synth16 = {
	var freq = [48, 49];
	var freq2 = 48 * 32;
	var gate1 = LFDClipNoise.ar(11);
	var gate2 = LFDNoise0.ar(11);
	var mul1 = Env.perc.ar(0, gate1);
	var mul2 = Env.perc.ar(0, gate2);
	
	var soundA = { [SinOsc.ar(freq, mul:mul1), SinOsc.ar(freq, mul:mul2)] * 0.3 };	
	var soundB = { [Saw.ar(freq * 16, mul:mul1), Saw.ar(freq * 16, mul:mul2)] * 0.3 };	
	var soundC = { [Pulse.ar(freq2, mul:mul1), Pulse.ar(freq2, mul:mul2) ] * 0.3 };
	
	var out = soundA + soundB + soundC;
	
	out = FreeVerb.ar(out) + out;
	out = CombC.ar(out) + out;
	out = out * 0.3;
	out;
};
synth16.play;
)

I find the first way slightly easier to read, the second on is a little more condensed.

Note that I have substituted the EnvGen.ar(Env, gate) syntax with the Env.ar(doneAction, gate) syntax.

I feel that adding white spaces enhances readability a lot. Also I would generally avoid brackets, curly brackets etc. with uneven identation unless it is a one-liner or the brackets are the outer brackets like:

~f = {


}

~someArray = [
[~subArray1],
[~subArray2]


]

There is a good guide to best practice here. You are not forced to follow it but I think it is wise to do so because

  1. It makes the code easier to read for you (I think)
    and
  2. It makes the code much easier to read for anyone else, since many people (more or less) follow these guidelines.

Using variables for re-used elements (like freq1, gate1 etc) both enhances readability and creates more efficient code (no reason to use 2 Ugens if 1 is enuff).

1 Like

hi thor

very well done. maybe we are missing a freq3 (in fact there are 3 fundamental frequencies, for each of the voices not 2. even though i tried to fix that, i am not getting 3 independent voices. if you have ideas about how to fix this, I would be very thankfull. any further iterations, on how to improve the general structure of the code would also be highly appreciated

kind regards

(
var synth16 = {

	var freq = [48, 49];
	var freq2 = 48 * 16;
	var freq3 = 48 * 32;

	var gate1 = LFDClipNoise.ar(11);
	var gate2 = LFDNoise0.ar(11);

	var soundA = {
		[
			SinOsc.ar(freq, mul:Env.perc.ar(0, gate1)),
			SinOsc.ar(freq, mul:Env.perc.ar(0, gate2))
		] * 0.3
	};

	var soundB = {
		[
			Saw.ar(freq2, mul:Env.perc.ar(0, gate1)),
			Saw.ar(freq2, mul:Env.perc.ar(0, gate2))
		] * 0.3
	};

	var soundC = {
		[
			Pulse.ar(freq3, mul:Env.perc.ar(0, gate1)),
			Pulse.ar(freq3, mul:Env.perc.ar(0, gate2))
		] * 0.3
	};

	var out = soundA + soundB + soundC;

	out = FreeVerb.ar(out) + out;
	out = CombC.ar(out) + out;
	out = out * 0.3;

	out;

};

synth16.play;
)

Note also that in the above example there is nothing gained from using functions but I kept it if you wanted to reuse the code in larger context. In that case you would probably want include some arguments in the functions, like

var soundA = {|freq| .... }

For the above you can simply do

var soundA = [..., ....] * 0.3;
var soundB = [..., ...] * 0.3;
etc.

I actuallly did the opposite, but i added in a couple variables for improving sound variation.
any feedback would be highly appreciated

(
var synth16 = {

	var freq = [48, 49];
	var freq2 = 48 * 16;
	var freq3 = 48 * 32;

	var triggerRate1=[10,5,2.5].choose;
	var triggerRate2=[10,5,2.5].choose;
	var triggerRate3=[10,5,2.5].choose;

	var gate1_1 = LFDClipNoise.ar(triggerRate1);
	var gate2_1 = LFDNoise0.ar(triggerRate1);

	var gate1_2 = LFDClipNoise.ar(triggerRate2);
	var gate2_2 = LFDNoise0.ar(triggerRate2);

	var gate1_3 = LFDClipNoise.ar(triggerRate3);
	var gate2_3 = LFDNoise0.ar(triggerRate3);

	var soundA = [
		SinOsc.ar(freq, mul:Env.perc.ar(0, gate1_1)),
			SinOsc.ar(freq, mul:Env.perc.ar(0, gate2_1))
		] * 0.3;

	var soundB = [
			Saw.ar(freq2, mul:Env.perc.ar(0, gate1_2)),
			Saw.ar(freq2, mul:Env.perc.ar(0, gate2_2))
		] * 0.3;

	var soundC = [
			Pulse.ar(freq3, mul:Env.perc.ar(0, gate1_3)),
			Pulse.ar(freq3, mul:Env.perc.ar(0, gate2_3 ))
		] * 0.3;

	var out = soundA + soundB + soundC;

	out = FreeVerb.ar(out) + out;
	out = CombC.ar(out) + out;
	out = out * 0.3;

	out;

};
synth16.play;
)

maybe turning the top variables into arrays of variables, and passing in what is needed would be a decent option, but i sort of get stuck there

bearing now this in mind, how could i define the range of tRateMul, and the other multipliers

(
Ndef(\synth, {|mulFreq1=1,mulFreq2=1,mulFreq3=1,tRateMul=1|

	var freq = [48, 49] * mulFreq1;
	var freq2 = 48 * 16 * mulFreq2;
	var freq3 = 48 * 32 * mulFreq3;

	var triggerRate1=[10,5,2.5].choose*tRateMul;
	var triggerRate2=[10,5,2.5].choose*tRateMul;
	var triggerRate3=[10,5,2.5].choose*tRateMul;

	var gate1_1 = LFDClipNoise.ar(triggerRate1);
	var gate2_1 = LFDNoise0.ar(triggerRate1);

	var gate1_2 = LFDClipNoise.ar(triggerRate2);
	var gate2_2 = LFDNoise0.ar(triggerRate2);

	var gate1_3 = LFDClipNoise.ar(triggerRate3);
	var gate2_3 = LFDNoise0.ar(triggerRate3);

	var soundA = [
		SinOsc.ar(freq, mul:Env.perc.ar(0, gate1_1)),
			SinOsc.ar(freq, mul:Env.perc.ar(0, gate2_1))
		] * 0.3;

	var soundB = [
			Saw.ar(freq2, mul:Env.perc.ar(0, gate1_2)),
			Saw.ar(freq2, mul:Env.perc.ar(0, gate2_2))
		] * 0.3;

	var soundC = [
			Pulse.ar(freq3, mul:Env.perc.ar(0, gate1_3)),
			Pulse.ar(freq3, mul:Env.perc.ar(0, gate2_3 ))
		] * 0.3;

	var out = soundA + soundB + soundC;

	out = FreeVerb.ar(out) + out;
	out = CombC.ar(out) + out;
	out = out * 0.3;

	out;

}).gui;
)

you forgot the synth16.play at the end in the second to last example.

In the Ndef: change last line from

out

to

Out.ar(0, out);

or better and more flexible, rename the out in the Ndef to something else, I like to use

var sig = …

Then

Ndef(\synth, {|out = 0,…}

Out.ar(out, sig);

The convention is to name the output bus ‘out’ (or ‘outbus’). For many people, including myself, naming the signal ‘out’ is confusing because of this.

finally, how can I:

  • add midi maps to this Ndef
  • turning the Ndef with the UI and the Midi Mappings into a class?

You seem very determined to turn every piece of code you write into a Class. I still fail to see the reasoning behind this. You stated in an earlier post that

because then it becomes easier to map these things into NDefs, and Classes Hierarchically to achieve some opensoundcontrol mapping and that sort of thing

I don’t really follow that argument. I don’t have much experience writing classes but the general idea behind classes is to create rather small building blocks which can be reused many times. SynthDefs and Ndefs are generally higher abstractions than classes. My advice would be to develop a better overall understanding of SC before you decide to ‘break the rules’. Search for ‘writing classes’ in the help browser if you wanna know more about it.

As far as the midi stuff goes: Write some MIDIdefs which take incoming MIDI nns, MIDI ccs or whatever you use and map the values to your Ndefs or SynthDefs inside the MIDIdef. The easy thing to do is to use one of the MIDIdef convenience classes, like MIDIdef.cc or MIDIdef.noteOn:

MIDIdef.noteOn(\noteOn, {|vel, nn, chan, srcID|
	nn.postln;
	// map the nn (or cc if you are using MIDIdef.cc) to the required value
	// use .set( ) messages to set the args of the Ndef or SynthDef.
})

This looks so much better already, I can actually see what the code is doing.
You can probably stop there… but just to show off some more language features…

{
	var mk_gate = {|freq| 
		[LFDClipNoise.ar(freq), LFDNoise0.ar(freq)]
	};
	var mk_wv = { |wv, freq, gate|
		wv.asClass.ar(freq) * Env.perc.ar(0, mk_gate.(gate))
	};
	
	var sa = mk_wv.(wv: \SinOsc, freq: [[48, 49]], gate: [10, 5, 2.5].choose);
	var sb = mk_wv.(wv: \Saw,    freq: 48 * 16,    gate: [10, 5, 2.5].choose);
	var sc = mk_wv.(wv: \Pulse,  freq: 48 * 32,    gate: [10, 5, 2.5].choose);
	
	var dry = (sa + sb + sc) * 0.3;
	var rev = FreeVerb.ar(dry) + dry;
	var comb = CombC.ar(rev) + rev;
	comb * 0.3;
}.play

There are two things here. First you can use more multichannel expansion, note the double brackets for sa’s freq, this makes sure the envelope is applied to both.

And since you are varying the Ugen class, you can actually just pass this as a symbol and use .asClass.

This is what I love about supercollider. Say you want to talk about different waveforms. Make a function that takes a waveform and now you can talk/compose/think using just what you care about: chef’s kiss.

Now for just the waveforms you have, the above was probably overkill and you’d be better off writing the classes directly, however…

var dry = [
		mk_wv.(wv: \SinOsc, freq: [[48, 49]], gate: [10, 5, 2.5].choose),
		mk_wv.(wv: \Saw,    freq: 48 * 16,    gate: [10, 5, 2.5].choose),
		mk_wv.(wv: \Pulse,  freq: 48 * 32,    gate: [10, 5, 2.5].choose)
].sum * 0.3;

If you rejig it into an array… now the code is just asking for more elements to be added.

Here you said that the refactoring invited multiplier be added to the code.

The point being, how you write the code, structure it and group it together, engenders different musical actions. Supercollider is flexible enough to allow you to talk about what you want.

Also, the [10, 5, 2.5].choose is evaluated when the code is executed just the first time, so could all be 10.

Dear @jordan and @Thor_Madsen

thanks for the code instructions so far. I will keep this code structure right now, as it shows better syntax construction. Bearing this in mind:

  • please give me concise examples, on how to map freqMul and gateMul, to something like a cc coming from an external app to supercollder
  • please tell me how can I convert both the ndef, the ui, the synthesis routine, and the midi maps can be embedded into a class

@Sam_Pluta if you have some spare time please give a shot to this. by the way, please send greetings to papa Joel (Ryan), and to uncle George (E. Lewis)

(
Ndef(\ikeda, {
	|freqMul=1, gateMul=1|
	
	var mk_gate = {|freq|
		[LFDClipNoise.ar(freq*freqMul), LFDNoise0.ar(freq*freqMul)]
	};
	var mk_wv = { |wv, freq, gate|
		wv.asClass.ar(freq*freqMul) * Env.perc.ar(0, mk_gate.(gate*gateMul
		))
	};

	var sa = mk_wv.(wv: \SinOsc, freq: [[48, 49]], gate: [10, 5, 2.5].choose);
	var sb = mk_wv.(wv: \Saw,    freq: 48 * 16,    gate: [10, 5, 2.5].choose);
	var sc = mk_wv.(wv: \Pulse,  freq: 48 * 32,    gate: [10, 5, 2.5].choose);

	var dry = (sa + sb + sc) * 0.3;
	var rev = FreeVerb.ar(dry) + dry;
	var comb = CombC.ar(rev) + rev;
	comb * 0.3;
}).gui;
)

may thanks for the insight and the feedback to everyone

Have you read and studied the documentation for:

  • Routines
  • MIDIdefs
  • Writing Classes

?

for writing classes and routines, i did. i was reading documentation for mididefs. the only thing i couldn’t figure out is how to set conditionals for a specific note on value (i.e. if(nn == 60) {Server.freeAll; synth1.play}), but have gotten some basic understanding of how it works, i can understand routines and classes at a basic level