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.

02 | Tones | 02 | Embedded Structures

from klotho import plot, play
from klotho.tonos import ToneLattice, Scale, PitchCollection as PC
from klotho.utils.algorithms import ratios_to_coordinates
Loading...

We’ve seen that paths through a lattice create melodies and arpeggios. But the lattice can encode more than sequences — shapes on the lattice correspond to chords and scales.

Chords as Shapes

A chord is a set of simultaneously sounding intervals. On the lattice, a chord is simply a shape — a collection of points. The geometric relationships between the points determine the harmonic quality of the chord.

tl = ToneLattice.from_generators(('3/2','5/4'), resolution=2, equave_reduce=True)

major_triad = [(0,0), (1,0), (0,1)]
print("Major triad (1/1, 3/2, 5/4):")
plot(tl, shape=major_triad, figsize=(7,5), fit=True).play(dur=2, strum=0.05)

minor_triad = [(0,0), (1,0), (1,-1)]
print("\nMinor triad (1/1, 3/2, 6/5):")
plot(tl, shape=minor_triad, figsize=(7,5), fit=True).play(dur=2, strum=0.05)
Major triad (1/1, 3/2, 5/4):
Loading...

Minor triad (1/1, 3/2, 6/5):
Loading...

Notice: the major and minor triads are different shapes on the same lattice. The geometry directly encodes the harmonic character. A triangle pointing one way is major; rotated, it’s minor.

This idea extends to any chord — seventh chords, extended harmonies, even microtonal sonorities. Every chord has a unique geometric fingerprint on the lattice.


Scales Embedded in Lattices

If chords are small shapes, scales are larger ones. Let’s embed some scales in the lattice and see what structures emerge.

We can use a harmonic space like a coordinate system that we can freely navigate, as we did in the previous notebook, or we can use the space to build structures.

Let’s embed a scale in a lattice. We’ll start with a major scale:

scale = Scale()
print(scale)
# plot(scale, layout='circle')
play(scale.root('C5'), dur=0.25)
Scale([1, 9/8, 5/4, 4/3, 3/2, 5/3, 15/8], equave=2)
Loading...

What does it mean to embed a scale in a lattice?

It means that we take each interval in the scale and perform the prime decomposition operation to attain the vector of that interval.

Ok... but what does that mean?

It means, we express each interval as the product of primes and exponents (as we did in the first example) and use the exponent values like coordinates in the lattice space. This means that each interval in the scale can be placed at a specific point in the space.

coords = ratios_to_coordinates(scale.degrees, generators=(2, '6/5', '5/4'))
for degree, coord in zip(scale.degrees, coords):
    print(f"{str(degree):>8s}  ->  {tuple(int(x) for x in coord)}")
       1  ->  (0, 0, 0)
     9/8  ->  (-1, 2, 2)
     5/4  ->  (0, 0, 1)
     4/3  ->  (1, -1, -1)
     3/2  ->  (0, 1, 1)
     5/3  ->  (1, -1, 0)
    15/8  ->  (0, 1, 2)

Octave-reducing and omitting the 2-axis, we get this structure:

# tone_lattice = ToneLattice(2, resolution=3)
tone_lattice = ToneLattice.from_generators(('6/5', '5/4'), resolution=3)
nodes = [tuple(int(x) for x in c[1:]) for c in coords]
plot(tone_lattice, nodes=nodes, figsize=(7,4), fit=True)
Loading...
Loading...

This is the standard major scale embedded in a lattice.

I know, not that exciting. Let’s try something else...

bagpipes_scale = Scale.bagpipes()
print(bagpipes_scale)
play(bagpipes_scale.root('G4'))
Scale([1, 9/8, 5/4, 4/3, 27/20, 3/2, 5/3, 7/4, ...], equave=2)
Loading...

This is an ancient scale that originates from Scotland known as the Highland Bagpipes scale.

Yes, bagpipes.

Believe it or not, bagpipes are actually tuned.

coords = ratios_to_coordinates(bagpipes_scale.degrees)
for degree, coord in zip(bagpipes_scale.degrees, coords):
    print(f"{str(degree):>8s}  ->  {tuple(int(x) for x in coord)}")
       1  ->  (0, 0, 0, 0)
     9/8  ->  (-3, 2, 0, 0)
     5/4  ->  (-2, 0, 1, 0)
     4/3  ->  (2, -1, 0, 0)
   27/20  ->  (-2, 3, -1, 0)
     3/2  ->  (-1, 1, 0, 0)
     5/3  ->  (0, -1, 1, 0)
     7/4  ->  (-2, 0, 0, 1)
    16/9  ->  (4, -2, 0, 0)
     9/5  ->  (0, 2, -1, 0)

Notice that it’s a bit more complex than our plain, ol’ major scale. In fact, it has an additional prime dimension. Meaning, this scale must be embedded in a 3-D space:

tone_lattice = ToneLattice(3, resolution=4)
nodes = [tuple(int(x) for x in c[1:]) for c in coords]
plot(tone_lattice, nodes=nodes, figsize=(9,5), mute_background=False, fit=True)
Loading...
Loading...

Pretty cool. Let’s try another...

janus_scale = Scale.janus()
# plot(janus_scale, layout='circle')
play(janus_scale.root('C4'), dur=0.25)
Loading...

This is known as the Janus scale. Let’s do some prime decomposition...

coords = ratios_to_coordinates(janus_scale.degrees)
for degree, coord in zip(janus_scale.degrees, coords):
    print(f"{str(degree):>8s}  ->  {tuple(int(x) for x in coord)}")
       1  ->  (0, 0, 0, 0, 0)
   33/32  ->  (-5, 1, 0, 0, 1)
     9/8  ->  (-3, 2, 0, 0, 0)
     7/6  ->  (-1, -1, 0, 1, 0)
     5/4  ->  (-2, 0, 1, 0, 0)
   21/16  ->  (-4, 1, 0, 1, 0)
    11/8  ->  (-3, 0, 0, 0, 1)
     3/2  ->  (-1, 1, 0, 0, 0)
   99/64  ->  (-6, 2, 0, 0, 1)
     5/3  ->  (0, -1, 1, 0, 0)
     7/4  ->  (-2, 0, 0, 1, 0)
    15/8  ->  (-3, 1, 1, 0, 0)

Ah ha... do you notice something?

This scale is even more complex. It has a prime limit of 11, which means it needs a 4-D space.

But what do we do when we have more than three dimensions?

Dimensionality Reduction

We need to do something called dimensionality reduction. This means, we need to take positions in a higher-dimensional space and represent them in a lower-dimensional space.

There are many different algorithms for doing this, and they all produce different results. In the below examples, we’re going to use a technique called multi-dimensional scaling (MDS).

Basically, we’re going to do our best to preserve the distances between points in the high-dimensional space and project them into a lower-dimensional space:

tone_lattice = ToneLattice(4)
nodes = [tuple(int(x) for x in c[1:]) for c in coords]
plot(tone_lattice, nodes=nodes, figsize=(7,7), mute_background=False, dim_reduction='mds', fit='tight')
/Users/ryanmillett/klotho-venv/lib/python3.13/site-packages/sklearn/manifold/_mds.py:744: FutureWarning: The default value of `n_init` will change from 4 to 1 in 1.9. To suppress this warning, provide some value of `n_init`.
  warnings.warn(
/Users/ryanmillett/klotho-venv/lib/python3.13/site-packages/sklearn/manifold/_mds.py:754: FutureWarning: The default value of `init` will change from 'random' to 'classical_mds' in 1.10. To suppress this warning, provide some value of `init`.
  warnings.warn(
/Users/ryanmillett/klotho-venv/lib/python3.13/site-packages/sklearn/manifold/_mds.py:779: FutureWarning: Use metric_mds=True instead of metric=True. The support for metric={True/False} will be dropped in 1.10.
  warnings.warn(
Loading...
Loading...

The math is the same at any dimensionality—monzos, basis matrices, unimodularity—just with bigger vectors and matrices. The main practical challenge is visualization: beyond 3-D we need dimensionality reduction techniques like MDS.

Observation

Now that you’ve seen scale embedding in multiple dimensions, go back and look again at each one.

Do you notice a common quality they all share?

Each point is adjacent to at least one other point in the scale structure. Meaning, we don’t see scales where there’s a cluster of connected points and then one lone point disconnected from the others.

Maybe that doesn’t seem all that profound but if we do this kind of embedding on other scales throughout human history (and pre-history), they almost always have this quality. It’s curious because many scales were created hundreds or thousands of years before we had the mathematics to formalize them like this—many of them before we even had mathematics at all. And this quality holds, not only throughout history, but across world cultures—not just Western music.

I don’t know... I just think that’s really interesting.


Summary

Here’s what we covered:

  1. Scale embedding means decomposing a scale’s intervals into lattice coordinates via their monzos. The resulting geometry reveals structural properties of the scale.

  2. Different scales produce different geometric shapes in the lattice. Simple scales like the major scale live in 2-D, while more exotic scales (like the Highland Bagpipes or Janus scales) require higher dimensions.

  3. Dimensionality reduction is sometimes necessary when a scale’s prime limit exceeds our ability to visualize. We drop the least significant axes while preserving the most important structural relationships.

  4. The geometry of an embedded scale—which nodes it occupies and how they connect—is a fingerprint of that scale’s harmonic character.

In the next notebook, we’ll explore how to change the axes of a lattice using custom generators, and see how the same geometric structure produces entirely different music when re-interpreted through a different harmonic basis.