Musical Score Interpreter

11

3

Given an ascii musical score, you must be able to output the note and its corresponding length. The score will contain between 5 and 15 notes inclusive, and is transcribed on a stave. A stave is made up of five horizontal lines comprising of - (minus) characters separated by lines of spaces. The bottom line in the stave is equivalent to the note 'E'. The line of spaces immediately above the bottom line indicates an 'F', and is of a higher pitch that the 'E' below it. This continues as below. Note that notes only go up to 'G' before starting again at 'A'. See below:

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

Note that the letters aren't included in the input. The notes are superimposed on top of the stave using a o (lowercase ooh) character to indicate the 'note head'. This note head indicates the frequency of the note, and therefore the alphabetic representation of it as above. For example, a note placed on the score as below indicates an 'A' :

----

----

----
o   
----

----

A note, like the 'A' above, is called a 'whole note' and would be played for one whole beat. Other durations can be indicated by including a 'stem' rising from the note, and between zero and three 'flags'. A stem is made up of three | (pipe, or vertical bar) characters stacked immediately above the note head . A stem with no flags is considered a 'quarter-note', and plays for quarter of a beat. Flags are \ (backslash) characters, and hand on the right hand side of the stem. Each stem halves the time the note is played for. The length of each note will be one of the following: a whole note, a quarter note, an eighth note, a sixteenth note or a thirty-second note. This how each type of note would look for A :

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

----|---|\--|\--|\--
    |   |   |\  |\
----|---|---|---|\--
o   o   o   o   o
--------------------

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

Putting more than one note together gives you a score. Each note can be considered to be four characters wide, with a note being in the first column of each four-character block. For example :

    |\             
----|\--|\----------
    |\  |       |\  
----o---|---o---|\--
|       o       |   
|---------------o---
|                   
o-------------------

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

The example above contains the following notes, in order: a quarter note 'G', a thirty-second note 'D', an eighth note 'C', a whole note 'D' and a sixteenth note 'B'. Each note in your output should be in the format letter/length, where letter is A-G and length is the fraction of the note's length when compared to a whole note. As an exception, the length and / character should not be printed if the note is a whole note. Each note in your output should be separated by a single space. Therefore, for the score above, your code should output the following:

G/4 D/32 C/8 D B/16
  • The notes will be in the following range: E F G A B C D E F. Note that only the letter needs to be printed, the octave is ignored.
  • Note that the number of lines of input varies from 9 to 12, since notes with quarter time or less on line D or higher will require more lines to show completely.
  • There is no half-note in this case.

Shortest code wins (whitespace doesn't count).

Edit: Fixed error in spacing in one input.

Some sample inputs:

        |\                    
----|\--|-------------------
|\  |   |                   
|---|---o---------------o---
|   o               |\      
o---------------|\--|\------
            |\  |\  |\      
------------|\--|\--o-------
            |\  o           
------------o---------------

Output: B/8 C/8 D/8 E/32 F/32 G/32 D


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

Output: E G B D F F A C E


            |\                  
            |\                  
            |\                  
------------o-------|-----------
|               o   |   |\      
|---|\--------------|---|\------
|   |               o   |\      
o---|---|\--------------o---|\--
    o   |\                  |\  
--------|\------------------|---
        o                   o   
--------------------------------

Output: B/4 A/8 F/32 F/32 E C/4 B/32 F/16

Neil

Posted 2011-07-22T10:12:26.200

Reputation: 459

Why doesn't whitespace count? – J B – 2011-07-22T11:59:33.027

@J: So that people won't feel inclined to submit programs one line long with no spaces. – Neil – 2011-07-22T12:19:38.150

1

It is conventional to count whitespace but to not count new-lines that are only there to keep the entry under a reasonable width. George's userscript does this with some languages (including c).

– dmckee --- ex-moderator kitten – 2011-07-22T14:26:31.383

2@Neil well right now all I feel inclined to submit is a Whitespace program. – J B – 2011-07-22T16:36:16.267

@Neil, yeah, but then you get smartasses who write really verbose solutions, pack them into a string of whitespace, and golf a decode-and-exec: http://codegolf.stackexchange.com/questions/3203/meta-golf-challenge/3209#3209

– boothby – 2011-07-22T18:28:25.803

How about whitespace that is required for your code to run counts? – Briguy37 – 2011-07-22T18:51:52.040

Is input given on stdin, or as command line arguments to a program or as input to a function? – Gareth – 2011-07-22T21:15:17.313

not counting whitespace is good for Haskell, since it has layout (similar to python) – FUZxxl – 2011-07-23T13:19:32.017

Assume it's stdin, though for Javascript, assume an input variable called input which holds the equivalent input string (assigning of the input string doesn't count in the length of the program). – Neil – 2011-07-25T08:36:58.340

