Voice leading algorithm

More sharing from my Sonic Pi archive of experiments and ideas…

This one concerns “voice leading” which is a musical technique for making chord sequences sound nice(er).

To give a brief explanation, it helps to think about some notes on a piano. If you’re changing chords between C major and F major there are a couple of ways to do it (imagine each note is a “voice” like a singer in a choir)

C -> F
E -> A
G -> C

This is ok and gets used in a lot of rock and pop (think the first two chords of “Louie Louie”).

But if you want your music to sound like butter melting on a stack of freshly cooked pancakes, then this is better:

C -> C
E -> F
G -> A

Same set of notes on the right and the left but they’re now reordered slightly on the right to make sure that each voice moves as little as possible. This is voice leading in a nutshell and it’s a great thing to bear in mind when working with chords.

Now, is there an algorithm that can help us with this? Yes! It’s called the taxi cab metric and I stole it from the book “The Geometry of Music” by Dmitri Tymoczko. It’s in this gist here with an example https://gist.github.com/xavriley/1ea12a3d319dfcf86152

It looks pretty complicated but it’s not too bad really. That code is pretty scrappy though and I’m sure it could be more elegant. The reason I haven’t pushed for this to be in Sonic Pi proper yet is because I don’t know what it should look like.

Essentially it needs two arguments - a starting chord and a sequence (array) of chords to move to. It should probably yield an enumerator of nicely voice led chords as an output. If anyone has more ideas about how they’d like this to work then lets discuss…

13 Likes

Recently @JoeMac mentioned Michael New’s excellent series of music tutorial videos., and in particular one where he looks at getting smooth transitions in progressions which is similar to Xav’s ideas here. Useful to look at the video and example Joe gives Using Chord Inversion to Smooth a Chord Progression

2 Likes

Thanks Robin - it looks like that covers the same idea but the difference is that each inversion in @JoeMac’s code is being chosen manually. In theory, the algorithm here should be able to work these out automatically but trying those same chords I realise I have a bug in my implementation! I’ll see if I can fix it up and re-post.

For a really good example of the C -> F voice leading that I was talking about, Michael New covers the exact same thing here: https://youtu.be/Nr2XBoanNJY?t=6m23s

Here’s a possible solution

#ChordProgression1.rb
# 26 Oct 2017
##############
#helper function
def midi2note(n)
  nn=note(n)
  if nn==nil
    return nn
  else
    nn= note_info(nn)
    nnn=nn.to_s.split(":")
    mmm= nnn[3].chop
    return mmm
  end
end # midi2note
def listnotes(n)
  i=0
  while i<n.length
    puts midi2note(n[i])
    i+=1
  end
end

#Define tempo and note lengths and release fraction
#####
tempo=1.0  ### try changing tempo
#define note timings
whole=1.0
half=whole/2.0
dothalf=half*1.5
quart=half/2.0
dotquart=quart*1.5
eighth=quart/2.0
doteighth=eighth*1.5
sixteenth=eighth/2
#########
# function to normalize chord in n to octave oct
def norm(n,oct)
  oct+=1
  i=0
  m=[]
  while i<n.length
    m=m.push(n[i]%12)
    i+=1
  end
  i=0
  while i<m.length
    m[i]=m[i]+oct*12
    i+=1
  end
  return m
end

### try different keys
key= note(:e4)
puts midi2note(key)
puts " "
### try major
mode=:minor
oct=4 # define octave where ajusted chord notes will play
# define a chord progression using chord degree
# and normalize the chords to fit in the target octave
a=chord_degree :i, key, mode,3
listnotes(a)
a=norm(a,oct)
listnotes(a)
puts " "
b=chord_degree :vi, key, mode,3
#puts b
listnotes(b)
b=norm(b,oct)
#puts b
listnotes(b)
puts " "
c=chord_degree :ii, key, mode,3
listnotes(c)
c=norm(c,oct)
listnotes(c)
puts " "
d=chord_degree :v, key, mode,3
listnotes(d)
d=norm(d,oct)
listnotes(d)
puts " "
##########
use_synth :fm
with_fx :level, amp: 0.3 do
  
  i=0
  while i<5
    play a
    sleep quart*tempo
    play b
    sleep quart*tempo
    play c
    sleep quart*tempo
    play d
    sleep quart*tempo
    i+=1
  end
end
4 Likes

In an octave all the chords of any key can be played, although not always in their fundamental disposition in which the most serious note gives name to the chord.

Starting from an initial chord and regardless of whether the most serious note is the first, third or fifth of the chord, a single octave can be generated in which we can use all the chords.

There are only three possibilities of initial chord and I proceed to explain the process.

Initial chord 1 3 5, covers 8 notes you just have to add the previous two to the first note of the chord, and the two after the third note of the chord.

Ej D F A -> C Db D Eb E F Gb G Ab A Bb B

Initial chord 3 5 1, covers 10 notes you just have to add the previous one to the first note of the chord, and the one after the third note of the chord.

Ej F A D -> E F Gb G Ab A Bb B C Db D Db

Initial chord 5 1 3, covers 9 notes you only have to add the previous two to the first note of the chord, and the one after the third note of the chord.

Ej A D F -> G Ab A Bb B C Db D Db E F Gb

In this case we could also the one before the first note of the chord and the two after the third note of the chord.

Ej A D F -> Ab A Bb B C Db D Db E F Gb G

A simple algorithm that does this, would work

Hi

I revive this post because it is touching an area that I’m very interested in.

I used to create harmonic progressions with OpenMusic, but I used non tonal progressions.
This said nowdays I went back to the functionnal harmony.
The voice leading is a vast subject and work by set of rules.

One of them is the non following 5te and 8ve and the resolution of the tritone.

My questions is regarding your knowledge of functionnal harmony and his syntax. I think that if you get into that you’ll find more solutions.
This said, when I compose I use predefine chords that I previously wrote on a music sheet. I have limited knowledge of Sonic Pi
Very curious to see where this idea went since 2017

2 Likes

Hi @Lecavalier - thanks for bringing this up again

You’re right - there are a vast number of considerations to harmonic writing, many of which I haven’t addressed. The algorithm I linked above only solves for moving to the closest chord in the minimum number of steps. It doesn’t take account of other “rules” like parallel 5ths/octaves etc.

There are a few computational approaches to those rules but the ones I’ve seen tend to focus on harmonizing a given melody (cantus firmus) rather than the moves between two chords. Focusing on each chord in turn makes it easier to apply the rules computationally but harmonzing a whole melody might make more sense musically.

I think the thing that Sonic Pi is missing to tackle this is a good syntax to express chord progressions. If anyone has any ideas of what their ideal representation would be I’d love to hear them.

At the moment I’m leaning towards something like a parser that works on strings

chord_progression("Cmaj7 Am7 | Dm9 G13b9")

which converts those to an array of arrays (or ring of rings) behind the scenes. Then in the internals of that method it’s a good place to start applying rules and other algorithmic tricks. What do you think?

1 Like