Let's Make Music!

11

2

Lots of people like to play music for fun and entertainment. Unfortunately, music is pretty difficult sometimes. That is why you're here!

Task

It's your job to make reading music much easier for those struggling with it. You need to write a program or function that takes as input a musical staff, and outputs the names of the notes written on that staff.

Staff, clef, and notes

A musical staff , or stave, is five horizontal lines, inbetween which are four spaces. Each line or space represents a different note (pitch), depending on the clef.
There are a fair few different musical clefs to choose from, but we'll only be dealing with one for now: the treble clef. On the treble clef, the notes are represented on the staff as follows:

Lines
F  ----------
D  ----------
B  ----------
G  ----------
E  ----------
Spaces  
   ----------  
E
   ----------  
C
   ----------  
A
   ----------  
F
   ----------

Formatting of the input

Input will be given as a single string, as follows:

---------------

---------------

---------------

---------------

---------------

The five lines and four spaces of the staff are constructed out of nine rows of characters. Lines of the staff are constructed with - (hyphen) characters, and spaces with (space). Each row is separated from the next by a single newline character, eg:
-----\n \n-----\n \n-----\n \n-----\n \n-----\n
The rows are of arbitrary length (to a reasonable amount that can be handled by your programming language), and each row is exactly the same length in characters as the others. Also note that the rows will always be of a length that is divisible by three (to fit the pattern of one note followed by two columns without a note).

Notes are placed on this staff by replacing the appropriate - or character with o. Notes can also be raised (sharp) or lowered (flat) in pitch by a semitone (about half the frequency difference between a note and its adjacent notes). This will be represented by the characters # and b, respectively, in place of the o. Each note will be separated from the next by exactly two - characters, and the first note will always occur on the first "column" of - and (space) characters.

