Slicing and subdividing patterns: alternative to @ mini notation?

Hello club,

I've been exploring rhythms created by combining two simple operations: slicing and subdividing. Slicing means selecting an arc or time span on a grid by specifying grid indices. Subdividing means dividing that selection into a number of equal-sized parts. The following example shows a rhythm created with these two operations:

If i want to subdivide 16th notes 3-6 by 7, 7-12 by 4, and 12-15 by 9, it's tricky to write out because it requires counting and summing several numbers. The subdividing part is easy in Tidal. What's tougher is slicing or "sub-gridding" in mini-notation. You need to ensure the total sum of arcs adds up to 16 for this example. I'm pretty sure this is how you'd write it out:

d1 $ struct "t!3  [t!7]@3 t  [t!4]@5  [t!9]@3 t " 

Coding the subdivisions 7, 4, and 9 is beautifully straightforward with Tidal. The time spans (arcs), less so--I had to subtract values to get each time span (after the '@' symbol) and keep track of the total time span by adding the parts.

For notation I'm using 0-based indexing and 3-6 means the arc spanning notes 3, 4, 5. Can be written as [3, 6) (left-inclusive, right-exclusive).

Here's another example and a figure to show the notation:

Cycle 1: divide the last 6 8th notes by 7. Cycle 2: divide the last 5 8th notes by 7.

d1 $ struct "< [t@2 [t!7]@6]  [t@3 [t!7]@5] >"

I wonder if there is a better way to do slicing / select subgrids than with multiple @ symbols and summing them in my head? Maybe a function i'm missing out on or a trick that requires less counting?

:elephant:

2 Likes

Slight offtopic, but is there a reason to use struct instead of n notation ?

it's portable, separating rhythmic and harmonic elements so you could, for example, take the rhythm component and double it with a kick drum sample which you won't want pitch adjusted.

Or you could provide a rhythmic counterpoint but reuse the harmonic elements.

Even more simply, you can easily change up/pattern the rhythm differently while leaving the harmonic elements alone.

2 Likes

@RTylerMclaughlin I seem to have missed this interesting question until now..

Can you think of what a clearer notation for this could be?

I've been trying to think of how to notate this in mini-notation and it's... tricky.

Instead, I think I've found a function can get the job done concisely. I'm almost there but i need a Haskell helping hand.

Outline: doing this with base tidal functions, base function results, compare syntax to mini-notation, my attempt at functionalizing and making it concise.

using base tidal functions

You can get this slicing and subdividing (sliceDiv-ing) done with combinations of within', fast, and slow. (within' and not within is needed to keep things tame). I modified my first example from above for easier discussion. It is now "subdivide 16th notes 3-6 by 7 and 16th notes 12-15 by 9."

d1 $ slow 2 $ (within') ((3/16), (6/16)) ((fast 7 ) . (slow (6 - 3))) $         
     within' ((12/16), (15/16)) ((fast 9) . (slow (15 - 12)))   $
     struct "t!16" $ n "0 3 5 7  10 7 5 3  7 10 12 14  12 10 7 5"   # s "midi"

Hopefully you can see where the numbers 3, 6, 7, 12, 15, 9, and 16 go. The 6-3 (and 15-12) bit slows the sped up pattern by the correct amount.

base function results

So this nicely subdivides 16th notes 3-6 by 7 and 16th notes 12-15 by 9, as expected. It has the cool effect of making trills, because it's applying the transformation to the pattern of pitches too.

Aside: If you don't want that trill side effect, you can put the notes to the left of the transformation via (# n ....) $

compare syntax to mini-notation,

This syntax looks awful compared to the equivalent mini-notation that motivated this,

d1 $ slow2 $ struct "t!3  [t!7]@3 t!6 [t!9]@3 t " $
 n "0 3 5 7  10 7 5 3  7 10 12 14  12 10 7 5"  # s "midi"

but the functional form is easier on the brain (more so with more complex transformations) and maybe less error prone.

my attempt at functionalizing and making it concise

The chains of within', fast and `slow are complicated, but many of the parameters are re-used. A concise, functionalized version would make it very natural for live-coding. Here's my attempt:

let sliceDivFail posL posR subdivWhat subdivBy p = within' ((posL/subdivWhat), posR/subdivWhat) (fast subdivBy) (slow (posR |- posL)) p

This gives an error because Haskell can't match type ‘Ratio Integer’ with ‘Pattern Time’ . It's the slow (posR |- posL) part. Because Haskell thinks posL is a ratio integer?

The following works without an error, but it's missing the slow expression, which you do need to correctly balance the rate.

let sliceDivAlmost posL posR subdivWhat subdivBy p = within' ((posL/subdivWhat), posR/subdivWhat) (fast subdivBy) p

Anyway, if i could get this to work, the chainable syntax would be:

d1 $ slow 2 $ sliceDiv 3 6 16 7 $    
            sliceDiv 12 15 16 9 $ 
            struct "t!16" $ n "0 3 5 7  10 7 5 3  7 10 12 14  12 10 7 5"   # s "midi"

If you have time @yaxu or others, seeing why the Haskell code in sliceDivFail fails would be marvelous. I've tried using :: Integer in where clauses and stuff like that to cast the variable as the correct type, but it's not working. So close!

An interesting idea and good work so far!

Is |- (subtracion in patterns with structure from left) by purpose? Your other examples use - (standard subtraction, not pattern related).

thanks!

Ehh, just throwing darts really.

- would be fine too. I know you can pattern slow, but you can't pattern within or within', so i don't think any of this would be patternable.... Except subDivBy, but that would only have an effect for the part of the pattern that falls between posL and posR.

I believe the type error you are getting is because posL and posR are used as fractions in the divisions but as patterns in the subtraction. Easiest fix might be to use - which should make the types consistently fractions.

The problem is that within' doesn't accept patterns in its first argument. This will be fixed amongst all the breaking changes in tidal 2.0..

In the meantime you can use _fast/_slow instead of fast/slow which also accept plain time values rather than patterns.

sliceDiv :: Time -> Time -> Time -> Time -> Pattern a -> Pattern a
sliceDiv posL posR subdivWhat subdivBy p = within' ((posL/subdivWhat), posR/subdivWhat) (fast subdivBy . _slow (posR - posL)) p

@yaxu thanks so much!!! working great.

very satiisfying. Especially when subDivBy close to posR - posL. Love putting it on the last few notes of a drum pattern for fills.

:grinning:

1 Like

the rhythms coming from this idea are quite similar to Kyle Gann's 'totalist rhythms': Totalism as a New Rhythmic Paradigm

thanking Nathan Ho for sharing the article and helping me make the connection.

2 Likes