Generate chord charts for ukulele

Your task is to write a function which outputs a ukulele chord chart in the following format for a given chord symbol. For instance, for input "G7", it must output:

 G C E A
---------
| | |#| |
---------
| |#| |#|
---------
| | | | |
---------
| | | | |
---------
| | | | |
---------


It must support all chords of these forms: X Xm X7 Xm7 Xmaj7 Xsus4 Xdim Xdim7

where X is any of A A# B C C# D D# E F F# G G#

(Supporting flats, such as Bb and Eb, is not required.)

The rest of the challenge description (until "Output and scoring") is purely explanatory: if you know ukulele chords already, you don't need it.

There are three steps parts to producing the chart for a chord.

1. First, work out which notes are in the chord.
2. Choose the correct fret for each string to match the notes.
3. Draw the chart.

Work out which notes are in the chord

There are 12 notes in the cyclical music scale, each a semitone apart: A, A#, B, C, C#, D, D#, E, F, F#, G, G#, then A, A#....

(If you're not into music, just mentally translate every "A" into a 0, every "A#" into a 1, etc, and everything will work out just fine.)

A chord is a set of 3 or 4 notes, according to a defined pattern of intervals starting from the base note:

• X: 0 + 4 + 3 ("major"). Example: C, C+E+G
• Xm: 0 + 3 + 4 ("minor"). Example: Cm, C+D#+G
• X7: 0 + 4 + 3 + 3 ("seven"). Example: C7, C+E+G+A#
• Xm7: 0 + 3 + 4 + 3 ("minor seven"). Example: Cm7, C+D#+G+A#
• Xmaj7: 0 + 4 + 3 + 4 ("major seven") . Example: Cmaj7, C+E+G+B
• Xsus4: 0 + 5 + 2 ("sus four"): Example: Csus4, C+F+G
• Xdim: 0 + 3 + 3 ("diminished"). Example: Cdim, C+D#+F#
• Xdim7: 0 + 3 + 3 + 3 ("diminished seven"). Example: C+D#+F#+A#. [You may find it helpful to know that this is the same chord as A#dim7, D#dim7 and F#dim7].

(The name in parentheses is just for your music education, it's not relevant to the challenge.)

For instance: "Am" is a chord, where "A" is the base note, and "m" is the chord type. Looking up the table ("Xm: 0 + 3 + 4"), we see it has 3 notes:

• the base note (A),
• the note 3 semitones above (C, counting A# and B), and
• the note 4 semitones above that (E).

(Chords that have 3 notes will by necessity be repeating one of the notes across the 4 strings. That's fine.)

Choose the correct frets for the notes

Ukulele is an instrument with four strings, that play the notes G, C, E and A. Pressing the string against a fret makes it sound higher, one semitone per fret. (Pressing fret 1 on the A string turns it to A#, fret 2 turns it to B, etc).

Chord charts

The chord chart represents the first few frets, played by the left hand, read from top to bottom ("higher" frets are lower down the page). The strings are the columns separated by | symbols. The frets are the rows separated by - symbols. The symbol # means that the string is pressed against that fret ("is fretted"), while the right hand strums.

So in the chord chart at the start:

• The G string is not fretted (it is "open"), so it sounds like G.
• The C string is fretted on fret 2 (two semitones up), so it sounds like D.
• The E string is on fret 1 (one semitone up), and sounds like F.
• The A string is on fret 2, so sounds like B.

So this chord has notes: G, D, F, B. (It's G7, G+B+D+F - the order of the pitches doesn't matter.)

Calculating chords

To calculate which fret to play for a given string, for a given chord:

• if the string's base note (G, E, A or C) is not already in the chord, play the lowest fret that belongs to the chord
• repeat for each of the four strings.

Example #1

For instance, let's play E7 (consisting of E + G# + B + D).

1. The G string is not in the chord. Raise it to G# (fret 1).
2. The C string is not in the chord. Raise it to D (fret 2).
3. The E string is in the chord. Don't fret it. (Leave the string open.)
4. The A string is not in the chord. Raise it to B (fret 2).

So we are playing: G#, D, E, B.

So we output:

 G C E A
---------
|#| | | |
---------
| |#| |#|
---------
| | | | |
---------
| | | | |
---------
| | | | |
---------


Example #2

Another example: G# (consisting of G# + B# + D#):

1. The G string is not in the chord, raise it to G# (fret 1).
2. The C string is in the chord (B#), don't fret it.
3. The E string is not in the chord, raise it to G# (fret 4).
4. The A string is not in the chord, raise it to B#(C) (fret 3)

So we are playing G#, C, G#, C

 G C E A
---------
|#| | | |
---------
| | | | |
---------
| | | |#|
---------
| | |#| |
---------
| | | | |
---------


(You may notice that sometimes not every note in the chord ends up getting played. Don't worry about it. "Proper" ukulele chords are slightly more complicated than this basic algorithm.)

Output and scoring

Your output must closely match the above. (You can use other characters instead of |, - and #, such as em-dashes or ASCII line-drawing characters, if they are visually superior. An unfilled circle character would be better than #). Trailing linespace on each line is ok. Whitespace before the first line or after the last line is not ok. Final linefeed is optional.

Normal forms of input and output. You will receive one chord name, guaranteed to be in the table above, in the correct case.

Standard loopholes disallowed. Online services are disallowed. If your language has music functions in its standard library, you may use them (but not if they're plugins, packages, etc.)

This is . Lowest score (in bytes) wins.

Extra credit

This simple algorithm sometimes produces useless chord fingerings that don't include all the important notes of the chord. (For instance, for Dm, it produces ACFA, the same as Fm.)

For extra credit, modify the algorithm so that:

• For all chords, the root and third (first two notes, usually 0 and 3 or 0 and 4) are played on at least one string each.
• For 4-note chords (X7, Xm7, Xmaj7, Xdim7), the root, third and seventh (first, second and fourth notes) are played on at least one string each.
• G# is G# B# D# (B# is the same as C, which is why this chord is more often written as Ab). That said, I assume in this challenge, we only have to support sharps in the input and not flats? – Level River St May 28 '17 at 1:34
• Thanks, sorry I really messed that example up - kept redoing it. Flats aren't required - clarified there. – Steve Bennett May 28 '17 at 7:07

Javascript 280 282287 bytes

f=

a=>' G C E A'+[1,2,3,4,5].map(y=>
(l=
---------
)+[-2,3,7,0].map(
x=>(z=a.match(/(.#?)(.*)/),
[...'9'+{'':14,7:147,m:40,m7:740,maj7:148,sus4:24,dim:30,dim7:360}[z[2]]]
.map(i=>(i-x+'D EF G A BC'.search(z[1][0])+!!z[1][1]+8)%12)
.sort((a,b)=>a-b)[0]-y?'| ':'|#'
)).join+'|').join+l

document.getElementById("mysubmit").onclick=function() {
document.getElementById("out").innerHTML=f(document.getElementById("myinput").value)
}
<input type="text" value="G7" id="myinput">
<input type="submit" id="mysubmit">
<pre id="out"></pre>

Commentary

I've never been so happy at Javascript's weird type conversions. This is probably the first time I've ever wanted 40 + 9 to equal 409 :) It's also really fortunate that integers are acceptable as keys in Objects.

There's nothing too clever going on here. I subtract 3 from each chord note in order to keep the range within 0-9, so that I can temporarily express a chord as a 3-digit number. Luckily, the order of notes isn't important, so we can express 036 as 360.

• FYI, if anyone wants to use this program, I've put it in a JSFiddle for easy access: jsfiddle.net/o5z28vbr/embedded/result – Beta Decay May 28 '17 at 8:23
• Sadly it's not that useful for actual chord charts, because of the simplification of the algorithm. Maybe I'll add an extra credit section. – Steve Bennett May 28 '17 at 10:09
• I dunno, it seems pretty accurate when I've tested it – Beta Decay May 28 '17 at 10:33
• Try C, Dm7, G#, F7, B7. They're all missing a pretty important note or two. – Steve Bennett May 28 '17 at 10:37
• Huh, that's the way I usually play them – Beta Decay May 28 '17 at 10:44

Python 2, 518, 448 bytes

Thanks to Wheat Wizard for the coding suggestions!

S='GCEA'
N=['A','A#','B','C','C#','D','D#','E','F','F#','G','G#']
C={'':(0,4,7),'m':(0,3,7),'7':(0,4,7,10),'m7':(0,3,7,10),'maj7':(0,4,7,11),'sus4':(0,5,7),'dim':(0,3,6),'dim7':(0,3,6,9)}
x=raw_input()
j=1+('#'in x)
c=[]
for s in S:c.append(min((N.index(i)-N.index(s))%12for i in [N[(a+N.index(x[:j]))%12]for a in C[x[j:]]]))
print' '+''.join(a+' 'for a in S)
for f in range(5):f=['|o'if i==f+1else'| 'for i in c];print'-'*9,'\n',''.join(f)+'|'



Try it online!

Sample Output

G
G C E A
---------
| | | | |
---------
| |o| |o|
---------
| | |o| |
---------
| | | | |
---------
| | | | |


Commentary

Thanks for a fun challenge. My daughter and I are off to buy a uke this weekend. I've never played the uke, but I play mandolin, and the theory is similar.

For this submission (my first), I addressed the original challenge without the extra credit components. I fear addressing the extra credit questions will add a lot of of bytes to the code. I hope to submit again with the extra credit bits, time permitting.

The logic is pretty simple if you trace your way back through the successive functions: 1. Parse the chord description. 2. Identify what notes are in that chord. 3. Figure out what frets on each string would fit 4. Assemble a chord by selecting the lowest fret that works on each string 5. Print out the chart fret by fret.

The core of the golfed code lies in the gnarly mess of nested list comprehensions in the eighth -- now seventh -- line of the code. The best way I can explain what's going on is by presenting the ungolfed version.

(Wheat Wizard suggested a shift from list comprehensions to generators that was not reflected in this ungolfed code)

STRINGS = 'GCEA' # Ukelele
#STRINGS = 'GDAE' # Mandolin
NOTES=['A','A#','B','C','C#','D','D#','E','F','F#','G','G#']
TYPES = {'':(0,4,7),
'm':(0,3,7),
'7':(0,4,7,10),
'm7':(0,3,7,10 ),
'maj7':(0,4,7,11),
'sus4':(0,5,7),
'dim':(0,3,6),
'dim7':(0,3,6,9)}

def parse(x):
# parses the chord into the root and the chord type
i=2 if '#' in x else 1
return x[:i],x[i:]

def strng(string,notes):
# Return a list of possible fret locations based on notes in a chord
# This will return all possible fret locations, including those
# beyond the fifth fret, where this code stops drawing.
return sorted([(NOTES.index(i)-NOTES.index(string))%12 for i in notes])

def chordnotes(note,typ):
#return the list of Notes that belong to the chord
return  [NOTES[(a+NOTES.index(note))%12] for a in TYPES[typ]]

def chord((note,typ)):
# Returns a list of four fret locations, in order of the strings.
# This is the heart of the musical logic of the code. Here I use a naive
# approach, and simply grab the lowest note on each string that fits the
# chord.  This function needs to be rewritten to address the extra
# credit parts of the challenge.
# The extra parentheses in the function declaration allow us to pass in
# the results of the parse function directly.
x = []
for s in STRINGS:
x.append(min(strng(s,chordnotes(note,typ))))
return x

def printchart(x):
# Do the heavy lifting of formatting the output
# I calculate the layout of each fret, and print them in turn.
ch = chord(parse(x))
print  ' '+''.join([a+ ' ' for a in STRINGS])
fret = '-'*9
for f in range(5):
note = ['|o' if i==f+1 else '| ' for i in ch]
print fret + '\n' + ''.join(note)+'|'

def main():
x = raw_input('Chord?')
printchart(x)

if __name__ == '__main__':
main()

• Hello and welcome to our site! This post looks good except for one thing, you should not prompt Chord? before input, its not in the specifications and thus is extra output. This is good because not prompting saves more bytes anyway. You also have a lot of whitespace you can trim off that is not required by python. – Sriotchilism O'Zaic May 31 '17 at 3:33
• Here is a link with most of the extra whitespace trimmed away: Try it online! – Sriotchilism O'Zaic May 31 '17 at 3:33
• Our site rules also allow you to use input() instead of raw_input() even though its not really good python practice. If you have any more questions about the site, or golfing python I'd be happy to answer them so just ping me in a comment or in chat! – Sriotchilism O'Zaic May 31 '17 at 3:35
• I don't want to bother you anymore so one last thing, but you use a bunch of list comprehensions in this code, which is a good idea. However you can save bytes by using generators instead in some situations. Built in functions like .join can use generators instead of lists meaning that you can get rid of the [...] inside the function. My next comment will include a link showing how this might be implemented in your case. – Sriotchilism O'Zaic May 31 '17 at 3:45
• Try it online! – Sriotchilism O'Zaic May 31 '17 at 3:45