Black or white keys of jazz chords?

When playing the piano, I realized that to play a chord right, I usually need to remember just which tones of the chord are played on white and which on black keys. The rest is usually handled by the "autopilot".

Let's write a program that given a 7th chord, it outputs which of its tones are to be played on black (character ^) and which on white keys (character -). Each chord name has a root, which is a capital letter A to G, optionally followed by b or #, and then by its type, which is one of the following. All 7th chords contain 4 notes, and the formulas are given in integer notation below.

• 7 Dominant (0,4,7,10)
• M7 Major (0,4,7,11)
• m7 Minor (0,3,7,10)
• dim7 Diminished (0,3,6,9)
• m7b5 Half-Diminished (0,3,6,10)
• -M7 Minor/Major (0,3,7,11)

Integer notation means that you must play the four notes that are (w,x,y,z) semitones from the root. (w is the root and is therefore always zero.)

So for example for input Bb-M7 the output would be ^^-- (corresponding to Bb-Db-F-A).

The program should either read the chord form stdin and output the result to stdout, or it should be a function that accepts a string and returns a string.

• I like the challenge, but I felt for people like me, who can barely read music, the explanation in the link was rather difficult to follow. I've gone ahead and added integer notation to the question, which is much easier to follow for non-musicians. Roll it back if you don't like it. Also '-M7' should be minor/major. C major minor 7th would just be C dominant. – Level River St Feb 5 '15 at 22:04
• @PeterTaylor I would say not, because the bonus for the 7th chords on the question you linked was quite low, so nobody did it. Also the fact that this question only requires an output of black/white will lead to very different strategies, such as the one I introduced in this question codegolf.stackexchange.com/q/36497/15599 – Level River St Feb 5 '15 at 23:33
• Is "Bb-M7" a Major or Major/Minor chord? – aditsu quit because SE is EVIL Feb 5 '15 at 23:56
• Is the example a typo meant to be "BbM7"? – dennisdeems Feb 6 '15 at 0:24
• @dennisdeems No, it really is Bb-M7 - it's the Minor/Major chord, denoted by -M7. – Petr Feb 6 '15 at 14:43

CJam - 75

q('@m2*_6/-\_0="b #"#_){(@+\1>}{;}?1b81*A+H/6%8+2b1>{1$3++}/]2774Ybf="^-"f=  Try it at http://cjam.aditsu.net/ Brief explanation: 1. ('@m2*_6/- Convert the first character to a number of semitones (starting from G for convenience) 2. \_0="b #"#_){(@+\1>}{;}? Adjust for b or # 3. 1b81*A+H/6%8+2b1>{1$3++}/] Convert the rest of the chord to a unique number (I used the sum of ASCII codes here) and then do some clever calculations with it to obtain the 4 notes of the chord
4. 2774Ybf="^-"f= Convert the notes to black/white as 0/1 (binary digits of 2774), then convert 0/1 to ^/-.

Python3, 293 242 bytes

This is my first CodeGolf. I am curious to see how others do this.

def f(s):
for j in 4,3,2,1:
try:return ''.join(['-^--^-^--^-^'[('A BC D EF G'.index(s[0])-('b'==s[1])+('#'in s)+i)%12]for i in(0,)+{'7':(4,7,10),'M7':(4,7,11),'m7':(3,7,10),'dim7':(3,6,9),'m7b5':(3,6,10),'-M7':(3,7,11)}[s[-j:]]])
except:pass


Edit on 01.06.15

Thanks to the commenters, we found some tricks to shorten the code. I also discovered that changing to a function is slightly shorter. Old code: http://ideone.com/j5gmV2. P.S. I am just copying the code into triple quotes in Python and calling len to get the number of bytes.

Note:

I am assuming that Bb-M7 is Bb Major/Minor, i.e. that there is no delimiter between the note and the chord type, as described in the OP's second paragraph. Thus Bb-M7 gives ^^--. If Bb-M7 means Major, then what is Bb Major/Minor? Should it be Bb--M7?

• Dicts where the key or value is a single char tend to be shorter with a bit of zipping, e.g. dict(zip('ABCDEFG',[0,2,3,5,7,8,10])) – Sp3000 Feb 6 '15 at 1:16
• @Sp3000 even better: 'A BC D EF G'.find(s[0]) – Justin Feb 6 '15 at 4:26
• Golfed it down a lot: ideone.com/1N1BJx. It's at 247 chars now – Justin Feb 6 '15 at 4:27
• And 238 bytes with a while loop I think. – grc Feb 6 '15 at 8:05
• Welcome, not bad for a first golf :) Feel free to edit your answer using the suggestions from the comments. – aditsu quit because SE is EVIL Feb 6 '15 at 17:37

C, 154 151 bytes

Rev 1 eliminated variable f by doubling with p, and placed formula search inside for bracket.

char c[9],s[]="d7m+mM-";n;main(p){gets(c);n=*c*2%7+(c[p]&4?0:5+c[p++]%2*2);for(p=strchr(s,c[p])-s<<!!c[p+3]|8;p;p/=2)putchar(45+n%12/7*49),n+=9-p%2*5;}


