Another idea: Try scheduling the same events from sclang instead of from the shell. If this resolves the issue it might be the terminal window command you’re using that’s stalling for some reason.

2 Likes

Just diving into sc this week and I’m eager to explore a more in-depth resource. Does anyone have any thoughts on Andrea Valle’s Introduction to SuperCollider? Looking for something detailed but relatively approachable for a newcomer.

I enjoyed this https://ccrma.stanford.edu/~ruviaro/texts/A_Gentle_Introduction_To_SuperCollider.pdf from Stanford’s music technology school as a free, thorough, and well-written resource

6 Likes

if you haven’t worked through this series, yet, this is the place to start:

7 Likes

On tutorial 8 now and loving it. I personally go back and forth on text-based learning (especially when it’s self-guided), but it seems like a good idea for something this dense :slight_smile:

1 Like

Supercollider: Pulsar synthesis with GrainSin

Using Pulsar synthesis with GrainSin to make harmonic spectrum and formants

In this video I discuss the use of sinusoidal grains to synthesize formants and harmonic spectrum. Also I present some tips on how to naturalize the signal and provide a degree of spatiousness.

Requirements

SuperCollider

These tutorials on SuperCollider are not beginner level. It’s assumed that viewer knows basics of SuperCollider language.

#supercollider

11 Likes

Just found this SuperCollider resource. Lots of stuff there!

2 Likes

Is it possible to modulate the Env or EnvGen parameters while the envelope is running similar to how one might the attack/decay on an analog envelope generator? From what I’ve seen so far, everything is fixed at initialization.

1 Like

Just diving into sc myself, but I think you’re able to modulate envelope parameters so long as you declare corresponding arguments in your SynthDef. I just wrote this as a test and it seems to be working just fine (I’m setting arguments for both the attack and release times, with initial values, and then using a pattern to modulate them):

(
SynthDef(\sine, {
	arg atk=0.01, dec=0.5, freq=440, amp=0.30;
	var sig, env;
	sig = SinOsc.ar({freq}!2);
	env = EnvGen.kr(Env.perc(atk,dec),doneAction:2);
	sig = sig * env * amp;
	Out.ar(0,sig);
}).add;
)

(
Pbind(
	\instrument, \sine,
	\dur, 3,
	\midinote, Pseq([45,50,55,60,65],inf),
	\atk, Pwhite(0.01,4.00,inf),
	\dec, Pwhite(0.5,2,inf),
).play;
)
2 Likes

@Olivier Thanks for the example! There’s some new stuff in here for me to learn about.

Does Pbind re-use/re-trigger a single synth instance or does it create a new one for each “note”? It sounds like it’s creating a new one with new parameters each time.

I’m trying to achieve something like this:

/* start new sound with 10 second decay */
s = Synth.new(\sine, [\dec, 10]);
/* a moment later, change running envelope decay to 1 second */
s.set(\dec, 1);

In this case my desired behavior would be to have the envelope scale the remaining decay time to the new total decay. So if I fired the envelope with a decay of 10 seconds and then changed it to 1 second after 1 second had already elapsed, I would expect it to decay for 0.9 more seconds (I think).

Instead, the envelope continues to decay for 10 seconds and I can’t change it while it’s running.

1 Like

That’s a good question, though the answer lies beyond my skillset, I’m afraid. Would be curious to know myself.

I don’t think you can change envelope parameters while the envelops is still running.

1 Like

Bummer. I wonder if it would be possible to write my own envelope UGen that behaves like I want…

You can modulate the parameters of an envelope while it is running. Here is the basic set up.

(
SynthDef(\a, {
	var trig = \trig.tr;
	var freq = \freq.kr(220);
	var sig = SinOsc.ar(freq);
	var aeg = Env.perc(attackTime:\atk.kr(0.01).poll).kr(gate:trig);
	sig = sig * aeg * \amp.kr(0.1);
	sig = Splay.ar(sig);
	Out.ar(\out.kr(0), sig);
}).add;
)

x = Synth(\a);
(
Pdef(\p, Pbind(
	\type, \set, 
	\id, x.nodeID, 
	\args, #[\freq, \trig, \atk], 
	\degree, Pwhite(0, 4), 
	\trig, 1,
	\atk, Pwhite(0.01, 0.5)
)).play;
)

