Skip to contents

Introduction

The goal of musicMCT is to provide computational tools for the study of musical scales. It includes functions that will probably be familiar to music theorists from pitch-class set theory in the vein of Allen Forte’s The Structure of Atonal Music (1973). For instance, we can calculate the interval-class vector of the diatonic scale (Forte’s set class 7-35) as follows:

ivec(sc(7,35))
#> [1] 2 5 4 3 6 1

The top line of the block above is what you would input to R; the bottom line is the output of the function. Ignore the [1], which just indicates that you’re seeing the beginning of the output. The ic-vector is 2 5 4 3 6 1 as we’d expect.

The main purpose of the package, however, is not to reproduce traditional pc-set theory but to let us explore the geometry of musical scales described in “Modal Color Theory” (Sherrill 2025, Journal of Music Theory 69/1: 1-49). As that article explains, seven-note scales live in a 6-dimensional geometry populated by 1,824,229 qualitatively distinct scalar structures. I’m happy to calculate an ivec() by hand, but we’re going to need computers to explore such a complicated space!

This vignette will introduce many of the main functions of musicMCT that serve this purpose, especially as they relate to the concerns of the Journal of Music Theory article. The problem that we’ll discuss is not of deep significance, but I hope a toy example will teach you how to use this software and give you a sense of the kinds of questions that deeper study might get into.

How acoustic is the “acoustic scale”?

1. Introducing the main characters

A familiar object from the theory of 20th-century music is the scale (C, D, E, F-sharp, G, A, B-flat). This is the fourth mode of melodic minor. We can verify that fact by defining the melodic minor scale for R and then calling sim(), the package’s function for finding the modes of a scale:

melodic_minor <- c(0, 2, 3, 5, 7, 9, 11)
sim(melodic_minor)
#>      [,1] [,2] [,3] [,4] [,5] [,6] [,7]
#> [1,]    0    0    0    0    0    0    0
#> [2,]    2    1    2    2    2    2    1
#> [3,]    3    3    4    4    4    3    3
#> [4,]    5    5    6    6    5    5    4
#> [5,]    7    7    8    7    7    6    6
#> [6,]    9    9    9    9    8    8    8
#> [7,]   11   10   11   10   10   10   10

Note the top line’s syntax, which defines the melodic minor scale. We’ll be using it a lot! The arrow <- assigns the value on the right side to the variable name on the left side. The c( ... ) on the right side of this statement is the standard way to tell R to treat a bunch of things as a single collection. (See c(). musicMCT also has a couple functions that are meant to mimic c() while letting you enter intervals as just-intonation frequency ratios instead of pitch-space distances: see j() and z().)

Pitch-classes are represented by numbers: C=0, C-sharp = 1, and so on. For familiar sets in 12edo (12 equal divisions of the octave), these are all integers. Later we’ll see that it’s easy to work with integers in any k edo. But the values that define our scales don’t have to be integers at all: Modal Color Theory works in continuous pitch-class space. For instance, here’s a common just tuning of the major scale (Ptolemy’s “intense” or “syntonic” diatonic):

j(dia)
#> [1]  0.000000  2.039100  3.863137  4.980450  7.019550  8.843587 10.882687

Getting back to melodic minor, the function sim() above computes the scalar interval matrix of a set (as defined by Tymoczko 2008), which presents to us the modes of a scale as the columns of the matrix. Our input, the melodic minor scale, is in the first column, and the scale we want to study can be read from the fourth column of the SIM:

sim(melodic_minor)[, 4]
#> [1]  0  2  4  6  7  9 10

Just to practice using R, let’s pretend that we’ve forgotten how to interpret pitch-class integers and that we therefore need to verify that those numbers do correspond to the notes (C, D, E, F-sharp, G, A, B-flat). We’ll do that by checking the voice leading from C major to this scale:

c_major <- c(0, 2, 4, 5, 7, 9, 11)
minimize_vl(c_major, sim(melodic_minor)[, 4])
#> [1]  0  0  0  1  0  0 -1

As Tymoczko 2007 points out, key signatures are essentially just voice leadings from the C major scale to another heptachord, and here we’ve found a voice leading that raises the fourth step (F) a semitone while lowering the seventh step (B). So the numbers (0, 2, 4, 6, 7, 9, 10) do indeed correspond to the scale (C, D, E, F-sharp, G, A, B-flat).