Rev 0

char c[9],s[]="d7m+mM-";n;f;main(p){gets(c);n=*c*2%7+(c[p]&4?0:5+c[p++]%2*2);f=strchr(s,c[p])-s<<!!c[p+3]|8;for(;f;f/=2)putchar(45+n%12/7*49),n+=9-f%2*5;}


EXPLANTATION

I've used a bit of music theory to help reduce my score.

Terminology

A seventh chord is composed of the 1st, 3rd, 5th and 7th notes of a diatonic scale. These names are also used to describe intervals.

A "3rd" is an interval of 3 or 4 semitones, known as "minor" and "major" 3rds respectivelty.

A "5th" is made by stacking two "3rds", and a "7th" is made by stacking a "5th" and a "3rd."

The standard 5th is the "perfect 5th" (of 3+4 or 4+3 = 7 semitones.) "Diminished" (3+3=6) and "augmented" (4+4=8) 5ths are also possible.

There are two standard 7ths: "minor" (7+3=10) and "major" (7+4=11) and one nonstandard 7th: "diminished" (6+3=9.) Note that there is no "augmented" 7th interval, because 8+4=12 sums to a whole octave.

Stacked Thirds

All of the seventh chords required in this question are based on stacked thirds. That is, the inteval between one note and the next is always three or four semitones. Therefore we can encode the formula for the chord type as a 3-bit binary number as below. (The increments are coded into binary from right to left and the 4th bit is set to 1 to signal the end of the information.)

Symbol   Name             Formula   Increments  Binary
dim7     Diminished       0,3,6,9       3,3,3   1 000
7        Dominant         0,4,7,10      4,3,3   1 001
m7       Minor            0,3,7,10      3,4,3   1 010
+M7      Augmented/Major* 0,4,8,11      4,4,3   1 011
m7b5     Half-Diminished  0,3,6,10      3,3,4   1 100
M7       Major            0,4,7,11      4,3,4   1 101
-M7      Minor/Major      0,3,7,11      3,4,4   1 110

*The augmented major seventh chord is not required by the question, nor is it implemented correctly, because it clashes with the #/b correction.


The binary is obtained from the first character of the chord type by finding its position in the string "d7m+mM-". There is a problem with the m7b5 chord, because there are two m's in the string and strchr will always find the first one. To fix this, the binary is leftshifted one place whenever the chord type has more than 3 characters.

Circle of fifths

Instead of organising the notes as A A#/Bb B C etc, I use an imporant alternate representation called the Circle of fifths. In this we start at F and count 7 semitones each time, giving the following sequence:

F C G D A E B F#/Gb C#Db G#/Ab D#/Eb A#/Bb


As can be seen, all the white notes come first, followed by all the black notes. This is no coincidence: the fifth (7 semitones, frequency ratio 3/2) is the most important musical interval after the octave (12 semitones, ratio 2/1). The familiar scale of 7 out of the 12 available notes is constructed by stacking these "fifths."

The input note letter can be converted to its position in the circle of 5ths by ASCII code*2%7, then 5 is added for a flat or 7 for a sharp.

In the domain of fifths, a minor third is nine fifths (minus five octaves): 9*7=63, 63%12=3 and a major third is four fifths (minus two octaves): 4*7=28, 28%12=4.

For output, any number below 7 is a white note, any number 7 and above is a black note.

REV 0 UNGOLFED CODE

char c[9],s[]="d7m+mM-";n;f;                     //array c is filled with zeroes
main(p){                                         //if no arguments are given, p initialised to 1
gets(c);
n=*c*2%7                                       //convert character zero to its position in cycle of fifths, then correct for #/b in the next line
+(c[p]&4?0:5+c[p++]%2*2);                      //# and b are the only characters in position 1 whose ASCII codes have the 4's bit clear.
f=strchr(s,c[p])-s<<!!c[p+3]|8;                //find binary representation of stacked 3rds, correct for m7b5, and OR with 8
for(;f;f/=2)                                   //at end of iteration, rightshift f. loop will end when f=0 (when 8's bit shifted out)
putchar(45+n%12/7*49),                       //if n%2<7, put ASCII 45='-', else put ASCII 45+49='^'
n+=9-f%2*5;                                  //increment n by a minor third (9 fifths) or major third (4 fifths)
}


C# 468

string f(string p){var x=new Dictionary<char,int>{{'C',1},{'D',3},{'E',5},{'F',6},{'G',8},{'A',10},{'B',12}};var y=new Dictionary<string,string>{{"7","0,4,7,10"},{"M7","0,4,7,11"},{"m7","0,3,7,10"},{"dim7","0,3,6,9"},{"m7b5","0,3,6,10"},{"-M7","0,3,7,11"}};var m="--^-^--^-^-^-";var r="";int j=1,i=0,k=0;if(p[1]=='b')i--;if(p[1]=='#')i++;if(i!=0)j=2;i=i+x[p[0]];var a=y[p.Substring(j)].Split(new char[]{','});for(;k<a.Length;k++)r+=m[(int.Parse(a[k])+i)%12];return r;}


