What did I just play? Translate guitar fingerings to chords

22

4

Related: Music: what's in this chord?, Notes to Tablature, Generating guitar tabs?, Translate number pairs to guitar notes

Given a guitar fingering, output the chord it represents. You can use standard input and output, or write a function that returns a string.

The input fingerings will be classifiable as one of the following chords, to be expressed as follows (if the root note were C):

  • major triad: C
  • minor triad: Cm
  • (dominant) seventh: C7
  • minor seventh: Cm7

The chord might be inverted, so you can't rely on the lowest note being the root. Nor can you rely on this being an easy or common fingering in the real world. More generally, your program's output must ignore the octaves of the pitches, and treat all pitches that correspond to the same musical note (i.e., A) as equal.

This is , so the shortest code in bytes wins.

Input format

The input is a series of 6 values that indicate, for each string of a 6-string guitar in standard tuning (E A D G B E), which fret that string will be played at. It could also indicate that the string is not played at all. The "zeroth" fret is also known as the open position, and fret numbers count up from there. Assume the guitar has 21 fret positions, such that the highest fret position is number 20.

For example, the input X 3 2 0 1 0 means placing ones fingers at the following positions at the top of the guitar's neck:

(6th) |---|---|---|---|---
      |-X-|---|---|---|---
      |---|---|---|---|---
      |---|-X-|---|---|---
      |---|---|-X-|---|---
(1st) |---|---|---|---|---

and strumming the 2nd through the 6th strings. It corresponds to this ASCII tab:

e |-0-|
B |-1-|
G |-0-|
D |-2-|
A |-3-|
E |---|

