from klotho import RhythmTree as RT, plot, play
from klotho.chronos import TemporalUnit as UT, TemporalUnitSequence as UTS, TemporalBlock as BT
from fractions import FractionA single UT is a self-contained rhythmic unit. But music unfolds over time and across voices. We need ways to arrange temporal units into larger structures:
Sequences — UTs placed one after another in time (horizontal).
Blocks — UTs stacked simultaneously (vertical / polyphonic).
Sequences¶
A TemporalUnitSequence (UTS) is an ordered sequence of temporal units. Each UT plays in succession, and each can have its own tempo, time signature, and internal subdivision.
ut1 = UT(tempus='4/4', prolatio=(1, 1, 1, 1), beat='1/4', bpm=120)
ut2 = UT(tempus='3/4', prolatio=(2, 1, 3), beat='1/4', bpm=90)
ut3 = UT(tempus='5/8', prolatio=(1, 1, 1, 1, 1), beat='1/8', bpm=160)
uts = UTS([ut1, ut2, ut3])
print(uts) Tempus Type Tempo Start End Duration
0 4/4 S 1/4 = 120 00s:000ms 02s:000ms 02s:000ms
1 3/4 S 1/4 = 90 02s:000ms 04s:000ms 02s:000ms
2 5/8 S 1/8 = 160 04s:000ms 05s:875ms 01s:875ms
Visualization placeholder — UTS plotter in development.
Notice that each UT in the sequence can have a completely different tempo, time signature, and subdivision structure. The sequence simply plays them in order — ut1 finishes, then ut2 begins, then ut3.
Inspecting the Sequence¶
A UTS tracks the absolute time offsets for each UT. We can iterate over it to see how the units are laid out:
for i, ut in enumerate(uts):
print(f"UT {i}: tempus={ut.tempus}, bpm={ut.bpm}, offset={ut.offset:.3f}s, duration={ut.duration:.3f}s")UT 0: tempus=4/4, bpm=120, offset=0.000s, duration=2.000s
UT 1: tempus=3/4, bpm=90, offset=2.000s, duration=2.000s
UT 2: tempus=5/8, bpm=160, offset=4.000s, duration=1.875s
Each UT’s offset is automatically computed from the cumulative durations of the preceding units. The total duration of the sequence is the sum of all its parts.
Building Sequences Incrementally¶
You can also build sequences by appending units one at a time:
ut_a = UT(tempus='2/4', prolatio=(3, 1), beat='1/4', bpm=100)
ut_b = UT(tempus='2/4', prolatio=(1, 2, 1), beat='1/4', bpm=100)
ut_c = UT(tempus='2/4', prolatio=(1, 1, 1, 1), beat='1/4', bpm=100)
phrase = UTS([ut_a, ut_b, ut_c])
print(phrase)
print(f"\nTotal duration: {phrase.duration:.3f}s") Tempus Type Tempo Start End Duration
0 2/4 S 1/4 = 100 00s:000ms 01s:199ms 01s:199ms
1 2/4 S 1/4 = 100 01s:199ms 02s:399ms 01s:199ms
2 2/4 S 1/4 = 100 02s:399ms 03s:599ms 01s:199ms
Total duration: 3.600s
Blocks¶
A TemporalBlock (BT) is a collection of temporal units played simultaneously — polyphonic layering. Each UT in the block starts at the same point in time, and their individual tempos and subdivisions run in parallel.
This is how you create rhythmic counterpoint: multiple independent rhythmic voices sounding at once, each with its own internal logic.
bt = BT([ut1, ut2])
print(bt)Rows: 2
Axis: -1
Duration: 02s:000ms
Time: 00s:000ms - 02s:000ms
--------------------------------------------------
Visualization placeholder — BT plotter in development.
The block overlays ut1 (4/4 at 120 bpm) with ut2 (3/4 at 90 bpm). Because each UT has its own tempo and meter, the result is a polyrhythmic texture — two independent rhythmic streams sounding simultaneously.
Nesting¶
Sequences and blocks can be composed freely: a sequence of blocks, a block of sequences, or any recursive nesting thereof. These are the structural primitives that connect the rhythmic trees we’ve been building to the larger architecture of a musical work. blocks that connect the rhythmic structures we’ve been exploring to the larger architecture of a musical work.
bt_layer = BT([ut_a, ut_b])
uts_of_blocks = UTS([bt_layer])
print("Block of two UTs:")
print(bt_layer)
print(f"\nBlock duration: {bt_layer.duration:.3f}s")Block of two UTs:
Rows: 2
Axis: -1
Duration: 01s:199ms
Time: 00s:000ms - 01s:199ms
--------------------------------------------------
Block duration: 1.200s
Summary¶
A TemporalUnitSequence (UTS) arranges UTs in succession. Each UT can have its own tempo, time signature, and subdivision. Offsets are computed automatically.
A TemporalBlock (BT) layers UTs simultaneously for polyrhythmic textures.
UTS and BT can be nested freely — blocks of sequences, sequences of blocks, or deeper structures.
Plotters and audio playback for raw UTS/BT are still in development. Once implemented, they’ll work like the UT plotter:
plot(uts).play().