Now, this scale is sometimes called the “acoustic” scale because in some sense it is close to a seven-note chunk of the overtone series. Our task in this vignette is to use musicMCT to see how real that similarity is. Let’s agree to call these two scales the “acoustic” and “overtone” scales, respectively.

The overtone scale is defined by treating the 7th through 13th harmonics of the overtone series as a scale (noting that the 14th harmonic is an octave above the 7th). We’ll convert that into semitone measurements as follows:

overtones <- 7:13
frequency_ratios <- overtones / 7
semitone_values <- 12 * log2(frequency_ratios)
overtone_scale <- sim(semitone_values)[, 2]
print(overtone_scale)
#> [1] 0.000000 2.039100 3.863137 5.513179 7.019550 8.405277 9.688259

Let’s find the voice leading from the overtone scale to the acoustic scale, since voice leading distance offers an approximate measure of musical similarity (as Callender, Quinn, and Tymoczko 2008 argue):

acoustic_scale <- sim(melodic_minor)[, 4]
minimize_vl(overtone_scale, acoustic_scale)
#> [1]  0.00000000 -0.03910002  0.13686286  0.48682058 -0.01955001  0.59472338
#> [7]  0.31174094

Intuitions about voice leading distances can be tricky, but this initially doesn’t seem like a big distance between the two scales. The average amount that each voice has to move is about 23 cents (a quarter of a semitone). On the other hand, our voice leading from C major to C acoustic was also pretty small: the average distance an individual voice had to move there was about 29 cents (2/7 of a semitone). Thus, by one measure, the acoustic_scale is about as good of an approximation of c_major as it is of the overtone_scale!

We need better tools than mere voice-leading distance to think about scale similarity. One is the idea of quantization, which we’ll address in the next section. We’ll see that it doesn’t tell the whole story either.

2. A few supporting characters

The normal justification for calling the acoustic_scale “acoustic” is that it’s what you get if you simply round the values of the overtone_scale to the nearest integer (in 12edo):

round(overtone_scale, digits=0)
#> [1]  0  2  4  6  7  8 10

Wait a second, that’s not even the acoustic scale! It has two consecutive semitones (6, 7, 8) and actually represents sc7-33, the “whole-tone plus one” scale. What’s going on here!?

Tymoczko (2013, 130) explains that there’s really no such thing as the quantization of the overtone_scale. There are 7 different quantizations. The rounding above unfairly privileges C (0) as a note that’s guaranteed not to move. If we treat all the degrees of the scale more equally, sometimes a different note gets to stay fixed. Let’s try it.

If the scale’s tonic starts on a 12edo integer, it won’t change when we quantize the scale. Transposing the whole scale up 1 semitone by using tn(), we should expect to get essentially the same quantization, just with 1 added to every value:

round(overtone_scale, digits=0)
#> [1]  0  2  4  6  7  8 10
round(tn(overtone_scale, 1), digits=0)
#> [1]  1  3  5  7  8  9 11

To find the other possible quantizations, we need to explore the range of transpositions where the scale’s tonic is some value 0 < x < 1. If we transpose the overtone_scale so that it starts on every cent (i.e. hundredth of a semitone) in that range, that should be a fine enough sampling to encounter every quantization:

amounts_to_transpose <- (0:99)/100
transposed_scales <- sapply(amounts_to_transpose, tn, set=overtone_scale)
quantized_scales <- apply(transposed_scales, 2, round, digits=0)
unique_quantizations <- unique(quantized_scales, MARGIN=2)
print(unique_quantizations)
#>      [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8]
#> [1,]    0    0    0    0    1    1    1    1
#> [2,]    2    2    3    3    3    3    3    3
#> [3,]    4    4    4    4    4    5    5    5
#> [4,]    6    6    6    6    6    6    6    7
#> [5,]    7    7    7    8    8    8    8    8
#> [6,]    8    9    9    9    9    9    9    9
#> [7,]   10   10   10   10   10   10   11   11

We’re almost there, but some of the scales start on 0 and some on 1. The eighth column is just \({T_1}\) of the first. So let’s make them all start on zero to double-check that they’re truly unique:

