Is there a way to send the freq value from a pbind to Processing?

I’m quite the noob in both Supercollider and Processing, yet here I am trying to get these two to work together for a university project because I thought it was fun(oh well).

So I have a list of Events in a Dictionary roughly looking like this (based of some Eli Fieldsteel tutorials):

e = [Dictionary.new](https://Dictionary.new);

e.add(\\event1 -> {

	\~drone = Pbind(

		\\instrument, \\bpfsaw,

		\\dur, 1,

		\\freq, 26.midicps,

		\\detune, Pwhite(0.03,0.2),

		\\rqmin, 0.08,

		\\rqmax, 0.12,

		\\cfmin, 50,

		\\cfmax, 400,

		\\atk, 2,

		\\sus, 0.1,

		\\rel, 2,

		\\amp, 0.5,

		\\group, \~mainGrp,

		\\out, \~bus\[\\reverb\],

	).play;

});

So is there a way to send the freq value or in this case the detune value over to processing in order to trigger some visual elements? So far I’m aware of the NetAddr.sendMsg method but I’m unable to grab the freq of my pbinds. I also tried a SendTrig but I’m honestly not quite sure how to use that one correctly.

I’m not even sure if this is the best way to get data or music in general over to Processing. Does anyone know a different method in case that doesn’t work? Any help is much appreciated!

First you probably don’t want to use a Dictionary of Pbinds, it would be better to use Pdef, at least to manage all the definitions.

To tap into the event play system, you use Event.addEventType. There are many different ways you could implement what you have asked, I’ve done one below, but you might need to alter it to suit your needs. Let me know if you have any questions! I’ve broken it down in sections, but the complete code is at the bottom.

The new event type.

For your use case, you can just ignore all this, other than its name \netAddrRelay.

Event.addEventType('netAddrRelay', {
	
	currentEnvironment
	.select{ |v,k| ~keys_to_send.includes(k) }
	.keysValuesDo{ |k, v| 
		~net_addr.sendMsg(~base_osc +/+ k, v) 
	};
	
	// reset to old type and play
	// if you are using a different \type, set it to \real_type instead
	~type = ~real_type;
	currentEnvironment.play;
	
});

Definition…

SynthDef(\synthA, { ....
SynthDef(\synthA, { 
	var env = Env.perc(0.01, \dur.kr - 0.01).ar(doneAction: 2);
	var sig = SinOsc.ar(\freq.kr) * 0.2 * env;
	Out.ar(0, sig) 
}).add

Here the net addr is defined, and the pdef.
\keys_to_send is an array of the key values you want to send. The key \base_osc defins the first part of the osc address.

The value of \freq will be sent on the osc address /sc/freq, \dur on /sc/dur … etc.

The keys \type, \net_addr, \keys_to_send, and \base_osc must be set.

// replace with your port
~to_processing = NetAddr("localhost", 57120);

Pdef(\event1, Pbind(
	\type, 'netAddrRelay',           // this was defined above ^^
	\net_addr, ~to_processing,     
	\keys_to_send, [\freq, \dur],    
	\base_osc, "/sc",

	\* ... continue as normal ... */

	\instrument, \synthA,
	
	\freq, Pwhite(200, 400),
	\dur, Pwhite(0.2, 2)
));

Usage

Here we make an instance of an EventStreamPlayer.
This object may benifit from being stored in a dictionary if needed.
There is also a little oscdef to show it all working.

~event1_player = Pdef(\event1).play;

// check it works!
OSCdef(\test, _.postln, '/sc/freq');
OSCdef(\test, _.postln, '/sc/dur');

Here is the complete code to copy and paste

...
SynthDef(\synthA, { 
	var env = Env.perc(0.01, \dur.kr - 0.01).ar(doneAction: 2);
	var sig = SinOsc.ar(\freq.kr) * 0.2 * env;
	Out.ar(0, sig) 
}).add


// the new event type
Event.addEventType('netAddrRelay', {
	
	currentEnvironment
	.select{ |v,k| ~keys_to_send.includes(k) }
	.keysValuesDo{ |k, v| 
		~net_addr.sendMsg(~base_osc +/+ k, v) 
	};
	
	// reset to old type and play
	// if you are using a different \type, set it to \real_type instead
	~type = ~real_type;
	currentEnvironment.play;
	
});

// replace with your port
~to_processing = NetAddr("localhost", 57120);

Pdef(\event1, Pbind(
	\type, 'netAddrRelay',
	\net_addr, ~to_processing,
	\keys_to_send, [\freq, \dur],
	\base_osc, "/sc",
	
	\instrument, \synthA,
	
	\freq, Pwhite(200, 400),
	\dur, Pwhite(0.2, 2)
));

// here we make an instance of an EventStreamPlayer
// this object may benifit from being stored in a dictionary if needed.
~event1_player = Pdef(\event1).play;

// check it works!
OSCdef(\test, _.postln, '/sc/freq');
OSCdef(\test, _.postln, '/sc/dur');

2 Likes

It may be simpler to use the \callback key in the event – a function that’s called after event processing (i.e. after the frequency has been calculated).

hjh

thank you very much Jordan, that’s fantastic!

I have a question about the Event though. Currently, if I have several events and say I want to send the detune from event1, the freq from event2 and the amp from event3, they basically get transmitted simultaneously. Is there a way to maybe store different parameters from different events in an array so that I can pick them in processing?

Thanks again!

is it possible to send the osc message directly from the callback function then?

Literally had no idea this existed… is it documented anywhere, I can’t find any reference to it?

Pdef(\t, Pbind(
	\freq, Pwhite(200, 400),
	\dur, 1,
	\callback, {|c| topEnvironment.use{
        ~net_addr.sendMsg("whatever/whaterver", c[\freq]) 
     }}
))
1 Like

http://doc.sccode.org/Tutorials/A-Practical-Guide/PG_08_Event_Types_and_Parameters.html#User%20function%20hooks

hjh

Is there any reason why it is only mentioned in a tutorial?

I did a search using the built in search functionality for ‘callback’ and got nothing relevant, I also did a ctl+f on the Pbind and Event pages.
Also, before writing the answer I re-read all of the event and pbind help docs.
Either this should be added to the Pbind page where the rest of the keys are listed, or you should get results from tutorials when you search - with preference for the former.

I doubt many people know of this feature as result.

1 Like

Its not quite that simple as there is no guarantee the event will happen at the same time, in fact, although they might be very close together, the server messages will all be sent at different times. Also, it depends what format you want them in.

Below, I’ve assumed you want them as a flat array, one after the other. Thats probably not the best idea as you will have to remember what order you put them in. Using different osc address for different piece of information is a better idea, but this is what it might look like.

SynthDef(\synthA, { 
	var env = Env.perc(0.01, \dur.kr - 0.01).ar(doneAction: 2);
	var sig = SinOsc.ar(\freq.kr) * 0.2 * env;
	Out.ar(0, Pan2.ar(sig, \pos.kr)); 
}).add;

~net_addr = NetAddr("localhost", 57120);

~keys_to_send = (
	\freq: 200, // a bunch of default values
	\amp: 1
);

~update_key = {|key, value|
	~keys_to_send =  ~keys_to_send ++ (key.asSymbol : value); // replaces values
     // here we send
	~net_addr.sendMsg('/my/osc/addr', ~keys_to_send[\freq], ~keys_to_send[\amp] );
};


Pdef(\pat1, Pbind(
	\instrument, \synthA,
	\freq, Pwhite(200, 400),
	\pan, -1,
	\dur, 0.5,
	\callback, {|c| topEnvironment.use{ ~update_key.(\freq, c[\freq]) } }
));

Pdef(\pat2, Pbind(
	\instrument, \synthA,
	\freq, Pwhite(600, 800),
	\amp, Pwhite(0.01, 1.0),
	\pan, 1,
	\dur, 1,
	\callback, {|c| topEnvironment.use{ ~update_key.(\amp, c[\amp]) } }
));

Pdef(\pat1).play
Pdef(\pat2).play


OSCdef(\t, _.postln, '/my/osc/addr')
1 Like

Actually none of that should be listed in the Pbind page.

It’s a tricky documentation problem because there’s the Event class (which does not define keys), the default event prototype (which does define keys, including callback), and various ways of populating data into events (of which Pbind is only the most popular – but Pbind and all the other event-populating techniques are 100% agnostic about the resulting events’ behavior – therefore Pbind really should not be documenting event keys).

The documentation probably needs a thorough rewrite, since it seems currently to be misleading about the design.

I would not call that help file a tutorial. It is part of a series but that particular document reads as a reference. It may be tagged as a tutorial, but… is it really tutorial style?

Sorry to be a bit of a grump, but I put quite some time into the Practical Guide to Patterns some years ago, only to find that it seems to be overlooked.

hjh

1 Like

Oh I think the guides are great - I’ve read them several times!
But I can’t remember them all off the top of my head, and no longer regularly refer to them as they are quite difficult to search through in a hurry. Essentially, if it doesn’t come up as a result of the search in the help browser when looking for specific terms (Event, Event Types, Pbind, callback…), it might as well not exist - and I don’t think my laziness is unique there. This might be something fixable, where the heading of the document appear in the search results, its just markdown right(?) so should be possible? Same goes for a lot of programming languages, if the IDE doesn’t auto complete or at least let one jump to source, does it even exist?

I’m not really fussed where they are listed, as long as one can search for it and get an exhaustive list. Currently, the only place they all appear is in Event.sc. Honestly, I’ve rather have each \type be an actually type (class), that way all the methods(keys) are listed and searchable.
eg…

Pbind(PTypeNote(
	\instrument, \synthA, 
	\freq, Pwhite(200, 400)
	...
)).play

Where \instrument, \synthA is short for PTypeNode().instrument_(\synthA). That way all the keys are listed, including inherited ones.

Its worth noting, that you are the author of the guide to patterns, and the \callback Classlib: Move default Event's ~callback into the ~play function · supercollider/supercollider@0cf63b8 · GitHub. If it requires the author to verbally tell everyone about it, something is broken (probably the code and generated documentation as the guides really are great!).

I wouldn’t mind helping out document things … actually, I’ve just looked at the source (or at least, what I think is the source for this), and since I don’t already know what is going on, there is no way I’m gonna read this implementation… its 1000 lines long.

I’m gonna post it without the back ticks just to emphasise that… [okay that was a little much…]

*makeParentEvents {
		// define useful event subsets.
		partialEvents = (
			pitchEvent: (
				mtranspose: 0,
				gtranspose: 0.0,
				ctranspose: 0.0,
				octave: 5.0,
				root: 0.0,					// root of the scale
				degree: 0,
				scale: #[0, 2, 4, 5, 7, 9, 11], 	// diatonic major scale
				stepsPerOctave: 12.0,
				detune: 0.0,					// detune in Hertz
				harmonic: 1.0,				// harmonic ratio
				octaveRatio: 2.0,
				note: #{
					(~degree + ~mtranspose).degreeToKey(
						~scale,
						~scale.respondsTo(\stepsPerOctave).if(
							{ ~scale.stepsPerOctave },
							~stepsPerOctave
						)
					);
				},
				midinote: #{
					(((~note.value + ~gtranspose + ~root) /
						~scale.respondsTo(\stepsPerOctave).if(
							{ ~scale.stepsPerOctave },
							~stepsPerOctave) + ~octave - 5.0) *
						(12.0 * ~scale.respondsTo(\octaveRatio).if
							({ ~scale.octaveRatio }, ~octaveRatio).log2) + 60.0);
				},
				detunedFreq: #{
					~freq.value + ~detune
				},
				freq: #{
					(~midinote.value + ~ctranspose).midicps * ~harmonic;
				},
				freqToNote: #{ arg self, freq; // conversion from frequency to note value
					self.use {
						var midinote;
						var steps = ~scale.respondsTo(\stepsPerOctave).if(
							{ ~scale.stepsPerOctave }, ~stepsPerOctave
						);
						midinote = cpsmidi((freq / ~harmonic) - ~ctranspose);
						midinote / 12.0 - ~octave * steps - ~root - ~gtranspose
					}
				},
				freqToScale: #{ arg self, freq;
					// conversion from frequency to scale value.
					self.use {
						var steps = ~scale.respondsTo(\stepsPerOctave).if(
							{ ~scale.stepsPerOctave }, ~stepsPerOctave
						);
						var degree = self.freqToNote(freq).keyToDegree(~scale, steps)
						- ~mtranspose;
						degree.asArray.collect {|x, i|
							x = x.round(0.01);
							if(x.floor != x) {
								"could not translate: %\n".postf(freq[i]);
								nil
							} { x }
						}.unbubble;
					}
				}
			),
			durEvent: (
				tempo: nil,
				dur: 1.0,
				stretch: 1.0,
				legato: 0.8,
				sustain: #{ ~dur * ~legato * ~stretch },
				lag: 0.0,
				strum: 0.0,
				strumEndsTogether: false
			),
			ampEvent: (
				amp: #{ ~db.dbamp },
				db: -20.0,
				velocity: 64, 		// MIDI units 0-127
				pan: 0.0, 			// pan center
				trig: 0.5
			),
			serverEvent: (
				server: nil,
				synthLib: nil,
				group: { ~server.defaultGroup.nodeID },
				out: 0,
				addAction: 0,
				instrument: \default,
				variant: nil,
				// this function should return a msgFunc: a Function that
				// assembles a synth control list from event values
				getMsgFunc: { |instrument|
					var	synthLib, desc;
					// if user specifies a msgFunc, prefer user's choice
					if(~msgFunc.isNil) {
						instrument = ~instrument = instrument.asDefName;
						synthLib = ~synthLib ?? { SynthDescLib.global };
						desc = synthLib.at(instrument);
						if (desc.notNil) {
							~hasGate = desc.hasGate;
							~msgFunc = desc.msgFunc;
						} {
							~msgFunc = ~defaultMsgFunc;
						};
					} { ~msgFunc };
				},
				synthDefName: { |instrument, variant, synthDesc|
					// allow `nil to cancel a variant in a pattern
					instrument = instrument.asDefName;
					variant = variant.dereference;
					if(variant.notNil and: { synthDesc.notNil and: { synthDesc.hasVariants } })
					{ "%.%".format(instrument, variant).asSymbol }
					{ instrument.asSymbol };
				},
				getBundleArgs: { |instrument|
					~getMsgFunc.valueEnvir(instrument).valueEnvir;
				}.flop,
				hasGate: true,		// assume SynthDef has gate
				sendGate: nil,		// false => event does not specify note release
				defaultMsgFunc: #{|freq = 440, amp = 0.1, pan = 0, out = 0|
					[\freq, freq, \amp, amp, \pan, pan, \out, out] },
				// for \type \set
				args: #[\freq, \amp, \pan, \trig],
				timingOffset: 0,
				// it is more efficient to directly use schedBundleArrayOnClock
				// we keep these for compatibility.
				schedBundle: #{ |lag, offset, server ... bundle |
					schedBundleArrayOnClock(offset, thisThread.clock, bundle, lag, server);
				},
				schedBundleArray: #{ | lag, offset, server, bundleArray, latency |
					schedBundleArrayOnClock(offset, thisThread.clock, bundleArray, lag, server, latency);
				},
				schedStrummedNote: {| lag, strumTime, sustain, server, msg, sendGate |
					var dur, schedBundle = ~schedBundle;
					schedBundle.value(lag, strumTime + ~timingOffset, server, msg);
					if(sendGate) {
						if (~strumEndsTogether) {
							dur = sustain ;
						} {
							dur = sustain + strumTime
						};
						schedBundle.value(lag, dur + ~timingOffset, server,
							[15 /* \n_set */, msg[2], \gate, 0])
					}
				}
			),
			bufferEvent: (
				bufnum: 0,
				filename: "",
				frame: 0,
				numframes: 0,
				numchannels: 1,
				gencmd: \sine1,
				genflags: 7,
				genarray: [1],
				bufpos: 0,
				leaveOpen: 0
			),
			midiEvent: (
				midiEventFunctions: (
					noteOn:  #{ arg chan=0, midinote=60, amp=0.1;
						[chan, midinote, asInteger((amp * 127).clip(0, 127)) ] },
					noteOff: #{ arg chan=0, midinote=60, amp=0.1;
						[ chan, midinote, asInteger((amp * 127).clip(0, 127)) ] },
					polyTouch: #{ arg chan=0, midinote=60, polyTouch=125;
						[ chan, midinote, polyTouch ] },
					control: #{ arg chan=0, ctlNum, control=125;
						[chan, ctlNum, control ] },
					program:  #{ arg chan=0, progNum=1; [ chan, progNum ] },
					touch:  #{ arg chan=0, val=125; [ chan, val ] },
					bend:  #{ arg chan=0, val=125; [ chan, val ] },
					allNotesOff: #{ arg chan=0; [chan] },
					smpte:	#{ arg frames=0, seconds=0, minutes=0, hours=0, frameRate=25;
						[frames, seconds, minutes, hours, frameRate] },
					songPtr: #{ arg songPtr; [songPtr] },
					sysex: #{ arg uid, array; [array] } // Int8Array
				),
				midicmd: \noteOn
			),
			nodeEvent: (
				delta: 0,
				addAction: 0,
				group: { ~server.defaultGroup.nodeID },
				latency: 0.2,
				instrument: \default,
				hasGate: true,
				stopServerNode: #{
					if (~hasGate == true)
					{currentEnvironment.set(\gate, 0) }
					{currentEnvironment.sendOSC([11, ~id]) };
					~isPlaying = false;
				},
				freeServerNode: 	#{
					currentEnvironment.sendOSC([11, ~id]);
					~isPlaying = false;
				},
				releaseServerNode:	#{
					currentEnvironment.set(\gate, 0);
					~isPlaying = false;
				},
				pauseServerNode:	#{ currentEnvironment.sendOSC([12, ~id, false]); },
				resumeServerNode:	#{ currentEnvironment.sendOSC([12, ~id, true]);  },
				// perhaps these should be changed into mapServerNode etc.
				map: 	#{ | ev, key, busIndex | ev.sendOSC([14, ev[\id], key, busIndex]) },
				before: 	#{ | ev,target |
					ev.sendOSC([18, ev[\id], target]);
					ev[\group] = target; ev[\addAction] = 2;
				},
				after: 	#{  | ev, target |
					ev.sendOSC([19, ev[\id], target]);
					ev[\group] = target; ev[\addAction] = 3;
				},
				headOf: 	#{ | ev, group |
					ev.sendOSC([22, group, ev[\id]]);
					ev[\group] = group; ev[\addAction] = 0;
				},
				tailOf: 	#{ |ev,  group |
					ev.sendOSC([23, group, ev[\id]]);
					ev[\group] = group; ev[\addAction] = 1;
				},
				isPlaying: #{ |ev| ^(ev[\isPlaying] == true) },
				isPlaying_: #{ | ev, flag | ev[\isPlaying] = flag; },
				nodeID: #{ |ev| ^ev[\id].asArray.last },
				asEventStreamPlayer: #{|ev| ev }
			),
			playerEvent: (
				type: \note,
				play: #{
					var tempo, server, eventTypes, parentType;
					parentType = ~parentTypes[~type];
					parentType !? { currentEnvironment.parent = parentType };
					server = ~server = ~server ? Server.default;
					~finish.value(currentEnvironment);
					tempo = ~tempo;
					tempo !? { thisThread.clock.tempo = tempo };
					if(currentEnvironment.isRest.not) {
						eventTypes = ~eventTypes;
						(eventTypes[~type] ?? { eventTypes[\note] }).value(server)
					};
					~callback.value(currentEnvironment);
				},
				// synth / node interface
				// this may be better moved into the cleanup events, but for now
				// it avoids confusion.
				// this is a preliminary implementation and does not recalculate dependent
				// values like degree, octave etc.
				freeServerNode: #{
					if(~id.notNil) {
						~server.sendBundle(~server.latency, ["/n_free"] ++ ~id);
						~isPlaying = false;
					};
				},
				// for some yet unknown reason, this function is uncommented,
				// it breaks the play method for gatelesss synths
				releaseServerNode: #{ |releaseTime|
					var sendGate, msg;
					if(~id.notNil) {
						releaseTime = if(releaseTime.isNil) { 0.0 } { -1.0 - releaseTime };
						sendGate = ~sendGate ? ~hasGate;
						if(sendGate) {
							~server.sendBundle(~server.latency,
								*["/n_set", ~id, "gate", releaseTime].flop);
						} {
							~server.sendBundle(~server.latency, ["/n_free"] ++ ~id);
						};
						~isPlaying = false;
					};
				},
				// the event types
				parentTypes: (),
				eventTypes: (
					rest: #{},
					note: #{|server|
						var freqs, lag, strum, sustain;
						var bndl, addAction, sendGate, ids;
						var msgFunc, instrumentName, offset, strumOffset;
						// var schedBundleArray;
						freqs = ~detunedFreq.value;
						// msgFunc gets the synth's control values from the Event
						msgFunc = ~getMsgFunc.valueEnvir;
						instrumentName = ~synthDefName.valueEnvir;
						// determine how to send those commands
						// sendGate == false turns off releases
						sendGate = ~sendGate ? ~hasGate;
						// update values in the Event that may be determined by functions
						~freq = freqs;
						~amp = ~amp.value;
						~sustain = sustain = ~sustain.value;
						lag = ~lag;
						offset = ~timingOffset;
						strum = ~strum;
						~server = server;
						~isPlaying = true;
						addAction = Node.actionNumberFor(~addAction);
						// compute the control values and generate OSC commands
						bndl = msgFunc.valueEnvir;
						bndl = [9 /* \s_new */, instrumentName, ids, addAction, ~group] ++ bndl;
						if(strum == 0 and: { (sendGate and: { sustain.isArray })
							or: { offset.isArray } or: { lag.isArray } }) {
							bndl = flopTogether(
								bndl,
								[sustain, lag, offset]
							);
							#sustain, lag, offset = bndl[1].flop;
							bndl = bndl[0];
						} {
							bndl = bndl.flop
						};
						// produce a node id for each synth
						~id = ids = Array.fill(bndl.size, { server.nextNodeID });
						bndl = bndl.collect { | msg, i |
							msg[2] = ids[i];
							msg.asOSCArgArray
						};
						// schedule when the bundles are sent
						if (strum == 0) {
							~schedBundleArray.(lag, offset, server, bndl, ~latency);
							if (sendGate) {
								~schedBundleArray.(
									lag,
									sustain + offset,
									server,
									[15 /* \n_set */, ids, \gate, 0].flop,
									~latency
								);
							}
						} {
							if (strum < 0) {
								bndl = bndl.reverse;
								ids = ids.reverse
							};
							strumOffset = offset + Array.series(bndl.size, 0, strum.abs);
							~schedBundleArray.(
								lag, strumOffset, server, bndl, ~latency
							);
							if (sendGate) {
								if (~strumEndsTogether) {
									strumOffset = sustain + offset
								} {
									strumOffset = sustain + strumOffset
								};
								~schedBundleArray.(
									lag, strumOffset, server,
									[15 /* \n_set */, ids, \gate, 0].flop,
									~latency
								);
							}
						}
					},
					// optimized version of type \note, about double as efficient.
					// Synth must have no gate and free itself after sustain.
					// Event supports no strum, no conversion of argument objects to controls
					grain: #{|server|
						var freqs, lag, instr;
						var bndl, addAction, sendGate, ids;
						var msgFunc, instrumentName, offset;
						freqs = ~detunedFreq.value;
						// msgFunc gets the synth's control values from the Event
						instr = ( ~synthLib ?? { SynthDescLib.global } ).at(~instrument);
						if(instr.isNil) {
							"Event: instrument % not found in SynthDescLib"
							.format(~instrument).warn;
							^this
						};
						msgFunc = instr.msgFunc;
						instrumentName = ~synthDefName.valueEnvir;
						// update values in the Event that may be determined by functions
						~freq = freqs;
						~amp = ~amp.value;
						~sustain = ~sustain.value;
						addAction = Node.actionNumberFor(~addAction);
						// compute the control values and generate OSC commands
						bndl = msgFunc.valueEnvir;
						bndl = [9 /* \s_new */, instrumentName, -1, addAction, ~group.asControlInput] ++ bndl;
						~schedBundleArray.(
							~lag,
							~timingOffset,
							server,
							bndl.flop,
							~latency
						);
					},
					on: #{|server|
						var freqs;
						var bndl, sendGate, ids;
						var msgFunc, desc, synthLib, bundle, instrumentName;
						freqs = ~detunedFreq.value;
						~freq = freqs;
						~amp = ~amp.value;
						~isPlaying = true;
						msgFunc = ~getMsgFunc.valueEnvir;
						instrumentName = ~synthDefName.valueEnvir;
						bndl = msgFunc.valueEnvir;
						bndl = [9 /* \s_new */, instrumentName, ~id,
							Node.actionNumberFor(~addAction), ~group] ++ bndl;
						bndl = bndl.flop;
						if ( (ids = ~id).isNil ) {
							ids = Array.fill(bndl.size, {server.nextNodeID });
							bndl = bndl.collect { | msg, i |
								msg[2] = ids[i];
								msg.asOSCArgArray
							};
						} {
							bndl = bndl.asOSCArgBundle;
						};
						~schedBundleArray.value(~lag, ~timingOffset, server, bndl, ~latency);
						~server = server;
						~id = ids;
					},
					set: #{|server|
						var freqs, lag, dur, strum, bndl, msgFunc;
						freqs = ~freq = ~detunedFreq.value;
						~server = server;
						~amp = ~amp.value;
						if(~args.size == 0) {
							msgFunc = ~getMsgFunc.valueEnvir;
							bndl = msgFunc.valueEnvir;
						} {
							bndl = ~args.envirPairs;
						};
						bndl = ([15 /* \n_set */, ~id] ++  bndl).flop.asOSCArgBundle;
						~schedBundleArray.value(~lag, ~timingOffset, server, bndl, ~latency);
					},
					off: #{|server|
						var gate;
						if (~hasGate) {
							gate = min(0.0, ~gate ? 0.0); // accept release times
							~schedBundleArray.value(~lag, ~timingOffset, server,
								[15 /* \n_set */, ~id.asControlInput, \gate, gate].flop,
								~latency
							)
						} {
							~schedBundleArray.value(~lag, ~timingOffset, server,
								[\n_free, ~id.asControlInput].flop, ~latency)
						};
						~isPlaying = false;
					},
					kill: #{|server|
						~schedBundleArray.value(~lag, ~timingOffset, server,
							[\n_free, ~id.asControlInput].flop, ~latency)
					},
					group: #{|server|
						var bundle, cmd;
						if (~id.isNil) { ~id = server.nextNodeID };
						bundle = [21 /* \g_new */, ~id.asArray, Node.actionNumberFor(~addAction),
							~group.asControlInput].flop;
						~schedBundleArray.value(~lag, ~timingOffset, server, bundle, ~latency);
					},
					parGroup: #{|server|
						var bundle, cmd;
						if (~id.isNil) { ~id = server.nextNodeID };
						bundle = [63 /* \p_new */, ~id.asArray, Node.actionNumberFor(~addAction),
							~group.asControlInput].flop;
						~schedBundleArray.value(~lag, ~timingOffset, server, bundle, ~latency);
					},
					bus: #{|server|
						var array;
						array = ~array.asArray;
						~schedBundle.value(~lag, ~timingOffset, server,
							[\c_setn, ~out.asControlInput, array.size] ++ array);
					},
					fadeBus: #{ |server|
						var bundle, instrument, rate, bus;
						var array = ~array.as(Array);
						var numChannels = min(~numChannels.value ? 1, array.size);
						if(numChannels > SystemSynthDefs.numChannels) {
							Error(
								"Can't set more than % channels with current setup in SystemSynthDefs."
								.format(SystemSynthDefs.numChannels)
							).throw;
						};
						if (~id.isNil) { ~id = server.nextNodeID };
						// the instrumentType can be system_setbus or system_setbus_hold
						instrument = format(
							if(~hold != true) { "system_setbus_%_%" } { "system_setbus_hold_%_%" },
							~rate.value ? \control,
							numChannels
						);
						// addToTail, so that old bus value can be overridden:
						bundle = [9, instrument, ~id, 1, ~group.asControlInput,
							"values", array,
							"out", ~out.value,
							"fadeTime", ~fadeTime,
							"curve", ~curve
						].asOSCArgArray;
						~schedBundle.value(~lag, ~timingOffset, server, bundle);
						if(~rate == \audio) { // control rate synth frees by itself, because bus holds the value
							~stopServerNode = { server.sendBundle(server.latency, [\n_set, ~id, \gate, 0]) }
						};
					},
					gen: #{|server|
						~schedBundle.value(~lag, ~timingOffset, server,
							[\b_gen, ~bufnum.asControlInput, ~gencmd, ~genflags] ++ ~genarray);
					},
					load: #{|server|
						~schedBundle.value(~lag, ~timingOffset, server,
							[\b_allocRead, ~bufnum.asControlInput, ~filename,
								~frame, ~numframes]);
					},
					read: #{|server|
						~schedBundle.value(~lag, ~timingOffset, server,
							[\b_read, ~bufnum.asControlInput, ~filename,
								~frame, ~numframes, ~bufpos, ~leaveOpen]);
					},
					alloc: #{|server|
						~schedBundle.value(~lag, ~timingOffset, server,
							[\b_alloc, ~bufnum.asControlInput, ~numframes, ~numchannels]);
					},
					free: #{|server|
						~schedBundle.value(~lag, ~timingOffset, server,
							[\b_free, ~bufnum.asControlInput]);
					},
					midi: #{|server|
						var freqs, lag, dur, sustain, strum;
						var bndl, midiout, hasGate, midicmd;
						freqs = ~freq = ~detunedFreq.value;
						~amp = ~amp.value;
						~midinote = (freqs.cpsmidi).round(1).asInteger;
						strum = ~strum;
						lag = ~lag;
						sustain = ~sustain = ~sustain.value;
						midiout = ~midiout.value;
						~uid ?? { ~uid = midiout.uid };  // mainly for sysex cmd
						hasGate = ~hasGate ? true;
						midicmd = ~midicmd;
						bndl = ~midiEventFunctions[midicmd].valueEnvir.asCollection;
						bndl = bndl.asControlInput.flop;
						bndl.do {|msgArgs, i|
							var latency;
							latency = i * strum + lag;
							if(latency == 0.0) {
								midiout.performList(midicmd, msgArgs)
							} {
								thisThread.clock.sched(latency, {
									midiout.performList(midicmd, msgArgs);
								})
							};
							if(hasGate and: { midicmd === \noteOn }) {
								thisThread.clock.sched(sustain + latency, {
									midiout.noteOff(*msgArgs)
								});
							};
						};
					},
					setProperties:  {
						var receiver = ~receiver,
						go = {
							~args.do { |each|
								var selector, value = each.envirGet;
								if(value.notNil) {
									selector = each.asSetter;
									if(~doTrace == true) {
										postf("%.%_(%)\n",receiver,selector,value)
									};
									receiver.perform(selector.asSetter, value)
								};
							}
						};
						if(~defer ? true) {
							// inEnvir is needed
							// because we'll no longer be in this Event
							// when defer wakes up
							go.inEnvir.defer
						} {
							go.value
						};
					},
					monoOff:  #{|server|
						if(~hasGate == false) {
							~schedBundle.value(~lag, ~timingOffset, server,
								[\n_free] ++ ~id.asControlInput);
						} {
							~schedBundle.value(~lag, ~timingOffset, server,
								*([15 /* \n_set */, ~id.asControlInput, \gate, 0].flop) );
						};
					},
					monoSet: #{|server|
						var freqs, lag, bndl;
						freqs = ~freq = ~detunedFreq.value;
						~amp = ~amp.value;
						~sstain = ~sustain.value;
						bndl = ([15 /* \n_set */, ~id.asControlInput] ++ ~msgFunc.valueEnvir).flop;
						bndl = bndl.collect(_.asOSCArgArray);
						~schedBundle.value(~lag, ~timingOffset, server, *bndl);
					},
					monoNote:	#{ |server|
						var bndl, id, ids, addAction, f;
						addAction = Node.actionNumberFor(~addAction);
						~freq = ~detunedFreq.value;
						f = ~freq;
						~amp = ~amp.value;
						bndl = ( [9 /* \s_new */, ~instrument, ids, addAction, ~group.asControlInput]
							++ ~msgFunc.valueEnvir).flop;
						bndl.do { | b |
							id = server.nextNodeID;
							ids = ids.add(id);
							b[2] = id;
						};
						~id = ids;
						if ((addAction == 0) || (addAction == 3)) {
							bndl = bndl.reverse;
						};
						bndl = bndl.collect(_.asOSCArgArray);
						~schedBundle.value(~lag, ~timingOffset, server, *bndl);
						~updatePmono.value(ids, server);
					},
					Synth: #{ |server|
						var instrumentName, desc, msgFunc, sustain;
						var bndl, synthLib, addAction, group, latency, ids, id, groupControls;
						~server = server;
						addAction = Node.actionNumberFor(~addAction);
						group = ~group.asControlInput;
						~freq = ~detunedFreq.value;
						~amp = ~amp.value;
						~sustain = sustain = ~sustain.value;
						ids = ~id;
						msgFunc = ~getMsgFunc.valueEnvir;
						instrumentName = ~synthDefName.valueEnvir;
						bndl = [9 /* \s_new */, instrumentName, ids, addAction, group]
						++ msgFunc.valueEnvir;
						if ((addAction == 0) || (addAction == 3)) {
							bndl = bndl.reverse;
						};
						bndl = bndl.collect(_.asOSCArgArray);
						server.sendBundle(server.latency, *bndl);
						~id = ids;
						~isPlaying = true;
						NodeWatcher.register(currentEnvironment);
					},
					Group: #{|server|
						var ids, group, addAction, bundle;
						ids = ~id = (~id ?? { server.nextNodeID }).asArray;
						addAction = Node.actionNumberFor(~addAction);
						group = ~group.asControlInput;
						~server = server;
						if ((addAction == 0) || (addAction == 3) ) {
							ids = ids.reverse;
						};
						bundle = ids.collect {|id, i|
							[21 /* \g_new */, id, addAction, group];
						};
						server.sendBundle(server.latency, *bundle);
						~isPlaying = true;
						NodeWatcher.register(currentEnvironment);
					},
					tree: #{ |server|
						var doTree = { |tree, currentNode, addAction=1|
							if(tree.isKindOf(Association)) {
								~bundle = ~bundle.add([21 /* \g_new */,
									tree.key.asControlInput, Node.actionNumberFor(addAction),
									currentNode.asControlInput]);
								currentNode = tree.key;
								tree = tree.value;
							};
							if(tree.isSequenceableCollection) {
								tree.do { |x, i|
									x ?? { tree[i] = x = server.nextNodeID };
									doTree.(x, currentNode)
								};
							} {
								~bundle = ~bundle.add([21 /* \g_new */,
									tree.asControlInput, Node.actionNumberFor(addAction),
									currentNode.asControlInput]);
							};
						};
						~bundle = nil;
						~treeGroups = ~treeGroups ?? { ~tree.deepCopy };
						~treeGroups !? {
							doTree.(~treeGroups, ~group, ~addAction);
							CmdPeriod.doOnce { ~treeGroups = nil };
						};
						~bundle !? {
							server.sendBundle(server.latency, *~bundle);
						};
						~bundle = nil;
					},
					composite: { |server|
						~resultEvents = ~types.collect { |type|
							if(type != \composite) {
								currentEnvironment.copy.put(\type, type).play;
							};
						};
					}
				)
			)
		);
		parentEvents = (
			default: ().putAll(
				partialEvents.pitchEvent,
				partialEvents.ampEvent,
				partialEvents.durEvent,
				partialEvents.bufferEvent,
				partialEvents.serverEvent,
				partialEvents.playerEvent,
				partialEvents.midiEvent
			),
			groupEvent:	(
				lag: 0,
				play: #{
					var server, group, addAction, ids, bundle;
					~finish.value;
					group = ~group.asControlInput;
					addAction = Node.actionNumberFor(~addAction);
					~server = server= ~server ?? {Server.default};
					ids = Event.checkIDs(~id, server);
					if (ids.isNil) { ids = ~id = server.nextNodeID };
					if ((addAction == 0) || (addAction == 3) ) {
						ids = ids.asArray.reverse;
					};
					bundle = ids.collect {|id, i|
						[21 /* \g_new */, id, addAction, group];
					};
					server.sendBundle(server.latency, *bundle);
					~isPlaying = true;
					~isRunning = true;
					NodeWatcher.register(currentEnvironment);
			}).putAll(partialEvents.nodeEvent),
			synthEvent:	(
				lag: 0,
				play: #{
					var server, latency, group, addAction;
					var instrumentName, synthLib, desc, msgFunc;
					var msgs, cvs;
					var bndl, ids;
					~finish.value;
					~server = server = ~server ?? { Server.default };
					~sustain = ~sustain.value;
					group = ~group.asControlInput;
					addAction = Node.actionNumberFor(~addAction);
					synthLib = ~synthLib ?? { SynthDescLib.global };
					instrumentName = ~instrument.asDefName;
					desc = synthLib.synthDescs[instrumentName];
					if (desc.notNil) {
						msgFunc = desc.msgFunc;
						~hasGate = desc.hasGate;
					} {
						msgFunc = ~defaultMsgFunc;
					};
					msgs = msgFunc.valueEnvir.flop;
					ids = Event.checkIDs(~id, server);
					if (ids.isNil) { ids = msgs.collect { server.nextNodeID } };
					bndl = ids.collect { |id, i|
						[9 /* \s_new */, instrumentName, id, addAction, group]
						++ msgs[i]
					};
					if ((addAction == 0) || (addAction == 3)) {
						bndl = bndl.reverse;
					};
					bndl = bndl.asOSCArgBundle;
					if (~lag !=0) {
						server.sendBundle(server.latency ? 0 + ~lag, *bndl);
					} {
						server.sendBundle(server.latency, *bndl);
					};
					~id = ids;
					~isPlaying = true;
					~isRunning = true;
					NodeWatcher.register(currentEnvironment);
				},
				defaultMsgFunc: #{|freq = 440, amp = 0.1, pan = 0, out = 0|
					[\freq, freq, \amp, amp, \pan, pan, \out, out] }
			).putAll(partialEvents.nodeEvent)
		);
		defaultParentEvent = parentEvents.default;
	}

Yeah that actually makes sense, I’m still going to try the Array Version since it’s just a couple of values and I know which value is where. Thanks again!

Cute stunt. It’s a lot of noise in the thread. It’s so much noise, I’m tempted to suspect drunk posting…? (Really. Let’s try to avoid that.)

I’d suggest here that discussion of how to document events/patterns belongs in another thread under Development. It’s a big topic and this thread has been marked solved. I could perhaps open a new thread later.

hjh

I did open up the other thread. SCDoc: Events/patterns, and searchability of non-class docs

But I also think I should dispel this one right away:

Pbind is a way to populate values into an event. There is intentionally no restriction on the event keys at this stage. I can populate 10 values under non-reserved keys, and use these to calculate a degree or midinote or frequency. This is an absolutely critical feature that we must not remove, under any circumstances.

When the resulting event is performed, there are two senses in which keys are relevant. One is for the built-in calculations (degree, note, octave, scale, etc. have specific meanings in this schema). The other is SynthDef arguments. Keys are accessed from the event for both of these. Keys may exist in the event that are relevant neither for calculations nor for synth arguments. These are just ignored.

If keys are arguments in an event-type class, then it muddles the distinction between data preparation and data performance, by limiting the data that one is allowed to prepare to permit only those data that will be directly performed. This would fundamentally change the nature of the pattern system.

So, no. I would definitely not be in favor of this.

hjh

1 Like