Imprimer les tailles d'intervalles à l'intérieur d'un morceau de musique

10

Contexte

Dans la musique occidentale, chaque note de musique a un nom attribué. Dans chaque octave, il y a douze notes uniques dans l'ordre suivant: "CC # / Db DD # / Eb EFF # / Gb GG # / Ab AA # / Bb B C", où le do final est une octave au-dessus de la première.

Pour faire la différence entre les notes d'octaves différentes, un numéro (pour ce défi limité à un seul chiffre) est ajouté à la fin du nom de la note. Ainsi, C5 est la note qui est une octave au-dessus de C4. Bb6 est supérieur à B5.

Un fait important est que B5 et C6 sont des notes qui sont côte à côte et que C0 et B9 sont les notes les plus basses et les plus hautes.

Entre deux notes, il y a une distance qui est le nombre de demi-tons entre elles. Bb4 est un demi-ton en dessous de B4, qui est lui-même un demi-ton en dessous de C5. Il y a douze demi-tons dans une octave, donc Bb4 est à une distance de 12 de A # 3 car c'est une octave au-dessus (remarquez comment une seule note peut avoir jusqu'à deux noms).

Le défi

Votre défi est d'écrire le programme le plus court possible qui peut prendre une liste de notes de musique de STDIN et imprimer la liste des changements d'intervalle dans STDOUT.

L'entrée sera une liste de notes de musique séparées par des espaces. Chaque note se compose d'une lettre majuscule AG, d'un signe b ou # facultatif et d'un numéro à un chiffre. Vous n'aurez pas à gérer E # / Fb ou B # / Cb. Exemple d'entrée:

C4 D4 E4 F4 G4 A4 B4 C5 C4

La sortie sera une liste d'entiers séparés par des espaces qui représentent la distance entre chaque note successive, toujours préfixée par un + ou - pour montrer si la note était ascendante ou descendante par rapport à la précédente. Il y aura toujours un nombre de moins émis que les notes entrées. Exemple de sortie pour l'entrée ci-dessus:

+2 +2 +1 +2 +2 +2 +1 -12

Quelques exemples d'entrées supplémentaires:

E5 D#5 E5 B4 E5 F#5 E5 B4
C0 B0 Bb1 A2 G#3 G4 F#5 F6
G4 Ab4 Gb4 A4 F4 A#4

Et leurs sorties correspondantes:

-1 +1 -5 +5 +2 -2 -5
+11 +11 +11 +11 +11 +11 +11
+1 -2 +3 -4 +5

Règles et restrictions

  1. Le gagnant est déterminé par le nombre de caractères dans le code source

  2. Votre programme ne doit comprendre que des caractères ASCII imprimables

  3. Vous n'êtes pas autorisé à utiliser toute sorte de fonction intégrée liée à la musique ou au son

  4. Autre que cela, les règles de golf de code standard s'appliquent

PhiNotPi
la source
Doit-il imprimer +0ou -0ou 0pour deux notes identiques?
Howard
@Howard Puisque je n'ai pas précisé, l'un ou l'autre est acceptable.
PhiNotPi
1
"Bb4 est un demi-ton en dessous de B4, qui est lui-même un demi-ton en dessous de C4". Vous voulez dire C5 à la fin de cela, non?
Keith Randall
Wow, je n'ai jamais remarqué ça. Merci d'avoir repéré l'erreur. C'est réparé maintenant.
PhiNotPi

Réponses:

6

GolfScript, 61