unique_quantizations_from_0 <- apply(unique_quantizations, 2, startzero)
final_quantizations <- unique(unique_quantizations_from_0, MARGIN=2)
colnames(final_quantizations) <- apply(final_quantizations, 2, fortenum)
print(final_quantizations)
#>      7-33 7-34 7-31 7-28 7-29 7-27 7-34
#> [1,]    0    0    0    0    0    0    0
#> [2,]    2    2    3    3    2    2    2
#> [3,]    4    4    4    4    3    4    4
#> [4,]    6    6    6    6    5    5    5
#> [5,]    7    7    7    8    7    7    7
#> [6,]    8    9    9    9    8    8    8
#> [7,]   10   10   10   10    9    9   10

And thus we really do get exactly the seven quantizations that Tymoczko predicts. Of these seven, is there any reason to think that the acoustic_scale is a better quantization than the other possibilities? Tymoczko proposes one perspective: of the 100 scales in transposed_scales, how many of them quantize to each of the seven final_quantizations? This is easy to answer too (though here I’m going to gloss over the details of the code that gets these numbers):

#> 7-33 7-34 7-31 7-28 7-29 7-27 7-34 
#>   11   37    2    2   13   18   17

That is, the WT-plus-1 scale that we first quantized to accounts for about 11% of the range of quantizations, whereas there are two distinct modes of melodic minor (sc7-34) which together account for 54% of the quantizations. The first mode, which is the acoustic_scale proper, accounts on its own for 37%–definitely the lion’s share of them.

This lends credence to conventional view, suggesting that the acoustic_scale might be a preferable quantization of the overtone_scale even if it isn’t the only quantization.

3. But what about scale structure?

So far we’ve used some of the tools of musicMCT but we haven’t really applied the concepts of Modal Color Theory. That’s because we’ve been treating the scales’ continuous voice-leading space as essentially undifferentiated, whereas the central argument of MCT is that there are discrete regions in the geometry that correspond to qualitative differences in scale structure.

MCT models scale structure by comparing intervals that belong to the same generic size. For instance, a big part of the character of the familiar major scale lies in the fact that most of its steps have the same size, except for \(\hat{3}\)-\(\hat{4}\) and \(\hat{7}\)-\(\hat{1}\) which are smaller (but match each other). MCT breaks this down into individual comparisons: is the step \(\hat{1}\)-\(\hat{2}\) bigger or smaller than the step \(\hat{2}\)-\(\hat{3}\)? Is the skip \(\hat{1}\)-\(\hat{3}\) bigger or smaller than the skip \(\hat{3}\)-\(\hat{5}\)? And so on.

How long before we have exhausted all the potential comparisons that we could make? Each comparison corresponds to a hyperplane in the geometry, and musicMCT offers a complete list of all the hyperplanes in a matrix called an ineqmat (for “inequality matrix”). The relevant matrix for four-note scales looks like this:

getineqmat(4)
#>      [,1] [,2] [,3] [,4] [,5]
#> [1,]   -1    2   -1    0    0
#> [2,]   -1    1    1   -1    0
#> [3,]    0   -1    2   -1    0
#> [4,]   -2    1    0    1   -1
#> [5,]   -1   -1    1    1   -1
#> [6,]   -1    0   -1    2   -1
#> [7,]   -2    0    2    0   -1
#> [8,]    0   -2    0    2   -1

Each row of the matrix represents a different pairwise interval comparison: for tetrachords, we apparently need to consider 8 different comparisons to have a complete accounting of a scale structure. Two scales have the same structure (or belong to the same “color”) if they answer all 8 comparisons in the same way. musicMCT summarizes this information in a scale’s signvector(). For instance, let’s consider set classes 4-6 (prime form 0127) and 4-24 (prime form 0248). Here are their sign vectors:

signvector(sc(4, 6))
#> [1]  0 -1 -1 -1 -1  0 -1  0
signvector(sc(4, 24))
#> [1]  0 -1 -1 -1 -1  0 -1  0

These vectors are the same, so MCT considers the tetrachords to have the same scalar structure. For instance, both start with two identical steps (0-1-2 and 0-2-4) and then have a larger leap (2-7 and 4-8).

For heptachords, the relevant ineqmat has 42 rows, so the space of scale structures is considerably more complicated. Generally you wouldn’t learn a lot from trying to read sign vectors directly–musicMCT has more human-readable functions that will help you interpret them–but let’s take a gander at them for the acoustic and overtone scales:

signvector(acoustic_scale)
#>  [1]  0  0  0  1  1  1  0  0  0 -1  1  1  1  0  1  0  0  0 -1  0 -1  1  1  1  1
#> [26]  1  0  1  1  0  0  0 -1 -1 -1  1  1  0  0  0 -1 -1
signvector(overtone_scale)
#>  [1]  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1 -1 -1 -1 -1 -1 -1  1  1  1  1
#> [26]  1  1  1 -1 -1 -1 -1 -1 -1 -1  1  1  0 -1 -1 -1 -1

One visually apparent difference between these is that the acoustic_scale has many more values of 0 in its sign vector than the overtone_scale does. In fact, the latter has only a single 0: all the rest of its values are 1 or -1. What does this tell us about the structure of the two scales?

Each entry in a sign vector is a comparison between two intervals in a scale: both 1 and -1 mean that one interval is bigger than the other, whereas a 0 means that the two intervals are identical. For instance, the first 0 in the sign vector for 0127 and 0248 is what encodes the fact that both of them have a first step that equals their second step.

From this, we can see that the acoustic_scale is considerably more regular than the overtone_scale, in the sense that it has many specific interval sizes that repeat inside it (like the 5 whole tones that dominate its generic steps, and the 4 perfect fifths that are the majority of its generic fifths). This contrast between the scales isn’t too surprising. The acoustic_scale is constrained to twelve-tone equal temperament, so there are only so many intervals that it could possibly be made up of. By contrast, the overtone_scale is defined in continuous pitch-class space and has a lot more variety to choose from. Moreover, given the logarithmic relationship between frequency ratios and pitch intervals, each successive step in the overtone series is smaller than the previous, so it makes sense that the overtone_scale would have a lot of intervallic variety. If anything, it’s surprising that it repeats any intervals.

We can learn what interval it does repeat by putting together the information from the sign vector with the ineqmat for heptachords. We could manually count which entry of the sign vector is a 0, but musicMCT also has a function for that:

whichsvzeroes(overtone_scale)
#> [1] 38

We can now look at the row of the heptachordal ineqmat to see what interval comparison it defines:

getineqmat(7)[whichsvzeroes(overtone_scale),]
#> [1] -1 -1  0  0  2  0  0 -1

This tells us that the fourth above \(\hat{5}\) equals the fourth below \(\hat{5}\) in size. (See page 43 of “Modal Color Theory” for a discussion of how to read the rows of an ineqmat like this.) And, sure enough, this is true:

signed_interval_class(overtone_scale[5]-overtone_scale[1])
#> [1] -4.98045
signed_interval_class(overtone_scale[5]-overtone_scale[2])
#> [1] 4.98045

If the acoustic_scale is to be an approximation of the overtone_scale, we might want it to retain this repeated interval, which it does: the perfect fourth from D to G matches the perfect fourth from G to C. We could also verify that by checking that the 38th entry of its sign vector is also 0:

signvector(acoustic_scale)[38]
#> [1] 0

In fact, why don’t we consider all seven of the distinct quantizations from the previous section?

all_signvectors <- apply(final_quantizations, 2, signvector)
all_signvectors[38, ]
#> 7-33 7-34 7-31 7-28 7-29 7-27 7-34 
#>    0    0   -1    1    0    0    0

Most of them do match the overtone_scale on this point, except for sc7-31 and sc7-28. Note that these are also the scales that barely occurred at all as quantizations: each accounts for only about 2% of all the quantizations according to the table at the end of the previous section.

We can do better than looking at only position 38 in the sign vector. Why don’t we compare the entire sign vectors at once? There are various ways to do this, but one that’s simple to calculate is to treat the sign vectors as if they were real vectors in a 42-dimensional space and compute their distances:

signvectors_with_overtone_scale <- rbind(signvector(overtone_scale), t(all_signvectors))
rownames(signvectors_with_overtone_scale)[1] <- "o.s."
dist(signvectors_with_overtone_scale)
#>          o.s.     7-33     7-34     7-31     7-28     7-29     7-27
#> 7-33 4.795832                                                      
#> 7-34 5.000000 4.472136                                             
#> 7-31 5.385165 5.291503 4.000000                                    
#> 7-28 5.196152 5.656854 5.477226 4.472136                           
#> 7-29 5.099020 5.744563 6.403124 5.744563 4.358899                  
#> 7-27 3.605551 5.099020 5.830952 6.000000 5.291503 4.358899         
#> 7-34 5.385165 4.472136 6.480741 6.324555 5.830952 5.567764 3.464102

