Skip to content
🚧 Under active construction — content being developed and verified.

1. Foundations: Euclidean Rhythms and Cycle Independence

Every pattern Poly generates starts from a single principle: distribute k onsets as evenly as possible across n time steps. This is the Euclidean rhythm, and it connects a 2003 particle-accelerator timing algorithm46 to the oldest drumming traditions on earth1.

In 2005, Godfried Toussaint demonstrated that Bjorklund’s algorithm for distributing neutron beam pulses produces the same onset patterns found in traditional West African, Afro-Cuban, and Middle Eastern percussion.1 The connection is not metaphorical — the algorithm is mathematically identical.

Given steps = 12 and hits = 7, the algorithm produces the pattern [x . x x . x . x x . x .] — seven onsets distributed as evenly as possible across twelve positions. This is the standard West African bell timeline, found identically in Ewe, Yoruba, and Afrobeat contexts.

E(7,12)
// Bjorklund's pairing/elimination algorithm — the maximally-even distribution
// of k pulses across n steps, matching the normative reference in
// site/src/components/EuclideanDiagram.astro so diagrams show what the engine
// plays (enforced by the exhaustive equality test in tests/euclidean_tests.cpp).
//
// RT-safe reformulation: the reference builds an array-of-arrays and repeatedly
// concatenates pattern[i] ++ remainder[i]. Two invariants let us collapse that
// to fixed-size storage with no heap use:
//   1. Every sequence currently in `pattern` is identical to the others, as is
//      every sequence in `remainder`. Concatenation and slicing preserve this,
//      so the whole state is just two bit-sequences (P, R) plus their counts.
//   2. Each sequence is a subsequence of the original n bits, so len(P),
//      len(R) <= n <= kMaxSteps and every buffer is a std::array<bool,kMaxSteps>.
// The final pattern is pCount copies of P followed by rCount copies of R.
void euclidean(int k, int n, int rotation, std::array<bool, kMaxSteps>& out) {
    out.fill(false);

    if (n <= 0 || k <= 0)
        return;
    if (n > kMaxSteps)
        n = kMaxSteps;
    if (k >= n) {
        for (int i = 0; i < n; ++i)
            out[i] = true;
        return;
    }

    // State: pCount copies of sequence seqP[0..lenP) and rCount copies of
    // seqR[0..lenR). Start with k singleton pulses and (n-k) singleton rests.
    std::array<bool, kMaxSteps> seqP{};
    std::array<bool, kMaxSteps> seqR{};
    seqP[0] = true;
    seqR[0] = false;
    int lenP = 1, lenR = 1;
    int pCount = k, rCount = n - k;

    // Pair each pattern group with a remainder group until at most one remainder
    // group is left, exactly as the reference `while (remainder.length > 1)`.
    while (rCount > 1) {
        const int minLen = std::min(pCount, rCount);

        // newP = P ++ R (all `minLen` resulting pattern groups are identical).
        std::array<bool, kMaxSteps> newP{};
        const int newLenP = lenP + lenR;
        for (int i = 0; i < lenP; ++i)
            newP[i] = seqP[i];
        for (int i = 0; i < lenR; ++i)
            newP[lenP + i] = seqR[i];

        // Leftover groups (the longer collection beyond minLen) become the next
        // remainder; mirrors `pattern.length > remainder.length ? pattern : remainder`.
        std::array<bool, kMaxSteps> newR{};
        int newLenR;
        int newRCount;
        if (pCount > rCount) {
            newR = seqP;
            newLenR = lenP;
            newRCount = pCount - minLen;
        } else {
            newR = seqR;
            newLenR = lenR;
            newRCount = rCount - minLen;
        }

        seqP = newP;
        lenP = newLenP;
        pCount = minLen;
        seqR = newR;
        lenR = newLenR;
        rCount = newRCount;
    }

    // Flatten: pCount copies of P, then rCount copies of R. This is the rotation-0
    // base pattern.
    std::array<bool, kMaxSteps> base{};
    int idx = 0;
    for (int c = 0; c < pCount; ++c)
        for (int i = 0; i < lenP; ++i)
            base[idx++] = seqP[i];
    for (int c = 0; c < rCount; ++c)
        for (int i = 0; i < lenR; ++i)
            base[idx++] = seqR[i];

    // Right-shift rotation, matching the reference's slice-based rotation.
    for (int i = 0; i < n; ++i)
        out[i] = base[((i - rotation) % n + n) % n];
}
The Bjorklund algorithm — O(n) iterative implementation

Many of the world’s most fundamental rhythmic cells are Euclidean distributions. Here are the patterns you will encounter throughout this guide:

E(3,8)

The tresillo — E(3,8) — is the rhythmic atom of Afro-Cuban music and appears in almost every chapter that follows.

E(5,8)

The cinquillo — E(5,8) — is the tresillo’s denser complement, central to Cuban contradanza and New Orleans second line.

E(5,16)

E(5,16) is the bossa nova bass pattern, explored in the Brazilian chapter.

The second foundation is cycle independence: each lane in Poly has its own step count, its own hit count, and its own subdivision. When Lane 1 runs a 7-step cycle and Lane 2 runs a 12-step cycle, the combined pattern does not repeat until lcm(7, 12) = 84 steps have elapsed.

This is not a bug or a special mode — it is the default behaviour. Polymetric interaction emerges automatically from independent cycle lengths, exactly as it does in the interlocking percussion ensembles of West Africa and Southeast Asia.

There is no “master lane”: every lane locks directly to the DAW’s quarter-note grid, and the tempo, subdivision, and step count of each lane combine to determine how fast its cycle rotates. The Timing Model appendix walks through the arithmetic with worked examples if you want to reason about cycle lengths and lane relationships from first principles.

The rotation parameter shifts the onset pattern around the cycle without changing which steps are hits. E(7, 12, 0) and E(7, 12, 3) have the same rhythmic content but different phase relationships with the other lanes.

This matters because many traditional rhythms are defined not just by their onset count but by their rotational position relative to a timeline. The Ewe bell at rotation 0 is the standard orientation; rotating it does not change the seven onsets (that is what rotation means — see above), it only shifts where the cycle begins, so the same timeline can lock to a different downbeat relative to the other lanes.

Foundations: Two-Lane Polymetric Demonstration
Lane Role Steps Hits Rotation Subdivision
1 Bell timeline 12 7 0 1/8
2 Counter pattern 7 5 0 1/8
Polymetric FoundationTwo-lane polymetric demonstration — 12-step bell against 7-step counter pattern

Preview audio uses CC0 and CC-BY drum samples. Every sample is credited on theCredits & Licenses page.