Rhythmical Alternations

rhythmical

January 29, 2022

Last time, I looked at tidal.pegjs, which has an AST that is conceptually similar to rhythmical objects. In addition to sequential and parallel nodes, there are more ideas I haven't yet explored.

In this post, I want to take a look at alternating rhythms, which are enclosed with < and > in tidal mini notation.

Disclaimer: This post will be heavily repetitive and mathematical. As I am not a mathematician, I might not use conventional symbols for tree theory (if there are any).

Table of Contents

Example

C3 <Eb3 F3>

Playing back this pattern will have the following result:

C3 Eb3 C3 F3 C3 Eb3 C3 F3 C3 ...

In traditional western music notation, this could be notated like that:

voltas

In tidal.pegjs, the (unified) AST for C3 <Eb3 F3> looks like this:

not a browser

... which can be visualized with this tree:

Event Querying

So far, rendering the events for a rhythmical tree involved traversing the tree and calculating time and duration for each leaf.

When having alternating nodes, we need to know at which run through of the tree we are, to know which of the possible choices to pick. In the example above, we have two different ways to go, so for every first run, we pick the first and for every second run, we pick the second.

This can be made clearer with a table:

Q0123
VC3 Eb3C3 F3C3 Eb3C3 F3
B00101
  • Q = query count
  • V = event value(s)
  • B0 = index of selected value in branch
  • the dash indicates where it repeats

In this case, we can say:

B0=Q%2B_0 = Q \% 2

To know which value to pick, the C3 is completely irrelevant, so we can ignore any non alternating nodes before and after.

Building Intuition

To further understand the behaviour of alternations, let's look at some more examples

Variable Branching Factor

The branching factor is the number of children of a node. The pattern <A B C> has three children, so the branching factor is 3. The table looks like this:

Q0123
VABCA
B00120

Now, the index of the selected value is modulo 3:

B0=Q%3B_0 = Q \% 3

More general, we can deduce:

B0=Q%F0B_0 = Q \% F_0

Nested Alternations

It gets more complicated when we nest alternations into each other. Example: <<A B> C>

Q01234
VACBCA
B001010
B10_1_0
B1=(Q/2)%2F0=F1=2Qr=F0F1=4B_1 = (Q / 2) \% 2 \newline F_0 = F_1 = 2 \newline Q_r = F_0 * F_1 = 4

where Qr is the total length of the pattern until it repeats (dotted line).

Nested + Variable Branching Factor

<<A B> C D>

Q0123456
VACDBCDA
B00120120
B10__1__0
B1=(Q/3)%2F0=3,F1=2Qr=F0F1=6B_1 = (Q / 3) \% 2 \newline F_0 = 3, F_1 = 2 \newline Q_r = F_0 * F_1 = 6

<<A B C> D>

Q0123456
VADBDCDA
B00101010
B10_1_2_0
B1=(Q/2)%3F0=2,F1=3Qr=F0F1=6B_1 = (Q / 2) \% 3 \newline F_0 = 2, F_1 = 3 \newline Q_r = F_0 * F_1 = 6

From the two examples, we can deduce:

B1=(Q/F0)%F1B_1 = (Q / F_0) \% F_1

Nested with offset

<A <B C>>

Q01234
VABACA
B001010
B1_0_1_
B1=((Q1)/2)%2B_1 = ((Q - 1) / 2) \% 2

<A B <C D>>

Q0123456
VABCABDA
B00120120
B1__0__1_
B1=((Q2)/3)%2B_1 = ((Q - 2) / 3) \% 2

<A <B C D>>

Q0123456
VABACADA
B00101010
B1_0_1_2_
B1=((Q1)/2)%3B_1 = ((Q - 1) / 2) \% 3

From the above, we can deduce:

B1=((QI1)/F0)%F1B_1 = ((Q - I_1) / F_0) \% F_1

where I1 is the index of B1 inside B0.

Nesting even more

<<<A B> C> D>

Q012345678
VADCDBDCDA
B0010101010
B10_1_0_1_0
B20___1___0
B1=(Q/2)%2B2=(Q/4)%2B_1 = (Q / 2) \% 2 \newline B_2 = (Q / 4) \% 2