The first column above, labeled “o.s.” for overtone_scale, represents the distance between the overtone_scale’s sign vector and the 7 other sign vectors (as labeled on the rows of the matrix). The other columns represent distances among the various quantizations.

In a surprise upset, the quantization with the closest sign vector to the overtone_scale is apparently sc7-27 as represented by its mode (0, 2, 4, 5, 7, 8, 9). This doesn’t mean that sc7-27 is a better quantization than the acoustic_scale, just that it’s one that better reflects the structure of the overtone_scale. (In particular, it comes closest to capturing the fact that the overtone_scale’s step sizes get smaller as you go up the scale. We’ll return to this consideration as we approach our final approximation at the end of the next section.) The modes of melodic minor simply have too much regularity–too many zeroes in their sign vectors–to be very structurally similar to the overtone_scale.

Another scalar property that we might consider is how many degrees of freedom a scale has (“Modal Color Theory,” 26-27): how freely can you vary its individual scale degrees while maintaining the same overall structure? A minimally constrained heptachord has six degrees of freedom, and we know that the overtone_scale has one zero in its sign vector, which ought to make it lose a degree of freedom. (Our choices about \(\hat{1}\) and \(\hat{5}\) ought to determine where \(\hat{2}\) goes.) Let’s compare the overtone_scale’s freedom to its seven quantizations:

howfree(overtone_scale)
#> [1] 5
apply(final_quantizations, 2, howfree)
#> 7-33 7-34 7-31 7-28 7-29 7-27 7-34 
#>    1    1    2    1    1    1    1

All of these are much less free to vary than the overtone_scale! All but sc7-31 can only vary along their line of saturation (“Modal Color Theory,” 20), and sc7-31 is only a little bit more flexible. This is a consequence of quantizing to twelve-tone equal temperament, which simply doesn’t have enough distinct notes to accommodate more flexible heptachords. We can check the degrees of freedom of all 38 heptachordal set classes in 12edo:

all_12edo_heptachords <- sapply(1:38, sc, card=7)
heptachord_freedoms <- apply(all_12edo_heptachords, 2, howfree)
table(heptachord_freedoms)
#> heptachord_freedoms
#>  1  2 
#> 11 27

This table shows that there are 11 set classes with 1 degree of freedom and 27 set classes with 2 degrees of freedom. None are freer than that. If we were looking for an approximation of the overtone_scale with similar structural properties (like 5 degrees of freedom), we unwittingly set ourselves up to fail by restricting our attention to the twelve-tone chromatic universe.

4. Looking for better approximations outside of 12edo

If we are willing to quantize to other equal divisions of the octave, we can find much better structural approximations. In particular, musicMCT has a function that will find a quantized representative of any scalar color, in the lowest n-note equal division of the octave where such a quantization exists.

quantized_overtone_color <- quantize_color(overtone_scale)
print(quantized_overtone_color)
#> $set
#> [1]  0  6 11 15 18 20 21
#> 
#> $edo
#> [1] 30

Note that this function returns a list object. The first entry of the list defines the scale itself: pitch class integers 0, 6, 11, 15, 18, 20, 21. The second entry tells us interpret those integers in 30 equal divisions of the octave. If we want to use this as an input to other functions, usually we only want the pitch-class values themselves, which we can access as quantized_overtone_color$set. We’ll need to tell most functions to interpret that information in 30edo separately, usually by setting the function’s edo parameter explicitly.

We should check this quantization in two ways. Let’s convert it to twelve-tone equal temperament to get a sense for how objectively close it is to the overtone_scale, and let’s verify that it belongs to the same “color” as the overtone_scale by verifying that it has the same sign vector as the scale it approximates:

round(overtone_scale, digits=2)
#> [1] 0.00 2.04 3.86 5.51 7.02 8.41 9.69
convert(quantized_overtone_color$set, 30, 12)
#> [1] 0.0 2.4 4.4 6.0 7.2 8.0 8.4
isTRUE(all.equal(signvector(overtone_scale), signvector(quantized_overtone_color$set, edo=30)))
#> [1] TRUE

So this quantization does indeed have all the same structural properties as the overtone_scale, but its actual pitches place it rather farther away than the acoustic_scale was. This just reinforces the point that there’s no such thing as one single quantization of a given scale: there are only different quantizations that satisfy different equivalences we might choose to care about.