Pdef(\p).stop;
x.free;
2 Likes

I can see that the envelope parameters in this example are changing while the envelope is running, but the envelope doesn’t seem to take the new parameters into account until it is re-triggered.

For example:

x = Synth(\a, [\atk, 10]);
x.set(\trig, 1);
x.set(\atk, 0.01);

If I execute these sequentially in the REPL I get a note with 10 seconds of attack. The envelope doesn’t change when I execute x.set(\atk, 0.01).

I was looking at some of the other envelope UGens and Decay and Decay2 seem to sort of give me what I’m looking for.


(
SynthDef(\sine, {
	arg dec=0.5, freq=440, amp=0.30;
	var sig, env;
	sig = SinOsc.ar({freq}!2);
	env = Decay2.kr(Impulse.kr(0), 0, dec);
	sig = sig * env * amp;
	Out.ar(0,sig);
}).add;
)

s = Synth.new(\sine, [\dec, 10]);
s.set(\dec, 1);

A word of warning with using Decay like this (it bites me EVERY time): decay will produce very large values if you send it triggers that are longer than 1 sample (or 1 control period for kr signals). Even triggering it before it fully decays can produce values >1. This can occasionally be valuable, but it’s easy to get in trouble if you decide later “oh, I’ll trigger this really fast, that’ll be cool” and it blows up.

I believe you’re only having a problem because you’re not sending your \attack and \trig at the same time / in the same message - the attack will only take effect when the envelope is triggered, if it’s received just after a trigger, you’ll be running through the envelope with the old value. This should work:

x = Synth(\a, [\atk, 10]);
x.set(\trig, 1, \atk, 0.01);

You can also modify the control a little by making e.g. your \atk parameter a trigger parameter, e.g. \atk.tr instead of atk.kr - this means when you send it a new value, it outputs that value for 1 sample (a trigger), and then goes back to zero. Then, you could do:

attack = \atk.tr;
Env.perc(Latch.kr(attack, attack), 1).kr(gate:attack); // latch will hold the value when triggered

and simply trigger with x.set(\atk, 1). Though to be totally honest, I would avoid this unless you’re building a really complex synth, because it’s slightly less clear at a glance what’s going on :slight_smile:

Also, there’s a pattern object that encapsulates the “run a synth once, and then send changes to it” - take a look at Pmono. Here’s an example of what that looks like, with a re-triggering envelope and different note values:

(
SynthDef(\envel, {
	var sig, env, freq;
	
	// Perc envelope, with retriggering.
	env = Env.perc(\attack.kr(0.01), \release.kr(4), curve:[-4, -20]).kr(gate:\trig.tr);
	
	// Two freqs, slightly detuned, with a lag to smooth changes
	freq = \freq.kr.lag(0.03);
	freq = [
		freq,
		freq * env.linlin(0, 1, 1.005, 1.02)
	];
	
	// Two oscillators, with a fade parameter
	sig = XFade2.ar(
		LFCub.ar(freq),
		LFSaw.ar(freq) * 0.2,
		\osc.kr(-1).lag(0.5).poll
	);
		
	// Apply envelope and a low-pass
	sig = env * sig;
	sig = LPF.ar(sig, env.linexp(0, 1, 200, 9000));
	
	// ... and some delay, with a param for delay time
	//     (and nice clicksssss)
	sig = sig + CombC.ar(sig, 4, \delay.kr(2)/[4, 3], 4);
	
	Out.ar(\out.kr(0), sig * [1, 1]);
}).add;

Pdef(\e1, Pmono(
	\envel,
	\trig, 		1,
	\delay,     	Prand([2, 4], inf),
	\dur, 		Prand([1, Pseq([1/3], 4)], inf),
	\attack, 	Prand([0.01, 0.1, 0.5], inf),
	\osc, 		Prand([-1, 0, 1], inf),
	\degree, Pseq([
		Pseq([0, 1, 3, -2], 4),
		Pseq([0, 2, 5, -1], 4)
	], inf)
)).play;
)

And finally, if you’re getting in to heavy envelope modulation (beyond just one or two parameters), you can also just send whole envelopes to the synth as a parameter. Here’s something I wrote up with a few tricks for doing that: https://scsynth.org/t/envelope-passing-for-synths-and-patterns/2048/6

2 Likes

Oh, I remember now that you can get closer to this behavior by using IEnvGen, which is the same as EnvGen except you can look up the envelope position yourself - but the extra bit is that when you change Env parameters, they take effect immediately:

	envPosition = Sweep.kr(\trig.tr, 1); // ramp up from 0 every time a trigger happen - this moves through the envelope
	env = IEnvGen.kr(
		Env.perc(\attack.kr(0.01).lag(0.5), \release.kr(4), curve:[-4, -20]),
		envPosition
	);

I THINK you’ll have to put a lag on parameters you’re going to modulate, otherwise you’ll get clicks when you change. I don’t recall exactly how this works, but it’s worth experimenting to see if you can get closer to what you want.

3 Likes

Thanks for the help everyone! I was finally able to get it working using IEnvGen. I used Decay to generate an end-of-rise signal which then Selects between attack and release rates. The rate for the current phase of the envelope gets sent to Sweep which controls the position of an exponential envelope with 1s rise and fall.

(
SynthDef.new(\sine, {|hz=440,amp=0.3,atk=0.01,rel=1|
	var osc = SinOsc.ar(hz);
	
	var rise = Decay.ar(Impulse.ar(0), atk);
	var env_phase = rise <= 0.001;

	var atk_rate = 1 / atk;
	var rel_rate = 1 / rel;
	var env_rate = Select.kr(env_phase, [atk_rate, rel_rate]);
	var env_pos = Sweep.ar(Impulse.ar(0), env_rate);
	
	var amp_env = Env([0.01, 1, 0.001], [1, 1], 'exp');
	var env_gen = IEnvGen.ar(amp_env, env_pos);
	var sig = osc * env_gen * amp;

	var done = env_gen <= 0.001;
	FreeSelf.kr(done);

	Out.ar(0, sig.dup);
}).add;
)

x = Synth.new(\sine, [\atk, 10, \rel, 10]);
x.set(\atk, 0.01);
x.set(\rel, 1);
2 Likes

Trying to do some additive synthesis in SC. I ran into the issue of not being able to fill an array based on an argument. I’m curious why this is and I wasn’t able to find an answer after a half an hour of googling. The REPL says it’s because the index isn’t an integer, even if it’s set as one.

If this approach is impossible, what’s another way to define an additive SynthDef?

(
SynthDef(\additive, {
	arg out = 0, 
		partials = 2,
		fundamental = 20,
		decay = 0.5;
	
	var freqs = Array.fill(partials, {
		arg i;
		fundamental * i
	});
	
	var sins = freqs.collect({
		arg freq, i;
		var mult = decay / (i+1);
		SinOsc.ar(freq)*mult
	});
	
	var mix = Mix.new(sins);

	Out.ar(out, mix)
}).add;
)

Arguments to a synth aren’t integers, they are streams of numbers that can change over time (Controls, although if you partials.postln you’ll see an OutputProxy for… reasons). Anyway, Array.fill expects an actual fixed integer that will never change.

The standard way to approach what you’re trying to do is compile multiple different synths, one per different argument that could happen (there are a few tutorials about this on the web, e.g. this official one from SuperCollider: https://supercollider.github.io/tutorials/error-primitive-basicnew-failed)

Here’s your code, adapted to that basic approach:

(
(1..8).do({|partials|
	SynthDef(\additive ++ partials, {
		arg out = 0, 
		fundamental = 20,
		decay = 0.5;
		
		var freqs = Array.fill(partials, {
			arg i;
			fundamental * (i+1);
		});
		
		var sins = freqs.collect({
			arg freq, i;
			var mult = decay / (i+1);
			SinOsc.ar(freq)*mult;
		});
		
		var mix = Mix.new(sins);
		
		Out.ar(out, mix)
	}).add;
});
)

Synth.new(\additive8, [\fundamental, 261.6]);

(I also fixed what I assume was a bug where you multiplied fundamental * i instead of fundamental * (i + 1), assuming you didn’t want to zero out the actual fundamental because i starts at 0)

If pre-defining the range of all possible number of partials is limiting for some reason, there’s slightly trickier approaches where you can dynamically make new synthdefs using SynthDef.wrap, but that probably isn’t needed in your case so I’ll leave it out for now :+1:

2 Likes