Modular Synthesis GUIs for SuperCollider

Hi there -

I have been trying to figure out a basic approach to the SuperCollider GUI that would make the most sense for a modular synthesis-related application.

To my knowledge, there isn’t a basic patch-cable based approach available - but the Quarks library goes deep. I’ve run into a few challenges with Order-of-Execution so far - it’s easy enough to move a Synth to the head or tail - but it’s hard to keep track of where everything is, when working with a larger system.

I’m curious about how people have tackled this problem and some of the rationale in approaches. Are there shared examples of this kind of thing somewhere that I’ve missed?

Thanks

Have you looked at JITLib ? NodeProxy and Ndef take care of order of execution transparently. IIRC here is a special dialect of JITLib expressly for modular patching but I can’t remember who or where!

Yes - I am struggling a little bit with the JITLib paradigm. Since it requires new nodes and buses for recursive patching, it ends up being a bit unwieldy.

This recent thread demonstrates how much can end up going into branching off from a synthesizer patch, in order to work within NodeProxy:
Isolating parts of nodeproxy

If you start to think about managing that many NodeProxies for a big, integrated modular system, where they are branching off in many different directions - it’s hard to imagine a good way to keep them organized or visualized.

The JITModular approach seems to offer a different workflow - but I’m curious about how one would handle something like:
~osc <>> ~osc <>> ~osc <>> ~out;
…where the first ~osc and the last oscillator are the “same” and the second is a different modulator.

All of these problems can be coded around, I’m sure - it’s easy enough to do any of these things as isolated functions or one-off SynthDefs., but I don’t know what the best practice would be, in making a fluid, integrated modular system and I would imagine this is the kind of problem that has been solved many times over.

I’m not clear exactly what is the calculation graph that this represents. That’s three references to a single oscillator module; how does the signal feed back 2/3 of the time and feed to output 1/3 of the time, from physically the same output?

What I’m driving at is that expressing the problem clearly and unambiguously is at least as important as the code design. The above expression is easy to write but I have a feeling that its meaning is ambiguous (and likely should be made illegal).

If the second is a different module, then it must not be called ~osc because that name already refers to a module. Different module, different name.

JITLib uses InFeedback for audio routing, so an a <>> b <>> a structure is allowed.

hjh

Sorry, let me see if I can rephrase this better.

I think the ideal would be to have some sort of definition that could be reused in multiple places, instead of having an ~osc1, ~osc2, ~osc3 for each instance. Granted, when working with Nodes, they would still need to be located at certain numbered slots anyway.

Something like this would be the example I was thinking of:

~osc = { |freq = 20, amp = 0.1|
	Saw.ar((freq+(LeakDC.ar(\in.ar)*freq)), amp).dup
};

The scaling of the amplitude of the preceding oscillator would determine how much of modulation would occur in the successive oscillator.

Something like this, I would think would work:

~osc.(\freq, 2, \amp, 3000) <>> ~osc <>> ~out;

The issue, for some sort of feedback loop, then becomes how to refer to specific instances of each function. This is possible in NodeProxy, but maybe that’s not part of JITModular?

I also seem to be experiencing some blow-ups with the following, which may be part of my misunderstanding the paradigm here:

s.boot;
p = ProxySpace.new.push;

// a stable output location,
// connected (by .play) to the hardware output
~out = { \in.ar(0!2) }; ~out.play;

// a sawtooth oscillator
~osc = { |freq = 6, amp = 0.1|
	Saw.ar(freq, amp*5000).dup.range(0, 1)
};

~osc2 = { |freq = 20, amp = 0.1|
	Saw.ar((freq+(LeakDC.ar(\in.ar)*freq)), amp).dup
};

~osc.(\freq, 20, \amp, 3000) <>> ~osc2 <>> ~out;
//then the blowup: 
~osc.set(\freq, 1);

Maybe there’s a confusion here between module templates (“some sort of definition that could be reused”) and the actual modules doing the calculations.

In a ProxySpace, the ~envVars refer to actual, operating modules. There is not going to be any way to make the environment variables be both templates and modules. ProxySpace already defines environment variables to be the latter. So, templates would have to be stored somewhere else.

That could be an event-style object prototype stored in an interpreter variable, e.g.

t = (
	osc: { ... stuff to define an oscillator ... },
	rlpf: { ... etc ... }
);

Then ~osc1 = t[\osc].

The other way would be to create a class with constructor methods:

JITModule {
	*osc { ... }

	*rlpf { ... }

	*fmOp { ... }
}

And ~osc1 = JITModules.osc(...).

For JITLib, you’re pretty much free to use the node proxy control inputs as you like.

JITModular adds a further condition: if the same control input name appears in multiple modules, it’s assumed that they are the same parameter. That’s not enforced, but if you then go to the next step and sequence the inputs by ~player = \psSet -> Pbind(...), then all freq inputs would get the same value.

So if you have multiple instances of the same module, but their parameters need to be independent, then the input names would need to be generated dynamically. That’s not hard using NamedControl (same as \name.kr()), just thought I should mention it.

If you’re not sequencing JITModular with the psSet proxy role, then you could ignore that.

hjh