C major scale analysis

chord similarity
datavis
circle of fifths
music theory
rstats
C major scale
movement
Looking at the C major scale from the geometry of vector space.
Author

Matt Crump

Published

January 29, 2024

Show the code
from diffusers import DiffusionPipeline
from transformers import set_seed
from PIL import Image
import torch
import random
import ssl
import os
ssl._create_default_https_context = ssl._create_unverified_context

#locate library
#model_id = "./stable-diffusion-v1-5"
model_id = "dreamshaper-xl-turbo"

pipeline = DiffusionPipeline.from_pretrained(
    pretrained_model_name_or_path = "../../../../bigFiles/huggingface/dreamshaper-xl-turbo/"
)

pipeline = pipeline.to("mps")

# Recommended if your computer has < 64 GB of RAM
pipeline.enable_attention_slicing("max")

prompt = "Letter C. Brought to you by the letter C. C. Sesame Street. Muppets."

for s in range(30):
  for n in [5,10]:
    seed = s+21
    num_steps = n+1
    set_seed(seed)
    
    image = pipeline(prompt,height = 1024,width = 1024,num_images_per_prompt = 1,num_inference_steps=num_steps)
    
    image_name = "images/synth_{}_{}.jpeg"
    
    image_save = image.images[0].save(image_name.format(seed,num_steps))

Letter C. Brought to you by the letter C. C. Sesame Street. Muppets. - Dreamshaper v7

I’ve been representing chords as vectors and plotting them in 2-dimensional space to visualize similarity relationships.

Some of the graphs in previous posts are very busy to look at, and it is difficult for me to appreciate what is going on. This post is about interpreting something very familiar, the C major scale, in terms of that similarity space.

In two dimensions, the multi-dimensional scaling algorithm positions notes in terms of the circle of fifths. So, this analysis is similar to what you would get thinking about the C major scale using the circle of fifths. The major difference is the idea of “vectors” that go in a particular direction.

Without further ado, here is a plot of all the notes, all of the notes in the C major scale, and the triad chords from the C major scale.

C major scale components in vector space

Show the code
library(tidyverse)
# pre-processing to get the chord vectors

# load chord vectors
c_chord_excel <- rio::import("chord_vectors.xlsx")

# grab feature vectors
c_chord_matrix <- as.matrix(c_chord_excel[,4:15])

# assign row names to the third column containing chord names
row.names(c_chord_matrix) <- c_chord_excel[,3]

# define all keys
keys <- c("C","Db","D","Eb","E","F","Gb","G","Ab","A","Bb","B")


# the excel sheet only has chords in C
# loop through the keys, permute the matrix to get the chords in the next key
# add the permuted matrix to new rows in the overall chord_matrix
for (i in 1:length(keys)) {

  if (i == 1) {
    # initialize chord_matrix with C matrix
    chord_matrix <- c_chord_matrix

  } else {
    #permute the matrix as a function of iterator
    new_matrix <- cbind(c_chord_matrix[, (14-i):12],c_chord_matrix[, 1:(13-i)] )

    # rename the rows with the new key
    new_names <- gsub("C", keys[i], c_chord_excel[,3])
    row.names(new_matrix) <- new_names

    # append the new_matrix to chord_matrix
    chord_matrix <- rbind(chord_matrix,new_matrix)

  }
}

chord_properties <- tibble(
  type = rep(c_chord_excel$type,length(keys)),
  key = rep(keys, each = dim(c_chord_matrix)[1]),
  chord_names  = row.names(chord_matrix),
  synonyms = list(NA),
  database_chord = FALSE
)

first_order <- lsa::cosine(t(chord_matrix))

# find repeats and build synonym list
repeat_indices <- c()

first_occurrence <- c()

