Pspawner and Patterns control

OK I’m sure I’m missing something basic here but I’ve been hammering at this problem for quite a bit.

I have some Pbindef patterns that are similar in functionality to synthesizer modules (they’re sending data to an external source through osc). I have for a example a LFO module that it’s polling volume data.

Each Pbindef is set on an environment variable.

My goal is to be able to organize the way these Patterns are playing and stopping through time, roughly like a music tracker. Each structure will be labeled as a “piece” and then there will be a top level structure shuffling a sequence of the accumulated “pieces”.

I found out two ways to realize this concept but both of them has some issues that I need your help with.
The first way is through Routines, let’s take the following case:

 ~piece1 = Routine ({ 	
		~LFO1.play;
		~LFO2.play;
		7.wait;
                ~LFO1.stop;
                ~LFO2.stop;
                ~LFO3.play;
	})

The problem presented here is that the Routine ~piece1 doesn’t control the patterns inside of the routine, that means that when the top level structure .stops the ~piece1 to start another piece, the ~LFO3 Pbindef will hang. More problems arise if you loop the pattern, you can hopefully see the issue.

The second case is with Pspawner:

~piece1 = Pspawner ({ 	|sp|
		sp.par(~LFO1);
		sp.par(~LFO2);
		sp.wait(7);
                sp.suspend(~LFO1);
                sp.suspend(~LFO2);
                sp.par(~LFO3);
	}).play

This is working well for the most part, when the piece stops all the subpatterns stops as well and it almost does what I want.
The problem presented here is (if I"m understanding correctly), that I have to explicitly save the sp.par wrapper into a variable so I can be able to suspend it (above example does not work). This is too cumbersome for big pieces.

So how can I achieve what I want here? Thank you!

If I summarize what you are trying to achieve is to define a kind of musical object composed of ~LFO1, ~LFO2 and ~LFO3. And to play and sequence that musical object. Correct ?

If yes, this my approach:

(
// 2 basic SynthDef. Not relevant for the demonstration.
SynthDef(\1,{ |freq=440, amp=0.2|
	var env=EnvGen.kr(Env.asr(0.01,1,0.5),\gate.kr(1),doneAction:2);
	var sig=SinOsc.ar(freq) * amp * env;
	Out.ar(\out.ir(0),Pan2.ar(sig));
	}).add;
SynthDef(\2, { |atk=0.01, amp=0.2|
	var env=EnvGen.kr(Env.asr(atk,1,0.1, -8),\gate.kr(1),doneAction:2);
	var sig=Pulse.ar(40 + [0,1]) * env * amp;
	Out.ar(\out.ir(0),Pan2.ar(sig));
}).add;

// Define my musical object, composed of 2 Pbind played in parallel.
Pdef(\single, { |freq=440, split=1, dur=4|
	// This is some custom parametrization to demonstrate how the orchestrator can modify each occurence of this musical object.
	var pre=0.2;
	split=split.clip(pre,dur-pre);
	postf("% // % - % \n",dur,pre,split);
	Ptpar([
		0.0,
		Pbind(
			\instrument, \1,
			\dur, split.value*1.25,
			\freq, Pn(freq.value,1), // freq is a function, has to be evaluated
			\amp,0.2,
		)
		,split.value-pre,
		Pbind(
			\instrument, \2,
			\dur, (dur.value-split.value+pre).postln,
			\atk,pre,
			\amp,0.08,
			\freq, Pn(freq.value,1), // freq is a function, has to be evaluated
		)
	],1)
});
)

(
// Orchestrator that will sequence \single and vary its definition at each run.
Pdef(\orchestrator,
	Pbind(
		\type, \phrase,
		\instrument, \single,
		\dur, Pseries(2,1,4),
		\split, Pgauss(1,0.2), // Pwhite(0.2,Pkey(\dur)-0.2),
		\freq,Pseq([420,440],2),
	)
).play;

)

// Stop the orchestrator
Pdef(\orchestrator).release

The key here is that \orchestrator Pdef is of \type, phrase.
More info on this approach:
https://depts.washington.edu/dxscdoc/Help/Tutorials/JITLib/recursive_phrasing.html

1 Like

This is an interesting approach I need to look into it more to see if it does solve my problem (and I will).

One problem that seem to arise is because I’m sending my patterns through osc, I’m using a custom \play key and I’m not using the server at all. Meaning that play does not adhere to the default behavior, so I don’t think the Pdef of \type, phrase will work for me.

I believe what I"m trying to do is much simpler. I don’t need to vary the definition at all. I want a structure that controls when the ~LFO patterns start/stop and to be able to control manually when the structure starts and stops (and when it stops all patterns inside the structure stop too). Something like a master transport of sorts? I hope it makes sense.

A custom \play method ?
In the Pbind of the “musical object” the default type is \note. You could define your own type that instead of driving Synths would send your OSC messages. That way you could keep the proposed logic.

Have a look the method Event.addEventType which is discussed in this question.

I think ya’ll are hitting the same thing from different angles :slight_smile: - addEventType is more or less a way of overriding the default \play function. Actually, the primary role of event \type's is to define what the play function is. So setting a \play function, or defining a type and then using the \type key, achieve the same thing.

I’m guessing that you’re running into a fairly classic problem with patterns - having instantaneous control over a pattern from the outside (from the sound of it, you want something like the ability to hit a controller button to immediately advance to the next section of your piece?). Maybe I can sketch a few solutions that might help?

  1. If you want to play patterns in parallel and control them as one unit, try using Ppar or it’s related classed (Ptpar is useful as well).
(
~a = Pbind(\degree, -4);
~b = Pbind(\degree, -7, \dur, 3/7);

~routine = Routine({
	~player = Ppar([
		~a,
		~b
	]);
	~player.play;
	10.wait;
}).play;
)
  1. If you want to swap patterns in and out without needing to manually manage playback (and, potentially have musically sensible transitions, e.g. changing on a beat increment), you can use PatternProxy, or it’s slightly easier to use subclass Pdef.
(
~a = Pbind(\degree, -4);
~b = Pbind(\degree, -7, \dur, 3/7);

~routine = Routine({
	Pdef(\player).play;
	Pdef(\player).source = Ppar([
		~a, ~b
	]);
	4.wait;
	Pdef(\player).source = (
		Pfunc({ |e| e[\degree] = e[\degree] - 3 }) 
		<> Ppar([
			~a, ~b
		])
	)
}).play;
)

If you check the docs for Pdef / PatternProxy, there are a number of parameters that control how to handle the transition when you swap out the patterns by doing source =

  1. If you want more freeform control (rather than pre-baking a bunch of timing info into your Routine), you can use a Condition to wait after each section, and then trigger it from the outside to advance your Routine:
(
~advance = Condition(false);

~a = Pbind(\degree, -4);
~b = Pbind(\degree, -7, \dur, 3/7);

~routine = Routine({
	Pdef(\player).play;
	
	////////////////////////////////////////////////////////////////////////
	
	Pdef(\player).source = Ppar([
		~a, ~b
	]);
	
	~advance.wait; /////////////////////////////////////////////////////////
	
	Pdef(\player).source = (
		Pfunc({ |e| e[\degree] = e[\degree] - 3 }) 
		<> Ppar([
			~a, ~b
		])
	);
	
	~advance.wait; /////////////////////////////////////////////////////////
	
	Pdef(\player).source = (
		Ppar([
			Pset(\dur, 3/5, ~a), 
			Pset(\dur, 4/11, ~b)
		])
	);

}).play;
)

This is a format I sometimes use if I want to step through sections e.g. with a controller.

1 Like

With \type, \phrase, there are two levels:

  • Upper level: \phrase type plays other Pdefs.
  • Lower level: The other Pdefs do the real work. These would be your LFO1, LFO2 etc. patterns.

The upper level must have \type, \phrase.

The lower level patterns are completely independent of that. They can have any type you want, or a custom play function. The fact that they are being played by a phrase-player event has absolutely no effect on what the lower level pattern is allowed to do. “… play does not adhere to the default behavior” is absolutely 100% supported in the lower level patterns!

I actually over-engineered this problem about 12(?) years ago: https://github.com/jamshark70/ddwTimeline. I never documented it, though – there were some problems that I never fully solved to my satisfaction, related to a composition made up of multiple sections.

The Command design pattern is exactly for tracking the state of in-progress activities.

Instead of just playing a pattern, this framework uses a pattern-player command. While the pattern is playing, the command object is active, and its parent tracks its state. When the pattern stops, then the command object finishes, and notifies the parent that it has ended. The parent is active as long as it has any children that are active; when all of the children stop, then it stops (and notifies its parent, etc.).

So at any point, you can stop the top-level TLSequenceIterator, and it will stop all of its active children (and if any of those are sequence iterators themselves, they will stop their children, recursively).

Pdef(\cmajUp, Pbind(\degree, Pseries(0, 1, 14), \dur, 0.125));
Pdef(\cmajDown, Pbind(\degree, Pseries(14, -1, 14), \dur, 0.125));

t = TLSequenceIterator([
	pdefCmd: (name: \cmajUp),
	\sync,  // wait for all active cmds to stop
	pdefCmd: (name: \cmajDown),
]).play;

The structure itself works, though it’s a bit heavy (though some of this heaviness supports user input triggers – not everything has to be strictly timed).

Where I got into trouble was having a TLSq for one section of the piece, and another TLSq for the next section – passing ongoing processes from one to the next was never really beautiful to write. Since it isn’t the main line of my activity, eventually I stopped maintaining it.

But someone else is welcome to take it over, if interested.

hjh

Lovely, thank you all for helping! I really appreciate it! This problem seems more complicated than I first thought.

I will study all the proposed ideas and get back to you!

Hi, so I’ve been checking out the proposed solutions to my problem, I will lay out some thoughts, please bear in mind that I’m not a seasoned supercollider user, so if I’ve misunderstood some of the concepts point them out and I will study more! :slight_smile:

@jamshark70 the scope of the ddwTimeline is way beyond what I’m trying to do also the fact that it’s heavyweight is not helping since my project will be raspberry pi based (more on that later)

@lgvr123 @scztt I’ve tested out all of the examples posted what I’ve observed is that there must be some predefined “musical objects” that will run as one, but are not able to be managed independently. Also there’s a lot of weight on how to change parameters which is something that I’ve already solved. Lastly, the syntax seems excessive, I need to keep things simple because this project will become open source for hopefully non-collider users as well.
I will try to explain a little better what my project is and what I’m trying to achieve because I do feel it must be an easier solution, I’m already so close with the examples of my OP.

I have some Pbindefs that send OSC, those are my building blocks. Let’s say for example: ~LFO1, ~LFO2, ~LFO3, ~LFO4. I can change the parameters of the Pbindefs through a function: for example ~LFO1Params.value([1,1,1,1]). What I’m trying to do is to control through fixed timing, when a Pbindef plays, when it stops and when a Params function triggers(and maybe loop the process). This structure will constitute a “piece” and it must be able to be played or stopped. When played, the “piece” will start from the beginning and when stopped, all the Pbindefs inside the piece must stop as well. The goal is that the “pieces” will be shuffled by a top-level structure, like the shuffle function of a media player. Also it is important to either keep the syntax simple (like a simple routine syntax as in my OP) or somehow create a top-level UI (which I’m not sure how), that someone could easily write into and create his own little pieces. Check my image I think it demonstrates the system better than I can describe it.

Again this might be lack of understanding on my part, so bear with me hah!

There are many kinds of heavy: CPU heavy, memory heavy, conceptually heavy.

My timeline framework is conceptually heavy (it’s an unfamiliar way of working and not beautifully documented apart from an old style html page of examples), but it is not CPU/memory heavy.

There’s no repetitive polling – the timeline itself is active only at transitions. So there’s no constant CPU load.

hjh

So I found my solution, now my lfo modules are called lfoPat and by saving them and the spawner on a global argument I can freely recall them.

~pspawnerInit = Pspawner({ |sp| s = sp; ~lfo1 = s.par(~lfo1Pat); ~lfo2 = s.par(~lfo2Pat); ~lfo3 = s.par(~lfo3Pat); ~lfo4 = s.par(~lfo4Pat); s.suspend(~lfo1); s.suspend(~lfo2); s.suspend(~lfo3); s.suspend(~lfo4); ~envAD1 = s.par(~envAD1Pat); ~envAD2 = s.par(~envAD2Pat); ~envAD3 = s.par(~envAD3Pat); ~envAD4 = s.par(~envAD4Pat); s.suspend(~envAD1); s.suspend(~envAD2); s.suspend(~envAD3); s.suspend(~envAD4); ~tuner1 = s.par(~tuner1Pat); ~tuner2 = s.par(~tuner2Pat); ~tuner3 = s.par(~tuner3Pat); ~tuner4 = s.par(~tuner4Pat); s.suspend(~tuner1); s.suspend(~tuner2); s.suspend(~tuner3); s.suspend(~tuner4); })

This is my initialization spawner that runs one time, all the stuff inside are just building blocks.
So now I can freely play through the spawner and stop patterns like so:
~piece1 = Pspawner ({ |sp| s = sp s.par(~lfo1); s.par(~lfo2); s.wait(7); s.suspend(~lfo1); s.suspend(~lfo2); s.par(~lfo3); }).play

Just a side note: s by tradition points to the default Server. It’s a bit scary to see it refer to something else here (?)

Yes you’re too correct! Although, this program does not use the server at all, it’s still a bad practice, I’m going to change it. Thank you!