Interprète de partitions musicales

11

Étant donné une partition musicale ascii, vous devez être en mesure de produire la note et sa longueur correspondante. La partition contiendra entre 5 et 15 notes incluses et sera transcrite sur une portée. Une portée est composée de cinq lignes horizontales comprenant - (moins) caractères séparés par des lignes d'espaces. La ligne du bas de la portée équivaut à la note «E». La ligne d'espaces immédiatement au-dessus de la ligne inférieure indique un «F», et est d'un pas plus élevé que le «E» en dessous. Cela continue comme ci-dessous. Notez que les notes remontent uniquement à «G» avant de recommencer à «A». Voir ci-dessous:

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

Notez que les lettres ne sont pas incluses dans l'entrée. Les notes sont superposées au-dessus de la portée en utilisant un caractère ao (ooh minuscule) pour indiquer la «tête de note». Cette tête de note indique la fréquence de la note, et donc sa représentation alphabétique comme ci-dessus. Par exemple, une note placée sur la partition comme ci-dessous indique un «A»:

----

----

----
o   
----

----

Une note, comme le «A» ci-dessus, est appelée «note entière» et serait jouée pendant un temps entier. D'autres durées peuvent être indiquées en incluant une «tige» s'élevant de la note, et entre zéro et trois «drapeaux». Une tige est composée de trois | (pipe ou barre verticale) empilés immédiatement au-dessus de la tête de la note. Une tige sans drapeau est considérée comme une «noire» et joue pendant un quart de temps. Les drapeaux sont des caractères \ (barre oblique inverse), et la main sur le côté droit de la tige. Chaque tige divise par deux le temps pendant lequel la note est jouée. La longueur de chaque note sera l'une des suivantes: une note entière, une noire, une huitième, une seizième ou une trente-seconde. Voici à quoi ressemblerait chaque type de note A:

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

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

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

Mettre plus d'une note ensemble vous donne un score. Chaque note peut être considérée comme ayant une largeur de quatre caractères, une note se trouvant dans la première colonne de chaque bloc de quatre caractères. Par exemple :

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

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

L'exemple ci-dessus contient les notes suivantes, dans l'ordre: une noire "G", une trente-deuxième note "D", une huitième note "C", une note entière "D" et une seizième note "B". Chaque note de votre sortie doit être au format lettre / longueur, où la lettre est AG et la longueur est la fraction de la longueur de la note par rapport à une note entière. Par exception, la longueur et / le caractère ne doivent pas être imprimés si la note est une note entière. Chaque note de votre sortie doit être séparée par un seul espace. Par conséquent, pour le score ci-dessus, votre code doit générer les éléments suivants:

G/4 D/32 C/8 D B/16
  • Les notes seront dans la plage suivante: EFGABCDE F. Notez que seule la lettre doit être imprimée, l'octave est ignorée.
  • Notez que le nombre de lignes d'entrée varie de 9 à 12, car les notes avec un quart de temps ou moins sur la ligne D ou supérieure nécessiteront plus de lignes pour s'afficher complètement.
  • Il n'y a pas de demi-note dans ce cas.

Le code le plus court gagne (les espaces ne comptent pas).

Modifier: correction d'une erreur d'espacement dans une entrée.

Quelques exemples d'entrées:

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

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


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

Sortie: EGBDFFACE


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

Sortie: B / 4 A / 8 F / 32 F / 32 EC / 4 B / 32 F / 16

Neil
la source
Pourquoi les espaces blancs ne comptent-ils pas?
JB
@J: Pour que les gens ne se sentent pas enclins à soumettre des programmes d'une ligne sans espaces.
Neil
1
Il est classique de compter les espaces, mais de ne pas compter les nouvelles lignes qui ne sont là que pour garder l'entrée sous une largeur raisonnable. Le script utilisateur de George le fait avec certaines langues (y compris c).
dmckee --- chaton ex-modérateur
2
@Neil bien en ce moment tout ce que je me sens enclin à soumettre est un programme Whitespace.
JB
@Neil, oui, mais vous obtenez des smartasses qui écrivent des solutions vraiment verbeuses, les emballent dans une chaîne d'espaces blancs et jouent au
stand le

Réponses:

6

Javascript, 284,279,278,225,221 , 220 caractères (y compris les espaces nécessaires)

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;}

Lisible ( test du violon ):

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
la source
1
En supprimant les ;s inutiles et en faisant quelques trucs, vous pouvez rendre cela encore plus court. 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 caractères)
JiminP
4

Perl, 103 caractères

(108 si vous comptez les caractères d'espacement nécessaires)

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

Avec un espace pour la présentation:

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

Notez que je suppose que toutes les lignes ont la même longueur (selon la version révisée de la question).

Version réarrangée avec explications:

#!/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 " ";
    }
}

(ancienne solution plus longue, conservée car elle peut être intéressante même si elle est un peu plus longue)

Perl, 147 126 caractères

( 149 131 si vous comptez les espaces nécessaires)

$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

Avec un espace pour la présentation:

$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

Réarrangé un peu pour ne pas trop abuser de la langue:

#!/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
    }
}

Notez que je suppose que toutes les lignes ont la même longueur. Si vous souhaitez autoriser des lignes plus courtes, une solution évidente consiste à ajouter $_.=1x$c,au début du programme, au coût de 9 caractères.

J'ai pensé à une autre approche pour éviter les longs mots comme splitet mapet laisser les espaces faire plus de travail, mais le passe-partout et la ponctuation ont pris leur revanche, et je ne peux que le ramener à un 130 coqueluche (144 avec les espaces nécessaires).

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

Le patch pour faire face aux lignes inachevées est un peu plus étrange cette fois (quoi, vous pensiez qu'il ne pouvait pas devenir plus bizarre?). 139 caractères, 155 avec les espaces nécessaires.

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- arrête d'être méchant'
la source
2

Scala (2.9), 352 313 291 294 290 277 274 273 caractères

Si une fonction est tout ce dont vous avez besoin:

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

Si un programme complet est requis:

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
la source
Il y a des espaces vides entre les barres jusqu'à la fin de la partition, même si je ne l'ai pas mentionné, le programme devrait donc fonctionner malgré tout. Si la ligne avec des espaces vides se termine brutalement, cela signifie qu'il n'y a plus d'entrée à prendre en compte de toute façon. Il suffit de ne pas planter .. :)
Neil
2

J - 108 caractères

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

Non golfé:

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
la source
2

Golf Python, 207 caractères.

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],

J'ai commence le golf de code avec Python pendant 2 jours et j'ai trouvé que des choses comme import sys, sys.stdin.read, sys.stdout.writesont vastes.

Rayon
la source
Si vous êtes nouveau au golf en Python, vous trouverez peut-être cette question de conseils sur les goflings en python utile.
Gareth