You have some flexibility in choosing the kind of input you want: each fret position can be expressed as a string, or a number. Guitar strings that are not played are commonly indicated with an X, but you can choose a different sentinel value if that makes it easier for you (such as -1 if you're using numbers). The series of 6 fret positions can be input as any list, array, or sequence type, a single space-separated string, or as standard input—once again, your choice.

You can rely on the input corresponding to one of the 4 chord types mentioned above.

Please explain in your post what form of input your solution takes.

Output format

You must either return or print to standard output a string describing the chord the fingering is for. This string is composed of two parts concatenated together. Capitalization matters. Trailing whitespace is allowed.

The first part indicates the root note, one of A, A#/Bb, B, C, C#/Db, D, D#/Eb, E, F, F#/Gb, G, or G#/Ab. (I'm using # instead of , and b instead of , to avoid requiring Unicode.) Root notes that can be expressed without a sharp or flat must be expressed without them (never output B#, Fb, or Dbb); those that cannot must be expressed with a single sharp or flat symbol (i.e. either C# or Db, but never B##). In other words, you must minimize the number of accidentals (sharps or flats) in the note's name.

The second part indicates the type of chord, either empty for a major triad, m for a minor triad, 7 for the dominant seventh, or m7 for the minor seventh. So a G major is output simply as G, while a D♯ minor seventh could be output as either D#m7 or Ebm7. More examples can be found in the test cases at the end.

Theory & hints

Musical notes

The chromatic scale has 12 pitches per octave. When tuned to equal temperament, each of these pitches is equally distant from its neighbors1. Pitches that are 12 semitones apart (an octave) are considered to be the same musical note. This means we can treat notes like integers modulo 12, from 0 to 11. Seven of these are given letter names2 from A to G. This isn't enough to name all 12 pitches, but adding accidentals fixes that: adding a ♯ (sharp) to a note makes it one semitone higher, and adding a ♭ (flat) makes it one semitone lower.

Chords

A chord is 2 or more notes played together. The type of chord depends on the relationships between the notes, which can be determined by the distances between them. A chord has a root note, as mentioned earlier. We'll treat the root note as 0 in these examples, but this is arbitrary, and all that matters in this challenge is the distance between notes in modulo arithmetic. There will always be one unique chord type for the answer, either a triad or a seventh chord. The root note will not always be the lowest-frequency pitch; choose the root note such that you can describe the chord as one of the four following chord types:

  • A major triad is a chord with the notes 0 4 7.
  • A minor triad is a chord with the notes 0 3 7.
  • A dominant (or major/minor) seventh chord has the notes 0 4 7 10.
  • A minor (or minor/minor) seventh chord has the notes 0 3 7 10.3

Guitar tuning

Standard tuning on a 6-string guitar starts with E on the lowest string, and then hits notes at intervals of 5, 5, 5, 4, then 5 semitones going up the strings. Taking the lowest E as 0, this means strumming all the strings of the guitar gives you pitches numbered 0 5 10 15 19 24, which modulo 12 is equivalent to 0 5 10 3 7 0, or the notes E A D G B E.

Worked examples

If your input is 0 2 2 0 0 0, this corresponds to the notes E B E G B E, so just E, B, and G. These form the chord Em, which can be seen by numbering them with the root as E, giving us 0 3 7. (The result would be the same for X 2 X 0 X 0, or 12 14 14 12 12 12.)

If your input is 4 4 6 4 6 4, numbering these with a root of C♯ gives 7 0 7 10 4 7, or 0 4 7 10, so the answer is C#7 (or Db7). If it was instead 4 4 6 4 5 4, the numbering would give 7 0 7 10 3 7, or 0 3 7 10, which is C#m7 (or Dbm7).

Test cases

X 3 2 0 1 0  --->  C
0 2 2 0 0 0  --->  Em
X 2 X 0 X 0  --->  Em
4 4 6 4 6 4  --->  C#7  (or Db7)
4 4 6 4 5 4  --->  C#m7 (or Dbm7)
0 2 2 1 0 0  --->  E
0 0 2 2 2 0  --->  A
X X 4 3 2 2  --->  F#   (or Gb)
3 2 0 0 0 1  --->  G7
X X 0 2 1 1  --->  Dm7
3 3 5 5 5 3  --->  C
4 6 6 5 4 4  --->  G#   (or Ab)
2 2 4 4 4 5  --->  B7
0 7 5 5 5 5  --->  Am7
7 6 4 4 X X  --->  B
8 6 1 X 1 3  --->  Cm
8 8 10 10 9 8 -->  Fm
0 19 5 16 8 7 -->  Em
6 20 0 3 11 6 -->  A#   (or Bb)
X 14 9 1 16 X -->  G#m  (or Abm)
12 14 14 12 12 12 --> Em
15 14 12 12 12 15 --> G
20 X 20 20 20 20  --> Cm7
X 13 18 10 11 10  --> A#7 (or Bb7)

1by the logarithms of their frequencies

2or, in solfège, names like do, re, mi. In this challenge, use the letter names.

3This could also be called a major sixth chord, with a different choice of root note. In this challenge, call it by its minor seventh name.

Dan Getz

Posted 2017-01-04T21:05:23.300

Reputation: 533

3Great challenge! – Luis Mendo – 2017-01-04T21:10:42.643

1Tempted to close as a dupe from my future challenge :D (I had a very similar challenge in mind, but you were obvously quicker.) – flawr – 2017-01-04T21:18:02.483

Is trailing whitespace allowed in the output string? – Luis Mendo – 2017-01-04T22:37:11.933

@LuisMendo sure; that's fine. – Dan Getz – 2017-01-04T22:44:08.390

Are there any cases where the chord cannot be named or sth like that? like augmented chords? – officialaimm – 2017-01-05T10:16:10.090

1@officialaimm no, you don't need to handle any other situations. You can assume it will always be one of those 4 chord types. In other words, your code can do whatever you want (including error or give a wrong answer) if it gets a different chord. – Dan Getz – 2017-01-05T17:56:48.713

Answers

9

MATL, 115 114 bytes

[OAXICO]+tZN~)Y@!"@t1)XH- 12\XzXJK7hm?O.]JI7hm?'m'.]J[KCX]m?'7'.]J[ICX]m?'m7'.]]'FF#GG#AA#BCC#DD#E'l2741B~QY{HX)wh

Input format is [N 3 2 0 1 0], where N indicates unused string.

The output string always uses #, not b.

Try it online! Or verify all test cases, in two parts to avoid the online compiler timing out:

Explanation

[OAXICO]            % Push [0 5 10 3 7 0]. This represents the pitch of each open
                    % string relative to the lowest string, modulo 12
