Patterns & value ramping

Hi, I’m new to SuperCollider & learning about patterns.

I want to perform value ramping using Pseq (with the ramping passing in synthdef with XLine or similar). So, each successive value sent by Pseq would ramp up from the previous value at a defined duration. That could be just pitch glissando/portamento, or something more interesting.

I am not exactly sure how to do this. I can create the ramping effect in the synthdef, controlled for example by two parameters \freqStart and \freqEnd, as shown here :

(
SynthDef(\test, { |out, freqStart = 20, freqEnd = 20|
    Out.ar(out,
		SinOsc.ar(XLine.kr(freqStart,freqEnd,1), 0, 0.2) *
		Line.kr(1, 0, 1, doneAction: Done.freeSelf)
    )
}).add;
)

Pbind(\instrument, \test, \dur, 1, \freqStart, Pseq([100, 200, 500]), \freqEnd, 5000).play;

. . . but I am hoping someone can show me how to take it a step further. . . so that \freqStart will always be the previous value, and \freqEnd always be the incoming value?

Thanks for your help.

Hi and welcome,

looking forward resp. backwards with patterns - this comes up quite frequently and to my knowledge there’s no built-in datatype, which can handle it smoothly.
The underlying reason is that mostly patterns are thought to be stateless, but here at least you have to store one state. The good news is that the pattern system is flexible enough to make a workaround with an external variable and functional statements, e.g.:

(
~lastFreq = 300;

Pbind(
	\instrument, \test, 
	\dur, 1, 
	\freqRamp, Pseq([100, 700, -100, -700]),
	\freqStart, Pfunc { ~lastFreq },
	\freqEnd, Pfunc { |ev| 
		var next = ~lastFreq + ev[\freqRamp];
		~lastFreq = next;
	}	
).trace.play;
)

The Pfunc takes over the current event with its Function, so inside you can refer to all current values for a calculation.

PSx classes, included in my miSCellaneous_lib quark, deal with parts of this problem. E.g. with PSrecur you can easily define recursive Functions. Now it comes to my mind that this specific (and quite natural) demand of building pairs [current, next] from a given pattern is even not perfectly easy also with PSrecur. It could look like this:

(
~freqRamp = Pseq([100, 700, -100, -700]).asStream;

Pbind(
	\instrument, \test, 
	\dur, 1, 
	[\freqStart, \freqEnd], PSrecur({ |x| [x[0][1], x[0][1] + ~freqRamp.next] }, 4, start: [[0, 300]] )	
).trace.play;
)

Admittedly this is also clumsy. Maybe I should make a dedicated PS pattern, which gives pairs of the form [current, next] based on a pattern of differences. BTW main lib’s Pdiff (undocumented) does the opposite: it gives differences from a given Pattern.

Maybe James has a further solution, I remember related discussions in the past.

An alternative approach would be doing it server-side and using DemandEnvGen, which is quite comfortable for sequenced ramping. Usually I don’t vote for demand rate control as Patterns are more flexible, but as you haven’t defined an (amplitude) envelope in your SynthDef maybe a monophonic solution like this would also be feasible.

HI dkmayer, many thanks for your suggestions !

I have decided to go with your first solution, which seemed to be visually the clearest of the two. However, there did seem to be a bug—the adding of ~lastFreq + \freqRamp value was unnecessary (and provided an incorrect value for \freqEnd).

All I need is the current \freqRamp value, like so :

(
~lastFreq = 100;

Pbind(
	\instrument, \test,
	\dur, 1,
	\freqRamp, Pseq([100, 700, 200, 400]),
	\freqStart, ~lastFreq,
	\freqEnd, Pfunc { |ev|
		~lastFreq = ev[\freqRamp];
	}
).trace.play;
)

Given my modification. . . does there exist an even clearer method to perform the ramping?

I have not yet dug into DemandEnvGen. The synthdef I provided in my first post was just the most basic I could come up with. The synthdef will be performed with polyphony. . . & ramping will control parameters like grain duration, grain trigger frequency, filter cutoffs, amplifier, etc.

It seems to me that, in principle, it would be more simple to conduct this calculation in the synthdef itself. That way I don’t need \freqRamp, \freqStart, \freqEnd, and \freqLag (ramping duration) parameters in every pattern----I could use just \freq, and \freqLag, and the synthdef could perform the rest of the calculations. Maybe this is not possible, or inadvisable.

I think that could be super useful ! I have downloaded the miscellaneous quark, and poked through much of the documentation. Thank you for your efforts on that project.

Peter

I don’t get that, the example works as I understood your initial question:

So
first start is 300, first end 400 (ramp 100)
second start is 400, second end 1100 (ramp 700)
third start is 1100, third end 1000 (ramp -100)

You can see it in the trace.

Your version doesn’t do what you described, It always starts from the same value (as ~lastValue is not in a Pfunc) and takes the ramp as end value - see the trace in the post window.

Definitely possible, DemandEnvGen is one of the tools that can be used for that, EnvGen is also an option. Much depends on the way you define granulation.

My apologies, I did indeed mess up the example. But this one is indeed showing the behaviour I expected

(
~lastFreq = 100;

Pbind(
	\instrument, \test,
	\dur, 1,
	\freqRamp, Pseq([100, 700, 200, 400]),
	\freqStart, Pfunc { ~lastFreq },
	\freqEnd, Pfunc { |ev|
		~lastFreq = ev[\freqRamp];
	}
).trace.play;
)

The behaviour I expected is :

\freqStart value ramps to \freqEnd value across a pre-defined duration, currently preset to 1 within the synthdef XLine (eventually replaced by a \freqLag in the pattern).

So ideally, in the pattern, I would enter just \freq (with a Pseq) and \freqLag (ramp duration). As each new value is input to the synthdef from \freq, the previous value would ramp up to the new value across the duration of \freqLag. That calculation would ideally be performed directly inside of the synthdef, but I haven’t figured out how to perform yet.

Ah ok, I see. From the word ramp I concluded that you wanted to give the difference, but you just want to pass the end values, that’s also fine.

E.g. with Lag or VarLag

(
SynthDef(\test_1, { |out, freq = 200, rampDur = 1|
	Out.ar(out, SinOsc.ar(freq.lag(rampDur)), 0, 0.2)
}).add;
)

(
SynthDef(\test_2, { |out, freq = 200, rampDur = 1, curve = -4|
	Out.ar(out, SinOsc.ar(VarLag.kr(freq, rampDur, curve)), 0, 0.2)
}).add;
)

(
p = Pmono(\test_1,
	\freq, Pseq([500, 300, 700, 200], inf),
	\dur, 1,
	\rampDur, 1
).trace.play
)

(
p = Pmono(\test_2,
	\freq, Pseq([500, 300, 700, 200], inf),
	\dur, 1,
	\rampDur, 1
).trace.play
)

You’d have to check out the appropriate rampDur / curve / warp combinations with VarLag. Imo it makes sense to separate dur from rampDur, so you have more flexible control.

1 Like

Aha, it is much more simple than I expected. & I will use the VarLag version.
Thanks for your help !!