" "/{)12*\{"bC#D EF G A B"?(+}/}%(\{.@-.`\0>{"+"\+}*\}/;]" "*
Howard
la source
4

Haskell, 161 caractères

f(c:r)=maybe(12*read[c])(+f r).lookup c$zip"bC#D.EF.G.A.B"[-1..]
g n|n<0=show n|1<3='+':show n
h x=zipWith(-)(tail x)x
main=interact$unwords.map g.h.map f.words
hammar
la source
4

Perl, 103

#!/usr/bin/perl -an
/.((b)|(\D))?/,(($~,$,)=($,,12*$'+ord()%20%7*2+(ord()%7>3)-$-[2]+$-[3]))[0]&&printf'%+d ',$,-$~for@F
éphémère
la source
3

C, 123 caractères

Basé sur la solution de leftaroundabout, avec quelques améliorations.

main(c,b,d)
    char*b;
{
    while(d=c,~scanf("%s",b)?c=-~*b*1.6,c%=12,c+=b[~b[1]&16?c+=1-b[1]/40,2:1]*12:0)
        d>1&&printf("%+d ",c-d);
}

Quelques astuces qui, à mon avis, méritent d'être mentionnées:
1. argv[0](appelé ici b) est un pointeur sur le nom du programme, mais utilisé ici comme tampon de travail. Nous n'avons besoin que de 4 octets (par exemple C#2\0), nous en avons donc assez.
2. cest le nombre d'arguments, il commence donc par 1 (lorsqu'il est exécuté sans arguments). Nous l'utilisons pour empêcher l'impression au premier tour.

Problème possible - c+=b[..c+=..]est plutôt étrange. Je ne pense pas que ce soit un comportement indéfini, car ?:c'est un point de séquence, mais je me trompe peut-être.

ugoren
la source
Si vous pensez à cela c = c + b[..c+=..], alors c'est un comportement assez clairement indéfini. Quel que soit le séquençage à l'intérieur [..], vous ne savez pas si l'extérieur cest récupéré avant, pendant ou après b[..].
éphémère
@ephemient, je suppose qu'en théorie un compilateur pourrait faire REG=c;REG+=b[..c+=..];c=REG. Cependant, je serai surpris de voir quelque chose comme ça dans la pratique. Mais c'est toujours UB.
ugoren
C'est Code Golf - nous avons déjà invoqué UB en utilisant scanfsans prototype, et ça va. C'est juste bon de savoir ce qui est et ce qui n'est pas légal dans la vraie vie :)
éphémient
2

C, 241 229 183

F(c){c-=65;return c*1.6+sin(c/5.+.3)+9;}
#define r if(scanf("%s",b)>0){c=F(*b)%12;c+=b[b[1]<36&&++c||b[1]>97&&c--?2:1]*12
main(e,c,d){char b[4];r;}s:d=c;r;printf("%+d ",c-d);goto s;}}
a cessé de tourner dans le sens antihoraire
la source
Au lieu de faire le signe plus vous-même, vous pouvez simplement le faire printf("%+d ",c-d).
hammar
Vous pouvez omettre les inclus ideone.com/G00fS
Hauleth
Très agréable. Quelques suggestions: F(*b-65)au lieu de c-=65;, b[1]<36&&++c||b[1]>97&&c--?2:1-> b[1]&16?1:(c+=b[1]%2*2-1,2), abusez d'argv par: main(e,b,c,d)char*b{(Utilisez le premier pointeur d'argument comme tampon de travail).
ugoren
Un autre - je pense que c=F(*b)%12peut être remplacé par c=-~*b*1.6;c%=12. Pourquoi? sindans l'original Fpeut être remplacé par 9,6. c*1.6+9.6est (c+6)*1.6, c-=65et (c+6)devient c-59, puis c+1(60 * 96% 12 == 0).
ugoren
Merci pour toutes les suggestions! Ils fonctionnent bien et le raccourcissent, mais je pense que je vais le laisser tel qu'il est maintenant; ce ne serait plus vraiment ma solution sans le sinus.
cessé de tourner dans le sens inverse des aiguilles d'une montre le
1

Facteur, 303 caractères

USING: combinators formatting io kernel math regexp sequences ;
f contents R/ [#-b]+/ all-matching-slices
[ 0 swap [ {
{ [ dup 48 < ] [ drop 1 ] }
{ [ dup 65 < ] [ 48 - 12 * ] }
{ [ dup 98 < ] [ "C-D-EF-G-A-B" index ] }
[ drop -1 ]
} cond + ] each
swap [ over [ - "%+d " printf ] dip ] when* ] each drop

Avec des commentaires,

! combinators => cond
! formatting => printf
! io => contents
! kernel => swap dup drop over dip when*
! math => < - * +
! regexp => R/ all-matching-slices
! sequences => each
USING: combinators formatting io kernel math regexp sequences ;

f       ! Push false, no previous note value.

! Find notes (as string slices) in standard input. The golfed regexp
! R/ [#-b]+/ matches all notes and no whitespace.
contents R/ [#-b]+/ all-matching-slices

! For each string slice:
[
    0       ! Push 0, initial note value.
    swap    ! Move note slice to top of stack, above note value.

    ! For each Unicode codepoint in note:
    [
        ! Convert the Unicode codepoint to its value in semitones.
        ! For golf, [ 48 ] is shorter than [ CHAR: A ].
        {
            ! Sharp # {35} has 1 semitone.
            { [ dup 48 < ] [ drop 1 ] }
            ! 0-9 {48-57} has 0 to 9 octaves (1 octave = 12 semitones).
            { [ dup 65 < ] [ 48 - 12 * ] }
            ! A-G {65-71} has 0 to 11 semitones.
            { [ dup 98 < ] [ "C-D-EF-G-A-B" index ] }
            ! Flat b {98} has -1 semitone.
            [ drop -1 ]
        } cond

        +       ! Add semitones to cumulative note value.
    ] each

    swap    ! Move previous note value to top of stack.
    ! When there is a previous note value:
    [
        ! Keep current note on stack.
        over [
            ! Compute and print interval.
            - "%+d " printf
        ] dip
    ] when*
    ! Current note replaces previous note at top of stack.
] each

drop    ! Drop previous note, so stack is empty.

Pour ce script, une "liste séparée par des espaces" peut avoir 1 ou plusieurs espaces entre les éléments et 0 ou plusieurs espaces au début ou à la fin. Ce script imprime un espace supplémentaire à la fin de la sortie, mais il accepte également un espace supplémentaire (ou une nouvelle ligne) à la fin de l'entrée.

Si j'adoptais une définition plus stricte, où une "liste séparée par des espaces" a exactement 1 espace entre les éléments et 0 espace au début ou à la fin, alors je peux raccourcir contents R/ [#-b]+/ all-matching-slicesen contents " " split(en utilisant splitting, pas regexp). Cependant, j'aurais besoin d'ajouter plus de code pour éviter l'espace supplémentaire à la fin de la sortie.

Si j'utilise le mot déconseillé dupd, je peux le raccourcir over [ - "%+d " printf ] dipen dupd - "%+d " printféconomisant 8 caractères. Je n'utilise pas de mots déconseillés car ils "sont destinés à être supprimés prochainement".

kernigh
la source