# Help Me Play My Harmonica

Yesterday, I bought a harmonica:

Figure 1: The harmonica.

However, my dreams of being able to play soulful blues harmonica that moves people and causes a grown man to cry were quickly dashed by two problems:

1. The harmonica can only play certain notes;
2. I am depressingly bad at playing the harmonica.

Despite my lack of skill at the harmonica, there are still some songs that I can play on it. However, it is not immediately obvious if I am able to play some piece of music on the harmonica or not. Given the notes of a piece of music, write a program to determine if I could play it on my harmonica or not.

As the above picture shows, my harmonica has ten holes in it. With each hole, I can either exhale into it or inhale into it -- the hole that I choose, and whether I inhale or exhale into it changes the pitch of the resulting sound. Each hole has a different pitch on exhaling and inhaling, but there are some combinations that result in the same note. Overall, my harmonica can play 19 unique different pitches. The pitches are presented in musical scientific notation - the letter represents the note, and the number represents which octave it is in.

 Hole   Breathing   Note
1      Exhale      C4
1      Inhale      D4
2      Exhale      E4
2      Inhale      G4
3      Exhale      G4
3      Inhale      B4
4      Exhale      C5
4      Inhale      D5
5      Exhale      E5
5      Inhale      F5
6      Exhale      G5
6      Inhale      A5
7      Exhale      C6
7      Inhale      B5
8      Exhale      E6
8      Inhale      D6
9      Exhale      G6
9      Inhale      F6
10     Exhale      C7
10     Inhale      A6


For example, if I exhaled on hole 3, I'd get a G4 note. If I inhaled on hole 2, I'd also get a G4 note. If I exhaled on hole 7, I'd get a C6.

When I breathe into the harmonica, apart from exhaling or inhaling, I can also choose whether to breathe thinly or widely. Breathing thinly causes only one hole to sound, whereas breathing widely causes one hole and both holes on either side of that hole to sound. I do not have the embouchure skills to blow onto two holes - it is either one or three.

For instance, if I thinly exhaled onto hole 4, only hole 4 would sound, so I would get a C5 sound. If I widely exhaled onto hole 4, holes 3, 4, and 5 would sound, and I'd get a G4,C5,E5 chord. If I widely inhaled onto hole 4, holes 3, 4, and 5 would sound but they would play the inhale pitches instead, which results in a B4,D5,F5 chord. Note that for the holes on either end, if I breath widely into them only two holes would sound (because there is no hole 0 or hole 11).

However, I cannot inhale and exhale at the same time. For instance, I could exhale into holes 4, 5, and 6 to result in the notes C5, E5, and G5 sounding at the same time, forming a chord. However, I can't inhale and exhale at the same time, so it would be impossible for me to play the chord C5,F5,A5 as I'd have to somehow exhale in hole 4 and inhale in hole 5 and 6. If this is stil unclear, this comment thread might be useful.

The input is the notes of the music. The notes are notated in the same way they are above in the table, and they are comma separated. Notes wrapped in curly brackets represent a chord. For instance:

C4,D4,G4,{D5,F5,A5},B5


This means, "C4, then D4, then G4, then D5, F5, and A5 at the same time, then B5." Your program will take a string in this format as input and output True if it is possible for me to play the music on my harmonica, or False otherwise. For sample inputs and outputs, the example above should output True. The input {C5,F5,A5} on the other hand will output False.

This is code golf, so the shortest entry wins.

Here are some test cases:

Input (A C Major scale):

C4,D4,E4,F4,G4,A4,B4,C5


Output:

False


(because the harmonica cannot play F4 or A4)

Input (the opening 2 bars of Let It Go):

E6,F6,A5,E6,F6,F6,E6,A5,F6,E6


Output:

True


Input:

{E6,G6,F6}


Output:

False


Input:

{G4,C5,E5},{F5,A5,B5}


Output:

True


You may assume that chords will come in lower to higher pitch order.

• Do you have to alternate exhaling and inhaling moving from note to note?
– COTO
Commented Sep 9, 2014 at 1:06
• ​​​​​​​​​​​​​​​@COTO No. Assume I have infinite breath capacity. Commented Sep 9, 2014 at 1:07
• the referenced comment thread link doesn't work for me. this works for me: meta.codegolf.stackexchange.com/a/2149/3348 Commented Sep 9, 2014 at 2:06
• do you have a set of test cases we can use? Commented Sep 9, 2014 at 2:15
• I CAN play bluesharp (Richter tuned harmonica), but I only play blues on it. Hole 7 with C6 going DOWN to B5 looks strange but is correct. You have two solutions: buy a more advanced harmonica with different tuning, or learn to bend the notes (if you can bend, you STILL won't be able to play all the notes, but you can play a howling blues to express your frustration.) The rule about only being able to play 1 or 3 notes is fairly accurate. Here's a nice representation of Richter tuning: en.wikipedia.org/wiki/Richter-tuned_harmonica. Section on different keys is also musically interesting Commented Sep 9, 2014 at 8:04

# Python - 218209 189 chars

Minimal:

def t(s):from re import sub as r;exec('l=bool([x for x in['+r('"{','("',r('}"','")',r(',','","','"'+s+'"')))+']if"".join(x)not in"C4E4G4C5E5G5C6E6G6C7|D4G4B4D5F5A5B5D6F6A6"])');return not l


def t(s):
from re import sub as r
exec('l=bool([x for x in'
' [' + r( '"{' , '("' ,
r( '}"' , '")' ,
r( ',' , '","' , '"' + s + '"' ))) +
']'
' if "".join(x) not in "C4E4G4C5E5G5C6E6G6C7|D4G4B4D5F5A5B5D6F6A6"])')
return not l


Given a string formated as in the problem description, t return True if the sequence is playable on the described harmonica, and False if it isn't.

There is no check for the order of the notes in the chords. Unless told otherwise, I believe this is sufficient since that isn't in the problem statement, and it passes all given tests in:

assert not t("C4,D4,E4,F4,G4,A4,B4,C5")
assert t("E6,F6,A5,E6,F6,F6,E6,A5,F6,E6")
assert not t("{E6,G6,F6}")
assert t("{G4,C5,E5},{F5,A5,B5}")

• You can remove a lot of spaces: ] if "".join(x) not -> ]if"".join(x)not Keywords can be adjacent to strings so "and" is correct. Commented Sep 9, 2014 at 9:36
• First code golf here so I figured I was missing something. Thanks!
– Poik
Commented Sep 9, 2014 at 12:51

# Javascript - 245 243 chars

Minified:

function p(s){function f(s){s=s.replace(/\W/g,'');l=s.length;return((l-6)*(l-2)?l-4?'':'C4E4 G6C7 D4G4 F6A6':'C4E4G4C5E5G5C6E6G6C7 D4G4B4D5F5A5B5D6F6A6').indexOf(s)<0?0:''}return s.replace(/{.*?}/g,f).split(',').map(f).join('')?'False':'True'}


And expanded:

function p(s) {
function f(s) {
s = s.replace( /\W/g, '' );
l = s.length;
return ( (l-6)*(l-2) ? ( (l-4) ? '' : 'C4E4 G6C7 D4G4 F6A6' ) : 'C4E4G4C5E5G5C6E6G6C7 D4G4B4D5F5A5B5D6F6A6' ).
indexOf( s ) < 0 ? 0 : ''
}
return s.replace( /{.*?}/g, f ).split( ',' ).map( f ).join( '' ) ? 'False' : 'True'
}


The function p accepts a string as input and returns True if the note/chord sequence is playable, False otherwise. It returns undefined results if the input isn't syntactically valid.

It also boldly assumes that chord notes are entered in order of ascending hole (such as in the example).

The character count can be reduced by 14 if the function is allowed to return logical true and false rather than their string equivalents.

# JavaScript (ES6), 230

Just a rewritten version of @COTO's answer:

f=s=>{s=s.replace(/\W/g,'');l=s.length;return((l-6)*(l-2)?(l-4?'':'C4E4 G6C7 D4G4 F6A6'):'C4E4G4C5E5G5C6E6G6C7 D4G4B4D5F5A5B5D6F6A6').indexOf(s)<0?0:''};p=s=>{return s.replace(/{.*?}/g,f).split(',').map(f).join('')?'False':'True'}


Would appreciate any tips on golfing this down further as I am starting to learn ES6! :-)

# Scala 178

print(readLine./:(""){(a,c)=>if("..C4E4G4C5E5G5C6E6G6C7...D4G4B4D5F5A5B5D6F6A6...."sliding(6)flatMap(y=>Seq("{"+y.diff("..")+"}",y take 2))contains a+c)""else a+c diff ","}=="")


Ungolfed:

print(
readLine.foldLeft(""){(a,c)=>                             # loop through the input
if("..C4E4G4C5E5G5C6E6G6C7...D4G4B4D5F5A5B5D6F6A6...."  # from the harmonica
.sliding(6)                                     # take 6 at a time
.flatMap(y=>Seq("{"+y.diff("..")+"}",y take 2)) # chords + single notes
.contains(a+c)) ""                              # clear matches
else a+c diff ","                                 # drop commas
}==""                                               # did everything match?
)


Note that malformed input is handled badly - any string is accepted, and many malformed strings return true, for example:

{C,7.D}.C


prints true.

# Rebol - 188

t: func[s][trim/with s ","h: next split n:"C4E4G4C5E5G5C6E6G6C7..D4G4B4D5F5A5B5D6F6A6"2 forskip h 2[i


Ungolfed:

t: func [s] [
trim/with s ","
h: next split n: "C4E4G4C5E5G5C6E6G6C7..D4G4B4D5F5A5B5D6F6A6" 2
forskip h 2 [insert h '|]

parse s [
any [
h | "{" x: copy c to "}" (unless find n c [c: n])
:x c "}"
]
]
]


Example usage (in Rebol console):

>> t "C4,D4,G4,{D5,F5,A5},B5"
== true

>> t "{C5,F5,A5}"
== false

>> t "C4,D4,E4,F4,G4,A4,B4,C5"
== false

>> t "E6,F6,A5,E6,F6,F6,E6,A5,F6,E6"
== true

>> t "{E6,G6,F6}"
== false

>> t "{G4,C5,E5},{F5,A5,B5}"
== true


While this code will catch gibberish like this:

>> t "{C,7.D}.C"
== false


However it will allow things like this through:

>> t "C,4,D4"
== true


Because its parsing it as "C4,D4".

Here's a more stricter version of the code:

t: func [s] [
h: next split n: "C4E4G4C5E5G5C6E6G6C7..D4G4B4D5F5A5B5D6F6A6" 2
forskip h 2 [insert h '|]
d: ["," | end]

parse s [
any [
[
h | "{" x: copy c to "}" (
unless all [
parse c [any [h d]]
find n trim/with copy c ","
] [c: n]
)
:x c "}"
]
d
]
]
]


This golfs down to 228 chars and now returns false on...

>> t "C,4,D4"
== false


## JavaScript ES6, 211209 190 chars

I know this can be golfed further. I will try to do so in a few hours;

Run this code in Latest Firefox's Web Console, you will get a method named C which you can then call like C("{G4,C5,E5},{F5,A5,B5}") and it will return True or False accordingly.

C=n=>(s='D4G4D5F5A5B5D6F6A6,C4E4G4C5E5G5C6E6G6C7',n.split(/[,}{]/g).some(a=>a&&!~s.search(a))||(m=n.match(/{[^}]+/g))&&m.some(a=>a.length!=9|!~s.search(a.replace(/{|,/g,"")))?'False':'True')


I am assuming a syntactically valid input.

EDIT: Simplified the regex and the length check.