Pdef into an effect

better explanation below. I want to start simple. Play a Pdef through an effect made with a SynthDef.

I really wanted to just do something simple, Like send a hat sample through a Klank SynthDef. But I see it, The Pdef is calling the hat in a folder. It’s just triggering that sample somewhere else. The Pdef not making the sound. I don’t know how to bring the two together

(
SynthDef(\dynklankEffect, { arg in, out=0, i_freq;
    var klank, n, harm, amp, ring;
     var input = In.ar(in, 2);
    // harmonics
    harm = \harm.ir(Array.series(4, 1, 1).postln);
    // amplitudes
    amp = \amp.ir(Array.fill(4, 0.05));
    // ring times
    ring = \ring.ir(Array.fill(4, 1));

	klank = Klank.ar(`[harm, amp, ring], input);

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

Pdef(\1,
    Pbind(*[
       type: \cln,
        snd: \mmd,
        num: 0,
        dur: 0.9,
        amp: 1,
        tempo: 1,
    ])
).play(quant:1);

1 Like

Claude helped me.
But I don’t know how to set the Arrays in the Klank SynthDef. if I add args I get "function variable ‘harm’ already declared " etc
But it helped me understand Pdefs and effects, atleast, If I add the Arrays hard wired in the SynthDef it works, but it would be nice to know how to Update them from within a Pbind.
These don’t work cause I can’t search for help on what “\harm.kr” is. It isn’t working. Looks like it should. But it’s not. If I modify the SynthDef with actual Arrays this all works. (other than the sending arrays part)


~klankBus = Bus.audio(s, 2);  // Assuming stereo

SynthDef(\dynklankEffect, { |in, out=0, mix=0.5, harm|
    var input = In.ar(in, 2);
    var harm = \harm.kr(Array.series(4, 1, 1));
    var amp = \amp.kr(Array.fill(4, 0.05));
    var ring = \ring.kr(Array.fill(4, 1));
    var klank = Klank.ar(`[harm, amp, ring], input);
    var output = XFade2.ar(input, klank, mix * 2 - 1);
    Out.ar(out, output);
}).add;

~klankSynth = Synth.tail(nil, \dynklankEffect, [\in, ~klankBus, \out, 0]);

Pdef(\1,
    Pbind(*[
        type: \cln,
        snd: \mmd,
        num: 0,
        dur: 0.9,
        amp: 1,
        tempo: 1,
        out: ~klankBus  // Route to Klank bus
    ])
).play(quant:1);

~klankSynth.set(\mix, 0.2);  // Adjust mix between dry and wet signal (only this works)
~klankSynth.set(\harm, [1333, 332, 443, 4]);  // Set harmonics
~klankSynth.set(\amp, [0.05, 0.04, 0.3, 0.02]);  // Set amplitudes
~klankSynth.set(\ring, [1, 0.8, 0.6, 0.4]);  // Set ring times

If I wanted to update from within the pattern it wrote me this. Im sure this is more complicated than it need be.

Pdef(\1,
    Pbind(*[
        type: \cln,
        snd: \mmd,
        num: 0,
        dur: 0.9,
        amp: 1,
        tempo: 1,
        out: ~klankBus,  // Route to Klank bus

        // Klank control parameters
        klankHarm: Pseq([Array.series(4, 1, 1), Array.geom(4, 1, 1.5)], inf),
        klankAmp: Pwhite(0.03, 0.08, inf),
        klankRing: Pwhite(2, 4, inf),
		klankMix: Pseq([4, 0, 0.5, 0.5],inf),  // oscillate between dry and wet

        // This function will update the Klank synth parameters
        finish: {|ev|
            ~klankSynth.set(
                \harm, ev.klankHarm,
                \amp, ev.klankAmp,
                \ring, ev.klankRing,
                \mix, ev.klankMix
            )
        }
    ])
).play(quant:1);

The first problem here is that you are using Klank | SuperCollider 3.13.0 Help which state

NOTE: for dynamic changes of parameters refer to DynKlank

So as your goal is to modulate the resonator bank, you should use DynKlank | SuperCollider 3.13.0 Help - disclaimer: Klank and Klang are a bid of an oddity in SC as they use a specificationsArrayRef which is somehow unique to them as its syntax relies on a prepended ` on the array.

Another thing that is really important to understand: Once you have synth playing, you can change its parameters but you can not change the DSP graph structure anymore. This is the reason why if control flows are not working on the server and one has to instead rely on methods such as Select, as every possible paths needs to be already there and calculated at all times.
As the Klank is a resonator bank internally will iterate over the specified array and create as much resonators as needed, we can not change the number of these resonators anymore after we created them, so we must be sure to always send the proper number of values to DynKlank from then on. Otherwise we would need to spawn additional resonators which is not possible (but ofc you can come up with more high level structures which would allow something like this).

As with many things in SuperCollider, there are many ways of doing the same thing, as there are different styles.

I personally found the more verbose style of declaring SynthDefs and creating groups where I have to take care of the order etc. not my cup of tea and I only started to dig more into SC when I found the jitlib dialect as it allows for more iterative progression (which is IMO more beginner friendly), so here is really vanilla style of doing some FX chaining and controlling them via a Pdef.
Sadly most of the tutorial resources do not introduce SC via this dialect which is not really obscure, so I’ll just post the stuff that I would have helped me some years ago.

// start clean
Ndef.clear;

// declare an ndef
Ndef.ar(key: \cool, numChannels: 2);

// play it - it will be still quiet as it is empty
Ndef(\cool).play;


// place an actual sound at position 0
(
Ndef(\cool)[0] = {
	var sig = PMOsc.ar(
		carfreq: \carfreq.kr(100.0),
		modfreq: \modfreq.kr(100.0),
		pmindex: \pmindex.kr(0.0),
		// make it stereo
		modphase: \modphase.kr(0.0) * [1.0, 1.2],
	);
	sig * \amp.kr(0.2);
}
)

// lets put a simple pattern at position 10 of our ndef
(
Ndef(\cool)[10] = \pset -> Pbind(
	\carfreq, Pseq([100, 400, 300], inf),
	\modfreq, Pseq([200, 300], inf),
	\pmindex, 2.0,
	\dur, 0.5,
	// we will need this for the next step
	\myGate, 1.0,
)
)

// lets add a gate to this at position 1
// the \filter directive will give us the output of the
// sounds "above us" as the first parameter of our function

(
Ndef(\cool)[1] = \filter -> {|in|
	var env = Env.perc(
		attackTime: \attackTime.kr(0.01),
		releaseTime: \releaseTime.kr(0.1),
		curve: \curve.kr(-4.0),
	// here we use the previous myGate variable at trigger rate
	).ar(gate: \myGate.tr(1.0));
	in * env;
}
)


// we can use the same approach to add effects to it
(
Ndef(\cool)[5] = \filter -> {|in|
	SelectX.ar(\klankWet.kr(0.4), [in,
		DynKlank.ar(
			specificationsArrayRef: `[
				// freq
				6.geom(\freqStart.kr(100.0), \freqGrow.kr(2.01)),
				\klankAmp.kr(0.01),
				\klankDecay.kr(1.0),
				
			],
			input: in,
			freqscale: \freqscale.kr(1.0),
		);
	]);
}
)

