Generating guitar tabs?

24

5

Write the shortest program that generates guitar tabs for the chords given as input.

So that the guitarists among you don't have an advantage, and to make it deterministic (and probably easier to code), here are the only forms of the chords authorized:

Major chords:

  E   F   F#  G   G#  A   A#  B   C   C#  D   D#
e 0---1---2---3---4---0---1---2---3---4---5---6---
B 0---1---2---3---4---2---3---4---5---6---7---8---
G 1---2---3---4---5---2---3---4---5---6---7---8---
D 2---3---4---5---6---2---3---4---5---6---7---8---
A 2---3---4---5---6---0---1---2---3---4---5---6---
E 0---1---2---3---4---0---1---2---3---4---5---6---

Minor chords:

  Em  Fm  F#m Gm  G#m Am  A#m Bm  Cm  C#m Dm  D#m
e 0---1---2---3---4---0---1---2---3---4---5---6---
B 0---1---2---3---4---1---2---3---4---5---6---7---
G 0---1---2---3---4---2---3---4---5---6---7---8---
D 2---3---4---5---6---2---3---4---5---6---7---8---
A 2---3---4---5---6---0---1---2---3---4---5---6---
E 0---1---2---3---4---0---1---2---3---4---5---6---

Note that the 5 first chords and the 7 last chords of each series have different forms.

All the chords are simple major or minor chords (no 7th or other variations).

You should take care of flats too. Reminder:

A# = Bb
C# = Db
D# = Eb
F# = Gb
G# = Ab

B#, Cb, E# and Fb are not used

Output must include the first column with the cord names, as shown above. It does not have to include the chord name on top. Chords must be separated by 3 - as shown above. The final 3 - are optional.

Input is a string consisting of chord names, separated by spaces.

An example input is:

Bm Gb A E G D Em F#

and the corresponding output is:

e 2---2---0---0---3---5---0---2---
B 3---2---2---0---3---7---0---2---
G 4---3---2---1---4---7---0---3---
D 4---4---2---2---5---7---2---4---
A 2---4---0---2---5---5---2---4---
E 2---2---0---0---3---5---0---2---

Jules Olléon

Posted 2011-06-23T20:44:45.183

Reputation: 731

... and side question: what's the example song? :) – Jules Olléon – 2011-06-23T20:55:44.567

5Hotel California :P – Matthew Read – 2011-06-23T21:16:08.573

Yep, you win! :) – Jules Olléon – 2011-06-23T21:41:43.857

Cool idea. Wish I had time to play! – Igby Largeman – 2011-06-29T00:26:27.987

Answers

9

JavaScript, 297 277 262 235 223 chars

No carriage returns in the golfed version are significant; they are there only to make the answer readable. Semicolons are significant.

Edit: Replaced the outer map with a while loop and other edits. Finally inside of 2× the size of the Golfscript version (for now)!

Edit: Replaced the indexOf with math, broke down the lookup table, other small improvements.

Edit: Another map to for and put in a final \n I had been unnecessarily eating. Finally inside of Jules's Python version.

i=prompt(o='').split(' ');for(r=6;o+=' EADGBe'[r]+' ',r--;o+='\n')
for(j=0;n=i[j++];o+=(([84,13,52,5][2*/m/.test(n)+x]*8>>2*r&3)+y-7*x)+'---')
y=n.charCodeAt(0),y=(2*y-(y>66)-(y>69)+(n[1]<'$')-(n[1]=='b')+2)%12,x=y>6;alert(o)

Output no longer takes advantage of the trailing --- being optional as:

e 2---2---0---0---3---5---0---2---
B 3---2---2---0---3---7---0---2---
G 4---3---2---1---4---7---0---3---
D 4---4---2---2---5---7---2---4---
A 2---4---0---2---5---5---2---4---
E 2---2---0---0---3---5---0---2---

DocMax

Posted 2011-06-23T20:44:45.183

Reputation: 704

Damn, I've got javascript peen-envy I think. Nicely done. – kekekela – 2011-11-14T01:04:40.553

7

Golfscript, 136 chars

