Generating an NRT with additional Functions

Hi all -

I know this is an unusual way to go about this , but I’m wondering if it is possible.
I’d essentially like to evaluate a function that arranges bigger clusters of Synths, within an NRT score.
I’m curious if the following is possible - or if it will be necessary to do all of this work within the SC pattern language.

As of now, I’m getting the ERROR: Message ‘sched’ not understood. I can reproduce the full text of that error, if it would be helpful - but here is the code I’m attempting to execute below, which will likely produce the same errors for anyone who tries to help…

Thank you.

SynthDef("sound",
	{|freq|
		var sig = SinOsc.ar(freq) * EnvGen.ar(Env.perc(0.01, 0.1), doneAction:2);
		Out.ar(0, sig*0.05);
}).store;

	

(
var server, chans = 2, duration = 100, pattern1, nrtScore;

server = Server(\nrt,
    options: ServerOptions.new
	.memSize_(8192*10)
    .numOutputBusChannels_(chans)
    .numInputBusChannels_(chans)
);

~arrayOfSounds = {Array.fill(40, {Synth("sound", [\freq, 1000.rand])})};


pattern1 = 
Pbind(\amp, 0,
	\sound, Pfunc({~arrayOfSounds.()}),
	\dur, Pseq([0.1], inf)).play;
	

nrtScore = Ppar([pattern1], inf).asScore(duration: duration, timeOffset:0.11);


nrtScore.recordNRT(outputFilePath: "/Users/asmr/Desktop/test.wav",
	headerFormat: 'WAVE',
	sampleFormat: 'float',
	options:server.options,
	duration:duration,
action: {
		(Date.getDate).postln;
		"NRT done".postln;

});

server.remove;
)

The structural problem here is that asScore captures the OSC messages produced when the pattern’s events are played, but you are generating the synths during the process of building the event, not the process of playing the event.

To resolve this, make the synths in the event’s play function.

I’m not at the computer right now so I don’t have a working example just this moment.

hjh

Hi @jamshark70 -
Thanks for chiming in on this question.

I can imagine a way of making a “standalone” Pbind that does a version of this - maybe using Pspawn or some kind of array message inside a Pattern object…is that what you mean by making the synths in the event’s play function?
I think I understand what you are saying, otherwise: the array stuffed with Synths is essentially playing to the server and not encapsulated by the routing and management that the ‘.asScore’ provides- what I don’t understand is if there some way to specify, in the array or Synth, that these Synths should be passed into event’s play function.

It turns out that this is a little trickier problem than I thought at first.

In the default event prototype, there are functions that send OSC messages to the server. All of the built-in event types (except MIDI) use these functions instead of sending directly.

ScoreStreamPlayer (which handles asScore) works by overriding these functions so that the events add OSC messages into an array.

So, your custom function needs to use this mechanism instead of just sending directly.

That is, part of the problem here is that you’re in an awkward in-between place, using patterns for timing but your own message-generation logic. It would probably be clearer to use patterns-events idiomatically, or not use them at all.

To use them idiomatically: One feature of events that might not have been obvious at first is that arrays expand into multiple synths.

pattern1 = Pbind(
	\instrument, \sound,
	\freq, Pfunc {
		Array.fill(40, { 1000.rand })
	},
	\dur, Pseq([0.1], inf)
);

hjh

Thanks for taking a closer look. If I’m understanding correctly, it sounds like the issue is that there would need to be some way of translating the function into OSC messages.

I’ve definitely considered a version of the example you furnished - but the actual algorithm and set of functions that I’m dealing with are much more complex than a bank of sine oscillators - and I’ve been trying to squish it all into the Pattern language since the latter has such an easy conversion to NRT.

Maybe there’s a way to bypass the pattern language component entirely?
Is there a way to simply deal with functions and routines in NRT? I found this post from last year, but it is as of yet unanswered.

One way to think about this is that it’s not just “the pattern language” – in fact, there’s the pattern language for producing events, and the event language for performing data. Usually, the distinction between these is mostly hidden (and this is probably one of the reasons why some users have been critical of patterns/events – because it’s not clear from user code which component is doing what), but they are separate components.

Making the score is one way of performing the data, so this is in the event-language domain. Here, there are two ways to go. One is to add your own event type, to do exactly and only what you want with the data.

(
Event.addEventType(\myComplexThing, { |server|
	// process event data...
	// use ~variables to access event members...
	
	// to send bundles:
	~schedBundleArray.(
		0,  // lag in seconds (or array, 1 value per message)
		0,  // offset in beats (or array, 1 value per message)
		server,  // should use the server argument!
		/* ... an array of messages ... */
	);
});
)

… where the array of messages is an array of arrays – each sub-array is one OSC message.

So, for example, if you have several parameter arrays and you’re doing an Array.fill(num, { Synth(...) }), I would do it like:

(
Event.addEventType(\myComplexThing, { |server|
	// process event data...
	// use ~variables to access event members...
	
	bundle = Array.fill(40, { |i|
		Synth.basicNew(\sound, server)
		.newMsg(
			target: ...,
			// use 'i' for array indexing to get the right parms
			args: [ ... you fill in args ... ],
		)
	});
	
	// Normal: Send the bundle
	// asScore: Collects the messages instead
	~schedBundleArray.(0, 0, server, bundle);
});
)

Alternately, you could figure out how to use a built-in event type to get the result you want, by putting data into the event in the right format. Without specifics, there’s not much I can guess about that, but for instance, the above \myComplexThing could probably be done with the default \note event type by preparing arrays of parameters, e.g. \freq, ... an array..., \otherParm, ... an array....

If you’re generating parameter values through a set of complex functions and don’t want to refactor that into patterns – the pattern player, and the events, don’t care how you populated the data. 99.999% of the time we use one of the Pbind variants, but there is no requirement to do it that way.

So for instance, this is fine:

(
p = Pfunc { |ev|
	ev.put(\freq, exprand(200, 800))
	.put(\dur, exprand(0.05, 0.4))
	.put(\amp, exprand(0.08, 0.25))
	.put(\legato, exprand(0.5, 4))
}.play;
)

p.stop;

This is using the event language to produce the synth nodes, but imperative (function) code for the parameters – no Pbind, no Pexprand.

You could use Pfunc to invoke your more complex logic.

If you need more statefulness than Pfunc supports, Prout can behave similarly, with just a little boilerplate wrapping around your logic.

(
p = Prout { |ev|
	loop {
		ev.put(\freq, exprand(200, 800))
		.put(\dur, exprand(0.05, 0.4))
		.put(\amp, exprand(0.08, 0.25))
		.put(\legato, exprand(0.5, 4));
		
		// "ev = " looks weird, but see
		// "Pattern Guide Reference 01: Pattern Internals"
		// section: "Streams' input values (inval, inevent)"
		ev = ev.yield;
	};
}.play;
)

p.stop;

Same behavior, but you can have vars that are internal to the Prout function, which persist as long as the routine is active.

Note that the Pfunc or Prout is only for generating data though – to get the data into a format that the event type will understand.

hjh

1 Like

Thank you for this illuminating post. I’m understanding things in a totally different way now.