for(i in 1:dim(chord_matrix)[1]){
  # get the current row
  evaluate_row <- first_order[i,]

  # don't count the current item as a repeat
  evaluate_row[i] <- 0

  # repeats are the ids for any other 1s found
  repeats <- which(evaluate_row == 1 )

  if(length(repeats) == 0){
  }

  if(length(repeats) > 0){
    #add to list of repeat items
    repeat_indices <- c(repeat_indices,repeats)

    # add synonyms
    chord_properties$synonyms[i] <- list(synonyms = row.names(chord_matrix)[repeats])
  }

  if(i %in% first_occurrence == FALSE){
    if(i %in% repeat_indices == FALSE){
      first_occurrence <- c(first_occurrence,i)
      chord_properties$database_chord[i] <- TRUE
    }
  }
}

chord_properties <- chord_properties %>%
  mutate(num_notes = rowSums(chord_matrix),
         id = 1:dim(chord_matrix)[1])

# keep only unique chord, recompute similarities
chord_matrix_no_repeats <- chord_matrix[first_occurrence,]
first_order_no_repeats <- lsa::cosine(t(chord_matrix_no_repeats))
second_order_no_repeats <- lsa::cosine(first_order_no_repeats)

# remove scales and individual notes
only_chords <- chord_properties %>%
  filter(type!="scale",
         type!="key",
         database_chord == TRUE)

first_order_chords <- first_order_no_repeats[only_chords$chord_names,
                                               only_chords$chord_names]
second_order_chords <- second_order_no_repeats[only_chords$chord_names,
                                               only_chords$chord_names]
Show the code
library(ggplot2)
library(ggrepel)
mds_first_order <- cmdscale((first_order-1))
mds_first_order <- as_tibble(mds_first_order) %>%
  cbind(chord_properties)

mds_first_order <- mds_first_order %>%
  mutate(key = forcats::fct_relevel(key,"C","F","Bb","Eb","Ab","Db","Gb","B","E","A","D","G")) %>%
  mutate(bold_me = case_when(key == "C" ~ 1,
                             key != "C" ~ 0)) %>%
  filter(type == "key" | chord_names %in% c("C major triad",
                                             "D minor triad",
                                             "E minor triad",
                                             "F major triad",
                                             "G major triad",
                                             "A minor triad",
                                             "Bdim",
                                             "C major scale") == TRUE) %>%
  mutate(V1 = V1*-1,
         V2 = V2*-1) %>%
  mutate(line_V1 = case_when(chord_names %in% c("C major triad",
                                             "D minor triad",
                                             "E minor triad",
                                             "F major triad",
                                             "G major triad",
                                             "A minor triad",
                                             "Bdim",
                                             "C major scale",
                                             "C note",
                                             "D note",
                                             "E note",
                                             "F note",
                                             "G note",
                                             "A note",
                                             "B note") ==TRUE ~ V1),
         line_V2 = case_when(chord_names %in% c("C major triad",
                                             "D minor triad",
                                             "E minor triad",
                                             "F major triad",
                                             "G major triad",
                                             "A minor triad",
                                             "Bdim",
                                             "C major scale",
                                             "C note",
                                             "D note",
                                             "E note",
                                             "F note",
                                             "G note",
                                             "A note",
                                             "B note") ==TRUE ~ V2))

p1 <- ggplot(mds_first_order, aes(V1, V2, 
                                  label = chord_names))+ 
  geom_point(color="black")+
  geom_text_repel(size=8, max.overlaps = 500)+
  geom_segment(arrow = arrow(length = unit(0.5, "cm")),
               aes(x=0,
                   y=0,
                   xend = line_V1,
                   yend = line_V2,
                   color = type))+
  theme_classic() 
p1

It’s morning coffee time and my goal is to make observations about the C major scale from these kinds of graphs. A big theme for this analysis is to better understand “where I am going” in chord space when playing different chords or notes. Direction is denoted in the graph using lines and arrows.

The graph shows the notes in C in the order of the circle of fifths = F C G D A E B (red lines). This is different from the order of white keys on the keyboard = C D E F G A B.

