ddwPlug quark

FWIW – I just added temporary SynthDef caching for Plugs based on functions.

Before today’s change, the following pattern would generate a new SynthDef for the Plug for every event. With the change, only the first event creates a SynthDef and the rest reuse the same one.

(
p = Pbind(
	\type, \syn,
	\instrument, \default,
	\freq, Pexprand(200, 500, inf),
	\freqPlug, { |freq|
		// closed function here, possible to hit cache
		Plug({ |freqSynthArg|
			freqSynthArg * LFDNoise3.kr(3).exprange(0.95, 1.05)
		}, [freqSynthArg: freq])
	},
	\dur, Pexprand(0.2, 0.8, inf),
	\legato, Pexprand(0.7, 1.4, inf)
).play;
)

p.stop;

One usage had to be deprecated, though:

(
p = Pbind(
	\type, \syn,
	\instrument, \default,
	\freq, Plug {
		exprand(200, 500) * LFDNoise3.kr(3).exprange(0.95, 1.05)
	},
	\dur, Pexprand(0.2, 0.8, inf),
	\legato, Pexprand(0.7, 1.4, inf)
).play;
)

Without caching, there’s a new exprand value per event. Now there’s only one, the first time. I think it’s a good trade-off, though, because, when Jordan’s improved SynthDefs optimizations land, it will be more expensive to build the same SynthDef repeatedly. Now it will skip the rebuild in common sequencing cases.

hjh

1 Like

This is a really cool idea!

Does this depend on any other quarks?

One thing I was thinking about was that it would be nice to have some kind of recommendations or spotlight for quarks, perhaps that updates 3 times a year? This could be linked to in the help browser, but still be a webpage. As both the quarks gui and this forum aren’t ideal to search through.


FYI, you will be able to disable certain optimizations.

I’ll also add a little helper to do this with useful defaults for this exact situation, where you need reasonably fast compile times.

Something like this…

Synthdef.newFast(...)

Another option might be to disable the deduplication (which is the slow bit) when you have less than, say 20 ugens?

for searching Quarks there’s https://baryon.supercollider.online

I’ve just pushed an experimental branch implementing a couple of new features. They’re not well tested yet, so I’m not merging them into main right away.

One could be considered a bugfix: If you’re modulating a main synth control using a Plug, this could effectively move the access point for .set-ting that parameter from Syn to Plug. If you have more than one layer of modulation, previously it didn’t work. That’s fixed now. There’s an example in the updated help file.

The second is something that I’ve wanted to make work correctly for awhile. I had tried to use Plug implement filter frequency key tracking (to raise the filter frequency when the note frequency goes up). This was fine for a single note, but the old parameter mapping implementation didn’t adjust the key tracking when .set-ting \freq. Now you can tell the ffreq plug that it should be interested in the note frequency:

(
SynthDef(\filterKeyTrack, { |out, ffreq = 1000, freq = 440, baseFreq = 220, ratio = 1.7|
	var adjust = Lag.kr(freq, 0.08).log2 - baseFreq.log2;
	Out.kr(out, (ffreq * (ratio ** adjust)).clip(20, 20000));
}).add;

SynthDef(\sawFilt, { |out, gate = 1, freq = 440, ffreq = 1000, rq = 0.8, amp = 0.5, detun = 1.008|
	var sig = Saw.ar(Lag.kr(freq, 0.08) * [1, detun]).sum;
	sig = BLowPass4.ar(sig, ffreq, rq);
	sig = sig * EnvGen.kr(Env.adsr(0.01, 0.3, 0.6, 0.1), gate, doneAction: 2);
	Out.ar(out, (sig * amp).dup);
}).add;
)

TempoClock.tempo = 132/60;

(
p = Pmsyn(PmonoArtic(\sawFilt,
	\degree, Pxrand([0, 2, 3, 4, 7, 9], inf),
	\octave, Pwrand([3, 4, 5], [0.6, 0.3, 0.1], inf),
	\scale, [0, 1, 3, 5, 7, 8, 10],
	\dur, Pwrand([0.25, 0.5], [0.8, 0.2], inf),
	\legato, Pwrand([0.4, 1.05], [0.3, 0.7], inf),
	\ffreq, 300,
	\ffreqPlug, { |ffreq, freq|
		Plug(\filterKeyTrack,
			[ffreq: ffreq, freq: freq, baseFreq: 65, ratio: 2.2],
			map: (freq: \freq)
		)
	}
)).play;
)

