What are the part and the whole in an Event?

First I'll review a little background, then I'll ask my questions.

Background

The EventF type looks like this:

data EventF a b
  = Event { context :: Context,
            whole :: Maybe a,
            part :: a,
            value :: b}
  	-- Defined at src/Sound/Tidal/Pattern.hs:619:1

That's a little more abstract than we need for this discussion. Most of the EventFs we work with are Events:

type Event a = EventF (ArcF Time) a
  	-- Defined at src/Sound/Tidal/Pattern.hs:627:1

And an ArcF Time is just a pair of Times representing an interval. Thus every Event carries at least two and maybe four Time values: WHen the part starts and stops, and maybe when the whole starts and stops. Since the whole is optional, I'm guessing the part is what determines the duration of the Event.

My questions

Am I correct that the part is what determiens the duration of the Event?
What is the meaning of the whole?
Why, when, and how should I use wholes?
Why can whole be missing sometimes?
When do otherwise-identical Events with differrent wholes behave differently?

I can take a crack at this, and I welcome others' additions if I'm missing anything!

Events are the result of querying a Pattern for a specific time range, and it's useful to think of any Event in relation to a specific query.

As an example, if you take the pattern "bd*4" :: Pattern String and query it for the timespan Arc 0 1/8, you'll get an event corresponding to the first half of the first "bd" symbol. Ignoring context, the resulting Event would be:

Event { whole=(Arc 0 1/4), part=(Arc 0 1/8), value="bd" }

So, the part is whatever portion of the event is active within the queried time, while the whole is the theoretical span of the entire event. If I'm not mistaken, the part is always the intersection of the whole arc and the query arc.

The algorithm for sending events to SuperDirt depends a lot on the interplay between part and whole. As an example of events with different wholes (per your last question), take the pattern stack [s "bd", s "bd*2"]. If you query for span Arc 1/2 3/4, you'll get the following two events:

[
 Event { whole=(Arc 0 1), part=(Arc 1/2 3/4), value=({s: "bd"}) },
 Event { whole=(Arc 1/2 1), part(Arc 1/2 3/4), value=({s: "bd"}) }
]

When sending these events to SuperDirt, Tidal would ignore the first one (because its whole begins outside of the active part), but would send the second one (because its whole begins within its part), using the span of the whole to calculate the duration of the sample (sent to SuperDirt as delta).

whole is optional, because some patterns (such as sine) are continuous, and so they only produce discrete events when you query them. Basically whole=Nothing implies a partial event whose full duration is infinite. In practice, these analog patterns get turned into patterns of discrete events (through functions such as segment), and the way they behave is somewhat undefined.

1 Like

Hi Jeff!

I've been considering renaming 'part' to 'active' to make this a little bit clearer.

Am I correct that the part is what determines the duration of the Event?

It depends what you mean by duration. If you want to know how long to play a note for, you'd probably use the whole.

What is the meaning of the whole?

'whole' is the arc or timespan of the event. 'part' is the timespan of the event that is active.

The 'part' will always be contained within the 'whole'.

Why, when, and how should I use wholes?

If they are not the same, i.e. if the part is smaller than the whole, then we can say that the event is a fragment. This can happen if you e.g. have an event that takes up a whole cycle, but you query for only part of that cycle. It can also happen with a lot of transformations, events can be chopped up into fragments.

Why can whole be missing sometimes?

Events that come from continuous signals don't have wholes.

When do otherwise-identical Events with differrent wholes behave differently?

If the onset is missing, i.e if the part starts later than the whole, then that event won't trigger a sound.

Thank you both! I'm starting to see it. As a result I have more questions.

Is the following true? The whole indicates the duration of the experience the Event is a window onto, the part is the duration of that window, and the part is always the intersection of the query arc and the whole.

Is the following true? When SuperDirt receives an Event, if the Event is a sample, it will only trigger the sample if the part beins no later than the whole.

