11
\$\begingroup\$

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

\$\endgroup\$
12
  • \$\begingroup\$ Why doesn't whitespace count? \$\endgroup\$
    – J B
    Jul 22, 2011 at 11:59
  • \$\begingroup\$ @J: So that people won't feel inclined to submit programs one line long with no spaces. \$\endgroup\$
    – Neil
    Jul 22, 2011 at 12:19
  • 1
    \$\begingroup\$ 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). \$\endgroup\$ Jul 22, 2011 at 14:26
  • 2
    \$\begingroup\$ @Neil well right now all I feel inclined to submit is a Whitespace program. \$\endgroup\$
    – J B
    Jul 22, 2011 at 16:36
  • \$\begingroup\$ @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: codegolf.stackexchange.com/questions/3203/meta-golf-challenge/… \$\endgroup\$
    – boothby
    Jul 22, 2011 at 18:28

5 Answers 5

6
\$\begingroup\$

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;
}
\$\endgroup\$
1
  • 1
    \$\begingroup\$ By 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) \$\endgroup\$
    – JiminP
    Dec 31, 2011 at 20:02
4
\$\begingroup\$

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
\$\endgroup\$
2
\$\begingroup\$

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)}
\$\endgroup\$
1
  • \$\begingroup\$ 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.. :) \$\endgroup\$
    – Neil
    Jul 25, 2011 at 8:33
2
\$\begingroup\$

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
\$\endgroup\$
2
\$\begingroup\$

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.

\$\endgroup\$
1

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.