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
.swingThreshold
to 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 ev.delta
.
~~
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:
timingOffset
may be nonzero, in which case, it would be wrong to overwrite. We need to adjust the timing offset: +.sustain
value may be calculated from dur
and legato
. That calculation is done by the ~sustain
function, 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.
Previous: Pattern Guide Cookbook 07: Rhythmic Variations
Next: Pattern Guide Reference 01: Pattern Internals