The 7 notes in the scale are symmetrical about the D note. The green arrow shows the vector for the C major scale, which is created by adding all of the notes together. It points in the same direction as the D note. The C major scale (Ionian) is the same as D Dorian. D Dorian has symmetrical 5th components, there are three anti-clockwise (GCF) and three clockwise (AEB). The C major scale is asymmetrical, there is only one anti-clockwise 5th (F), and 5 clockwise 5ths (GDAEB).

In some sense the D dorian scale is comfortably balanced and locked into the D direction. The C scale is more off-kilter, like it is in the middle of taking step, it’s going somewhere.

C major triad

The C major triad has C, E, and G. The vector for the triad is in red. The triad vector is the average of the component vectors for C, E, and G. It’s closer to C and G, than E. The major triad is asymmetrical. It’s direction lands between a G note and a D note. C and G form a perfect fifth, and as a simultaneous pair, their potential for circular motion is ambiguous. Adding the E applies a torque in the clock-wise direction. Similarly, with CE or GE, adding the missing note in the triad twists anti-clockwise.

It’s been a while since I took physics statics and dynamics…but, considering C major triad as a static body, with the elements, C, E, and G, as force vectors, does C major triad have an inherent spin? If we take the D note as the center position, the C major scale is a weighted a bit anti-clockwise from D. If we arranged everything so that D was 90 degrees, and then set up the triad, it looks like it would fall to the left. The curvy arrows represents the direction of spin.

Playing more E (saying scattering Es around the piano) would add to the weight of the E vector, and apply force consistent with spinning the other way.

Show the code
Cmajor <- ggplot(mds_first_order, aes(V1, V2, 
                                  label = chord_names))+ 
  geom_point(color="black")+
  geom_text_repel(size=8, max.overlaps = 500)+
  geom_segment(arrow = arrow(length = unit(0.5, "cm")),
               aes(x=0,
                   y=0,
                   xend = filter(mds_first_order,chord_names == "C note")$V1),
                   yend = filter(mds_first_order,chord_names == "C note")$V2)+
  geom_segment(arrow = arrow(length = unit(0.5, "cm")),
               aes(x=0,
                   y=0,
                   xend = filter(mds_first_order,chord_names == "E note")$V1),
                   yend = filter(mds_first_order,chord_names == "E note")$V2)+
  geom_segment(arrow = arrow(length = unit(0.5, "cm")),
               aes(x=0,
                   y=0,
                   xend = filter(mds_first_order,chord_names == "G note")$V1),
                   yend = filter(mds_first_order,chord_names == "G note")$V2)+
  geom_segment(arrow = arrow(length = unit(0.5, "cm")),
               color = "red",
               aes(x=0,
                   y=0,
                   xend = filter(mds_first_order,chord_names == "C major triad")$V1),
                   yend = filter(mds_first_order,chord_names == "C major triad")$V2)+
  geom_curve(arrow = arrow(length = unit(0.5, "cm")),
               color = "red",
               curvature = 1,
               aes(x=0,
                   y=0,
                   xend = filter(mds_first_order,chord_names == "C major triad")$V1/2),
                   yend = filter(mds_first_order,chord_names == "C major triad")$V2/2)+
  theme_classic()+
  ggtitle("C major triad")+ 
  theme(plot.title = element_text(size=40))
Cmajor

D minor triad

The D minor triad has D, F, and A. The vector for the triad is in red. Its location is right on top of the C major triad, but it gets there from different elements.

Interesting that C major triad and D minor triads go in the same direction. The D minor notes are also the 9th, 11th, and 13th, notes in C extended chords. Perhaps more fittingly, the C major notes are the 7th, 9th, and 11th extensions of D minor.

If the center point is the G note, the D minor triad appears biased to spin clock-wise.

The overall pattern is similar to a major chord, 2 contiguous fifths (DA), and one that is 3 away (F). In both the major and the minor, the outlying fifth is the 3rd. However, in the minor chord, the root (D) is closer to the third, than in the major chord.

