Help Me Play My Harmonica

8

Yesterday, I bought a harmonica:

my 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.

absinthe

Posted 2014-09-09T00:36:35.560

Reputation: 8 359

2Do you have to alternate exhaling and inhaling moving from note to note? – COTO – 2014-09-09T01:06:02.150

1​​​​​​​​​​​​​​​@COTO No. Assume I have infinite breath capacity. – absinthe – 2014-09-09T01:07:16.390

the referenced comment thread link doesn't work for me. this works for me: http://meta.codegolf.stackexchange.com/a/2149/3348

– ardnew – 2014-09-09T02:06:24.983

do you have a set of test cases we can use? – ardnew – 2014-09-09T02:15:44.980

@adrnew ​​​​​​​​​​​​​​​Sure, I'll edit some in now. – absinthe – 2014-09-09T02:18:20.477

4

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: http://en.wikipedia.org/wiki/Richter-tuned_harmonica. Section on different keys is also musically interesting

– Level River St – 2014-09-09T08:04:31.167

Answers

2

Python - 218 209 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

For ease of reading:

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}")

Poik

Posted 2014-09-09T00:36:35.560

Reputation: 121

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. – Bakuriu – 2014-09-09T09:36:39.707

First code golf here so I figured I was missing something. Thanks! – Poik – 2014-09-09T12:51:40.180

1

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.

COTO

Posted 2014-09-09T00:36:35.560

Reputation: 3 701

1

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! :-)

rink.attendant.6

Posted 2014-09-09T00:36:35.560

Reputation: 2 776

1

JavaScript ES6, 211 209 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.

Optimizer

Posted 2014-09-09T00:36:35.560

Reputation: 25 836

1

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.

paradigmsort

Posted 2014-09-09T00:36:35.560

Reputation: 66

1

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 '|]
    h: head 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 '|]
    h: head 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

draegtun

Posted 2014-09-09T00:36:35.560

Reputation: 1 592