p.stop;

Without the map: line, the filter frequency stays low and the higher notes are less clear; with the map, the note frequencies always get communicated to the key tracking synth, so the upper notes are brighter.

To try, check out the topic/refactorMap branch in GitHub - jamshark70/ddwPlug: SuperCollider dynamic per-note synth patching .

hjh

Found bugs in the previous approach, which are fixed now – if you tried the refactorMap branch, better update it.

hjh

Coming back to this question after quite some time – I’ll have to amend my answer to “In theory, yes, but it’s almost certainly uglier than what I have now.”

Syn Plug
Should (usually) get played on demand (there is basicNew but in general, Syn(...) should make sound) Should wait in a Syn arg list and get rendered when its parent is played
User may specify target and addAction Absolutely forbidden for the user to override node positioning!!! Because that would totally break signal routing
User is responsible for the target output bus (if you play a chord of Syns, you want them to mix in the same place) Bus is internally managed; you do not want parallel instances to mix

Currently, polymorphism handles these distinctions. Trying to flatten them into one class would introduce several kinds of weirdness: constructors with radically different signatures, instance variables that would be populated and used for some instances but ignored in others, methods with if(isRoot) { ... do one thing ... } { ... do another ... } sprinkled throughout (where part of the point of OOP was to eliminate that type of confusion – but it would be necessary in a single class, because some instances should obey the user’s node targeting, while other instances must be positioned automatically based on relationships within the Syn tree).

So it’s a big refactoring job, which would remove some code duplication at the cost of increased confusion. The more I look at it, the less convinced I am.

This was a “before possibly adding to core” investigation – I still think it might be a good addition. One thing I do want to refactor a bit more is the control-mapping logic. Currently this is all maintained in the Syn (top level), with traversal by a while loop. I think recursing through the actual Plug objects is probably more elegant.

So… leave it as it is (as a quark), or I try to do a bit of cleanup here and PR it?

hjh

FWIW (if anyone is watching?) I’ve done this – there’s a new branch, topic/refactorMap3, in the ddwPlug repository, which moves 60-70% of the code into a new abstract class.

The major functional change is: Previously, you’d .set only on the top-level Syn.

x = Syn(\default, [freq: Plug { |freq = 440| freq * (1.5 ** LFDNoise3.kr(1)) }]);

x.set('freq/freq', 880);  // works

x.set(\freq, 220);  // also works: it finds the link between the two levels of 'freq'

x.argAt(\freq).set(\freq, 110);  // not guaranteed to work in more complex cases

After this refactoring, the data structure related to control names and their associated nodes is consistent throughout the tree.

x = Syn(\default, [freq: Plug { |low = 200, high = 500, rate = 8| LFDNoise3.kr(rate).exprange(low, high) }]);

// 'controls' below are built in response to a 'set' request
// (it doesn't waste time populating this data structure
// unless the data in it will be used)
x.set('freq/low', 300);

x.controls.keys;
-> Set[freq/rate, freq, freq/i_out, freq/low, freq/high]

x.argAt(\freq).controls.keys;
-> Set[high, rate, low, i_out]

x.free;

… where, at the top level, the modulator’s parameters are accessed by way of a freq/ prefix, but if you drill down to the modulator itself, the prefix is gone. That is, the modulator’s low parameter is called low relative to the modulator Plug, but relative to the parent Syn, you need to navigate first to the Plug using the prefix freq/. I’ve tested this in a three-level nested case, and it works.

I couldn’t flatten the hierarchy completely for the reasons I noted, but I feel pretty good about the way that this erases one behavioral distinction between Syn and Plug.

I’m not merging it to main yet because I want to beat it up with production code for awhile. Anyway, if you’re using ddwPlug, feel free to check out the topic/refactorMap3 branch and let me know if you run into any breakage.

hjh

Small bugfix went in today – that’s why I didn’t merge to main right away!

hjh

FWIW I think the revision is ready, so I merged it to main today.

hjh