Skip to content
/ chord Public

*slaps roof of Chord* this bad boy can fit so many chords.

Notifications You must be signed in to change notification settings

pcorey/chord

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

23 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

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
   ├┼┼┼┼┤
   ││││││
   ├┼┼┼┼┤
   ││││││
   ├┼┼┼┼┤
   │●││││
   ├┼┼┼┼┤
   ││││││
   ├┼┼┼┼┤
   ││●│││
   ├┼┼┼┼┤
   ││││││
   ├┼┼┼┼┤
   │││││●

You can also use the Chord.Note.to_integer/1 function to provide notes in the standard notation:

Chord.Note.to_integer(["C", "Db", "D", "Eb"])
|> Chord.voicings()
|> List.first()
|> Chord.to_string()
|> IO.puts()
 0 ┬┬●┬┬┬
   ├┼┼┼┼┤
   ││││││
   ├┼┼┼┼┤
   ││││││
   ├┼┼┼┼┤
   │●││││
   ├┼┼┼┼┤
   ││││●│
   ├┼┼┼┼┤
   ││││││
   ├┼┼┼┼┤
   │││●││

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.

About

*slaps roof of Chord* this bad boy can fit so many chords.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages