Noob: Pbind name can't access SynthDef arg, although name matches arg

Please proceed with caution if your SC version is below 3.12.
The Pbind example below gave me extremely loud levels while I used a 3.11 version.
I updated thanks to this thread.

Hi, I’ve started to create my first SynthDefs, this one seems to work OK when I play it via .play (sorry for the verbose code, hope this helps me when I try to understand it at some point in the future):

(
SynthDef.new(\xylophone,
	{
		arg
		freq = 440,
		amp = 0.1,
		duration = 2,
		scale = 5,
		velocity = 1,
		harmonics = #[1, 4, 7, 9, 13];

		var
		env_volume,
		env_harmonics,
		signal,
		out;

		//make higher notes decay shorter
		duration = duration*440/freq;

		env_volume = XLine.kr(
			start: 1,
			end: 0.01,
			dur: duration,
			add: -0.01,
			doneAction: Done.freeSelf
		);

		env_harmonics = XLine.kr(
			start: 1,
			end: 0.01,
			//higher harmonics have shorter decay & lower velocities make envelopes shorter
			dur: (duration/((1-scale) + (harmonics*scale)))*velocity,
			add: -0.01
		);

		signal = amp*FSinOsc.ar(
			freq: freq*harmonics,
			iphase: 0,
			mul: velocity*1/harmonics*env_harmonics;
		);

		signal = Mix.ar(
			array: signal
		);

		signal = signal*env_volume*amp;

		signal = Pan2.ar(signal,0);

		out = Out.ar(
			bus: 0,
			channelsArray: signal
		);
	}
).play;
)

I can now access the synth and change its argument values like this:

x=Synth(\xylophone, [\freq, 200, \amp, 0.5, \velocity, 0.1]);

But when I play it via a Pbind, \velocity values are not respected, while \amp values are:

(
Pbind(
	\instrument, \xylophone,
	\amp, 0.3,
	\velocity, 0.1,
	\dur, Pseq([0.125,0.125,0.125,0.125,0.25,0.25],10),
	\freq,Prand([400,300,400,500,600],inf)
).play;
)

Please, where is my mistake? According to how I understood this page, corresponding names are key to making this work. Why does it work when accessing the Synth via x=Synth(\…, but not in the Pbind case? Thanks in advance!

hi,
without testing i guess your problem is here…

´´´

  signal = amp*FSinOsc.ar(
  	freq: freq*harmonics,
  	iphase: 0,
  	mul: velocity*1/harmonics*env_harmonics;
  );

´´´

rule of thumb: never modulate the frequency of a FSinOsc. see its helpfile for a more thorough explanation and this warning…

“WARNING: In the current implementation, the amplitude can blow up if the frequency is modulated by certain alternating signals.”

with a standard SinOsc i believe you’ll be ok.
_f

#|
fredrikolofsson.com musicalfieldsforever.com

1 Like

Thank you, I had read about FSinOsc's amplitude depending on frequency, so I tested different frequencies values to check if there were any dangerous areas in which amplitude values exploded – but it was all OK.

As I am also not modulating the FSinOsc, but supplying it with an array with static values (static until the next event, that is), I had hoped that I am on the safe side reg. amplitude.

Nevertheless, I exchanged FSinOsc with SinOsc, but alas, the \velocity values supplied by the Pbind are still not respected. So I must have made a different mistake. :frowning:

right, and use .add instead of .play for your synthdef.
then the velocity will work. .play is a shortcut more for testing while .add is the proper way to load your definitions.

2 Likes

Thanks much, using .add instead of .play did the trick – well, sort of. “Sort of” because these 2 ways of playing the SynthDef give audibly different results.

So when I use this SynthDef (now with SinOsc and .add)…

(
SynthDef.new(\xylophone,
	{
		arg
		freq = 440,
		amp = 0.1,
		duration = 2,
		scale = 5,
		velocity = 1,
		harmonics = #[1, 4, 7, 9, 13];

		var
		env_volume,
		env_harmonics,
		signal,
		out;

		//make higher notes decay shorter
		duration = duration*440/freq;

		env_volume = XLine.kr(
			start: 1.01,
			end: 0.01,
			dur: duration,
			add: -0.01,
			doneAction: Done.freeSelf
		);

		env_harmonics = XLine.kr(
			start: 1.01,
			end: 0.01,
			//higher harmonics have shorter decay & lower velocities make envelopes shorter
			dur: (duration/((1-scale) + (harmonics*scale)))*velocity,
			add: -0.01
		);

		signal = amp*SinOsc.ar(
			freq: freq*harmonics,
			mul: velocity*1/harmonics*env_harmonics;
		);

		signal = Mix.ar(
			array: signal
		);

		signal = signal*env_volume*amp;

		signal = Pan2.ar(signal,0);

		out = Out.ar(
			bus: 0,
			channelsArray: signal
		);
	}
).add;
)

…the audible result of this…
x=Synth(\xylophone, [\freq, 400, \amp, 0.3, \velocity, 1]);

…is significantly softer than this:

(
Pbind(
	\instrument, \xylophone,
	\amp, 0.3,
	\velocity, 1,
	\dur, Pseq([1],1),
	\freq, 400
).play;
)

Please, what is the reason for this? I am at a loss here, because the values for the keynames are the same.

Please, what is the reason for this? I am at a loss here, because the values for the keynames are the same.

it’s a hilarious issue. sorry to hear that you’ve run into this dark and shady alley of Pbind internals. it has to do with your SynthDef’s ‘scale’ argument.

to debug this problem enable reporting…
s.dumpOSC(1)

with that you can see that the simple Synth line creates a single synth node…
[ 9, "xylophone", 1018, 0, 1, "freq", 400, "amp", 0.3, "velocity", 1 ]
all good so far.

your Pbind on the other hand sends a bundle of create-new-synth messages. so here’s the reason for the 7x amplitude.

[ "#bundle", 16505376936713766509, 
  [ 9, "xylophone", 1010, 0, 1, "freq", 400, "amp", 0.3, "scale", 0, "velocity", 1 ],
  [ 9, "xylophone", 1011, 0, 1, "freq", 400, "amp", 0.3, "scale", 2, "velocity", 1 ],
  [ 9, "xylophone", 1012, 0, 1, "freq", 400, "amp", 0.3, "scale", 4, "velocity", 1 ],
  [ 9, "xylophone", 1013, 0, 1, "freq", 400, "amp", 0.3, "scale", 5, "velocity", 1 ],
  [ 9, "xylophone", 1014, 0, 1, "freq", 400, "amp", 0.3, "scale", 7, "velocity", 1 ],
  [ 9, "xylophone", 1015, 0, 1, "freq", 400, "amp", 0.3, "scale", 9, "velocity", 1 ],
  [ 9, "xylophone", 1016, 0, 1, "freq", 400, "amp", 0.3, "scale", 11, "velocity", 1 ]
]

now the very opaque and impossible-to-guess-for-a-beginner Pbind ‘feature’ is that when there’s a scale argument in the SynthDef, Pbind will send along the full list of integers for a scale [0, 2, 4… and thereby instantiate a cluster of synths. this is similar to how multi-channel expansion work.

one way to fix this issue is to rename ‘scale’ in your synthdef to for example ‘myScale’.
another would be to force scale to be a single number by overriding the default major scale in your Pbind
\scale, 10,

_f

#|
fredrikolofsson.com musicalfieldsforever.com

1 Like

There really ought to be a vanilla Pbind that without any magic that only uses keys in your synth and performs no transformations at all!

The default Event is a little bit of a tangle.

1 Like

Thank you very much for this explanation, this was quite an eye-opener. I renamed scale to scale_harmonics, and now it works as expected! :smiley:

And thanks for s.dumpOSC(1), which will hopefully help me to find the solution for a potentially related issue. So if you don’t mind… :wink:

One of the arguments in my SynthDef is the array harmonics, which defines the 5 first partial frequencies. My goal was to use a Pseq inside the Pbind, fill it up with some arrays, so that I can switch between different harmonic structures inside a sequence of notes. So I tried this:

(
Pbind(
	\instrument, \xylophone,
	\harmonics, #[1,4,7,9,13],
	\amp, 0.3,
	\velocity, 1,
	\dur, Pseq([1],1),
	\freq, 400
).play;
)

Unfortunately, the result is not one message with an harmonics array with 5 values, but 5 messages with 1 harmonic value each:

[ "#bundle", 16505408986159211837, 
  [ 9, "xylophone", 4011, 0, 1, "freq", 400, "amp", 0.3, "velocity", 1, "harmonics", 1 ],
  [ 9, "xylophone", 4012, 0, 1, "freq", 400, "amp", 0.3, "velocity", 1, "harmonics", 4 ],
  [ 9, "xylophone", 4013, 0, 1, "freq", 400, "amp", 0.3, "velocity", 1, "harmonics", 7 ],
  [ 9, "xylophone", 4014, 0, 1, "freq", 400, "amp", 0.3, "velocity", 1, "harmonics", 9 ],
  [ 9, "xylophone", 4015, 0, 1, "freq", 400, "amp", 0.3, "velocity", 1, "harmonics", 13 ]
]

Having the # before the array or not doesn’t make a difference. So I enclosed the harmonics array itself in square brackets:

(
Pbind(
	\instrument, \xylophone,
	\harmonics, [[1,4,7,9,13]],
	\amp, 0.3,
	\velocity, 1,
	\dur, Pseq([1],1),
	\freq, 400
).play;
)

And – hooray! – it worked:

[ "#bundle", 16505411136436961350, 
  [ 9, "xylophone", 4378, 0, 1, "freq", 400, "amp", 0.3, "velocity", 1, "harmonics", [ 1, 4, 7, 9, 13 ]
]

So my question is: Is this (enclosing the array in square brackets) the recommended way to transmit arrays in Pbinds? Again, thanks much helping me!

So my question is: Is this (enclosing the array in square brackets) the recommended way to transmit arrays in Pbinds?

correct.
there’s a paragraph about that in the helpfile for Event. see under Arrayed Arguments.
_f

#|
fredrikolofsson.com musicalfieldsforever.com

Thanks again, I’ll do some reading now!

I should point out here that this is a common confusion.

Pbind does not do any magic with any keys, no transformations.

You can add a “vanilla Pbind” and it will make no difference to the behavior…

… because the thing doing the transformations is the default Event prototype.

Pbind only puts data into the event. It doesn’t care what the data are. Pbind is 100% totally agnostic about any pitch/rhythm/etc scheme, or the shapes of arrays, anything. Pbind just stuffs the data you give into successive events, and nothing else.

It’s when the events are played that they are subject to transformations.

In fact, there is an alternate event type \grain which bypasses several of the \note calculations. I don’t remember if it bypasses array expansion or not.

This might seem inscrutable, but it’s an extension of the principle of multichannel expansion in UGens.

If you provide an array of frequencies to Saw, you get an array of Saws.

If you provide an array of pitches to an event, the event will produce an array of synths.

The top level of array in an event determines how many synths will be produced. So [100, 200, 300] has 3 top level items and produces 3 synths. [[100, 200, 300]] has one top level item → one synth, receiving the array as a synth argument.

The logic is justifiable but I still mess it up from time to time…

hjh

1 Like
Pbind(
	\parent, (),
	\delta, 1,
	\play, {
		"do this when my note is played".postln
	}
).trace.play(protoEvent:())

(playing with an empty default event)

Unfortunately you need both the protoEvent and \parent places, because both can and are used to attach prototype/parent events to your event stream. Note that this doesn’t do much. :slight_smile: If you omit the \delta, it only plays one event (because \delta denotes how long to wait until the next event - so nil means there… is no next event). Without \play, obviously nothing happens when the event is played.