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

Very clear examples, thank you a whole lot for responding immediately and taking the time to put this together. Nice collection of tips.
Well done for the site, the more tutorial material, the merrier.

2 posts were split to a new topic: On norns, can I access MIDI devices from supercollider or other processes?

4 Likes

I don’t quite get what it means

Hi everyone.
The example

// 60Hz Gabber Rave 1995

on the supercollider main website (https://supercollider.github.io/)contains an array preceded in by a star operator in pbind. like:

Pbind(*[

If I remove the star, Pbind complains about a missing part of the pair, which is expected.
Is this some multiplication or what is going on here? Would be really cool if anyone could shed some light. This makes me feel dumb :sweat_smile:
Any hint appreciated, thanks in advance.