Drucken Sie die Intervallgrößen innerhalb eines Musikstücks

10

Hintergrund

In der westlichen Musik hat jede einzelne Musiknote einen zugewiesenen Namen. Innerhalb jeder Oktave gibt es zwölf eindeutige Noten in der folgenden Reihenfolge: "CC # / Db DD # / Eb EFF # / Gb GG # / Ab AA # / Bb B C", wobei das letzte C eine Oktave über dem ersten liegt.

Um den Unterschied zwischen Noten unterschiedlicher Oktaven zu erkennen, wird am Ende des Notennamens eine Zahl (für diese Herausforderung auf eine einzelne Ziffer beschränkt) angehängt. Somit ist C5 die Note, die eine Oktave über C4 liegt. Bb6 liegt über B5.

Eine wichtige Tatsache ist, dass B5 und C6 Noten sind, die direkt nebeneinander liegen, und dass C0 und B9 die niedrigsten und höchsten Noten sind.

Zwischen zwei beliebigen Noten befindet sich ein Abstand, der der Anzahl der Halbtöne zwischen ihnen entspricht. Bb4 ist ein Halbton unter B4, der selbst ein Halbton unter C5 ist. Es gibt zwölf Halbtöne in einer Oktave, also ist Bb4 ein Abstand von 12 von A # 3, da es eine Oktave darüber ist (beachten Sie, dass eine einzelne Note bis zu zwei Namen haben kann).

Die Herausforderung

Ihre Herausforderung besteht darin, ein möglichst kurzes Programm zu schreiben, das eine Liste von Musiknoten von STDIN übernehmen und die Liste der Intervalländerungen in STDOUT drucken kann.

Die Eingabe erfolgt durch Leerzeichen getrennte Liste von Musiknoten. Jede Notiz besteht aus einem Großbuchstaben AG, einem optionalen b- oder # -Zeichen und einer einstelligen Zahl. Sie müssen sich nicht mit E # / Fb oder B # / Cb befassen. Beispieleingabe:

C4 D4 E4 F4 G4 A4 B4 C5 C4

Die Ausgabe ist eine durch Leerzeichen getrennte Liste von Ganzzahlen, die den Abstand zwischen jeder aufeinanderfolgenden Note darstellen, wobei immer ein + oder - vorangestellt wird, um anzuzeigen, ob die Note relativ zu der vorhergehenden Note auf- oder absteigend war. Es wird immer eine Nummer weniger ausgegeben als eingegebene Noten. Beispielausgabe für die obige Eingabe:

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

Einige weitere Beispieleingaben:

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

Und ihre entsprechenden Ausgaben:

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

Regeln und Einschränkungen

  1. Der Gewinner wird durch die Anzahl der Zeichen im Quellcode bestimmt

  2. Ihr Programm sollte nur aus druckbaren ASCII-Zeichen bestehen

  3. Sie dürfen keine eingebaute Funktion verwenden, die sich auf Musik oder Sound bezieht

  4. Ansonsten gelten die Standard-Code-Golfregeln

PhiNotPi
quelle
Sollte es gedruckt werden +0oder -0oder 0für zwei identische Notizen?
Howard
@ Howard Da ich nicht angegeben habe, ist beides akzeptabel.
PhiNotPi
1
"Bb4 ist ein Halbton unter B4, der selbst ein Halbton unter C4 ist". Du meinst C5 am Ende, oder?
Keith Randall
Wow, habe das nie bemerkt. Vielen Dank, dass Sie den Fehler entdeckt haben. Es ist jetzt behoben.
PhiNotPi

Antworten:

6

GolfScript, 61

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

Haskell, 161 Zeichen

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
quelle
4

Perl, 103

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

C, 123 Zeichen

Basierend auf der Lösung von leftaroundabout mit einigen Verbesserungen.

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