Show the code
Dminor <- ggplot(mds_first_order, aes(V1, V2, 
                                  label = chord_names))+ 
  geom_point(color="black")+
  geom_text_repel(size=8, max.overlaps = 500)+
  geom_segment(arrow = arrow(length = unit(0.5, "cm")),
               aes(x=0,
                   y=0,
                   xend = filter(mds_first_order,chord_names == "D note")$V1),
                   yend = filter(mds_first_order,chord_names == "D note")$V2)+
  geom_segment(arrow = arrow(length = unit(0.5, "cm")),
               aes(x=0,
                   y=0,
                   xend = filter(mds_first_order,chord_names == "F note")$V1),
                   yend = filter(mds_first_order,chord_names == "F note")$V2)+
  geom_segment(arrow = arrow(length = unit(0.5, "cm")),
               aes(x=0,
                   y=0,
                   xend = filter(mds_first_order,chord_names == "A note")$V1),
                   yend = filter(mds_first_order,chord_names == "A note")$V2)+
  geom_segment(arrow = arrow(length = unit(0.5, "cm")),
               color = "red",
               aes(x=0,
                   y=0,
                   xend = filter(mds_first_order,chord_names == "D minor triad")$V1),
                   yend = filter(mds_first_order,chord_names == "D minor triad")$V2)+
  geom_curve(arrow = arrow(length = unit(0.5, "cm")),
               color = "red",
               curvature = -1,
               aes(x=0,
                   y=0,
                   xend = filter(mds_first_order,chord_names == "D minor triad")$V1/2),
                   yend = filter(mds_first_order,chord_names == "D minor triad")$V2/2)+
  theme_classic()+
  ggtitle("D minor triad")+ 
  theme(plot.title = element_text(size=40))
Dminor

G major and A minor

The patterns for G major and A minor are similar to C major and D minor. The G major and A minor triads point in the same direction as each other.

The G major triad is notionally centered on A, and wants to spin anti-clockwise.

The A minor triad it notionally centered on D, but it wants to spin clockwise.

Show the code
Gmajor <- ggplot(mds_first_order, aes(V1, V2, 
                                  label = chord_names))+ 
  geom_point(color="black")+
  geom_text_repel(size=8, max.overlaps = 500)+
  geom_segment(arrow = arrow(length = unit(0.5, "cm")),
               aes(x=0,
                   y=0,
                   xend = filter(mds_first_order,chord_names == "G note")$V1),
                   yend = filter(mds_first_order,chord_names == "G note")$V2)+
  geom_segment(arrow = arrow(length = unit(0.5, "cm")),
               aes(x=0,
                   y=0,
                   xend = filter(mds_first_order,chord_names == "B note")$V1),
                   yend = filter(mds_first_order,chord_names == "B note")$V2)+
  geom_segment(arrow = arrow(length = unit(0.5, "cm")),
               aes(x=0,
                   y=0,
                   xend = filter(mds_first_order,chord_names == "D note")$V1),
                   yend = filter(mds_first_order,chord_names == "D note")$V2)+
  geom_segment(arrow = arrow(length = unit(0.5, "cm")),
               color = "red",
               aes(x=0,
                   y=0,
                   xend = filter(mds_first_order,chord_names == "G major triad")$V1),
                   yend = filter(mds_first_order,chord_names == "G major triad")$V2)+
  geom_curve(arrow = arrow(length = unit(0.5, "cm")),
               color = "red",
               curvature = 1,
               aes(x=0,
                   y=0,
                   xend = filter(mds_first_order,chord_names == "G major triad")$V1/2),
                   yend = filter(mds_first_order,chord_names == "G major triad")$V2/2)+
  theme_classic()+
  ggtitle("G major triad")+ 
  theme(plot.title = element_text(size=40))