For me it looks ok. I tested it but who knows ;)

I also think that for

Bb-M7


you should get

^^--


Java 513 492 characters

This is my first time entering as well. Hope I'm doing this right.

public class W{String n="C D EF G A B",a="b_#",w="-^-^--^-^-^-";
public String z(String h){int r=0,i=0;char k=h.charAt(i);int p=n.indexOf(k);char l=h.charAt(++i);
if(a.contains(String.valueOf(l))){i++;r=a.indexOf(l)-1;}
p=(p+r)%12;String q=h.substring(i);q="q"+q.replace("-","_");
C c=C.valueOf(q);int[] s=c.s;String y="";
for(int j:s){int t=(j+p)%12;y+=w.charAt(t);}return y;}
enum C{q7(0,4,7,10),qM7(0,4,7,11),qm7(0,3,7,10),qdim7(0,3,6,9),qm7b5(0,3,6,10),q_M7(0,3,7,11);
int[] s;C(int...a){s=a;}}}


Ungolfed

public class BlackWhiteKeys
{
String noteNames = "C D EF G A B";
String accidentals = "b_#";
final String blackWhite = "-^-^--^-^-^-";

public String analyze( String chordName )
{
int index = 0;
char tonic = chordName.charAt( index );
int pitch = noteNames.indexOf( tonic );
char alteration = chordName.charAt( ++index );
int transpose = 0;
if ( accidentals.contains( String.valueOf( alteration ) ) )
{
index++;
transpose = accidentals.indexOf( alteration ) - 1;
}
pitch = ( pitch + transpose ) % 12;

String quality = "q" + chordName.substring( index ).replace( "-" , "_" );
Chord c = Chord.valueOf( quality );
int[] semitones = c.semitones;
String pattern = "";
for ( int i : semitones )
{
int tone = ( i + pitch ) % 12;
pattern += blackWhite.charAt( tone );
}
return pattern;
}

enum Chord
{
q7(0, 4, 7, 10),
qM7(0, 4, 7, 11),
qm7(0, 3, 7, 10),
qdim7(0, 3, 6, 9),
qm7b5(0, 3, 6, 10),
q_M7(0, 3, 7, 11);
int[] semitones;

Chord( int... semitones )
{
this.semitones = semitones;
}
}
}

• I could use a map instead of the enum, but it would be less object-oriented that way. – dennisdeems Feb 6 '15 at 17:16
• Welcome :) Remember, the goal here is to make it as short as possible. That usually goes against good programming practices and coding conventions. – aditsu quit because SE is EVIL Feb 6 '15 at 17:18
• I just noticed that the question doesn't say so explicitly, however it is tagged as code-golf. – aditsu quit because SE is EVIL Feb 6 '15 at 17:22
• Welcome to PPCG! As this is code-golf, please show some effort shortening your code, by at least removing unnecessary whitespace and using single-letter variable/function/class names. You can always include an "ungolfed" (readable) version in addition to that. – Martin Ender Feb 6 '15 at 17:22
• Thanks for the guidance. I've modified to be more compact. I've seen other Java entries use character count, so I've done likewise. – dennisdeems Feb 7 '15 at 2:26

Mathematica 296

f[s_]:=StringJoin[Mod[#3+#1+Switch[#2,"",0,"b",-1,"#",1],12]&@@StringSplit[s,Join[Thread@Rule[Characters["C D EF G A B"],Range[0,11]],{"7"->{0,4,7,10},"M7"->{0,4,7,11},"m7"->"0,3,7,10","dim7"->{0,3,6,9},"m7b5"->{0,3,6,10},"-M7"->{0,3,7,11}}]]/.Thread@Rule[Range[0,11],Characters["-^-^--^-^-^-"]]]


For Cdim7 (<-my favourite one)

In:= f["Cdim7"]
Out= -^^-


Java - 247

String j(String s){int i=2,t=0,x=s.charAt(0)*2-128,c=s.charAt(1);x-=x/6+(c==98?1:c==35?-1:(i=1)-1);while(i<s.length())t+=s.charAt(i++);int[]a={0,t%11>0?3:4,t%9>0?7:6,(t%9-t%5)/3+10};s="";for(i=0;i<4;)s+=(1717&1<<(x+a[i++])%12)>0?"-":"^";return s;}


Basically a translation of my CJam solution :)

Executable program with better formatting:

public class J {
String j(String s) {
int i = 2, t = 0, x = s.charAt(0) * 2 - 128, c = s.charAt(1);
x -= x / 6 + (c == 98 ? 1 : c == 35 ? -1 : (i = 1) - 1);
while (i < s.length())
t += s.charAt(i++);
int[] a = { 0, t % 11 > 0 ? 3 : 4, t % 9 > 0 ? 7 : 6, (t % 9 - t % 5) / 3 + 10 };
s = "";
for (i = 0; i < 4;)
s += (1717 & 1 << (x + a[i++]) % 12) > 0 ? "-" : "^";
return s;
}

public static void main(final String... args) {
System.out.println(new J().j("D#m7"));
}
}


Output:

^^^^