Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

01 | Rhythm and Time | Principles of Proportion

from klotho import RhythmTree as RT, plot
from fractions import Fraction

Proportional Duration

This notebook introduces the concept of proportional duration — the foundational principle behind Rhythm Trees and much of Klotho’s approach to time.

Before diving into tree structures and nesting, it’s important to understand how durations in a rhythm are specified as relative proportions rather than absolute values.

Imagine some duration of time, let’s say 1.

But... 1 what, exactly? It doesn’t actually matter. It could be 1 second, 1 hour, 1 whole-note, 1 quarter-note, etc... the exact duration is not what’s important. We’ll see why shortly.

rt = RT(subdivisions=(1,))
plot(rt, layout='ratios', animate=True, beat='1/4', bpm=120)
Loading...

Ok, not too exciting, but you get the basic idea: this is one thing.

Now, divide that duration into four equal subdivisions. We’ll notate these subdivisions like this: (1 1 1 1). What does that look and sound like?

rt = RT(subdivisions=(1, 1, 1, 1))
plot(rt, layout='ratios', animate=True, beat='1/4', bpm=120)
Loading...

Again, not that exciting, but you can see/hear what’s happening: this one thing is divided into four equal slices.

The durations in an RT are not absolute. They specify relative proportions:

print('Durations:', *[str(d) for d in rt.durations])
print('Sum:      ', sum(rt.durations))
Durations: 1/4 1/4 1/4 1/4
Sum:       1

Four equal ratios summing to 1. Why do they sum to 1? Because that was the size of our original block.

The proportions describe how the total block is divided, not how long anything is in absolute time.

What do we mean by this? Well, what do you think the resultant ratios will be if we define our four subdivisions as (5 5 5 5) instead of (1 1 1 1)? Let’s try...

rt = RT(subdivisions=(5, 5, 5, 5))
plot(rt, layout='ratios', animate=True, beat='1/4', bpm=120)
Loading...
print('Durations:', *[str(d) for d in rt.durations])
print('Sum:      ', sum(rt.durations))
Durations: 1/4 1/4 1/4 1/4
Sum:       1

Ah ha, exactly the same. As they should be. Was that what you expected?

Also, there’s nothing inherently special about the number 4. We can divide this block of time into any number of segments:

for n in [3, 5, 7, 13]:
    rt = RT(subdivisions=(1,)*n)
    print(f'{n} equal slices:')
    plot(rt, layout='ratios', animate=True, beat='1/4', bpm=120)
    print(f"{' + '.join(str(f) for f in rt.durations)} = {sum(rt.durations)}")
    print()
print("...and so on...")
3 equal slices:
Loading...
1/3 + 1/3 + 1/3 = 1

5 equal slices:
Loading...
1/5 + 1/5 + 1/5 + 1/5 + 1/5 = 1

7 equal slices:
Loading...
1/7 + 1/7 + 1/7 + 1/7 + 1/7 + 1/7 + 1/7 = 1

13 equal slices:
Loading...
1/13 + 1/13 + 1/13 + 1/13 + 1/13 + 1/13 + 1/13 + 1/13 + 1/13 + 1/13 + 1/13 + 1/13 + 1/13 = 1

...and so on...

The important thing to notice is that, no matter how many subdivisions we make, the total duration of the rhythm remains the same. This is why when we increase the number of slices, each slice becomes shorter and shorter—we’re “packing” more pieces into the same space.

What if the subdivisions are unequal? Like, (4 2 1 1)?

rt = RT(subdivisions=(4, 2, 1, 1))
plot(rt, layout='ratios', animate=True, beat='1/4', bpm=120)
Loading...
print('Durations:', *[str(d) for d in rt.durations])
print('Sum:      ', sum(rt.durations))
Durations: 1/2 1/4 1/8 1/8
Sum:       1

Each segment accounts for a different proportion of the total block, with the last two being of the same proportion.

Let’s really drive home this notion of proportion. Above, we used subdivisions of (4 2 1 1). What would happen if we used (8 4 2 2) instead?

rt = RT(subdivisions=(8, 4, 2, 2))
plot(rt, layout='ratios', animate=True, beat='1/4', bpm=120)
Loading...
print('Durations:', *[str(d) for d in rt.durations])
print('Sum:      ', sum(rt.durations))
Durations: 1/2 1/4 1/8 1/8
Sum:       1

Again, exactly the same. As they should be. Do you see why?

Let’s go back to our (4 2 1 1), example. What if we changed the first segment from 4 to 7, giving us (7 2 1 1)? What happens then?

rt = RT(subdivisions=(7, 2, 1, 1))
plot(rt, layout='ratios', animate=True, beat='1/4', bpm=120)
Loading...

Alright, something changed. Do you see why? Let’s inspect the resultant ratios:

print('Durations:', *[str(d) for d in rt.durations])
print('Sum:      ', sum(rt.durations))
Durations: 7/11 2/11 1/11 1/11
Sum:       1

So, different ratios but the sum is still 1. Why?

The subdivisions (4 2 1 1) and (7 2 1 1) produce different ratios (and, thus, different rhythms), but in both cases the proportions account for the whole duration. These integers are relative proportions of a parent container, not absolute durations.


This principle of proportion holds even as rhythms become more complex. In the next notebook, we’ll see how Rhythm Trees use nesting and recursion to create rich hierarchical rhythmic structures — all while preserving the proportional relationships introduced here.