Aminor <- ggplot(mds_first_order, aes(V1, V2, 
                                  label = chord_names))+ 
  geom_point(color="black")+
  geom_text_repel(size=8, max.overlaps = 500)+
  geom_segment(arrow = arrow(length = unit(0.5, "cm")),
               aes(x=0,
                   y=0,
                   xend = filter(mds_first_order,chord_names == "A note")$V1),
                   yend = filter(mds_first_order,chord_names == "A note")$V2)+
  geom_segment(arrow = arrow(length = unit(0.5, "cm")),
               aes(x=0,
                   y=0,
                   xend = filter(mds_first_order,chord_names == "C note")$V1),
                   yend = filter(mds_first_order,chord_names == "C note")$V2)+
  geom_segment(arrow = arrow(length = unit(0.5, "cm")),
               aes(x=0,
                   y=0,
                   xend = filter(mds_first_order,chord_names == "E note")$V1),
                   yend = filter(mds_first_order,chord_names == "E note")$V2)+
  geom_segment(arrow = arrow(length = unit(0.5, "cm")),
               color = "red",
               aes(x=0,
                   y=0,
                   xend = filter(mds_first_order,chord_names == "A minor triad")$V1),
                   yend = filter(mds_first_order,chord_names == "A minor triad")$V2)+
  geom_curve(arrow = arrow(length = unit(0.5, "cm")),
               color = "red",
               curvature = -1,
               aes(x=0,
                   y=0,
                   xend = filter(mds_first_order,chord_names == "A minor triad")$V1/2),
                   yend = filter(mds_first_order,chord_names == "A minor triad")$V2/2)+
  theme_classic()+
  ggtitle("A minor triad")+ 
  theme(plot.title = element_text(size=40))
#Aminor

library(patchwork)

Gmajor+Aminor

F major, E minor and B dim

There’s only three left. F major wants to spin anti-clockwise. E minor wants to spin clockwise. In terms of the other chords, F major and E minor are further from the notional center (D note), and they have a weaker orbit, compared to Cmajor/Dminor and Gmajor/Aminor, which are both closer to the center.

The last chord is B diminished. It is symmetrical about the center (D note). It doesn’t have a spin, but is rather precariously balanced. The D note is keeping it centered, the other two could tip easily.

Show the code
Fmajor <- ggplot(mds_first_order, aes(V1, V2, 
                                  label = chord_names))+ 
  geom_point(color="black")+
  geom_text_repel(size=8, max.overlaps = 500)+
  geom_segment(arrow = arrow(length = unit(0.5, "cm")),
               aes(x=0,
                   y=0,
                   xend = filter(mds_first_order,chord_names == "F note")$V1),
                   yend = filter(mds_first_order,chord_names == "F note")$V2)+
  geom_segment(arrow = arrow(length = unit(0.5, "cm")),
               aes(x=0,
                   y=0,
                   xend = filter(mds_first_order,chord_names == "A note")$V1),
                   yend = filter(mds_first_order,chord_names == "A note")$V2)+
  geom_segment(arrow = arrow(length = unit(0.5, "cm")),
               aes(x=0,
                   y=0,
                   xend = filter(mds_first_order,chord_names == "C note")$V1),
                   yend = filter(mds_first_order,chord_names == "C note")$V2)+
  geom_segment(arrow = arrow(length = unit(0.5, "cm")),
               color = "red",
               aes(x=0,
                   y=0,
                   xend = filter(mds_first_order,chord_names == "F major triad")$V1),
                   yend = filter(mds_first_order,chord_names == "F major triad")$V2)+
  geom_curve(arrow = arrow(length = unit(0.5, "cm")),
               color = "red",
               curvature = 1,
               aes(x=0,
                   y=0,
                   xend = filter(mds_first_order,chord_names == "F major triad")$V1/2),
                   yend = filter(mds_first_order,chord_names == "F major triad")$V2/2)+
  theme_classic()+
  ggtitle("F major triad")+ 
  theme(plot.title = element_text(size=40))