When outputting note names, your program should always use the capitalised letters (A B C D E F G) corresponding to the note given on the staff. For sharp (#) and flat (b) notes, your program needs to append # and b, respectively, to the letter corresponding to the note. For a natural note that is not sharp or flat, a (space) should be appended instead.

Example

Input:

---------------------o--
                  o     
---------------o--------
            o           
---------b--------------
      o                 
---o--------------------
o                       
------------------------

*note all "empty space" in this example is actually (space character).
In this case (a simple F major scale), your program should output this:

F  G  A  Bb C  D  E  F

Note the spacing between the characters of the output should be exactly as shown above, to fit correctly with the notes on the staff. Between all the note names there are two (space) characters, except between the Bb and C. The b here replaces one of the (space) characters.

Another example
Input:

------------------------
                     o  
------------------#-----
               #        
------------o-----------
         o              
------#-----------------
   #                    
o-----------------------

Output:
E F# G# A B C# D# E

One more example for good luck
Input:

---------------------
o  o  o     o           
---------------------
         o              
---------------------

---------------o--o--

---------------------

Output:
E E E C E G G

Rules

  • Notes will only ever be given in the five line staff range of E flat up to F sharp (except for the challenges, see below)
  • Any note could be sharp or flat, not just those seen commonly in music (eg. despite B# actually just being played as C in reality, B# can still occur in the input)
  • You can assume there will be exactly one note per 3 columns (so there will be no chords or anything like that, and no rests either)
  • You can assume the last note will be followed by two columns with no notes
  • You can assume even the last line of the staff will be followed by a single newline character
  • Input should be taken from STDIN (or language equivalent) or as function parameter
  • Output should be to STDOUT (or language equivalent) or as a return result if your program is a function
  • Standard loopholes and built-ins are allowed! Music is about experimenting and playing around. Go ahead and have fun with your language (although recognise that exploiting a loophole may not produce the most interesting program)
  • This is , so shortest program in bytes wins

Bonus challenges

  • -10% if your program can also successfully process the space above the top line of the staff (G, G#, Gb).
  • -10% if your program can also successfully process the space below the bottom line of the staff (D, D#, Db)
  • In these cases your program would take as input an additional row at the start and end; these rows should be treated exactly the same as the other nine rows

MC ΔT

Posted 2015-11-08T05:56:37.043

Reputation: 399

Yes, I realise that one is quite similar to my question. However, that one only got one answer. I was hoping to make something simpler to get more languages involved. And actually, I believe that challenge involves the opposite, converting notes into a staff. – MC ΔT – 2015-11-08T06:17:39.697

Answers

3

CJam (40 37 * 0.8 = 29.6 points)

qN/z3%{_{iD%6>}#_~'H,65>=@@=+'oSerS}%

Online demo

Thanks to indeed for pointing out some pre-defined variables which I'd forgotten about.

Peter Taylor

Posted 2015-11-08T05:56:37.043

Reputation: 41 901

Very neat! You can shave off a few bytes by using S for space characters. Additionally, you replace 13 with D. – MC ΔT – 2015-11-08T08:43:07.607

1

Ruby, 106 bytes *0.8 = 84.8

->s{a=' '*l=s.index('
')+1
s.size.times{|i|s[i].ord&34>33&&(a[i%l,2]='GFEDCBA'[i/l%7]+s[i].tr(?o,' '))}
a}

Ungolfed in test program

f=->s{a=' '*l=s.index('
')+1                                 #l = length of first row, initialize string a to l spaces
  s.size.times{|i|                   #for each character in s
  s[i].ord&34>33&&                   #if ASCII code for ob#
   (a[i%l,2]=                        #change 2 bytes in a to the following string
   'GFEDCBA'[i/l%7]+s[i].tr(?o,' '))}#note letter, and copy of symbol ob# (transcribe to space if o)
a}                                   #return a



t='                        
---------------------o--
                  o     
---------------o--------
            o           
---------b--------------
      o                 
---o--------------------
o                       
------------------------

'

u='                        
------------------------
                     o  
------------------#-----
               #        
------------o-----------
         o              
------#-----------------
   #                    
o-----------------------

'

v='                     
---------------------
o  o  o     o        
---------------------
         o           
---------------------

---------------o--o--

---------------------

'

puts f[t]
puts f[u]
puts f[v]

Level River St

Posted 2015-11-08T05:56:37.043

Reputation: 22 049

1

JavaScript (ES6), 144 bytes - 20% = 115.2

f=s=>(n=[],l=s.indexOf(`
`)+1,[...s].map((v,i)=>(x=i%l,h=v.match(/[ob#]/),n[x]=h?"GFEDCBAGFED"[i/l|0]:n[x]||" ",h&&v!="o"?n[x+1]=v:0)),n.join``)

Explanation

f=s=>(
  n=[],                      // n = array of note letters
  l=s.indexOf(`
`)+1,                        // l = line length
  [...s].map((v,i)=>(        // iterate through each character
    x=i%l,                   // x = position within current line
    h=v.match(/[ob#]/),      // h = character is note
    n[x]=                    // set current note letter to:
      h?"GFEDCBAGFED"[i/l|0] //     if it is a note, the letter
      :n[x]||" ",            //     if not, the current value or space if null
    h&&v!="o"?n[x+1]=v:0     // put the sharp/flat symbol at the next position
  )),
  n.join``                   // return the note letters as a string
)

Test

Remember to add a line above the staff that is the exact length of the other lines because this solution includes parsing the lines above and below the staff.

f=s=>(n=[],l=s.indexOf(`
`)+1,[...s].map((v,i)=>(x=i%l,h=v.match(/[ob#]/),n[x]=h?"GFEDCBAGFED"[i/l|0]:n[x]||" ",h&&v!="o"?n[x+1]=v:0)),n.join``)
<textarea id="input" style="float:left;width:200px;height:175px">                        
---------------------o--
                  o     
---------------o--------
            o           
---------b--------------
      o                 
---o--------------------
o                       
------------------------
                        </textarea>
<div style="float:left">
  <button onclick="results.innerHTML=f(input.value)">Test</button>
  <pre id="results"></pre>
</div>

user81655

Posted 2015-11-08T05:56:37.043

Reputation: 10 181