Harmonic Lattices


August 18, 2021


In my post about Pure Intervals, I used two ways to bring order to the infinite amount of ratios of natural numbers: The harmonic series and the Lambdoma. Later, I investigated the 3-limit and 5-limit tuning systems, which are another way to organize the landscape of Just Intonation.

Another powerful way to vizualize the "harmonic space" of any collection of ratios is using a so called lattice.

Note: This post is not optimized for dark mode.

Table of Contents


A lattice organizes ratios by prime factorization. Before understanding how that works, let's have a look:

3 Limit Lattice

The (not really useful) "lattice" of a 3 limit pentatonic scale looks like this:

  • The y direction (vertical) represents powers of 3 (fifths + octave), in this case from -1 to 3.
  • The x direction (horizontal) represents powers of 2 (octaves), in this case -4, -3, -1, 0 and 2, deliberately chosen to move all pitches in the same octave (between 1 and 2).

We could further fill the x direction with different octaves of the same pitch:


To hear how the fractions sound, click on the colored circles

If we agree on the fact that octaves can be seen as the same pitch class (octave equivalence), we can ignore the dimension of the octave completely:


This leaves us just 1 dimension, where the 3 is the x axis. This "trick" saves us 1 dimension for the upcoming higher limit systems.

5 limit lattice

In a 5 limit system, we can keep the 3 (fifths) as the x axis, and add the 5 (thirds) on the y axis:


The slider controls the maximum complexity of intervals, using Tenney Height, which will be the topic of a future post.

In many cases, the lattice will be drawn like that:


Sheering the y axis to the right results in triangles, where those that point upwards are major and those that point downwards are minor.

7 limit lattice

In a 7 limit system, we need one more dimension for 7ths (btw: it's just a coincidence that 7 = 7ths). To draw it in 3d, we can just use a 45 degree angle:


With more and more fractions, it gets difficult to overview the lattice. As we now have 3 dimensions, we could draw it in 3d space:


11 limit lattice

If we add just another dimension with 11 limit (adding augmented 4ths), we run out of brain power.. vizualizing 4 dimensions in 2d is kind of impossible. But we can just use another angle for this new dimension that won't get too much in our way:


13 limit lattice

To go full on crazy, we can add a 5th dimension:


Calculating Prime Vectors aka monzos

Let's talk about the details on how to calculate the vector of any ratio. For example, a 3 limit tuning system consists of ratios generated by

r=2x3yr = 2^x * 3^y

To calculate the coordinates of any ratio inside 3 limit, we need to factorize the ratio into its prime components. We can then use the the exponents as a vector:

11=1=(00)\frac{1}{1} = 1 = \begin{pmatrix} 0\\0 \end{pmatrix}
32=2131=(11)\frac{3}{2} = 2^{-1}*3^{1} = \begin{pmatrix} -1\\1 \end{pmatrix}
43=223=2231=(21)\frac{4}{3} = \frac{2*2}{3} = 2^{2}*3^{-1} = \begin{pmatrix} 2\\-1 \end{pmatrix}
98=33222=2332=(32)\frac{9}{8} = \frac{3*3}{2*2*2} = 2^{-3}*3^{2} = \begin{pmatrix} -3\\2 \end{pmatrix}
2716=3332222=2433=(43)\frac{27}{16} = \frac{3*3*3}{2*2*2*2} = 2^{-4}*3^{3} = \begin{pmatrix} -4\\3 \end{pmatrix}

The resulting vectors can then be used to draw the ratio to the correct coordinates.

The same calculation can be used with more dimensions by just adding more prime factors.

Those prime vectors are also called monzo.

In Code

What we want:

expect(primevector(4)).toEqual([2]); // 2^2
expect(primevector(12)).toEqual([2, 1]); // 2^2 + 3^1
expect(primevector(126)).toEqual([1, 2, 0, 1]);
expect(primevector(15)).toEqual([0, 1, 1]);

How I did it:

export function primevector(n) {
  const limit = primelimit(n);
  const dimensions = primes(2, limit);
  const powers = primepowers(n);
  return dimensions.map((d) => powers.find(([f]) => f === d)?.[1] || 0);

export function primelimit(n) {
  return max(primefactors(n));

export function primefactors(n) {
  const factors = [];
  let divisor = 2;
  while (n >= 2) {
    if (n % divisor == 0) {
      n = n / divisor;
    } else {
  return factors;

export function primes(from, to) {
  const primes = [];
  for (let i = from; i <= to; ++i) {
    if (isPrime(i)) {
  return primes;

export function isPrime(num) {
  for (var i = 2; i < num; i++) {
    if (num % i === 0) {
      return false;
  return true;

export function primepowers(n) {
  const factors = primefactors(n);
  const powers = [];
  let latest;
  for (let factor of factors) {
    if (!latest || latest !== factor) {
      powers.push([factor, 1]);
    } else {
      powers[powers.length - 1][1] += 1;
    latest = factor;
  return powers;


In this post, we saw how to construct harmonic lattices for any number of dimensions. In a future post, I want to talk about alternative just intonation systems like combination product sets, which can also be vizualized in a lattice.

Felix Roos 2022