Eminor <- ggplot(mds_first_order, aes(V1, V2, 
                                  label = chord_names))+ 
  geom_point(color="black")+
  geom_text_repel(size=8, max.overlaps = 500)+
  geom_segment(arrow = arrow(length = unit(0.5, "cm")),
               aes(x=0,
                   y=0,
                   xend = filter(mds_first_order,chord_names == "E note")$V1),
                   yend = filter(mds_first_order,chord_names == "E note")$V2)+
  geom_segment(arrow = arrow(length = unit(0.5, "cm")),
               aes(x=0,
                   y=0,
                   xend = filter(mds_first_order,chord_names == "G note")$V1),
                   yend = filter(mds_first_order,chord_names == "G note")$V2)+
  geom_segment(arrow = arrow(length = unit(0.5, "cm")),
               aes(x=0,
                   y=0,
                   xend = filter(mds_first_order,chord_names == "B note")$V1),
                   yend = filter(mds_first_order,chord_names == "B note")$V2)+
  geom_segment(arrow = arrow(length = unit(0.5, "cm")),
               color = "red",
               aes(x=0,
                   y=0,
                   xend = filter(mds_first_order,chord_names == "E minor triad")$V1),
                   yend = filter(mds_first_order,chord_names == "E minor triad")$V2)+
  geom_curve(arrow = arrow(length = unit(0.5, "cm")),
               color = "red",
               curvature = -1,
               aes(x=0,
                   y=0,
                   xend = filter(mds_first_order,chord_names == "E minor triad")$V1/2),
                   yend = filter(mds_first_order,chord_names == "E minor triad")$V2/2)+
  theme_classic()+
  ggtitle("E minor triad")+ 
  theme(plot.title = element_text(size=40))
#Aminor

Bdim <- ggplot(mds_first_order, aes(V1, V2, 
                                  label = chord_names))+ 
  geom_point(color="black")+
  geom_text_repel(size=8, max.overlaps = 500)+
  geom_segment(arrow = arrow(length = unit(0.5, "cm")),
               aes(x=0,
                   y=0,
                   xend = filter(mds_first_order,chord_names == "B note")$V1),
                   yend = filter(mds_first_order,chord_names == "B note")$V2)+
  geom_segment(arrow = arrow(length = unit(0.5, "cm")),
               aes(x=0,
                   y=0,
                   xend = filter(mds_first_order,chord_names == "D note")$V1),
                   yend = filter(mds_first_order,chord_names == "D note")$V2)+
  geom_segment(arrow = arrow(length = unit(0.5, "cm")),
               aes(x=0,
                   y=0,
                   xend = filter(mds_first_order,chord_names == "F note")$V1),
                   yend = filter(mds_first_order,chord_names == "F note")$V2)+
  geom_segment(arrow = arrow(length = unit(0.5, "cm")),
               color = "red",
               aes(x=0,
                   y=0,
                   xend = filter(mds_first_order,chord_names == "Bdim")$V1),
                   yend = filter(mds_first_order,chord_names == "Bdim")$V2)+
  theme_classic()+
  ggtitle("Bdim")+ 
  theme(plot.title = element_text(size=40))

library(patchwork)

Fmajor+Eminor+Bdim

Wanting to go somewhere

Sequences of chords can produce feelings of tension and resolution. For some sequences it really feels like a particular chord “wants to go” to the resolving chord.

The above analysis of triad vectors puts a “spin” on each chord vector, showing which way the chord may want to spin in terms of the circle of fifths. Perhaps this is useful in terms of thinking about movement between chords.

ii-V-i

Show the code
Dminor+Gmajor+Cmajor

ii-V-i progressions are super common. The progression is consistent with the spin of these chords. D minor wants to go clockwise, and is met by a G major triad. G major triad wants to anti-clockwise, is met by a C major triad.

