Most MIDI sequencers have a "swing" feature, which handles a note's timing differently depending on its metric position. A note in a stronger metric position is played on time; a note in a weaker position is delayed by some fraction of the beat.
In SuperCollider patterns, it's more convenient to express rhythm in terms of equal note durations. To mimic the swing-quantize behavior of conventional sequencers, it's helpful to have a way to modify the output events from a pattern so that the metrically-weaker notes sound later, without requiring the original pattern to be aware of the notes' metric positions.
base_value * swing_amount.
swingThresholdto disable swing for notes that are too far away from the base rhythmic value.
Pchain applies one pattern to the result of another pattern. So, if we can write a pattern that will modify the events coming from the source, Pchain will be an easy way to combine them.
The parameters noted above should be provided in the source pattern. Alternately, they may be given as an event at the end of Pchain's list of inputs. (Pchain, following the model of function composition, evaluates its patterns in reverse order. See Pattern Guide 06c: Composition of Patterns.)
So... deep breath...
Swing should not apply to triplets. Note that the rhythmic value 1/6 introduces floating-point rounding error, so we need to raise the threshold slightly.
(1/6)+(1/6)+(1/6) is within 0.05 of an eighth-note, but
1/6 is not, causing triplet notes to pass through unchanged.
We need to measure the current metric position against some reference point. The most logical is the time when the pattern started processing. Prout allows variables to persist for the entire length of its stream (unlike Pfunc).
If the source event is nil, errors will follow, so we should stop looping in that case.
now is what the next time was. The time of the next event simply adds
As discussed above, there are two factors to decide whether or not this note should be delayed:
(now absdif: now.round(ev[\swingBase])) <= (ev[\swingThreshold] ? 0).
(now / ev[\swingBase]).round.asInteger.odd.
There's room also for a slight optimization. In the previous event, we decided whether the next event would need to swing or not. Now, in the current event, we are processing what used to be "next." So we can just copy the old value of
nextShouldSwing from last time, instead of redoing the calculation. (Note that this requires
nextShouldSwing = false in the beginning -- because
now is always 0 for the first event, and consequently can never swing.)
Naming the variables appropriately makes the subsequent "if" block almost self-explanatory. Two notes:
timingOffsetmay be nonzero, in which case, it would be wrong to overwrite. We need to adjust the timing offset: +.
sustainvalue may be calculated from
legato. That calculation is done by the
~sustainfunction, which must be executed from within the event (Environment: -use).
yield is a bit of a funny method. It doesn't return its result right away. It passes the yielded value to whichever block of code called
next on the stream, and then pauses. Then, the next time
next is called, the
yield method returns, taking its value from
next's argument. Here, that will be the event currently being processed, so we need to reassign it to
ev and loop back.
This is the normal, correct way to handle input values from
next within routines.