In that spirit, there’s yet another way to approximate the overtone_scale which we haven’t yet considered. This form of approximation uses the notion of adjacency between colors in the hyperplane arrangement (“Modal Color Theory,” 31-34). If two scalar colors are adjacent, one is a simplification of the other that preserves some aspects of scalar structure while also enforcing a greater degree of regularity on the scale. For instance, the major triad (0, 4, 7) and the minor triad (0, 3, 7) are non-equivalent structures that are both adjacent to the “neutral” triad (0, 3.5, 7). The neutral triad has only two sizes of step (3.5 and 5) as opposed to the three step sizes for major and minor (3, 4, and 5). It has a similar structure to them, in that its last step (5 semitones from fifth to root) is larger than the others, but it is more regular than major and minor because it makes no distinction between its root-third and third-fifth intervals. Another color adjacency which may be familiar to music theorists is the relationship between the 5-limit just diatonic scale (Ptolemy’s “intense diatonic”) and the meantone diatonic scale represented by (0, 2, 4, 5, 7, 9, 11) in 12edo. The just and meantone scales are similar in many ways, but the meantone scale elides distinctions that the just diatonic makes, such as the difference between major and minor whole tones. (The meantone diatonic is named for this particular elision of difference, since it averages out the sizes of the larger and smaller just whole tones.)

With this in mind, let us ask: what more regular colors is the overtone_scale adjacent to? We might first compare it to the 12edo quantizations above. musicMCT lets us do this with the function comparesignvecs(), which returns 1 if two sign vectors represent adjacent colors, 0 if two sign vectors are identical, and -1 if the colors are neither the same nor adjacent.

overtone_sv <- signvector(overtone_scale)
apply(all_signvectors, 2, comparesignvecs, signvecY=overtone_sv)
#> 7-33 7-34 7-31 7-28 7-29 7-27 7-34 
#>   -1   -1   -1   -1   -1   -1   -1

Alas, none of our 12edo quantizations are adjacent colors to the overtone_scale! This isn’t surprising given the table of sign-vector distances near the end of the previous section, but it reinforces our conclusion that twelve-tone equal temperament isn’t the temperament you’d choose if your only goal were to approximate these 7 overtones.

How do we find colors that are adjacent to the overtone_scale? It isn’t as simple as making a minimal change to the sign vector and creating a scale to match, since most conceivable sign vectors don’t correspond to scales that can actually exist. We could try to solve this problem from scratch, but luckily we don’t have to: part of MCT is a map of all the colors that exist (for scale sizes up to heptachords) and their adjacency relationships.

This map involves datasets that are too large to distribute with the musicMCT package. They can be downloaded from the GitHub repository modalcolortheory:

  • representative_scales.rds (~80 MB) contains a list of all scalar colors for scales of 2-7 notes, with one concrete scale to represent each color
  • representative_signvectors.rds (~10 MB) contains a list of their corresponding sign vectors
  • color_adjacencies.rds (~130 MB) contains information about the adjacency relationships between all these colors (in the form of an adjacency list)

If you wish to work with this data, you should save the files in your working directory, which you can find by calling getwd() in R. Then import the files into your R session with the following commands:

representative_scales <- readRDS("representative_scales.rds")
representative_signvectors <- readRDS("representative_signvectords.rds")
color_adjacencies <- readRDS("color_adjacencies.rds")

The file representative_signvectors is particularly useful because it enables us to assign a well-defined “color number” to identify each color in a MCT hyperplane arrangement (“Modal Color Theory,” 28). If you try to look up a scale’s color number without the representative_signvectors saved, you’ll probably get a NULL result:

colornum(overtone_scale)
#> NULL

but if you do have the file, you’ll discover that the overtone_scale belongs to heptachord color 1824025. From this, we can look up an accounting of all colors that are adjacent to it. color_adjacencies is structured as a list in R, whose top level organization reflects the size of the scales you are studying, so color_adjacencies[[7]] contains all the information about the geometry of seven-note scales. To find out which scales are adjacent to color 1824025, we want the list element color_adjacencies[[7]][[1824025]]. The following code won’t work on your device unless you have color_adjacencies saved and loaded into R, but its objective is to find the most regular scale that the overtone_scale is structurally adjacent to:

os_adjacent_colors <- color_adjacencies[[7]][[colornum(overtone_scale)]]
os_adjacent_scales <- representative_scales[[7]][, os_adjacent_colors]
regularity <- apply(os_adjacent_scales, 2, countsvzeroes)
most_regular_approximation <- os_adjacent_scales[, which.max(regularity)]
quantize_color(most_regular_approximation)