@J B: Obviously Whitespace is excluded as a possible language to use, though if you really wish to inflict pain on yourself, I'll let you use Whitespace, though unlike other languages, I will count whitespace characters. – Neil – 2011-07-25T08:40:21.760

What about chords? What about accidentals? If it's supposed to be musical scores then both should certainly be supported, making the task much more interesting! – ceased to turn counterclockwis – 2011-08-04T00:21:37.457

@leftaroundabout: Might make a good follow-up task. I didn't want to write an impossibly difficult task complete with rests, staccato, and a small accompanied symphony. :) – Neil – 2011-09-13T14:10:32.277

Answers

6

Javascript,284,279,278,225,221,220 Characters (including necessary whitespace)

One-liner (test fiddle):

function a(c){b='',d=c.split('\n');for(e=0;d[0][e++];){f=0;for(i=0;g=d[i++];){h=g[e-1];if(h=='o')b+=(b?' ':'')+String.fromCharCode((d.length+4-i)%7+65);if(h=='|')f=f||4;if(g[e]&&g[e]=='\\')f*=2;}if(f)b+='/'+f;}return b;}

Readable (test fiddle):

function getNotes(input){
    out='',lines=input.split('\n');

    for(col=0;lines[0][col++];){
        time=0;
        for(i=0;line=lines[i++];){
            char=line[col-1];
            if(char=='o')out+=(out?' ':'')+String.fromCharCode((lines.length+4-i)%7+65);
            if(char=='|')time=time||4;
            if(line[col]&&line[col]=='\\')time*=2;
        }
        if(time)out+='/'+time;
    }
    return out;
}

Briguy37

Posted 2011-07-22T10:12:26.200

Reputation: 2 616

1By removing unnecessary ;s and do some tricks, you can make this even shorter.function a(c){b='',d=c.split('\n');for(e=0;d[0][e++];){for(i=f=0;g=d[i++];){h=g[e-1];if(h=='o')b+=(b?' ':'')+String.fromCharCode((d.length+4-i)%7+65);if(h=='|')f=f||4;f*=1+(g[e]=='\\');}if(f)b+='/'+f}return b} (209 chars) – JiminP – 2011-12-31T20:02:42.870

4

Perl, 103 characters

(108 if you count necessary whitespace characters)

