How do Events get their defaults?

Hi,

I (SC noob) am trying to understand events…

This is what I understand so far: Events are dictionaries where attribute-lookup can be delegated to a parent or a prototype-event, so I get e.g:

p = ( foo: "abba", bar:"bubba")  // a parent

t = ( bar: "zappa")   // a prototype

e = ( parent: p, proto: t, play: { (~foo+~bar).postln } );   // a new event

e.play  // -> "abba zappa", prototype comes before parent

e[\parent]  // I can get to the parent

e[\proto]  // I can get to the prototype

Here is what I don’t get:

e = ()

e.play // actually plays sound

e[\parent]  // -> nil

e[\proto] // -> nil

So how does an empty event know how to play itself when there does not seem to be either a parent nor a prototype?

What actually happens behind the scences when I do “().play”?

Many thanks!

Events respond to the .play method depending on their type (see Event Types in the docs)

There is a global dictionary of event types and that the default type is \note.

the \note Event type in turn schedules a Synth(\default) and releases that after the event’s \dur key (1s default).

The e[\parent] result that you’re reporting is definitely not what it should be.

e = ();
-> (  )

e.parent;
-> nil  // this is OK, hasn't played yet

e.play;
-> ( 'instrument': default, 'msgFunc': a Function, 'amp': 0.1, 'server': localhost, 
  'sustain': 0.8, 'isPlaying': true, 'freq': 261.6255653006, 'hasGate': true, 'id': [ 1000 ] )

// after playing, this is not nil -- and should not be nil
e.parent;
-> ( 'instrument': default, 'db': -20.0, 'midiEventFunctions': ( 'polyTouch': a Function, 'program': a Function, 'bend': a Function, 'sysex': a Function, 
  'noteOn': a Function, 'allNotesOff': a Function, 'smpte': a Function, 'songPtr': a Function, 'control': a Function, 
  'touch': a Function, 'noteOff': a Function ), 'midinote': a Function, 
  'addAction': 0, 'frame': 0, 'parentTypes': (  ), 'out': 0, 'group': a Function, 
  'mtranspose': 0, 'harmonic': 1.0, 'strumEndsTogether': false, 'detunedFreq': a Func...etc...

e[\parent]
-> ( 'instrument': default, 'db': -20.0, 'midiEventFunctions': ( 'polyTouch': a Function, 'program': a Function, 'bend': a Function, 'sysex': a Function, 
  'noteOn': a Function, 'allNotesOff': a Function, 'smpte': a Function, 'songPtr': a Function, 'control': a Function, 
  'touch': a Function, 'noteOff': a Function ), 'midinote': a Function, 
  'addAction': 0, 'frame': 0, 'parentTypes': (  ), 'out': 0, 'group': a Function, 
  'mtranspose': 0, 'harmonic': 1.0, 'strumEndsTogether': false, 'detunedFreq': a Func...etc...

Do you maybe have an extension overriding Event:play?

The standard Event:play does populate parent (see supercollider/SCClassLibrary/Common/Collections/Event.sc at develop · supercollider/supercollider · GitHub).

hjh

No you are right.

After play parent is populated.

So an empty-event knows to play itself by relying on the data of a parent, but that parent is only populated once you call play.

Interesting…

How do you find out about such things in a systematic way?

Is there a way to dump the default implementation of (say) play via the ide or would I have to take a look into the actual source code?

http://doc.sccode.org/Guides/SCIde.html#Look%20up%20implementations

Some methods are primitives, and you’d have to look at the C source.

Most methods are written in SC language itself. Event:play is one of those. No need to dive into the source here.

Not exactly – all events know how to play because the Event class implements a method play, which looks in the event for a play function (this is not the same as the Event:play method). As a courtesy, if there’s no parent in the event currently, it assumes you want the default parent event (and this provides the default play function).

hjh

You can do this:

Event.parentEvents.default[\play].def.sourceCode

It’s intimidating to dig into the source code if you’re not used to it - but for Event, I would suggest trying to make the leap. Event is extremely powerful, but there’s really no straightforward way of harnessing all of the details without looking at the code once in a while - I promise learning how to look things up in the Event source will pay off :slight_smile:

A good place to start is the makeParentEvents method. This is where all of the defaults for the various kinds of Events are defined. There are a few important pieces here…


Partial events are defined starting with the line partialEvents = ( . These provide default values for different use cases - these aren’t used directly - rather they are just ways of breaking up functionality into smaller related pieces, and are combined together for parentEvents later on. For example, you have:

ampEvent: (
	amp: #{ ~db.dbamp },
	db: -20.0,

…which tells you that anything using ampEvent has a default \db of -20, and by default gets it’s \amp value based on that db.

Later on, in Event.sc you’ll see things like ().putAll(partialEvents.pitchEvent ..., which is where these partial events are mixed together into a parentEvent.

The most important partial event is playerEvent - it’s worth looking at the function it defines for it’s play key. This is the function that will be called in 95% of cases when you do Event:play. Importantly, this function looks up an event type in the \type key and calls the associated function - so the \type defines the real behavior for Event:play, the stuff in playerEvent[\play] just defines some high level logic. It also does some under-documented things, like called ~finish before ~play (this lets you define a \finish function in your event to set any final values, or e.g. do a postln before playing).


Event types are defined starting with the line eventTypes: ( .
These ONLY specify what happens when Event:play is called, and are looked up using the \type key of the event (see playerEvent above). So, an event like (type: \breakfast).play will look up and call playerEvent[\play], which will in turn call eventTypes[\breakfast][\play]. Note that you can get roughly the same behavior by just specifying a \play key in your event as well.

You can add your own event types using the addEventType method - this is useful if you want some special play behavior that you’ll re-use in many places. Once you do Event.addEventType(\breakfast, { "eggs and bacon".postln }), you can then trigger your custom \play function any time you have (type: \breakfast).

The critical event type is the default one, \note - this is the code that’s executed for common note-playing event scenarios. This gives you a detailed picture of exactly how an Event is actually turned into a synth playing on the server, where arguments come from, etc etc.


Parent events are events intended to be used as parents for a whole stream. They are defined starting with the line parentEvents = ( . In almost every conceivable case, the default event will be used, but there are two main places where different parentEvent’s might become useful.

There is one place where you would generally use these: when specifying a custom event type via addEventType, the third argument is a parentEvent. This will used as a parent when playing any Event of this \type. For example:

Event.addEventType(
	\breakfast,
	func: { ~items.postln }, 
	parentEvent: (
		items: ["eggs", "bacon"]
	)
);
Pbind(
	\type, \breakfast
).play;

Now, type: \breakfast stands in for both a play function AND a whole set of default keys.


I would highly encourage keeping Event.sc open while working with patterns, and using it at least for reference when you’re not sure of something. It’s hairy, but once you have an idea of what to look for, it’s not so bad.
And, the Event:addEventType workflow is a fantastic way to nail down common Event behaviors that you use often, in a way that’s simple and keeps your code clean. These days I usually define a new event type for any (non-trivial) instrument - I can specify default values, custom calculations, and whatever else I might need to use for it (even things like voicing behavior!).

(wrote this while making lunch, sorry for the food references, I’m hungry!)

4 Likes

Very helpful post.
Thanks!