ii-VI is an option too, but the A minor wants to keep spinning clockwise to E minor, and E minor wants to keep spinning in the same direction. However, there are no more chords in the clockwise direction, and spinning out more would quickly deteriorate the key center. E minor doesn’t “want to go” to F major, but going there is a good option for restoring balance to the center.

C chord charts

Show the code
(Cmajor | Fmajor | Gmajor) / (Dminor| Eminor | Aminor) / (Bdim | plot_spacer() | plot_spacer())

General conclusions

Here are some of my general conclusions.

A scale of notes, like C major scale, is a compound vector that has a direction in chord space. For example, C major happens to be centered on the D note, and goes in that direction. The triads for this scale reinforce the direction of the scale, as their individual chord vectors are arranged symmetrically about the same key center.

Individual chords may have natural rotational motion with respect to the key center of the scale. The major chords want to spin anti-clockwise, and the minor chords want to spin clockwise. The diminished chord doesn’t have a spin.

The spin of an individual chord can suggest a chord-progression. For example, D minor spins toward G major. G major spins back toward C major. These, oscillations are also symmetrical about the key center.

The well-formedness of the key center can suggest a chord-progression. For example, E minor spins out of C major scale, but can be counter-balanced by F major triad.

Performance vector concept

This is a short digression. I’m thinking about applying these ideas to my playing. Let’s say I’m going to improvise in the C major scale using only the notes from the C major scale. And, let’s also say that I want my performance to maintain symmetry about the key center (D note).

From the chord vector space perspective, every time I play a note I am adding another feature into my “performance vector”. I’m thinking of this as a single vector that aggregates all the notes I play. If I played all the notes of the C major scale once, the performance vector would equal the “C major scale” vector shown in the first graph.

If I then started playing a lot more Es and As, I would add more E and A features into the vector, and in that moment the cumulative direction of the vector would tilt away from center, in the clockwise direction. If I wanted to re-balance, I would need too play notes on the anti-clockwise side of D. This would accumulate the features in the performance vector that are necessary to shift the direction back to center.

Overall, it seems interesting to think about how chord progressions and note choices in the context of improvisation add to the performance vector, and either keep it on target, spin it away from center (creating tension), or spin it back (creating resolution?) to center.

That’s it for now. Maybe I’ll add the 7th chords another day.


12-bar blues analysis

This probably deserves it’s own post, but I want to quickly take a look at the 12 bar blues in terms of chord space analysis.

The 12-bar blues starting with C7 is: C7 (4 bars), F7 (2 bars), C7 (2 bars), G7, F7, C7, G7.

I (4), IV(2), I(2), V, VI, I, V

First, I’m curious what the performance vector for this series of chords looks like. I heart R indexing :)

Here are the chord vectors for the 12-bar blues:

Show the code
blues_progression_matrix <-  chord_matrix[c(rep('C7',4),
               rep('F7',2),
               rep('C7',2),
               'G7','F7','C7','G7'),]
knitr::kable(blues_progression_matrix)
C Db D Eb E F Gb G Ab A Bb B
C7 1 0 0 0 1 0 0 1 0 0 1 0
C7 1 0 0 0 1 0 0 1 0 0 1 0
C7 1 0 0 0 1 0 0 1 0 0 1 0
C7 1 0 0 0 1 0 0 1 0 0 1 0
F7 1 0 0 1 0 1 0 0 0 1 0 0
F7 1 0 0 1 0 1 0 0 0 1 0 0
C7 1 0 0 0 1 0 0 1 0 0 1 0
C7 1 0 0 0 1 0 0 1 0 0 1 0
G7 0 0 1 0 0 1 0 1 0 0 0 1
F7 1 0 0 1 0 1 0 0 0 1 0 0
C7 1 0 0 0 1 0 0 1 0 0 1 0
G7 0 0 1 0 0 1 0 1 0 0 0 1

I’ll take the column sum to compute the performance vector.

