*slaps roof of Chord* this bad boy can fit so many chords.
Switch branches/tags
Nothing to show
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
config
lib
test
.formatter.exs
.gitignore
README.md
mix.exs
mix.lock
scratch.exs
scratch2.exs

README.md

Chord

This is very much a one-off experiment. Enjoy as is with no guarantees of correctness or usefulness.

This project includes tools to generate all possibilities of a given chord across a guitar's fretboard, render chord charts with unicode, and calculate the "distance" between chords, which can be used to discover interesting voice leading possibilities.

Let's start by generating all possible Cmaj7 chords:

Chord.voicings([0, 4, 7, 11])

The numbers here refer to the mod 12 of the notes we want in our chord. In our case, we want a C (0), E (4), G (7), and a B (11).

The Chord.voicings/1 will impose some restrictions on the chords generated to keep things a little more manageable. Currently, it only looks for chord shapes in the first 12 frets, and rejects chords with a stretch greater than five frets. It's also looking for chords on a six stringed, standard tuned guitar. All of these restrictions and assumptions can be changed in the Chord module.

Once we have all of our possible voicings, we can render one as a chord chart:

[0, 4, 7, 11]
|> Chord.voicings()
|> List.first()
|> Chord.to_string()
|> IO.puts()
 0 ●┬┬●●┬
   ├┼┼┼┼┤
   ││││││
   ├┼┼┼┼┤
   ││││││
   ├┼┼┼┼┤
   │●││││
   ├┼┼┼┼┤
   ││││││
   ├┼┼┼┼┤
   ││●│││
   ├┼┼┼┼┤
   ││││││
   ├┼┼┼┼┤
   │││││●

We can also put a label on the chord by passing an optional second parameter to Chord.to_string/2:

|> Chord.to_string("Cmaj7")
 0 ●┬┬●●┬ Cmaj7
   ├┼┼┼┼┤
   ││││││
   ├┼┼┼┼┤
   ││││││
   ├┼┼┼┼┤
   │●││││
   ├┼┼┼┼┤
   ││││││
   ├┼┼┼┼┤
   ││●│││
   ├┼┼┼┼┤
   ││││││
   ├┼┼┼┼┤
   │││││●

We can also take all (4 note versions) of our Cmaj7 voicings, calculate their "semitone distances" from another chord, like a G7 ([nil, 10, 12, 10, 12, nil]), and render the three voicings with the shortest distance. These chords will, in theory, have nice voice leading between our G7 chord:

[0, 4, 7, 11]
|> Chord.voicings(4)
|> Enum.map(&{Chord.Distance.Semitone.distance(&1, [nil, 10, 12, 10, 12, nil]), &1})
|> Enum.sort()
|> Enum.take(3)
|> Enum.map(fn {distance, chord} -> Chord.to_string(chord, "Cmaj7") end)
|> Enum.join("\n\n")
|> IO.puts()
 9 │││●││ Cmaj7
   ├┼┼┼┼┤
   │●●│││
   ├┼┼┼┼┤
   ││││││
   ├┼┼┼┼┤
   ││││●│


 7 │││││● Cmaj7
   ├┼┼┼┼┤
   ││││││
   ├┼┼┼┼┤
   │││●││
   ├┼┼┼┼┤
   │●●│││


 5 ││●●●│ Cmaj7
   ├┼┼┼┼┤
   ││││││
   ├┼┼┼┼┤
   │││││●
   ├┼┼┼┼┤
   ││││││

Try it out.

This is great, but it doesn't take playability into account. We want nice voice leading between chords, but we also want the transitions to be easy to play for us, the guitar player!

We can generate all "possible" (for some values of "possible") fingerings for a given guitar chord using Chord.fingerings/1:

[nil, 10, 10, 9, 12, nil]
|> Chord.fingerings()
|> Enum.map(&Chord.to_string/1)
|> Enum.join("\n\n")
|> IO.puts()
 9 │││●││
   ├┼┼┼┼┤
   │●●│││
   ├┼┼┼┼┤
   ││││││
   ├┼┼┼┼┤
   ││││●│
    2213

 9 │││●││
   ├┼┼┼┼┤
   │●●│││
   ├┼┼┼┼┤
   ││││││
   ├┼┼┼┼┤
   ││││●│
    2214

 9 │││●││
   ├┼┼┼┼┤
   │●●│││
   ├┼┼┼┼┤
   ││││││
   ├┼┼┼┼┤
   ││││●│
    2314
    
    
...

We can also compare the "distance" between fingerings. Let's compare the distance between the normal fingering of our G7 chord with the third fingering suggested for our Cmaj7:

Chord.Distance.Fingering.distance(
  [nil, {10, 1}, {12, 3}, {10, 1}, {12, 4}, nil],
  [nil, {10, 2}, {10, 3}, {9, 1}, {12, 4}, nil]
)

This gives us a distance of 3. This fingering distance is calculated using a modified and weighted levenshtein distance where placing and lifting a finger both cost 1 unit of distance, sliding a finger to any other fret on the same string costs 1 unit of distance, and moving a finger to another string and fret costs the "manhattan distance" of that move.

We can combine "semitone distance" and "fingering distance" to find the Cmaj7 chords that offers the best voice leading from our G7 chord, while still being the "easiest" to play:

[0, 4, 7, 11]
|> Chord.voicings(4)
|> Enum.map(&{Chord.Distance.Semitone.distance(&1, [nil, 10, 12, 10, 12, nil]), &1})
|> Enum.uniq()
|> Enum.sort()
|> Enum.chunk_by(&elem(&1, 0))
|> List.first()
|> Enum.map(&elem(&1, 1))
|> Enum.map(fn chord ->
  chord
  |> Chord.fingerings()
  |> Enum.uniq()
  |> Enum.map(
    &{Chord.Distance.Fingering.distance(&1, [nil, {10, 1}, {12, 3}, {10, 1}, {12, 4}, nil]), &1}
  )
end)
|> List.flatten()
|> Enum.sort()
|> Enum.chunk_by(&elem(&1, 0))
|> List.first()
|> Enum.map(&elem(&1, 1))
|> Enum.map(&Chord.to_string/1)
|> Enum.join("\n\n")
|> IO.puts()
 9 │││●││
   ├┼┼┼┼┤
   │●●│││
   ├┼┼┼┼┤
   ││││││
   ├┼┼┼┼┤
   ││││●│
    2214

 9 │││●││
   ├┼┼┼┼┤
   │●●│││
   ├┼┼┼┼┤
   ││││││
   ├┼┼┼┼┤
   ││││●│
    2314

...

Cool stuff.