<<<A B> C> D E>

Q0123456789101112
VADECDEBDECDEA
B00120120120120
B10__1__0__1__0
B20_____1_____0
B1=(Q/3)%2B2=(Q/6)%2F0=3,F1=2,F2=2B_1 = (Q / 3) \% 2 \newline B_2 = (Q / 6) \% 2 \newline F_0 = 3, F_1 = 2, F_2 = 2

From that, we can deduce:

B2=(Q/(F0F1))%F2B_2 = (Q / (F_0*F_1)) \% F_2

<A <B <C D>>>

Q012345678
VABACABADA
B0010101010
B1_0_1_0_1_
B2___0___1_
B2=((Q3)/4)%2B_2 = ((Q-3) / 4) \% 2

<A <<B C> D>>

Q012345678
VABADACADA
B0010101010
B1_0_1_0_1_
B2_0___1___
B2=((Q1)/4)%2B_2 = ((Q-1) / 4) \% 2

<A <B <C <D E>>>>

Q012345678910111213141516
VABACABADABACABAEA
B001010101010101010
B1_0_1_0_1_0_1_0_1_
B2___0___1___0___1_
B3_______0_______1_
B0=Q%2B1=((Q1)/2)%2B2=((Q3)/4)%2B3=((Q7)/8)%2F0=F1=F2=F3=2I1=I2=I3=1B_0 = Q \% 2 \newline B_1 = ((Q-1)/2) \% 2 \newline B_2 = ((Q-3)/4) \% 2 \newline B_3 = ((Q-7)/8) \% 2 \newline F_0 = F_1 = F_2 = F_3 = 2 \newline I_1 = I_2 = I_3 = 1

General Formula

The above example can be deduced to:

B0=Q%F0B1=((QI1)/F0)%F1B2=((Q(I1F1+I2))/F0F1)%F2B3=((Q(I1F1+I2F2+F3))/F0F1F2)%F3B_0 = Q \% F_0 \newline B_1 = ((Q-I_1)/F_0) \% F_1 \newline B_2 = ((Q-(I_1*F_1+I_2))/F_0*F_1) \% F_2 \newline B_3 = ((Q-(I_1*F_1+I_2*F_2+F_3))/F_0*F_1*F_2) \% F_3

Even more general:

Pn=0<k<n1Fk=F0F1Fn1;P0=1On=1<k<nIkPk1=I1P0+I2P1++InFn1;O0=0Bn=((QOn)/Pn)%FnP_n = \prod_{\substack{0<k<n-1}} F_k = F_0*F_1* \dots * F_{n-1} ; P_0 = 1 \newline O_n = \sum_{\substack{1<k<n}} I_k*P_{k-1} = I_1*P_0 + I_2*P_1 + \dots + I_n*F_{n-1}; O_0 = 0 \newline B_n = ((Q - O_n)/P_n) \% F_n

In the example:

P0=1P1=F0=2P2=P1F1=22=4P3=P2F2=42=8O0=0;O1=I1P0=11=1O2=O1+I2P1=1+12=3O3=O2+I3P2=3+14=7B0=((Q0)/1)%2B1=((Q1)/2)%2B2=((Q3)/4)%2B3=((Q7)/8)%2P_0 = 1 \newline P_1 = F_0 = 2 \newline P_2 = P_1 * F_1 = 2 * 2 = 4 \newline P_3 = P_2 * F_2 = 4 * 2 = 8 \newline \newline O_0 = 0; \newline O_1 = I_1*P_0 = 1*1 = 1 \newline O_2 = O_1 + I_2*P_1 = 1 + 1*2 = 3 \newline O_3 = O_2 + I_3*P_2 = 3 + 1*4 = 7 \newline \newline B_0 = ((Q - 0)/1) \% 2 \newline B_1 = ((Q - 1)/2) \% 2 \newline B_2 = ((Q - 3)/4) \% 2 \newline B_3 = ((Q - 7)/8) \% 2

Conclusion

With the above general formula, we are able to know which branch will be selected at any query, without needing to calculate all preceeding query states. In a future post, I want to implement this + look at alternative ways to represent this. I think tidal cycles is doing this differently, using the slow function, which might be- simpler..

Felix Roos 2023