Show the code
blues_performance <- colSums(blues_progression_matrix)
knitr::kable(t(blues_performance))
C Db D Eb E F Gb G Ab A Bb B
10 0 2 3 7 5 0 9 0 3 7 2

Next, I’d like to project this vector into the chord space and see where it is heading.

Show the code
# row bind blues_performance

chord_matrix <- rbind(chord_matrix,blues_performance)
chord_properties <- rbind(chord_properties,tibble(type = "performance",
                                                  key = NA,
                                                  chord_names = "blues_performance",
                                                  synonyms = NA,
                                                  database_chord =NA,
                                                  num_notes=NA,
                                                  id = 757))

first_order <- lsa::cosine(t(chord_matrix))

mds_first_order <- cmdscale((first_order-1))
mds_first_order <- as_tibble(mds_first_order) %>%
  cbind(chord_properties)

mds_first_order <- mds_first_order %>%
  mutate(key = forcats::fct_relevel(key,"C","F","Bb","Eb","Ab","Db","Gb","B","E","A","D","G")) %>%
  mutate(bold_me = case_when(key == "C" ~ 1,
                             key != "C" ~ 0)) %>%
  filter(type == "key" | chord_names %in% c("C7",
                                             "F7",
                                             "G7",
                                            "blues_performance") == TRUE) %>%
  mutate(V1 = V1*-1,
         V2 = V2*-1) %>%
  mutate(line_V1 = case_when(chord_names %in% c("C7",
                                             "F7",
                                             "G7",
                                             "blues_performance",
                                             "C note",
                                             "D note",
                                             "Eb note",
                                             "E note",
                                             "F note",
                                             "G note",
                                             "A note",
                                             "Bb note",
                                             "B note") ==TRUE ~ V1),
         line_V2 = case_when(chord_names %in% c("C7",
                                             "F7",
                                             "G7",
                                             "blues_performance",
                                             "C note",
                                             "D note",
                                             "Eb note",
                                             "E note",
                                             "F note",
                                             "G note",
                                             "A note",
                                             "Bb note",
                                             "B note") ==TRUE ~ V2))

p1 <- ggplot(mds_first_order, aes(V1, V2, 
                                  label = chord_names))+ 
  geom_point(color="black")+
  geom_text_repel(size=8, max.overlaps = 500)+
  geom_segment(arrow = arrow(length = unit(0.5, "cm")),
               aes(x=0,
                   y=0,
                   xend = line_V1,
                   yend = line_V2,
                   color = type))+
  theme_classic() +
  coord_flip()
p1

There are three chords are centered around C7 (middle). I didn’t draw the spin, but C7 (CEGBb) is loaded to spin anti-clockwise. And, that is what happens after four bars of C7, we take a spin anti-clockwise to F7.

In general, dominant 7th chords want to spin anti-clockwise. However, going to Bb7 from F7 would shift the center of the performance vector. The four bars of C7 at the beginning can be viewed as impressing C7 as the center into the performance vector. If F7 is a deviation for two bars, then it needs to be re-balanced. In 12 bar blues, we go back to C7 for 2 bars, which does some re-balancing. I haven’t plotted this stuff, but the performance vector would still be tilted off C7. There would be six C7s and two F7s in the performance vector, which slightly tilts toward F7. As a result, the last four bars add in G7 twice, and C7 and F7 once each. The G7 counterbalances the F7s, and helps hold the center on C7. Nevertheless, there is one extra F7, as a result the performance vector is slightly off from C7 in the F direction.

The very last G7 is a valiant attempt to rotate the performance vector back onto C7, but it doesn’t quite get there by itself. It could use some extra notes to strengthen the force of the rotation. But, it seems palpable what the G7 is trying to do to the performance vector. It doesn’t really matter that it doesn’t get the performance vector to C7 because the 12-bar blues repeats, and the next chord is…C7, which resolves the question if there was any doubt.