What if SuperDirt receives an Event that is not a sample, but is instead a tail of a note to be played on a synthesizer? Will SuperDirt similarly ignore the entire Event because its part starts after its whole? (I can see why you might not want to start a sample in the middle -- you'd get a pop, for one thing -- but maybe synth notes are different.)

Is the whole optional only in the case of continuous, conceptually-infinite signals like sine?

Last, it seems like whole only matters for Events containing instructions to SuperDirt. Suppose, for instance, I've got a pattern of transformations that SuperDirt will never receive. It's got two Events: From 0 to 1/2 the transformation is fast 2, and from 1/2 to 1 the transformation is identity. Suppose I query it from 0 to 1/4. The "right" result would be an Event with a payload of fast 2, a part from 0 to 1/4, and a whole from 0 to 1/2. In this case it's hard for me to imagine the whole having any downstream effect. Right?

I've been considering renaming 'part' to 'active' to make this a little bit clearer.

Now that I've read your explanations the names actually seem pretty good to me. ("whole" and "fragment" also seem like good candidates.) What's missing is a nice detailed comment at the definition of Event. Once I've got all these questions ironed out I'll be happy to write one.

Not always, consider:

ghci> "2" |+ "1 ~"
(0>½)-1|3

By adding a longer event to a shorter one, we ended up with a fragment of the longer event. For the event in the first cycle, the part is 0-0.5, the whole is 0-1.

Tidal doesn't send a trigger message to superdirt in the first place, if the part begins later, because it doesn't have an 'onset'.

Superdirt doesn't know about parts. You can tell it to start halfway and finish early using the start/end parameters though.

That's right. They don't get sent to superdirt either.

Yes I guess it's mainly useful for knowing whether an event should create a trigger message, and for how long the event should play (if legato is on)

Thanks, Alex!

[In "2" |+ "1 ~", which results in a 3 with a whole from 0 to 1 and a part from 0 to 1/2, by] adding a longer event to a shorter one, we ended up with a fragment of the longer event. For the event in the first cycle, the part is 0-0.5, the whole is 0-1.

That's hard for me to understand. What does the whole mean in that? I understand, in the case of an Event that's a sub-interval of a longer sample, that the part tells you "you're only listening from start . part to end . part in this Event, but (assuming you're hearing it at all) the sample you're listening to runs from start . whole to end . whole.

But in "2" |+ "1 ~" it seems like the whole is describing something completely irrelevant. It will never affect anything. Am I wrong?

Consider two similar examples: "2" |+ "1 ~ ~ ~" vs. "2 ~" |+ "1 ~ ~ ~". If we evaluate those in GHCI we get:

> "2"   |+ "1 ~ ~ ~"
(0>¼)-1|3
> "2 ~" |+ "1 ~ ~ ~"
(0>¼)-½|3

Both have the same payload and the same part but different wholes. But aren't they functionally identical? Is there any circumstance in which one would cause different behavior than the other?

Tidal doesn't send a trigger message to superdirt in the first place, if the part begins later, because it doesn't have an 'onset' ... Superdirt doesn't know about parts. You can tell it to start halfway and finish early using the start/end parameters though.

Ah, so the logic of comparing parts and wholes happens entirely in Haskell. That makes sense, thanks. It still leaves unanswered, though, my question about onsets for synth Events. Are they the same -- will an Event whose part starts later than the whole fail to trigger a synth, or will it only fail to trigger a sample?

Yes—apologies if I was confusing on that point! In terms of synths vs. samples, Tidal doesn't know the difference and sends any event it encounters as a "play" event to SuperDirt as long as the event has a current onset (in terms of part and whole) and has the s parameter set.

The only counterexample I know of is the effect bus feature, which works by sending certain effect parameters as Supercollider messages regardless of the relationship between the part and the whole. As a result, these messages are sent every time Tidal samples the pattern (~20hz).

I think the answer to your general question is that Tidal has a lot to say about how complex patterns can be built and (by design) not much to say about what those patterns mean in terms of sounds or lights or whatever.

Another way of looking at this is that a pattern is basically a signal, that's a function from time to events.

We could stop there for continuous signals like audio. However we want to support discrete events, where each event has a timespan - i.e. a beginning and an end.

We then need to query the signal using a timespan rather than a single time value, otherwise we might miss some short events by not querying often enough.

We then can end up with events that aren't fully in the query window, so we need to represent the fragment we end up with, using an additional timespan on the event.

2 Likes

I submitted a pull request to add comments to the Event type based on this discussion. Thanks again to you both.

1 Like