+                   % Add to implicit input. May contain NaN's, for unused strings
tZN~)               % Remove NaN's
Y@!                 % Matrix of all permutations, each in a column
"                   % For each column
  @                 %   Push current column
  t1)               %   Duplicate and get first entry
  XH                %   Copy into clipboard H
  - 12\             %   Subtract. This amounts to considering that the first note
                    %   of the current permutation is the root, and computing
                    %   all intervals with respect to that
  12\               %   Modulo 12
  Xz                %   Remove zeros
  XJ                %   Copy into clipboard J
  K7hm?             %   Are all intervals 4 or 7? If so: it's a major chord
    O               %     Push 0 (will become space when converted to char)
    .               %     Break for loop
  ]                 %   End if
  J                 %   Push array of nonzero intervals again
  I7hm?             %   Are all intervals 3 or 7? If so: it's a minor chord
    'm'             %     Push this string
    .               %     Break for loop
  ]                 %   End if
  J                 %   Push array of nonzero intervals again
  [KCX]m?           %   Are all intervals 4, 7 or 10? If so: it's a dominant-7th
                    %   chord
    '7'             %     Push this string
    .               %     Break for loop
  ]                 %   End if
  J                 %   Push array of nonzero intervals again
  [ICX]m?           %   Are all intervals 3, 7 or 10? If so: it's a minor 7th chord
    'm7'            %     Push this string
    .               %     Break for loop
  ]                 %   End if
]                   % End for. The loop is always exited via one of the 'break'
                    % statements. When that happens, the stack contains 0, 'm',
                    % '7' or 'm7', indicating the type of chord; and clipboard H
                    % contains a number that tells the root note using the lowest 
                    % string as base (1 is F, 2 is F# etc)
'FF#GG#AA#BCC#DD#E' % Push this string. Will be split into strings of length 1 or 2
l                   % Push 1
2741B~Q             % Push [1 2 1 2 1 2 1 1 2 1 2 1] (obtained as 2741 in binary,
                    % negated, plus 1)
Y{                  % Split string using those lengths. Gives a cell array of
                    % strings: {'F', 'F#', ..., 'E'}
H                   % Push the identified root note
X)                  % Index into cell array of strings
wh                  % Swap and concatenate. Implicitly display

Luis Mendo

Posted 2017-01-04T21:05:23.300

Reputation: 87 464

4

MS-DOS .COM file (179 bytes)

The file (here displayed as HEX):

fc be 81 00 bf 72 01 31 db b9 06 00 51 e8 73 00
59 e2 f9 b9 0c 00 be 48 01 ad 39 c3 74 0d 40 75
f8 d1 fb 73 03 80 c7 08 e2 ec c3 31 db 88 cb 8a
87 59 01 e8 42 00 8a 87 65 01 e8 3b 00 81 c6 08
00 ac e8 33 00 ac eb 30 91 00 89 00 91 04 89 04
ff ff 00 00 6d 00 37 00 6d 37 42 41 41 47 47 46
46 45 44 44 43 43 00 23 00 23 00 23 00 00 23 00
23 00 04 09 02 07 0b 04 84 c0 74 06 b4 02 88 c2
cd 21 c3 8a 0d 47 ac 3c 20 76 fb 30 ed 3c 41 73
22 2c 30 72 0b 86 c5 b4 0a f6 e4 00 c5 ac eb ed
88 e8 00 c8 30 e4 b1 0c f6 f1 88 e1 b8 01 00 d3
e0 09 c3

The input is given via command line. Invalid input will lead to invalid program behaviour!

The assembler code looks like this:

.text
.code16
ComFileStart:
    cld
    mov $0x81, %si
    mov $(TuneTable-ComFileStart+0x100), %di
    xor %bx, %bx
    # 6 strings: Build the mask of played tones
    mov $6, %cx
NextStringRead:
    push %cx
    call InsertIntoMask
    pop %cx
    loop NextStringRead

    # Check all base tones...
    mov $12, %cx
TestNextTone:
    mov $0x100+ChordTable-ComFileStart, %si
TestNextChord:
    lodsw
    # Is it the chord we are searching for?
    cmp %ax, %bx
    je FoundChord 
    # Is it the end of the table?
    inc %ax
    jnz TestNextChord
    # Transpose the chord we really play
    # and go to the next tone
    # This code rotates the low 12 bits of
    # BX one bit right
    sar $1, %bx
    jnc NoToneRotated
    add $8, %bh
NoToneRotated:
    loop TestNextTone
EndOfProgram:
    ret

FoundChord:
    # Get and print the tone name
    xor %bx, %bx
    mov %cl, %bl
    mov (ToneNamesTable+0x100-1-ComFileStart)(%bx),%al
    call printChar
    mov (ToneNamesTable+0x100+12-1-ComFileStart)(%bx),%al
    call printChar
    # Get the chord name suffix and print it
    add $(ChordNamesTable-ChordTable-2),%si
    lodsb
    call printChar
    lodsb
    # Note: Under MS-DOS 0x0000 is the first word on
    # the stack so the "RET" of printChar will jump
    # to address 0x0000 which contains an "INT $0x21"
    # (end of program) instruction
    jmp printChar

ChordTable:
    # Major, Minor, Major-7, Minor-7
    .word 0x91, 0x89, 0x491, 0x489, 0xFFFF
ChordNamesTable:
    .byte 0,0,'m',0,'7',0,'m','7'
ToneNamesTable:
    .ascii "BAAGGFFEDDCC"
    .byte 0,'#',0,'#',0,'#',0,0,'#',0,'#',0
TuneTable:
    .byte 4,9,2,7,11,4

#
# Subfunction: Print character AL;
#              Do nothing if AL=0
#
printChar:
    test %al, %al
    jz noPrint
    mov $2, %ah
    mov %al, %dl
    int $0x21
noPrint:
    ret

#
# Subfunction: Get one finger position
#              and insert it into a bit mask
#              of tones being played
#
# Input:
#
#   [DS:DI] = 
#        Tuning of current string (0=C, 1=C#, ..., 11=B)
#        Actually only 2=D, 4=E, 7=G, 9=A and 11=B are used
#
#   DS:SI = Next character to read
#
#   DF = Clear
#
# Input and Output:
#
#    BX = Bit mask
#    DI = Will be incremented
#
# Destroys nearly all registers but SI and BX
#
InsertIntoMask:
    mov (%di), %cl
    inc %di
SkipSpaces:
    lodsb
    cmp $' ', %al
    jbe SkipSpaces
# Now evaluate each digit
    xor %ch, %ch
GetNextDigit:
    # Number = 10*Number+Digit
    cmp $'A', %al
    jae DigitIsX
    sub $'0', %al
    jb DigitsDone
    xchg %al, %ch
    mov $10, %ah
    mul %ah
    add %al, %ch
    lodsb
    jmp GetNextDigit
DigitsDone:
    # Add the tune of the string
    # and perform modulus 12
    mov %ch, %al
    add %cl, %al
    xor %ah, %ah
    mov $12, %cl
    div %cl
    mov %ah, %cl
    mov $1, %ax
    shl %cl, %ax
    or %ax, %bx
DigitIsX:
    ret

Test cases:

6 20 0 3 11 6 -->  A#   (or Bb)

I already saw two piano players playing together "four handed" on a piano.

This test case is the first time I read about guitar players doing this!

Even with right-hand tapping you cannot play a cord like this!

Martin Rosenau

Posted 2017-01-04T21:05:23.300

Reputation: 1 921

Hmm, maybe a squid could play that chord? I think that's one of the ones I found by a random search so that there could be some "hard" test cases. – Dan Getz – 2017-01-05T18:04:26.740

3

Ruby, 129 bytes

As previous version but uses a single loop, with ternary operator to sequence between the parsing step and output step. Some other minor modifications were required to make this work.

->a{r=0
18.times{|j|j<6?a[j]&&r|=8194<<(6--~j%5+a[j]*7)%12:(r/=2)&11==3&&puts("CGDAEBF"[j%7]+?#*(j/13)+['',?m,?7,'m7'][r>>9&3])}}

Ruby, 136 bytes

Llamda function accepts an array of 6 numbers as an argument and outputs to stdout. Unused string is represented by a falsy value (the only falsy values in ruby are nil and false.)

->a{r=0
6.times{|j|a[j]&&r|=4097<<(6--~j%5+a[j]*7)%12}
12.times{|j|r&11==3&&puts("FCGDAEB"[j%7]+?#*(j/7)+['',?m,?7,'m7'][r>>9&3]);r/=2}}

Explanation

I use a representation of the 12 pitches based on the circle of fifths . This means each pitch is followed by the pitch 7 semitones higher (or 5 semitones lower) giving the sequence F C G D A E B F# C# G# D# A#. There are 2 advantages to this. One is that all the sharps appear together. The other is that the open-string notes of the 5 string bass appear together: GDAEB (the guitar is related but slightly more complex, see below).

The first loop runs 6 times. The expression 6--~j%5(equivalently 6-(j+1)%5) gives the note values for the open strings: E=5 A=4 D=3 G=2 B=6 E=5. To this we add the fret number multiplied by 7 (as can be seen above, adding one semitone moves us 7 places forward in the sequence.) Then we take the whole thing modulo 12 and make a bitmap of the notes that are present (we use 4097<<note value to give 2 consecutive octaves.)

Having composed the bitmap, we are ready to search the chord and output it.

We are interested in the following notes:

Note       position in      position in             Note      position in 
           semitone domain  circle of fifths                  circle of fifths 
Root       0                0                       Root      0
Minor 3rd  3                9                       Fifth     1
Major 3rd  4                4                       Sixth     3
Fifth      7                1                       Major 3rd 4
Sixth      9                3                       Minor 3rd 9
Minor 7th  10               10                      Minor 7th 10

Starting by checking for chord F, we test to see if the root and fifth are present: bits 0 and 1 (counting from least significant: the 1's and 2's bits.) In order to reject sixth chords we also need to check that the sixth is absent: bit 3 (8's bit.) so we check that r&&11==3 and if so we print the chord.

We ignore the major third, and rely entirely on bit 9 (minor third) and bit 10 (minor 7th) to work out the chord type. The expression r>>9&3 is used to pick the correct chord type from an array.

At the end of the loop, we shift the bitmap right one bit r/=2 to test the possible chord roots in sequence: F C G D A E B F# C# G# D# A#.

Ungolfed in test program

f=->a{                            #Accept array of 6 numbers as argument.
  r=0                             #Setup an empty bitmap.

  6.times{|j|                     #For each string
    a[j]&&                        #if the fret value is truthy (not nil or false)
    r|=4097<<(6--~j%5+a[j]*7)%12  #calculate the note value in the circle of fifths and add to the bitmap.
  }

  12.times{|j|                    #For each possible root note
    r&11==3&&                     #if root and fifth are present (bits 0 and 1) and sixth is absent (bit 3) 
    puts("FCGDAEB"[j%7]+?#*(j/7)+ #output the note name and a sharp symbol if necessary, followed by
    ['',?m,?7,'m7'][r>>9&3])      #m and/or 7 as indicate by bits 9 and 10.
    r/=2
  }
}

print 1;f[[nil,3,2,0,1,0]]       #  C
print 2;f[[0,2,2,0,0,0]]         #  Em
print 3;f[[nil,2,nil,0,nil,0]]   #  Em
print 4;f[[4,4,6,4,6,4]]         #  C#7 
print 5;f[[4,4,6,4,5,4]]         #  C#m7 
print 6;f[[0,2,2,1,0,0]]         #  E
print 7;f[[0,0,2,2,2,0]]         #  A
print 8;f[[nil,nil,4,3,2,2]]     #  F#  
print 9;f[[3,2,0,0,0,1]]         #  G7
print 10;f[[nil,nil,0,2,1,1]]    #  Dm7
print 11;f[[3,3,5,5,5,3]]        #  C
print 12;f[[4,6,6,5,4,4]]        #  G#  
print 13;f[[2,2,4,4,4,5]]        #  B7
print 14;f[[0,7,5,5,5,5]]        #  Am7
print 15;f[[7,6,4,4,nil,nil]]    #  B
print 16;f[[8,6,1,nil,1,3]]      #  Cm
print 17;f[[8,8,10,10,9,8]]      #  Fm
print 18;f[[0,19,5,16,8,7]]      #  Em
print 19;f[[6,20,0,3,11,6]]      #  A#  
print 20;f[[nil,14,9,1,16,nil]]  #  G#m 
print 21;f[[12,14,14,12,12,12]]  #  Em
print 22;f[[15,14,12,12,12,15]]  #  G
print 23;f[[20,nil,20,20,20,20]] #  Cm7
print 24;f[[nil,13,18,10,11,10]] #  A#7

Level River St

Posted 2017-01-04T21:05:23.300

Reputation: 22 049

2

Javascript (ES6), 335 333 bytes

Love this challenge and PPCG SE! This is my first golf - suggestions welcome as I'm sure it could be improved a lot. (knocked off 2 bytes as I had included f= in the count)

Function f takes an array of strings, representing numbers and 'X', like f(['X','3','2','0','1','0']) and returns a chord (natural or sharp) like E#m7. Newlines added for clarity (not included in byte count)

f=c=>[s=new Map([[435,''],[345,'m'],[4332,7],[3432,'m7']]),
n=[...new Set(c.map((e,i)=>e?(+e+[0,5,10,3,7,0][i])%12:-1)
.filter(e=>++e).sort((a,b)=>a>b))],d=[...n,n[0]+12].reduce(
(a,c,i)=>i?[...a,(c-n[i-1]+12)%12]:[],0).join``.repeat(2),
m=+d.match(/(34|43)(5|32)/g)[0],'E0F0F#0G0G#0A0A#0B0C0C#0D0D#'
.split(0)[n[d.indexOf(m)]]+s.get(m)][4]

Usage example:

console.log(f(['0','2','2','0','0','0'])); // Em

To run test cases:

tests=`X 3 2 0 1 0 ---> C
0 2 2 0 0 0 ---> Em
X 2 X 0 X 0 ---> Em
4 4 6 4 6 4 ---> C#7 (or Db7)
4 4 6 4 5 4 ---> C#m7 (or Dbm7)`; // and so on...

tests.split`\n`.forEach(e=>{
    console.log(`Test: ${e}
      Result: ${f(e.split(' ').slice(0,6))}`)
})

Ungolfed version with explanation:

f = (c) => {
    s = new Map([
        [435,''], [345,'m'], [4332,7], [3432,'m7'] 
    ]) /* Each key in s describes the intervals (semitones)
          between consecutive notes in a chord, when it is
          reduced to a single octave, including the interval
          from highest back to lowest. The values describe
          the corresponding chord suffix. E.g. C-E-G has
          intervals C-4-E-3-G-5-C. 435=major=no suffix. */

    n = [ ...new Set(
        c.map( 
         (e,i) => e ? ( +e + [0,5,10,3,7,0][i] )%12 : -1 
         ).filter( (e) => ++e ).sort( (a,b) => a>b )
        ) ] /* take the input array, c, and transform each fret
               position into a note. remove non-notes (-1), sort
               in tone order, remove duplicates. An input of
               positions X 13 18 10 11 10 becomes notes
               (-1) 6 4 1 6 10 then 1 4 6 10. */

    d = [ ...n, n[0] + 12 ].reduce(
        (a,c,i) => i ? [ ...a, (c - n[i-1] + 12)%12 ] : [], 0
    ).join``.repeat(2)
    /* convert the note array, n, into an interval string, d,
       including the lowest note repeated above it to capture
       all intervals. Repeat it twice so that, regardless of the
       inversion played, the intervals will appear in root order
       somewhere. E.g. notes 1-4-6-10 and 13 (1+12)
       become intervals 3 2 4 3, and string for searching
       32433243 */

    m = +d.match( /(34|43)(5|32)/g )[0];
      /* m is the matched chord pattern. In this case, 4332. */

    return 'E0F0F#0G0G#0A0A#0B0C0C#0D0D#'.split(0)[
    n[ d.indexOf(m) ]
    /* get the position in the interval string where the root
       interval first occurs. this corresponds to the position
       of the chord root note in the note array, n. convert this
       number 0-12 to a note name E - D# */
    ] + s.get(m)
       /* add the suffix corresponding to the matched
       chord interval pattern */
}

Chris M

Posted 2017-01-04T21:05:23.300

Reputation: 191

1

Welcome to the site! I'm glad to hear you enjoy it. :) Unfortunately, I don't know any JS so I don't have any tips, but you might be able to find some here

– James – 2017-01-06T18:24:52.410