// lets update our simple pattern again to also control this klank fx
(
Ndef(\cool)[10] = \pset -> Pbind(
	// pattern tempo
	\dur, 0.125,
	// synth controls
	\carfreq, Pseq([100, 400, 300], inf),
	\modfreq, Pseq([200, 300], inf),
	\pmindex, Pseq((-10..10)/2, inf),
	\attackTime, 0.01,
	// our gate
	\myGate, 1.0,
	// klank controls
	\freqStart, Pseq([100, 400, 800], inf),
	\freqscale, Pseq((100..140)/100, inf),
	\klankDecay, Pseq([1.5, 0.1], inf),
	\freqGrow, Pseq(((201..210)/100).scramble, inf),
	\klankWet, 0.6,
)
)

While this is running you can tweak the implementation of your sound (at Ndef(\cool)[0]), change its gate or its fx.

If you find this way of doing things interesting you can take a look at NodeProxy roles | SuperCollider 3.13.0 Help or JITLib | SuperCollider 3.13.0 Help in general.

Hope this helps you, and even if you are not into this kind of SC dialect, it may help you to port it to your kind of style :slight_smile:

edit: I tried to implement it in a “vanilla” manner (by playbacking two patterns in parallel via Ppar, but I failed. It seems there is a bug in the event stack, see Can not set arbitrary parameters on running synth via `set` Event · Issue #6416 · supercollider/supercollider · GitHub)

Edit2: Figured it out, here is the more vanilla way of doing it

// declare the basic synthdef
(
SynthDef(\cool, {|out, gate=1, carfreq=100, modfreq=100, pmindex=0.0, modphase=0.0|
	var sig = PMOsc.ar(
		carfreq: carfreq,
		modfreq: modfreq,
		pmindex: pmindex,
		modphase: modphase * [1.0, 1.2],
	);
	var env = Env.adsr(
		attackTime: \attackTime.kr(0.01),
		releaseTime: \releaseTime.kr(0.1),
		curve: \curve.kr(-4.0),
	).ar(gate: gate, doneAction: Done.freeSelf);
	sig = sig * env * \amp.kr(0.1);
	Out.ar(out, sig);
}).add;
)


// declare the fx synthdef
(
SynthDef(\klankFx, {|out=0, fxBus|
	var in = In.ar(bus: fxBus, numChannels: 2);
	
	var sig = SelectX.ar(\klankWet.kr(0.4), [in,
		DynKlank.ar(
			specificationsArrayRef: `[
				// freq
				6.geom(\freqStart.kr(100.0), \freqGrow.kr(2.01)),
				\klankAmp.kr(0.01),
				\klankDecay.kr(1.0),
				
			],
			input: in,
			freqscale: \freqscale.kr(1.0),
		);
	]);
	
	Out.ar(bus: out, channelsArray: sig);
}).add;
)

// declare an fx bus
~fxBus = Bus.audio(server: s, numChannels: 2);
// create a klank group
~klankGroup = Group();
// create the fx synth
~fxSynth = Synth.after(~klankGroup, \klankFx, [\fxBus, ~fxBus]);


(
Pdef(\mySynth, Pbind(
	\instrument, \cool,
	\out, ~fxBus,
	\group, ~klankGroup,
	\dur, 0.5,
	\legato, 0.1,
	\pmindex, 1,
	\carfreq, Pseq([100, 200], inf),
	\modfreq, Pseq([100, 200, 400], inf),
)).play;
)

// change fx params
~fxSynth.set(\klankDecay, 0.2.exprand(2.0).postln)
~fxSynth.set(\freqGrow, 2.0.exprand(2.2).postln)

(
Pdef(\myFx, Pbind(
	\id, ~fxSynth.nodeID,
	\type, \set,
	\dur, 0.5,
	// some magic...
	\args, [\freqGrow, \klankDecay,],
	\klankDecay, Pexprand(0.2, 2.0),
	\freqGrow, Pexprand(2.0, 2.2),
));
)

Pdef(\mySynth).clear;
Pdef(\myFx).clear;

// or write patterns which are executed in paralell
(
Pdef(\myPattern, Ppar([
	Pbind(
		\instrument, \cool,
		\out, ~fxBus,
		\group, ~klankGroup,
		\dur, Pseg([0.1, 1.0], durs: 10, curves: \exp, repeats: inf),
		\legato, 0.1,
		\pmindex, 1,
		\carfreq, Pseq([1000, 2000], inf),
		\modfreq, Pseq([100, 200, 400], inf),
	),
	Pbind(
		\id, ~fxSynth.nodeID,
		\type, \set,
		\dur, 0.5,
		// some magic...
		\args, [\freqGrow, \klankDecay,],
		\klankDecay, Pexprand(0.2, 2.0),
		\freqGrow, Pexprand(2.0, 2.2),
		\freqStart, Pseq([100, 400, 800], inf),
	),
])).play
)

edit3: First approach for sequencing multiple freqs

Ndef.ar(\some, numChannels: 2);
Ndef(\some).play;

(
Ndef(\some)[0] = {
	var sig = Blip.ar(LFDNoise0.kr(0.5).exprange(1, 10), numharm: [200, 150]);
	sig = DynKlank.ar(specificationsArrayRef: `[
		\freqs.kr([400, 790, 1000, 4000]),
		\amps.kr(0.01),
	], input: sig);
	sig;
};
)

(
Ndef(\some)[5] = \pset -> Pbind(
	\dur, 2.0,
	// freqs needs to be an array!
	\freqs, Pseq([[400, 790, 1000, 4000], [900, 1790, 2000, 3000]], repeats: inf),
)
)

(
// some random choosing
Ndef(\some)[5] = \pset -> Pbind(
	\dur, 2.0,
	\freqs, Pclump(4, Pxrand((10..40)*100, inf)),
).trace
)
1 Like

Thank you very much. So much to learn. Wow. Thanks again

Thank you so much. I def prefer the Jitlib way of things. Im slowly working my way through the docs though. So I haven’t got to it yet

Thank you for the help.

Im using a package called Super Clean. That is my main concern. Should I invest time in it if it isn’t going to work with Jitlib and effects? It indexes samples in a folder on your hard drive. It’s very handy, but im not sure if it plays well with others. I don’t know.

Your Ndef work is great, I like the Jitlib, I tried to tac on some SuperClean code into your examples but I got errors. Super Clean is just these keys in the Pdef → \cln, (calls Super Clean) snd: \mmd (sample folder), and num: for the sample index. Thats all.

So I turned to Claude. It was able to route the Pdef using Super Clean through the DynKlank, But the Ndef is just tacked onto the main output bus and that is why the Pdef with Super Clean is working. I don’t know how to route it through your DynKlank Ndef. I like Super Clean, But it is just indexing samples on the hard drive , It has a lot of effects built in. It’s very cool, I don’t know if many people use it. What do others use? Is it a trivial matter to Play samples with the Jitlib and pattern system? I can always use my own effects later if Super Clean isn’t compatible with the JItlib. I don’t know. Im still going through the docs. Haven’t even got to the JitLib section, But I thought id post this since I was working on it , and it will help me decide whether to invest in using the super clean package

here is what Claude did Step 3 doesn’t work at all.


// Step 1: Define the Ndef with Klank effect

// start clean


Ndef.clear;


(
Ndef(\cool, {
    var input = In.ar(0, 2);  // Read from the main output bus
    var freqs = \freqs.kr(Array.geom(6, 200, 2.01));
    var amps = \amps.kr(Array.fill(6, 0.01));
    var times = \times.kr(Array.fill(6, 1.0));
    var klankEffect = DynKlank.ar(
        `[freqs, amps, times],
        input,
        freqscale: \freqscale.kr(1.0),
    );
    SelectX.ar(\klankWet.kr(0.4), [input, klankEffect]);
});
)

// Play the Ndef
Ndef(\cool).play;

// Step 2: Define the Pdef with SuperClean and Klank parameters
(
Pdef(\1,
    Pbind(*[
        type: \cln,
        snd: \mmd,
        num: 0,
        sustain: 0.4,
        dur: 0.4,
        amp: 0.8,
        tempo: 1,
       
    ])
).play(quant: 1);
)

// Step 3: Set initial values and map parameters
(
// Set initial values
Ndef(\cool).set(
    \freqs, Array.geom(6, 100, 2.01),
    \amps, Array.fill(6, 0.01),
    \times, Array.fill(6, 1.0),
    \freqscale, 1,
    \klankWet, 0.6
);

// Map parameters
Ndef(\cool).map(
    \freqs, Ndef(\1).bus(\freqs),
    \amps, Ndef(\1).bus(\amps),
    \times, Ndef(\1).bus(\times),
    \freqscale, Ndef(\1).bus(\freqscale),
    \klankWet, Ndef(\1).bus(\klankWet)
);
)

Claude’s. (None of Step 3 works)
Explanation:

  1. The Ndef(\cool) defines our Klank effect. It reads the input from the main output bus (where SuperClean is sending its audio) and applies a DynKlank to it. The Klank parameters (frequencies, amplitudes, and decay times) are controlled by kr (control rate) inputs, which allows us to modulate them later.

  2. The Pdef(\1) defines our SuperClean pattern along with the sequences for the Klank parameters. The SuperClean part (\cln type, \mmd sound, etc.) remains unchanged from your original code. The Klank parameters are now defined using Pfunc, which generates new random values for each event. This creates a constantly changing Klank effect.

  3. The final block sets initial values for the Klank parameters in the Ndef and then maps the parameters from the Pdef to the Ndef. Setting initial values ensures that the Klank effect is audible even before the Pdef starts sending values.

Key points to remember:

  • The Ndef processes the main output, which includes the SuperClean sounds.
  • The Pdef controls both the SuperClean sounds and the Klank effect parameters.
  • The Pfunc in the Pdef generates new random values for the Klank arrays on each event.
  • Mapping the parameters allows the Pdef to control the Ndef’s Klank effect.

To use this setup:

  1. Make sure SuperClean is running.
  2. Execute the Ndef definition.
  3. Execute the Pdef definition.
  4. Execute the final block to set initial values and map parameters.

You can modify the Pfunc definitions in the Pdef to create different sequences or patterns for the Klank parameters. This allows for a wide range of dynamic and evolving effects on your SuperClean sounds.

How do I get this

(
Pdef(\1,
    Pbind(*[
        type: \cln,
        snd: \mmd,
        num: 0,
        sustain: 0.4,
        dur: 0.4,
        amp: 0.8,
        tempo: 1,
       
    ])
).play(quant: 1);
)

into these below?

(
Ndef(\cool)[5] = \filter -> {|in|
	SelectX.ar(\klankWet.kr(0.4), [in,
		DynKlank.ar(
			specificationsArrayRef: `[
				// freq
				6.geom(\freqStart.kr(100.0), \freqGrow.kr(2.01)),
				\klankAmp.kr(0.01),
				\klankDecay.kr(1.0),

			],
			input: in,
			freqscale: \freqscale.kr(1.0),
		);
	]);
}
)

// lets update our simple pattern again to also control this klank fx
(
Ndef(\cool)[10] = \pset -> Pbind(
	// pattern tempo
	\dur, 0.125,
	// synth controls
	\carfreq, Pseq([100, 400, 300], inf),
	\modfreq, Pseq([200, 300], inf),
	\pmindex, Pseq((-10..10)/2, inf),
	\attackTime, 0.01,
	// our gate
	\myGate, 1.0,
	// klank controls
	\freqStart, Pseq([100, 400, 800], inf),
	\freqscale, Pseq((100..140)/100, inf),
	\klankDecay, Pseq([1.5, 0.1], inf),
	\freqGrow, Pseq(((201..210)/100).scramble, inf),
	\klankWet, 0.6,
)
)

Is this an actual person or a LLM? If it is a LLM, I’d advise you to not use it: LLMs claim confidence in areas where it has no clue and puts you on a wrong lead. Also the training and use of such networks is somehow amoral as the people who wrote the dataset on which it was trained on were never asked if they are fine with it and it also often violates the license of the code.

IMO time is much better spend digging into the documentation and reading some source code, as this is a skill you’ll need to have for SuperCollider or coding in general.

If you enjoy SuperClean then stick with it :slight_smile:
In the end, JITlib and SuperClean are just different abstractions with a different focus - SuperClean for more “traditional” beat-driven music, JITLib for a more “changing-synthesis-on-the-fly” approach - you gain and loose some on both approaches, but as both abstraction use the same basis, namely Busses, sclang and scsynth, etc. they should be somehow compatible.

I never looked into SuperClean, but here is a way to route an “arbitrary” sound through an Ndef by using Bus | SuperCollider 3.13.0 Help

// create a stero fx bus
~fxBus = Bus.audio(server: s, numChannels: 2);

// consume this fx bus via an ndef
(
Ndef(\myFx, {
	var sig = In.ar(bus: ~fxBus, numChannels: 2);
	CombC.ar(
		in: PitchShift.ar(sig, windowSize: 0.01, pitchRatio: 2.01),
		maxdelaytime: 0.2,
		delaytime: LFDNoise0.kr(0.5!2).exprange(0.001, 0.1),
		decaytime: 4.0,
	) + sig;
}).play(out: 0);
)

(
Pdef(\myPattern, Pbind(
	\legato, 0.1,
	// tell the pattern to use ~fxBus as its output
	\out, ~fxBus,
)).play;
)

Another way is to go even more into JITlib and use the fliter directive

// start clean
Ndef.clear;

(
// use the filter directive of the node proxy roles
// see https://docs.supercollider.online/Reference/NodeProxy_roles.html
Ndef(\myFx, \filter -> { |sig|
	CombC.ar(
		in: PitchShift.ar(sig, windowSize: 0.01, pitchRatio: 2.01),
		maxdelaytime: 0.2,
		delaytime: LFDNoise0.kr(0.5!2).exprange(0.001, 0.1),
		decaytime: 4.0,
	) + sig;
}).play(out: 0);
)

(
Pdef(\myPattern, Pbind(
	\legato, 0.1,
	// output on the bus of the Ndef
	\out, Ndef(\myFx).bus,
)).play;
)

I am pretty sure that SuperClean will allow you to specify where to output your signal (most likely via an out argument).
If you need to wire in an Ndef, you can also use the out argument of its play function to output it on a specific bus.

Hope this helps you a bit :slight_smile:

1 Like

The out: ~fxBus key, in the Pdef did the trick. Simple

Works, works, works! Thank you

This BTW is nonsense – see dscheiba’s comments about LLMs.

Calling bus on a NodeProxy returns the bus holding the NodeProxy’s signal. So that “step” is mapping Ndef(\cool)'s parameters onto Ndef(\1)'s audio output … ??? And, all of those parameters are mapped onto the same audio output (because bus() doesn’t take any arguments – bus(\freqs), bus(\amps) and bus are all the same).

Claude LLM led you down the wrong path here.

hjh

Noted. I agree. Nothing beats hard work and learning it yourself .