If that code runs on your device, it should tell you that the most regular quantized approximation of the overtone_scale is the scale (0, 2, 4, 6, 8, 10, 11) in 14-tone equal temperament. Converted to familiar semitone measurements, this is the scale (0.00, 1.71, 3.43, 5.14, 6.86, 8.57, 9.43). To understand better how this scale “regularizes” the overtone_scale, let’s consider the two scales in terms of their ranked step sizes:

asword(overtone_scale)
#> [1] 6 5 4 3 2 1 7
best_simple_approximation <- convert(c(0, 2, 4, 6, 8, 10, 11), 14, 12)
asword(best_simple_approximation)
#> [1] 2 2 2 2 2 1 3

This tells us that the overtone_scale starts with its 6th largest step and then ascends through progressively smaller step sizes, until its final step from \(\hat{7}\) to \(\hat{1}\) uses the scale’s largest step size.

Our best_simple_approximation reduces most of that variety out of the step sizes: all the steps from its \(\hat{1}\) up to \(\hat{6}\) are equal. The only intervallic variety that this approximation retains is that \(\hat{6}\) to \(\hat{7}\) is still the smallest step in the scale, and \(\hat{7}\) to \(\hat{1}\) is still the largest step in the scale.

By the logic of color adjacency, this is the most regular scale that simplifies the structure of the overtone_scale without contradicting any of its structure. By contrast, our original acoustic_scale does flatly contradict aspects of the overtone_scale’s structure. For instance, in the acoustic_scale the step from \(\hat{4}\) to \(\hat{5}\) is smaller than the step from \(\hat{5}\) to \(\hat{6}\), but in the overtone_scale \(\hat{5}\) to \(\hat{6}\) is smaller (because all its step sizes get smaller until the last one). We can see that contradiction by comparing the 10th place of their respective sign vectors:

signvector(overtone_scale)[10]
#> [1] 1
signvector(best_simple_approximation)[10]
#> [1] 0
signvector(acoustic_scale)[10]
#> [1] -1

The acoustic_scale is the odd one out here. It’s also, in terms of voice-leading distance, not clearly closer to the overtone_scale than our best_simple_approximation is:

round(minimize_vl(overtone_scale, acoustic_scale), 3)
#> [1]  0.000 -0.039  0.137  0.487 -0.020  0.595  0.312
round(minimize_vl(overtone_scale, best_simple_approximation), 3)
#> [1]  0.000 -0.325 -0.435 -0.370 -0.162  0.166 -0.260

It’s not easy to eyeball which one of those is a smaller overall voice leading, partially because the answer to that question depends on what measure of distance you want to use. (For those who care about such things, the acoustic_scale is a closer approximation using the taxicab norm, but our best_simple_approximation is closer under the Euclidean and Chebyshev norms.)

5. A tempered conclusion

It would make for a tighter narrative to end with a conclusive declaration that our best_simple_approximation really is the best way to quantize the overtone_scale. But reality isn’t so simple! The best approximation for a scale depends on what you’re trying to approximate for. This needs to be underscored, because the attempt to justify certain scale structures as mathematically ideal is probably the oldest tradition of European music theory. By using math to talk about scales, Modal Color Theory may look like it contributes to that tradition, when my goal with the theory is to do just the opposite. The questions that MCT should be useful for are descriptive ones like “If you’re using this scale, what can you do with it?” and “If you want to do X, how can we find some scale Y to facilitate X?” Sami Abu Shumays is right to decry as a fallacy “the idea that the tunings of musical scales are the result of mathematical laws, rather than cultural choices that intersect with acoustical realities.” My hope for MCT is that its math lets us get deeper insight into the nature of that intersection between cultural choice and acoustical affordance.

In the end, the acoustic_scale actually is a pretty decent approximation for many purposes, especially if you’re limited to the familiar 12 pitch-classes of the piano. Hopefully, though, this vignette shows how we might, when musicking with different priorities, come to make other choices–and how musicMCT can aid us in that process.

Coda: Brightness graphs

Up to this point, we’ve done everything numerically, but “Modal Color Theory” also has some visualization tools. The one I want to introduce here is the “brightness graph,” which lets us visualize the voice-leading relationships between a scale’s modes (and thus to some degree the internal structure of the scale, as described in “Modal Color Theory,” 7-11). Using musicMCT we can make quick mock-ups of brightness graphs with brightnessgraph(). First let’s take a look at the acoustic_scale:

brightnessgraph(acoustic_scale)

The information in this graph is displayed in the same way as Figure 2b of “Modal Color Theory” (p. 8), though you’ll notice a few differences if you compare the article’s figure to the one in this vignette. One difference is trivial: the mode at the bottom of our figure here is mode IV whereas it’s listed as mode VII in the Journal of Music Theory article. That’s just because the article treats melodic minor as mode I whereas we’ve asked for the modes of the acoustic_scale. (If you replace acoustic_scale with melodic_minor in the command above, you should get an identical graph with the roman numeral labels shifted around.) Either way, the precise pitch values for all the nodes are the same: the bottom mode in both graphs is (0, 1, 3, 4, 6, 8, 10).

The less trivial difference between the article and graph vignettes is their overall shape. The function brightnessgraph() determines the vertical position of graph nodes exactly as “Modal Color Theory” describes, but it has less guidance for how to make decisions about left-right placement of nodes. It produces graphs that are usually legible but which may not clarify the underlying structure: I tend to use the function as a first draft and then construct my own (ideally more revealing) graphs by hand. For instance, the Figure 2b of “Modal Color Theory” represents all \(T_2\) relationships as vertical arrows, resulting in a graph whose overall shape is a trapezoid. brightnessgraph() obscures the predominance of \(T_2\) in the structure of the scale by making the chain of whole steps bow out to the left at the middle, giving the graph a diamond-like shape.

Let’s now take a look at the brightness graph for the overtone_scale:

brightnessgraph(overtone_scale, numdigits=1, show_sums=FALSE)

This looks pretty messy! The graph is less connected (i.e. there are fewer arrows) and the vertical placement of its nodes seems haphazard. This reflects the fact that the overtone scale has less regularity as measured by its degrees of freedom or the number of zeroes in its signvector(). Nevertheless, the graph still has some useful information. Since the height of a mode corresponds to its sum brightness, we can see those comparisons at a glance. We can also use sum brightness relationships as a proxy for the relative height of individual pitches in the scale: if a pitch serves as the tonic of a very bright mode, the pitch itself is very low (“dark”) in relation to the scale. Thus, since mode VII is at the top of our graph here, \(\hat{7}\) must be the darkest (relatively lowest) pitch of the scale. Conversely, since mode III is at the bottom of the graph, \(\hat{3}\) is the brightest individual note of the scale.

Recall that one of the approximations we tested above was a quantization of the overtone_scale to a division of the octave into 30 steps. Let’s see what the brightness graph of that approximation looks like:

brightnessgraph(quantized_overtone_color$set, edo=30, show_sums=FALSE)

That’s impressively close to the graph for the actual overtone_scale! (In truth, we got a little lucky here: many colors offer enough room for variation that brightnessgraph() doesn’t always preserve such visually obvious similarity.) Aside from a check that quantize_color() works as intended, one other thing to note from this example is that brightnessgraph(), like almost all functions in musicMCT makes it convenient to work in other “chromatic cardinalities” (i.e. number of unit steps to the octave) besides 12edo. Just be careful not to make direct comparisons between absolute values reckoned in different systems: note, for instance, that the sum brightnesses for our 30edo quantization are all around 90 whereas they were about 36 in the brightness graph of the original overtone_scale. That’s because we’ve changed our unit of measurement to be 2.5 times smaller, so values like distances and sums scale correspondingly.

You might find it instructive to explore the brightness graphs of the remaining final_quantizations from above, which show considerable variety from one to the next. This gives a glimpse of the range of possible structures for seven note scales. Simply change the number 4 in the line of code below to any value from 1 to 7 to check the different scales:

brightnessgraph(final_quantizations[, 4])

Finally, let’s take a look at the brightness graph for our best_simple_approximation. I’m going to suppress its listing of the pitch-classes in each mode, since without manual tweaking they overlap to the point of illegibility:

brightnessgraph(best_simple_approximation, show_pitches=FALSE, show_sums=FALSE)

This feels right to me. At a glance, it’s a lot like the graph for the overtone_scale but without all the mess. All the modes beneath VII are now neatly lined up on a single row at the bottom. That’s the visual equivalent of our search for regularity and color adjacency in the previous section. This is something I like about brightness graphs: they summarize, in a picture or two, ideas that one could spend several thousand words developing.


Last updated: 28 May 2025