Overlapping in PmonoArtic?

Why am I getting multiple, overlapping synths from PmonoArtic when I assign a control to the lag argument of my frequency control? I should be able to use another control to set the lag time, yes?

(
SynthDef(\slidy, {
	var sig = SinOsc.ar(\freq.kr(261.63, \lag.kr(0.1)));
	var env = Env.adsr(\atk.kr(0.1), \dec.kr(0.2), \sus.kr(0.8), \rel.kr(0.3), 1, \curve.kr(-4)).ar(\doneAction.kr(2), \gate.kr(1));
	sig = sig!2 * \amp.kr(0.8) * env;
	Out.ar(\out.kr(0), sig);
}).add;
)

TempoClock.default.tempo_(120/60);

(
PmonoArtic(
	\slidy,
	\scale, Scale.at(\minorPentatonic),
	\degree, Prand((1..10),inf),
	\octave, 3,
	\dur, Prand([0.5, Rest(0.5)], inf),
	\lag, Pwhite(0, 0.5), //problem is more pronounced with larger values
	\legato, Prand([0.9, 1.0], inf) //synths shouldn't be noticeably overlapping and there really shouldn't ever be more than 2 at a time...
).play
)

In the default event type lag is used to delay the event. You could for example use it with small random values to make the timing of events a bit looser. In this case, you can rename the synth control (from lag to eg. freqlag) and it should work as you would expect.

That’s frustrating. A good reminder of why I prefer routines over patterns. Thank you!

Just a reminder that if you don’t like the baggage that comes with the default Event type, you can still use the entire pattern system without it. Patterns and Routines are the same system, the differentiating factor is whether you use the default Event type (which has \lag and other behavior) or another. You can also do e.g.:

// This is a simpler Event type that just plays a synth with fewer "feature" parameters like \lag
PmonoArtic(
  \type, Synth,
  // ... 
)

or:

Event.addEventType(\myEvent, {
   // stuff to do when your event is played
});
PmonoArtic(\type, \myEvent);

or:

PmonoArtic(
  \play, {
     // stuff to do when your event is played
  }
)
1 Like

To pick up on what scztt was saying:

The \lag keyword is not a pattern problem. The pattern system is 100% agnostic about this keyword.

It’s also not an event problem. (The minimum requirement for an event is that it should understand play, and delta, and playAndDelta methods… I think there are a few other methods like isRest but the surface area of the baseline Event interface is very small.)

\lag is defined in the default event prototype.

You can use patterns, and events, without using the default event prototype at all. Or, as scztt correctly notes, you can use the default event prototype, but define an event type function that uses \lag however you want. We commonly use the default event prototype’s built-in event types with events and patterns, but there is no inherent binding between those.

(However, in this case, Pmono and PmonoArtic specifically use event types \monoNote and \monoSet – you can’t override the \type so easily for these patterns, because the node-tracking requirement is more difficult than the usual case. But, you could define a completely separate event prototype that defines these two event types differently.)

~~

The major advantage of events is that they separate data production from data performance.

So you have a routine that plays notes in the way you want. Now you think, I’d like to spit these notes out over MIDI to, say, VCV Rack, or hardware. Probably you had just written Synth(something) embedded in routine… so now you have no choice but to hand-edit the manner of performance… in every affected routine… a potentially large and error-prone job.

The documentation should (but doesn’t) recommend using helper functions to play the notes.

~playNote = { |... parameters ...|
	... do stuff to play the note ...
};

r = Routine {
	loop {
		... make data...
		~playNote.(... data ...);
		delta.wait;
	}
};

Then you can just replace the function in order to change the routine’s performance behavior – data production and usage are abstracted away from each other, and you gain the benefit of easy extensibility.

Then someone will think, gosh, it would be great if there were a library of data-performance functions. But… there already is a library: the default event prototype. And it’s easy to add functions to this library: Event.addEventType.

It’s true that the provided data-performance functions bring with them a lot of assumptions, and not every user likes those assumptions. But then that leads to a handful of wrong conclusions. For example, “I prefer routines” seems to assume tacitly that the way to have control over the process is to do away with note-playing functions altogether. Well, no… the fact that the default \note event type defines \lag in a way that you don’t like doesn’t mean that the idea of a \note event type is a bad one. It’s actually a very good idea, for extensibility. It just means that this particular note-performance function is not a good fit for your use case, and that another one would be a better fit.

I’m soapboxing a bit, but I’m skeptical of the tendency to pitch out the baby with the bathwater where Events are concerned.

hjh

1 Like

The following classes are dependent on specific behaviors of keys being consistent with the default Event type:

  • Pfx
  • Pfxb
  • Pgroup
  • PparGroup
  • Pbus
  • Ppar
  • Pgpar
  • Ptpar
  • Pstretch
  • Pstretchp
  • Pfindur
  • Psync
  • Plambda
  • Pget
  • Pset

There may be others, these are just the ones I found.

As Events are in some sense duck typed (hopefully I’m using the term correctly, I’m not a typing expert) you could reimplement a subset of the default event type keys in your custom event type. However, there is no documentation on the expected behavior and which keys are required, so good luck plumbing the source code.

Events-Patterns-Streams are sometimes sold as decoupled systems, but depending on what features you use they may be anything but. The baby is frozen in a solid block of ice bathwater.

1 Like

Helpful list, and good point - so many documentation problems around all this, I wonder how things could be re-worked to make these dependencies clearer. Though to be fair, a bunch of these don’t require keys from the default Event - Plambda, Pget, Pset only set their own internal key (though it’s not well documented and so it is somewhat fragile) - and most of the time ones only interact with \delta, which isn’t related to the default Event though it does have “special” meaning in that code elsewhere expects it to be present and have a particular meaning.

Weeeelll… If there’s a pattern dealing with durations and it defines an interface for an event’s duration (or rather conforms to an interface that exists elsewhere), in general this wouldn’t be considered a negative…? I get what you’re saying, “it’s not as decoupled as you claimed,” but it reads a bit like looking for problems.

Pbus and Pgroup… these I think are a suboptimal solution to a real problem, so I don’t put much weight on them. But again, conventions such as out for output bus generally reduce confusion; it’s a bit surprising to see this cast here as a weakness.

What does increase confusion are seemingly innocuous keywords with specific, unexpected meanings such as lag. (Perhaps the issue is that there’s no commonly accepted convention around lag while there is for out or dur.) I tried my best once upon a time to document the default event prototype’s keys, but…

As the author of the linked page on event keys, I’m not certain that this reads quite like constructive criticism.

Let me ask instead: What would you like to see in the documentation that isn’t there? Then it’s something that can be addressed and perhaps improve the situation.

hjh

2 Likes

i’m curious how many people who prefer writing in the “routine style” all have c++ backgrounds :slight_smile:

idk, if the best path forward is writing what, to me, looks like repetitive low level code such as:

var synth;
s.bind({  
	synth = Synth(...)
})
dur.wait;
s.bind({
	synth.set(\gate, 0)
})

then…really?

In the middle of my diatribe, I gave an alternate solution: “The documentation should (but doesn’t) recommend using helper functions to play the notes.”

hjh

i would bet most people’s helper function might look something like:

~play = {|synthdef, dur, target, addAction, ...args|
	var synth;
	s.bind({  
		synth = Synth(synthdef, *args, target: target, addAction: addAction)
	})
	dur.wait;
	s.bind({
		synth.set(\gate, 0)
	})
}

it seems weird nobody thought to put something like this in SuperCollider to begin with

A few problems there. Let me revise.

~play = {|synthdef, dur, target(s.defaultGroup), addAction = \addToHead, args|
	var synth;
	s.bind({  
		synth = Synth(synthdef, args, target: target, addAction: addAction)
	});  // semicolon not optional
	// dur.wait;  // strongly advise against this!
	if(dur.notNil) {
		thisThread.clock.sched(dur, {
			s.bind({
				synth.set(\gate, 0)
			})
		})
	};
	synth  // why not let the caller have the synth?
}

Unsolved problems: What if the synth has a short duration and no gate argument, but you give a dur in the function call? And, what if you don’t want to use s? I’m out of time, leave that for you.

hjh