Einige Tricks, die meiner Meinung nach erwähnenswert sind:
1. argv[0](hier genannt b) ist ein Zeiger auf den Programmnamen, wird hier jedoch als Arbeitspuffer verwendet. Wir brauchen nur 4 Bytes (zB C#2\0), also haben wir genug.
2. cist die Anzahl der Argumente, daher beginnt sie mit 1 (wenn sie ohne Argumente ausgeführt wird). Wir verwenden es, um das Drucken in der ersten Runde zu verhindern.

Mögliches Problem - c+=b[..c+=..]ist irgendwie seltsam. Ich denke nicht, dass es undefiniertes Verhalten ist, weil ?:es ein Sequenzpunkt ist, aber vielleicht irre ich mich.

ugoren
quelle
Wenn Sie es als betrachten c = c + b[..c+=..], dann ist es ziemlich eindeutig undefiniertes Verhalten. Unabhängig von der Reihenfolge innerhalb [..]wissen Sie nicht, ob das Äußere cvorher, während oder nachher abgerufen wird b[..].
Ephemient
@ephemient, ich denke theoretisch könnte ein Compiler das tun REG=c;REG+=b[..c+=..];c=REG. Ich werde jedoch überrascht sein, so etwas in der Praxis zu sehen. Aber es ist immer noch UB.
Ugoren
Es ist Code Golf - wir haben UB bereits scanfohne Prototyp aufgerufen , und das ist in Ordnung. Es ist nur gut zu wissen, was im wirklichen Leben legal ist und was nicht :)
Ephemient
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;}}
hörte auf, gegen den Uhrzeigersinn zu drehen
quelle
Anstatt das Pluszeichen selbst zu machen, können Sie es einfach tun printf("%+d ",c-d).
Hammar
Sie können weglassen enthält ideone.com/G00fS
Hauleth
Sehr schön. Einige Vorschläge: F(*b-65)statt c-=65;, b[1]<36&&++c||b[1]>97&&c--?2:1-> b[1]&16?1:(c+=b[1]%2*2-1,2), Missbrauch argv von: main(e,b,c,d)char*b{(Verwenden Sie das erste Argument Zeiger als Arbeitspuffer).
Ugoren
Ein anderer - ich denke, c=F(*b)%12kann durch ersetzt werden c=-~*b*1.6;c%=12. Warum? sinim Original Fkann durch 9.6 ersetzt werden. c*1.6+9.6ist (c+6)*1.6, c-=65und (c+6)wurde c-59, und dann c+1(60 * 96% 12 == 0).
Ugoren
Danke für alle Vorschläge! Sie funktionieren gut und machen es kürzer, aber ich denke, ich werde es so lassen, wie es jetzt ist; Ohne den Sinus wäre es nicht mehr meine Lösung.
hörte auf, gegen den Uhrzeigersinn zu drehen
1

Faktor 303 Zeichen

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

Mit Kommentaren,

! 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.

Bei diesem Skript kann eine "durch Leerzeichen getrennte Liste" 1 oder mehr Leerzeichen zwischen Elementen und 0 oder mehr Leerzeichen am Anfang oder Ende enthalten. Dieses Skript druckt zwar ein zusätzliches Leerzeichen am Ende der Ausgabe, akzeptiert jedoch auch ein zusätzliches Leerzeichen (oder eine neue Zeile) am Ende der Eingabe.

Wenn ich eine strengere Definition verwenden würde, bei der eine "durch Leerzeichen getrennte Liste" genau 1 Leerzeichen zwischen Elementen und 0 Leerzeichen am Anfang oder Ende enthält, kann ich contents R/ [#-b]+/ all-matching-slicesauf contents " " split(mit splitting, nicht regexp) verkürzen . Ich müsste jedoch mehr Code hinzufügen, um den zusätzlichen Speicherplatz am Ende der Ausgabe zu verhindern.

Wenn ich das veraltete Wort verwende dupd, kann ich es over [ - "%+d " printf ] dipauf dupd - "%+d " printf8 Zeichen verkürzen . Ich verwende keine veralteten Wörter, weil sie "bald entfernt werden sollen".

Kernigh
quelle