$i=0,s/\|\\/h /g,map$b[$i++].=$_,/./g for<>;/o/&&print chr 65+(4+length$')%7,/[h|]/&&"/".4*2**y/h//," "for@b

With whitespace for presentation:

$i=0,
    s/\|\\/h /g,
    map $b[$i++]. = $_, /./g
  for <>;
/o/ && print chr 65 + (4 + length $') % 7,
             /[h|]/ && "/" . 4*2**y/h//,
             " "
  for @b

Note that I assume that all lines have the same length (as per the revised version of the question).

Rearranged version with explanations:

#!/usr/bin/env perl
# First transpose the list of lines into a list of columns.
my @b = ();               # @b[$i] will contain the characters in column $i
while (<>) {              # for each input line, do
    my $i = 0;            # start in column 0
    s/\|\\/h /g;          # replace '\|' by 'h ', to keep track of part notes in the first column
    foreach (/./g) {      # for each character, do
        $b[$i++] .= $_;   # append the character to the transposed matrix
    }
}
# Now process each column.
foreach (@b) {            # for each column, do
    if (/o/) {            # if it contains a note, then
        print chr(65 + (4 + length $') % 7);    # print the note pitch
        if (/[h|]/) {                           # if this is a part note (had |\ or just |)
            print "/", 4*2**y/h//;              # print /n where n = 2^(subdivision)
        }
        print " ";
    }
}

(old, longer solution, kept because it may be interesting even if it's a little longer)

Perl, 147 126 characters

(149 131 if you count necessary whitespace)

$c=0,map{/o/?$h[$c]=E:/\\/?$d[$c-1]*=2:/\|/?$d[$c]||=4:++$h[$c];++$c}/./g for<>;print grep{s~$~/$d[$i++] ~;s~/ ~ ~;y/E-M/EFGA-F/}@h

With whitespace for presentation:

$c = 0,
map { /o/ ? $h[$c]=E :
      /\\/ ? $d[$c-1]*=2 :
      /\|/ ? $d[$c]||=4 :
      ++$h[$c];
      ++$c
    } /./g for <>;
print grep {s~$~/$d[$i++] ~; s~/ ~ ~; y/E-M/EFGA-F/} @h

Rearranged a little so as not to abuse the language so much:

#!/usr/bin/perl
my @h;          # $h[$c] will contain the note in column $c, if any
my @d;          # $d[$c] will contain the note length (e.g. 4), if any
while (<>) {    # for each input line, do
    my $c = 0;  # column number
    foreach (split //) {   # for each character, do
        if (/o/) { $h[$c] = "E"; }      # o => it's a note; if this is the last line, it's E
        elsif (/\\/) { $d[$c-1] *= 2; } # \ => halve the duration of the note in the previous column
        elsif (/\|/) { $d[$c] ||= 4; }  # | => if this is the first | in the column, we have a quarter note
        else { ++$h[$c]; }              # anything else => bump the note by 1
        ++$c;
     }
}
for (my $i = 0; $i < @h; $i++) { # for each column, do
    $_ = $h[$i];                   # look up the potential note (or garbage if there is no note in this column)
    s~$~/$d[$i++] ~;               # append the duration and a space (or "/ " if there is no duration)
    s~/ ~ ~;                       # remove the spurious "/" if there is no duration
    if (y/E-M/EFGA-F/) {           # if it's a note (i.e. if it contains a letter E-M), then
                                   # fix the letter wraparound and then
        print $_;                    # print the note
    }
}

Note that I assume that all lines have the same length. If you want to allow shorter lines, an obvious fix is to add $_.=1x$c, at the beginning of the program, at a cost of 9 characters.

I thought of another approach to avoid long words like split and map and let spaces do more of the work, but the boilerplate and punctuation took their revenge, and I can only get it down to a whooping 130 (144 with necessary whitespace).

sub p{$-[0]}
%a=qw(o $h[p]=E \ $d[&p-1]*=2 | $d[p]||=4 - ++$h[p]);
y/ /-/,s~.~$a{$&}~gee for<>;
print grep{s~$~/$d[$i++] ~;s~/ ~ ~;y/E-M/EFGA-F/}@h

The patch to cope with unfinished lines is a little weirder this time (what, you thought it couldn't get weirder?). 139 characters, 155 with necessary whitespace.

sub p{$-[0]}
%a=qw(o $h[p]=E \ $d[&p-1]*=2 | $d[p]||=4 - ++$h[p]);
$_.=" "x p,y/
 /-/,s~.~$a{$&}~gee for<>;
print grep{s~$~/$d[$i++] ~;s~/ ~ ~;y/E-M/EFGA-F/}@h

Gilles 'SO- stop being evil'

Posted 2011-07-22T10:12:26.200

Reputation: 2 531

2

Scala(2.9), 352 313 291 294 290 277 274 273 characters

If a function is all that's needed:

def m(s:String){var(x,y,z,l)=(0,1,s.count(_=='\n'),Array.fill(99)(0))
var n=l.clone
for(c<-s){if(c=='\n'){x=0;y+=1}
if(c=='\\')l(x-1)+=1
if(c=='|')l(x)+=1
if(c=='o')n(x)="EFGABCDEF"(z-y)
x+=1}
(n,l).zipped.map((x,y)=>if(x>0)print(x.toChar+(if(y>0)"/"+(4<<y-3)else"")+" "))}

If a full program is required:

object M extends App{def m(s:String){var(x,y,z,l)=(0,1,s.count(_=='\n'),Array.fill(99)(0))
var n=l.clone
for(c<-s){if(c=='\n'){x=0;y+=1}
if(c=='\\')l(x-1)+=1
if(c=='|')l(x)+=1
if(c=='o')n(x)="EFGABCDEF"(z-y)
x+=1}
(n,l).zipped.map((x,y)=>if(x>0)print(x.toChar+(if(y>0)"/"+(4<<y-3)else"")+" "))}
m(io.Source.stdin.mkString)}

Gareth

Posted 2011-07-22T10:12:26.200

Reputation: 11 678

There are blank spaces inbetween bars until the end of the score, though I didn't mention it so the program should work regardless. If the line with blank spaces ends abruptly, it means there's no more input to consider for that line anyway. It simply just has to not crash.. :) – Neil – 2011-07-25T08:33:43.473

2

J - 108 characters

exit echo}.,>,&.>/_4<@((a.{~32,65+7|4+i.&'o'),(>&0#('/',0":2^]))@((+/@(=&'\'))+2*'|'&e.))@;\|:|.[;._2]stdin''

Ungolfed:

str =: stdin''
lines =: [;._2] str                          NB. split on the last character, the newline
rotated =: |: |. lines                       NB. lines reversed, then transposed
pitch =: 65 + 7 | 4 + i.&'o'                 NB. ord('A') + ( line.index('o') + 4 ) % 7
has_stem =: '|' & e.                         NB. '|' in line?
backslash_count =: (+/ @ (=&'\') )           NB. sum(char = '\\' for char in line)
denom_exp =: backslash_count + 2 * has_stem
fraction =: (>&0 # ('/', 0": 2 ^ ]))         NB. slash + 2^denom_exp, if denom_exp > 0
suffix =: fraction @ denom_exp
note_string =: (a. {~ 32,pitch) , suffix     NB. map(chr, (ord(' '), pitch)) + suffix
boxed_note_string =: < @ note_string @ ;     NB. box the string so it doesn't get padded
each_note_of_the =: boxed_note_string        NB. compute the note for a block of 4 lines
join_to_one_box =: , &. >
exit echo }. , > join_to_one_box / _4 each_note_of_the \ rotated

DCharness

Posted 2011-07-22T10:12:26.200

Reputation: 541

2

Python golf, 207 chars.

import sys
a=[x[:-1]+' '*99 for x in sys.stdin]
for x in range(0,99,4):
 b=''.join((y[x:x+4] for y in a))+'o'
 c=2**(b.count('\\')+('|'in b)*2)
 print'FEDCBAGFE '[b.index('o')/4-len(a)+9]+('','/'+`c`)[c>1],

I've start code golfing with Python for 2 days and I found that things like import sys, sys.stdin.read, sys.stdout.write are expansive.

Ray

Posted 2011-07-22T10:12:26.200

Reputation: 1 946

If you're new to golfing in Python, you might find this python gofling tips question useful.

– Gareth – 2012-12-05T12:38:04.357