[["eBGDAE"{[]+" "+}/]]\" "/{.-2\{"bm#A B C D E F G"?.)!!*(+}/14%.3>-.8>-.7/@109?0>2*+[963 780 882 753]{3base(;}%=\7%{+'---'+}+%}%+zip n*

Fully 23 chars (17.5%) deal with those two characters at the start of each output row.

Sample output, testing the edge cases:

$ golfscript.rb tabs.gs <<<"E G# Ab A Db D# Em G#m Abm Am D#m"
e 0---4---4---0---4---6---0---4---4---0---5---
B 0---4---4---2---6---8---0---4---4---1---6---
G 1---5---5---2---6---8---0---4---4---2---7---
D 2---6---6---2---6---8---2---6---6---2---7---
A 2---6---6---0---4---6---2---6---6---0---5---
E 0---4---4---0---4---6---0---4---4---0---5---

I've only spent about an hour on this, so it can probably be reduced by 5 to 10 chars at least. Conceptually it turns out to be quite similar to DocMax's solution: lookup table for four cases, then increment by an offset and join the strings in the right order.

Peter Taylor

Posted 2011-06-23T20:44:45.183

Reputation: 41 901

+1: Man I love Golfscript! Several times today I have found places to trim my code, but not by 50%! I don't have an interpreter handy: does it return D# for Eb? – DocMax – 2011-06-24T23:27:15.420

BTW, the last note in your sample matches C#m although the command line shows D#m. Typo or bug? – DocMax – 2011-06-25T06:04:58.277

@DocMax, bug. I don't understand why it only affects D#m and not D# - this is going to be interesting to debug. I reorder things because it's convenient to have the block of 7 first, so Eb isn't really an edge case. – Peter Taylor – 2011-06-25T07:38:05.110

Turns out that the last one was getting a \n included, which not being in the lookup table was pulling the value down by the equivalent of a letter. – Peter Taylor – 2011-06-25T12:09:43.253

4

After coding this up, I realized I could have done this a lot smarter...maybe I'll make another entry. Hopefully I'll just get points for being the quickest!

Anyway, there is 962 characters of Perl.

%c =(B=>{E=>0,F=>1,Gb=>2,G=>3,Ab=>4,A=>2,Bb=>3,B=>4,C=>5,Db=>6,D=>7,Eb=>8,Em=>0,Fm=>1,Gbm=>2,Gm=>3,Abm=>0,Am=>1,Bbm=>2,Bm=>3,Cm=>4,Dbm=>5,Dm=>6,Ebm=>7},G=>{E=>1,F=>2,Gb=>3,G=>4,Ab=>5,A=>2,Bb=>3,B=>4,C=>5,Db=>6,D=>7,Eb=>8,Em=>0,Fm=>1,Gbm=>2,Gm=>3,Abm=>4,Am=>2,Bbm=>3,Bm=>4,Cm=>5,Dbm=>6,Dm=>7,Ebm=>8},D=>{E=>2,F=>3,Gb=>4,G=>5,Ab=>6,A=>2,Bb=>3,B=>4,C=>5,Db=>6,D=>7,Eb=>8,Em=>2,Fm=>3,Gbm=>4,Gm=>5,Abm=>6,Am=>2,Bbm=>3,Bm=>4,Cm=>5,Dbm=>6,Dm=>7,Ebm=>8},A=>{E=>2,F=>3,Gb=>4,G=>5,Ab=>6,A=>0,Bb=>1,B=>2,C=>3,Db=>4,D=>5,Eb=>6,Em=>2,Fm=>3,Gbm=>4,Gm=>5,Abm=>6,Am=>0,Bbm=>1,Bm=>2,Cm=>3,Dbm=>4,Dm=>5,Ebm=>6},E=>{E=>0,F=>1,Gb=>2,G=>3,Ab=>4,A=>0,Bb=>1,B=>2,C=>3,Db=>4,D=>5,Eb=>6,Em=>0,Fm=>1,Gbm=>2,Gm=>3,Abm=>4,Am=>0,Bbm=>1,Bm=>2,Cm=>3,Dbm=>4,Dm=>5,Ebm=>6});
%b=('A#'=>'Bb','C#'=>'Db','D#'=>'Eb','F#'=>'Gb','G#'=>'Ab');
foreach(qw(e B G D A E)){p($_,@ARGV)}
sub p{$s = shift;print "$s ";$s = uc($s);foreach(@_){while(($h,$f)=each(%b)){s/$h/$f/}print "$c{$s}->{$_}---"}print "\n"}

Here is the corresponding output.

dhrasmus:Desktop standage$ perl guitar Bm Gb A E G D Em F#
e 2---2---0---0---3---5---0---2---
B 3---2---2---0---3---7---0---2---
G 4---3---2---1---4---7---0---3---
D 4---4---2---2---5---7---2---4---
A 2---4---0---2---5---5---2---4---
E 2---2---0---0---3---5---0---2---

Daniel Standage

Posted 2011-06-23T20:44:45.183

Reputation: 634

4

Since shorter solutions have been given already (damn you GolfScript!), here is mine:

Python, 229 chars

s=[("E EmF FmF#GbG GmG#AbA AmA#BbB BmC CmC#DbD DmD#Eb".find("%-02s"%s[:2])/4,s[-1]!='m')for s in raw_input().split()]
for c in range(6):
 l='eBGDAE'[c]+' '
 for(i,M)in s:x=i>4;l+=`i-5*x+2*(2<c+x<5)+(M+x)*(c==2-x)`+"---"
 print l

Output:

> echo "Bm Gb A E G D Em F#" | python guitar.py
e 2---2---0---0---3---5---0---2---
B 3---2---2---0---3---7---0---2---
G 4---3---2---1---4---7---0---3---
D 4---4---2---2---5---7---2---4---
A 2---4---0---2---5---5---2---4---
E 2---2---0---0---3---5---0---2---

Jules Olléon

Posted 2011-06-23T20:44:45.183

Reputation: 731

3

Python, 449 chars

z=int
f=str
r=range
j=''.join
n='E F F# G G# A A# B C C# D D#'.split()
n+=[x+'m'for x in n]
c=[j([f(z(x)+i)for x in'001220'])for i in r(5)]+[j([f(z(x)+i)for x in'022200'])for i in r(7)]
c+=[x[:2]+f(z(x[2])-1)+x[3:]for x in c[:5]]+[x[0]+f(z(x[1])-1)+x[2:]for x in c[5:]]
a=[c[n.index((chr(ord(i[0])-1)+'#'+i[2:]).replace('@','G')if len(i)-1 and i[1]=='b'else i)]for i in raw_input().split()] 
for i in r(6):print'eBGDAE'[i],j([x[i]+'-'*3 for x in a])

Fernando Martin

Posted 2011-06-23T20:44:45.183

Reputation: 131

3

C99 - 231 characters

The chords are given on the command line, one argument per chord, and of course there is no input validation of any kind.

#include<stdio.h>
int main(int c,char**v){for(char*o="e0)B2)G2*D2+A0+E0)",i,m;*o;o+=3,v-=c,puts(""))for(printf("%c ",*o);*++v;printf("%c---",i-(i>2)-i/9+o[1+i/8]-(*o-66-i/8*5?0:m?m+2[*v]>99:0)))m=1[*v],i=(**v*2-4+m/35-m/98*3)%14;}

Sample run:

$ ./a.out Bm Gb A E G D Em F#
e 2---2---0---0---3---5---0---2---
B 3---2---2---0---3---7---0---2---
G 4---3---2---1---4---7---0---3---
D 4---4---2---2---5---7---2---4---
A 2---4---0---2---5---5---2---4---
E 2---2---0---0---3---5---0---2---

Un-golfed

#include<stdio.h>
int main(int c,char**v){
     // o points to three characters per output line:
     //   string name, number for A, adjusted number for E (ASCII code minus 7)
     char* o="e0)B2)G2*D2+A0+E0)",
          i, // chord: A=0, A#=1, ..., G#=13, allowing also 3="E#" and 9="B#"
          m; // second character in chord name
     for (; *o; o+=3) {
          printf("%c ", *o);
          for (; *++v; ) {
               m = 1[*v],
               i = (**v*2-4+m/35-m/98*3)%14; // parse & adjust for sharp, flat
               printf("%c---",
                      i-(i>2)-i/9 // eliminate "E#", "B#"
                      +o[1+i/8] // get the number for a major chord
                      // adjust for minor...
                      -(*o-66-i/8*5
                        ? 0
                        : m ? m+2[*v]>99 : 0));
          }
          v -= c; // rewind argument pointer
          puts("");
     }
}

Nonstandard C - 206 characters

If we don't care about language specifications, GCC can compile the following one-liner into a functional binary, even though it mixes C99 variable declarations with a K&R style argument declaration (and an implicit declaration of printf).

main(c,v)char**v;{for(char*o="e0)B2)G2*D2+A0+E0)",i,m;*o;o+=3,v-=c,puts(""))for(printf("%c ",*o);*++v;printf("%c---",i-(i>2)-i/9+o[1+i/8]-(*o-66-i/8*5?0:m?m+2[*v]>99:0)))m=1[*v],i=(**v*2-4+m/35-m/98*3)%14;}

han

Posted 2011-06-23T20:44:45.183

Reputation: 1 226

2

C++, 432

#include <cmath>
#include <iostream>
F(int c){c-=65;return c*1.6+sin(c/5.+.3);}
T(int a,int s){if(s)return(a=(T(a,s-1)+2)%3)-=(a==1&s>2);return(a<7)*2;}
#define c(a,b) while(*(++j)==a)b;--j; 
#define O std::cout<<
main(int a,char*p[]){
int P=2;for(int i=-1;++i<6;P=2){O p[1][i];O" ";while(P<a){char*j=p[P++];
int f=F(*j);c('#',++f)c('b',--f)
int t=T(f,i)*3.5;if(*(++j)!='m'){--j;t+=(t==3);}
O(f-F(p[1][i])+t+24)%12;O"---";
}O'\n';}}

Note that this needs the guitar tuning as the first parameter. (Most non-standard-tunings will give you ridiculous fingerbreaking results, but I guess you're content with standard tuning.)

For Hotel California, you can do $./a.out EBGDAE Cbm Gb Bbb Fb G D Em F# Bm F# G## D## F## C## D##m E##. Result:

E 2---2---0---0---3---5---0---2---2---2---5---0---3---5---0---2---
B 3---2---2---0---3---7---0---2---3---2---5---0---3---7---0---2---
G 4---3---2---1---4---7---0---3---4---3---6---1---4---7---0---3---
D 4---4---2---2---5---7---2---4---4---4---7---2---5---7---2---4---
A 2---4---0---2---5---5---2---4---2---4---7---2---5---5---2---4---
E 2---2---0---0---3---5---0---2---2---2---5---0---3---5---0---2---

ceased to turn counterclockwis

Posted 2011-06-23T20:44:45.183

Reputation: 5 200

Tuning the upper four strings to minor thirds makes it very easy to play three- and four-string chords in many inversions, without open strings, and without having to place a finger "over" a string without touching it. Using strings DFG#B, a chord sequence like "Bbm F Bbm Gb Db Ebm Db F Bbm F F7 Bbm" (Mermaid Song) works out very easily. It's only necessary to shift up and down one fret. There's a half-step keychange, but that just means moving up a fret. Haven't figured out what best to do with the other two strings, though. – supercat – 2012-02-19T22:39:17.567

@supercat: interesting, I shall try this on my guitar tomorrow... – ceased to turn counterclockwis – 2012-02-19T22:59:43.467

I'd like to hear what you think. I'd picked up a guitar a few times, years apart, and kept giving up because the fingerings seemed both arbitrary and awkward. Then I got to thinking about what tunings would allow simple fingering. Since closed-form chords have intervals which range from a minor third to a perfect fourth, tuning strings to minor thirds means each string will be damped at a point which is no lower than the string below. If I can try a left-handed guitar, I might try perfect fourths with string order reversed, since it should be similar. – supercat – 2012-02-19T23:17:16.333

As it is, tuning to minor thirds means that for each position of the first finger on the lowest string, there will be three major chord inversions and three minor chord inversions available. One can also play a seventh chord by placing the second finger across the top three strings. For Mermaid Song, start on the third fret and play F-Bb-D-F (with fingers 1-3-3-4). Then F is F-A-C-F (1-2-2-4). Gb is up a fret, fingered 1-2-2-4 (like F). Db is back down a fret, 1-3-4-4. Ebm is back up, 1-2-4-4. – supercat – 2012-02-19T23:20:23.840

It took me only a few hours to get to the point where I could smoothly play some pieces (including the aforementioned Mermaid Song) after working out with the aid of a keyboard what the proper chord notes should be. Once I tried this style, it felt amazingly natural, and I really like the way one can use all three inversions of each major and minor chord. If one just wanted major and minor chords, a tuning like F-Ab-B-Eb-Gb-D could theoretically allow six-finger major and minor chords with easy fingerings (1-2-2-3-4-4 or 1-1-2-3-3-4) but without the inversions. – supercat – 2012-02-19T23:56:59.477

Had a chance to try the tuning yet? – supercat – 2014-02-14T03:37:28.993

2

390 345 340 Postscript

Simplified to a guitar-pragmatic approach (the E shape is just a variation of the A shape, shifted down a string, with one finger change). Borrowed the encoded-string idea from the other answers.

[/p{print}/x{exch}/e{cvx exec}/d{dup 0
get}/f{forall}(A0#1B2C3D5E7F8G:b;){}forall/m{dup
4 2 copy get 1 sub put}109{m 48}/+{[3 1 roll x{1
index add x}f]}/*{[0 0 2 2 2 0 0]x{load e 48 sub
+}f d 12 gt{-12 +}if d 6 gt{m -7 + 1}{0}ifelse 6
getinterval}>>begin[ARGUMENTS{*}f][(E)(A)(D)(G)(B)(e)]6{[x
e p( )p]x[x{[x e( )cvs p(---)p]}f]x()=}repeat

Previously:

450 442 418 Postscript

I fixed the output format with this one, too. (Previous versions began "E---" rather than "e ".)

<</i{index}/a{add}/p{pop}/x{exch}/g{getinterval}/r{print}/f{forall}/e{exec}>>begin<<65[0
2 3 5 -5 -4 -2]{1 i 1 a}f p 35{1 a}98{1 sub}109{x dup
4 20 put x}>>begin[ARGUMENTS{[x[0 5 12 17 21 24 29
0]x{load e}f x{1 i a x}f]dup 0 get 0 ge{0}{1}ifelse 7
g[0 -5 -10 -15 -19 -24 -29]0 1 6{2 copy get 3 i 2 i
get a 3 copy put p p}for x p 0 6
g}f][(E)(A)(D)(G)(B)(e)]6{[x cvx e r( )r]x[x{[x cvx
e( )cvs r(---)r]}f]x(\n)r}repeat

How to run it: gsnd -q -- tab.ps Bm Gb A E G D Em F\# (hide the sharp from the shell).

The un-golfed version was almost more difficult than the golfed one. But I tried to be thorough. Edit: a few more comments on the tricksy-bits.

%!PS
<<    %axioms and operations
/t{2 2 1}    %major tetrachord
/m{t t 2}    %mixolydian mode
/u{2 1 2}    %minor tetrachord
/a{u u}      %aolian mode
/s{m m t}    %2.5-octave mixolydian intervals
/r{3 1 roll}
/${[exch 0 exch{1 index add}forall]}    %running sum: convert (relative)intervals to (abstract)fretstops
/+{[r exch{1 index add exch}forall pop]}    %scale array by scalar
/@{[r{2 copy get r pop}forall pop]}    %select array elements from array of indices
/&{0 1 3 index length 1 sub{    %array2 += array1
    2 copy get 3 index 2 index get add 3 copy put pop pop}for exch pop}
>>begin<<    %map ascii values to scaling functions
65[a]$    %generate fretstops of the A aolian scale to assign scalars to note names
[0 0 0 0 -12 -12 -12]&    %drop E F and G down an octave
{[exch/+ cvx]cvx 1 index 1 add}forall pop    %generate the pairs 'A'->{0 +}, 'B'->{2 +}
35{1 +}     %'#'-> scale up by one
98{-1 +}    %'b'-> scale down by one
109{dup 4 2 copy get 1 sub put}     %'m'-> tweak the 'third' down by one
%generate chord pattern from (string)
/*{[s]$       %generate fretstops of the E mixolydian scale
  [1 4 8 11 13 15 18]    %A-shape figured bass: IV chord of E mixolydian
  -1 +       %convert scale degrees to array indices
  @       %generate chord template by selecting indices from mixolydian scale
  exch{load exec}forall       %execute ascii values, scaling the pattern
  dup 0 get 0 ge{0}{1}ifelse 6 getinterval    %discard first note if it has fallen off the bottom
  [0 -5 -10 -15 -19 -24]&}    %subtract the string offsets
>>begin    %activate definitions
%(A)* pstack()= clear    %[0 0 2 2 2 0]
%(B)* pstack()= clear    %[2 2 4 4 4 2]
%(F#)* pstack()= clear    %[2 4 4 3 2 2]
%(Abm)* pstack()=    %[4 6 6 4 4 4]
[ARGUMENTS{*}forall]    %convert array of strings to array of patterns
[(E)(A)(D)(G)(B)(e)]    %array of string names
6{    %for each "string"
    [exch cvx exec print( )print]    %pop string name and print with space
    exch       %put names behind numbers
    [exch{     %for each "chord"
        [exch cvx exec( )cvs print(---)print]    %pop number, convert, print with trailing hyphens
    }forall]    %zip up chord array for next iteration
    ()=         %print a newline
    exch        %put numbers behind names
}repeat

And how about House of the Rising Sun as a test?

04:51 PM:~ 0> gsnd -q -- tabb.ps Em G A C Em G B B Em G A C Em B Em B|sed 's/^/    /'
e 0---3---0---3---0---3---2---2---0---3---0---3---0---2---0---2---
B 0---3---2---5---0---3---4---4---0---3---2---5---0---4---0---4---
G 0---4---2---5---0---4---4---4---0---4---2---5---0---4---0---4---
D 2---5---2---5---2---5---4---4---2---5---2---5---2---4---2---4---
A 2---5---0---3---2---5---2---2---2---5---0---3---2---2---2---2---
E 0---3---0---3---0---3---2---2---0---3---0---3---0---2---0---2---

luser droog

Posted 2011-06-23T20:44:45.183

Reputation: 4 535

I written a commentary of this code in another answer here.

– luser droog – 2012-08-30T03:00:24.613