I agree. The complexity involved in forcing monophonic behavior out of polyphonic synths is a bit much for my lazy tastes.

Oh, you may be right. I remember trying to solve almost the same problem and my envelope was utterly busted until I added the “dummy” node. I’ll go check my synth code to confirm. It’s foolish of me to type SC advice on my phone :slight_smile:

Careful! This does not work (or, works very very badly…) for kicks. Kicks are usually implemented with, at least in part, a low frequency SinOsc or similar. The phase of the oscillator is not reset if you use a continuously running voice, so triggering it will start at a random place in the oscillators cycle, which can result in very different attacks. Sadly, there are not so many osc’s in SC whose phase can be reset, which makes this use case sorta difficult :frowning:

It’s also a good approach because the number of voices can quickly become out of control with fast triggers and longer release/decay times.

And, it’s pretty rare (outside of prog rock :slight_smile: ) that a drummer has 16 kicks that they play in perfect round-robin style to preserve the separate decay of each one… Monophonic percussion is what our ears are used to, in general.

2 Likes

Ah, I wasn’t aware of that gotcha. I wonder if a set of phase-resettable basic waveform custom UGens would be useful.

2 Likes

Interesting! I actually like the sound of the phase being different between notes so I seem to ignore this. Are there Osc UGens that let you reset the phase? For example, you could use the .sin of a phasor.

Synth kicks often have an initial impulse sound that may mask the phase variation of the “body” sound.

1 Like

I don’t think there are any that take a trigger to reset the phase. For SinOsc, you can set the freq to zero and drive it with a Phasor in the phase argument - this is much faster than .sin, at least. A bunch of the core oscs don’t have a phase argument you can adjust, which is too bad. This would be the better thing to implement than a resetable phase, I think.

1 Like

You’re kinda just shifting the problem though. You still need to reset the phase of the phasor…

It does look like I had to use the dummy node, even without a release. Here’s a snippet of the Synth I mentioned, where I’m using the Env as a sample index lookup for a BufRd (the comment was already there!):

	// need extra node at beginning
	// because EnvGen ONLY hits first ONCE
	// also MIND YOUR RELEASE node (OR lack thereof)
	var envSpec = Env.new(
		[ 0, 0, buffer.numFrames, 0],
		[ 0, (buffer.numFrames) / buffer.sampleRate / playSpeed, 0 ],
		0);
	var index = EnvGen.ar(envSpec, gate: impulse);

Coincidentally, this successfully uses an envelope as a Phasor here. Thus, along with some trig plumbing and/or looping, this might go at least part way to providing a Phasor with resettable phase.

Hmm - this synth behaves exactly the same for me if I delete the first node and first time value. I wonder if this was a bug in an older version of EnvGen that has since been fixed? There were some VERY subtle issues that have been fixed in the last few years…

(
SynthDef(\env, {
	var envSpec = Env.new([0, 1, 0], [1, 0], 10);
	//var envSpec = Env.new([0, 0, 1, 0], [0, 1, 0], 10);
	Out.ar(
		\out.kr,
		WhiteNoise.ar * envSpec.kr(gate:\trig.tr)
	)
}).add;

Pdef(\env, Pmono(
	\env,
	\dur, 1, \trig, 1,
)).play
)

Weird, it definitely works differently (i.e. not correctly) when I delete those nodes. I just tested it twice. For reference, buffer.numFrames was set to 18196 - maybe it works differently with larger values?

I just tested an older version of SC (3.8) and this was indeed broken back then - so It must’ve been fixed since!

2 Likes

Huh! I’m on 3.11.1 here.

Just thought I’d post this here cause it’s awesome. I finally got VST plug-ins working in SuperCollider thanks to this package extension from IEM’s repo:

Made a short video about how to run a VST plugin in SuperCollider thought it might be useful for folks to see:

14 Likes

OMG. Thanks for posting this! Will try this out later today.

1 Like

Yes thank you! It’s very helpful… I understand a lot more about VST in SuperCollider.

1 Like

Hello, supercollider beginner here. Thanks everyone (and nathan, here, specifically) for your input, it’s all very valuable.
I’m wondering if there is any documentation on pattern continuous modulation, as teased here, that you can turn us to, or simply any piece of code.
Thanks again. Hope all’s well.

You’re welcome! First off, I made a misguided suggestion in my earlier post. Using Group /n_set isn’t the best idea since while the /n_sets to broadcast to the synths, every synth still has to be initialized with the correct parameters.

@carltesta brought up control buses, which are much better. Let’s get into those… Single-control version looks like this:

var cutoffBus;

cutoffBus = Bus.control(nil, 1);

Pbind(*[
    instrument: \synth,
    cutoff: cutoffBus.asMap,
]).play;

MIDIdef.cc(\cutoff, { |val, num, chan, src|
    cutoffBus.set(val.linlin(0, 255, 1.0, 3.0));
}, 14);

For many controls, use a loop to create a new control bus for each control name. We build up a baseEvent dictionary as before, and a controlBuses dictionary to hold the buses:

var controlNames, controlBuses, baseEvent;

controlNames = [\cutoff, \pitchBend, \resonance];
baseEvent = ();
controlNames.do { |name|
    var controlBus;
    controlBus = Bus.control(nil, 1);
    controlBuses[name] = controlBus;
    baseEvent[name] = controlBus.asMap;
};

Pbindf(baseEvent, Pbind(*[
    instrument: \synth,
    dur: 0.1,
]).play;

MIDIdef.cc(\cutoff, { |val, num, chan, src|
    controlBuses[\cutoff].set(val.linlin(0, 255, 1.0, 3.0));
}, 14);

If you are using Patterns or Events, be careful not to use any control name that conflicts with the built-in Event type! For example, if you were to add a parameter called \detune, you’ll run into a bug since \detune has a built-in meaning in the default Event type. The patterns system is not smart enough to catch such issues for you.

(When I ran into this bug in my own work, it was the last straw that got me to quit using patterns/events entirely and go back to raw Synths/Routines. I have found that the patterns system has too much built-in magic and edge cases for my taste. Luckily control buses work just as well for Synths/Routines as for patterns.)

Beyond that, a few notes, can elaborate on request:

  • Use lags on the synthdef inputs to avoid stairstepping.
  • By default, every bus is initialized to a 0 value. You will want to set the buses to reasonable defaults immediately after they are created.
  • There is no need for Pfuncn here. Before we were sending numerical values from sclang to scsynth, now we are sending a bus number that points to a changing signal on scsynth. As such, the baseEvent object is no longer changing. only the buses that it points to are changing. But, if you were to mix per-note modulation with continuous modulation, you would need to use the Pfuncn trick above.
  • Since control buses exist on the server, you can play LFO synths into them. You can do this using a control-rate synthdef with an Out.kr to the appropriate bus. Watch out for node ordering, these nodes have to be ordered before the synth nodes that are playing.
  • If you have a lot of MIDI controls then you may want to consider using a loop to abstract over the MIDIdef creations, using a dictionary of ControlSpecs to define the linlin and linexp mappings.

P.S. would like to plug for my blog, where I have a post collecting some SuperCollider usage tips: https://nathan.ho.name/posts/supercollider-tips/ (sorry the https is screwed up, oops) Honestly this topic could become a new post in its own right…

8 Likes