Zorganizuj muzykę Kościoła Gregoriańskiego

19

Jest rok 930, a Kościół gregoriański ma problem. Mają tysiące stron śpiewanej muzyki, ale problem polega na tym, że całe nuty zostały po prostu wrzucone na stos, zamiast mieć prawdziwy system organizacji:

Obraz nuty
Zdjęcie użytkownika gamerprinter z Gildii Kartografów .

Kościół musi uporządkować wszystkie nuty, więc wynajęli średniowiecznego inżyniera oprogramowania, aby napisał program, który je dla nich zorganizuje. Jesteś inżynierem oprogramowania, który został zatrudniony. Jednak proces kompilacji w czasach średniowiecza wymaga zapisania programu na papierze przez zespół powolnych skrybów biblijnych. Aby skrócić czas potrzebny zespołowi skrybów na skompilowanie kodu, musisz zminimalizować program.

Kościół chce, aby muzyka śpiewana była zorganizowana na podstawie skali muzycznej, w której jest napisana. Cała muzyka śpiewana przez Kościół jest napisana w skalach doryckich . Biorąc pod uwagę nuty określonego utworu muzycznego, twój program wyświetli skalę Doriana, w której się znajduje. Tutaj wyjaśnię dokładnie, czym jest skala Doriana. Jeśli już wiesz, możesz pominąć tę sekcję.

W każdej melodii jest 12 możliwych nut. Oto ich kolejność:

C C# D D# E F F# G G# A A# B

Półtonowi (przedstawiony za pomocą S) jest zwiększany o jeden krok w prawo, owijając się wokół (a więc półtonowi się z B wróci do C). Tonu (przedstawiony za pomocą T) ma dwa półtony. Na przykład półton w górę od F # byłby G. Tonem w górę od F # byłby G #.

Aby utworzyć skalę Doriana, zaczynamy od dowolnej nuty na liście, a następnie przechodzimy w górę według następującego wzoru, wymieniając nuty, które napotykamy:

T, S, T, T, T, S

Przykład. Zaczynam od A. Nuty mojej skali Doriana stają się:

A
B  (up a tone)
C  (up a semitone)
D  (up a tone)
E  (up a tone)
F# (up a tone)
G  (up a semitone)

Skala ma notatki A, B, C, D, E, F # i G. Ponieważ zacząłem od A będziemy nazywają to Dorian skalę w . Istnieje zatem 12 różnych skal Doriana, z których każda nosi nazwę notatki, od której zaczęły. Każdy z nich używa tego samego wzoru tonów i półtonów, zaczynając od innej pozycji. Jeśli moje wyjaśnienie nie jest spójne, możesz również skonsultować się z Wikipedią .

Dane wejściowe programu można podać z dowolnego elementu odpowiedniego dla programu (np. STDIN, argument wiersza poleceń raw_input()). Nie może być wstępnie zainicjalizowany w zmiennej. Daną wejściową będzie lista nut oddzielonych przecinkami, reprezentujących melodię utworu. Mogą się powtarzać notatki. Na wejściu zawsze będzie wystarczająco dużo różnych nut, aby móc zdecydowanie wydedukować skalę utworu. Przykładowe dane wejściowe:

B,B,D,E,D,B,A,G#,A,G#,E,D,F#,E,F#,E,F#,G#,A

Wyjście programu powinno być ciągiem Dorian scale in X, gdzie X jest początkową nutą skali. Dane wyjściowe przykładowego wejścia:

Dorian scale in B

Porównując to ze skalą Doriana w B ( B C# D E F# G# A), widzimy, że wszystkie nuty melodii mieszczą się w tej skali. Uwaga C # nie jest w tym przypadku używana. Istnieją jednak wystarczające uwagi, aby jednoznacznie zidentyfikować B Dorian jako właściwy klucz. Żadna inna skala Doriana nie pasuje, ponieważ niezależnie od innej skali, której próbujemy, zawsze jest co najmniej jedna nuta melodii, która nie należy do skali.

To jest kod golfowy, więc wygrywa pozycja z najmniejszą liczbą znaków. Zapytaj w komentarzach, jeśli masz pytania.

Absynt
źródło
Więc co powinniśmy zrobić, to zinterpretować tylko pierwszy ton / półton?
avall
@Avall Przepraszam, nie rozumiem twojego pytania. Wkład nie zawsze zaczyna się od toniki, jeśli o to pytasz.
absynt
Podaj nam więcej przykładów. Zwłaszcza te, które nie zaczynają się od toniku.
avall
1
Odwrotnym problemem jest Skala z klucza i trybu
Peter Taylor
1
@David Zgodnie z tym meta pytaniem zaakceptowałem najkrótszą odpowiedź po upływie 12 dni od rozpoczęcia wyzwania. Tak się złożyło, że odpowiedź CJam została opublikowana właśnie wtedy, gdy zamierzałem zaakceptować następną najkrótszą.
absynt

Odpowiedzi:

2

CJam - 61

C,q',/f{"FCGDAEB"_5<'#f++:s@m<7<:A-!{"Dorian scale in "A3=}*}

Wypróbuj na http://cjam.aditsu.net/

aditsu
źródło
Wow, to musi być moja najszybsza wygrana .. mniej niż 1 minuta :)
aditsu
8

C 171 146

i,b;main(int c,char**v){for(;c=v[1][i];)b|=c/65<<c*2%7+v[1][++i]%2*7;for(i=12;i--;)b&(1016056>>i)||printf("Dorian scale in %c%c",65+i*3%7,(i<5)*35);}

Analiza łańcuchów w C nie jest taka łatwa, więc wybrałem podejście matematyczne.

Korzystam z Koła Piątych. Jeśli ułożymy nuty w następującej kolejności na podstawie zliczenia 7 półtonów naraz (znanej jako „piąta”), stwierdzimy, że wszystkie nuty dozwolone w dowolnej skali tworzą kolejny blok 7 nut i wszystkie zabronione nuty tworzą kolejny blok 5 nut.

F C G D A E B F# C# G# D# A#

(to koło, zawija się Fna końcu).

Położenie nuty naturalnej w powyższej sekwencji można obliczyć jako (ASCII code) * 2 % 7. Następnie, jeśli następny znak jest nieparzysty (dotyczy #przecinka, spacji lub bajtu zerowego), dodajemy 7, aby był ostry. Przechowujemy mapę bitową wykorzystanych notatek.

Liczba 243(binarna 11111000) odpowiada nutom zabronionym w skali A # Dorian. Pomnożyłem to przez (1<<12)+1=4097liczbę magiczną 1016056. Przesunięcie w prawo ma na celu sprawdzenie (przez ANDing), czy melodia zawiera niedozwolone nuty dla każdej z 12 skal z kolei. Jeśli melodia nie zawiera niedozwolonych nut, skala jest drukowana.

Aby uzyskać wynik, musimy wydrukować nazwę skali zakodowaną w odwrotnej kolejności, aby cykl piąty powyżej, pamiętajmy, że cofamy się, ponieważ zmieniamy prawa.) Sekwencja ASCII ADGCFBEADGCFjest generowana przez 65+i*3%7. W przypadku pierwszych pięciu z nich należy również wydrukować ostry.

Nieskluczony kod

i,b;
main(int c,char**v){
  for(;c=v[1][i];)                          //for each character in first commanline argument v[1]
                                               //if it is a letter (assume uppercase, ASCII 65 or over)
   b|=c/65<<c*2%7+v[1][++i]%2*7;               //convert to position in the circle of fifths. 
                                               //Add 7 if the next character is odd (ASCII'#')
                                               //leftshift 1 by this number and OR this with the contents of b.

  for(i=12;i--;)b&(1016056>>i)||printf         //if melody includes no prohibited notes for the scale i, print
   ("Dorian scale in %c%c",65+i*3%7,(i<5)*35); //the scale letter, and a # (ASCII 35) if required, otherwise an ASCII 0.
}

Nieprawidłowe zachowanie wejściowe: jeśli dostarczona zostanie niewystarczająca ilość notatek, aby jednoznacznie określić skalę, wyświetli ona wszystkie możliwe skale. Jeśli dostarczona zostanie niemożliwa kombinacja nut, nic nie wyda. Notatki muszą być rozdzielane przecinkiem (lub innym znakiem spacji z parzystym kodem ASCII <= 64.) Nie można używać spacji, ponieważ wszystko po pierwszej spacji byłoby traktowane jako inny argument. Kody ASCII> 64 będą interpretowane jako notatki w opisany sposób.

Level River St
źródło
Zszokowało mnie, że krąg piątych ma tę właściwość! Może mógłbym użyć go do golfa trochę więcej.
Ray
1
@Ray Właśnie dlatego mamy zestaw notatek, które mamy. Oktawa ma stosunek częstotliwości 2: 1. Piąta, zgodnie z definicją Pitagorasa, ma stosunek 3: 2 i jest najważniejszym interwałem muzycznym po oktawie. Ponieważ 1,5 ^ 12 jest bliskie, ale nie równe 2 ^ 7, współczesna równa temperowana piąta jest zmniejszona do 1,4983, tak że dokładnie 12 piątych mieści się w 7 oktawach. Staroświeckim rozwiązaniem było użycie tylko 7 notatek z dostępnych 12 z okręgu. Właśnie dlatego mamy skalę opartą na 7 nierównomiernie rozmieszczonych nutach. To nie jest żadna przypadkowa konwencja, kryje się za tym solidna matematyka.
Level River St
Istnieje wiele instrumentów, które ze względów wygody układają nuty w piątych (skrzypce są nastrojone w ten sposób, a gitara basowa w czwartych, co stanowi stosunek 4: 3). Najbardziej uderzającym przykładem (i jedynym znanym mi instrumentem, który ma nuty ułożone w kręgu piątym dla dobrego projektu akustycznego) jest steelpan: google.es/patents/US7696421 . W tym układzie nie ma znaczenia, czy nuta obok tego, do którego uderzasz, dzwoni trochę.
Level River St
4

Haskell - 152

w=words
n=w"C C# D D# E F F# G G# A A# B"
f s="Dorian scale in "++[n!!i|i<-[0..11],all(`elem`[(n++n)!!(i+j)|j<-[0,2,3,5,7,9,10]])s]!!0
main=interact$f.w

Nie golfił

type Note = String
type Scale = [Note]

notes :: [Note]
notes = words "C C# D D# E F F# G G# A A# B"

isScale :: Scale -> [Note] -> Bool
isScale scale notes = all (`elem` scale) notes

takeScale :: Int -> Scale
takeScale i = [(notes ++ notes) !! (i + j) | j <- [0, 2, 3, 5, 7, 9, 10]]

findScale :: [Note] -> Note
findScale xs = head [notes !! i | i <- [0..11], isScale (takeScale i) xs]

main = interact (("Dorian scale in "++) . findScale . words)
Promień
źródło
3

Python 2 - 177 znaków

Nie jest to takie krótkie, ale dla Pythona radością jest pisanie wielu zagnieżdżonych pętli dla jednej linii, nawet gdy nie gra w golfa. Niestety musiałem umieścić instrukcję wejściową w osobnym wierszu, aby nie wykonała się więcej niż raz.

j=set(raw_input().split(','))
print"Dorian Scale in",[x for x in[["A A# B C C# D D# E F F# G G#".split()[(b+n)%12]for n in[0,2,3,5,7,9,10]]for b in range(12)]if j<set(x)][0][0]

Nie używam Pythona 3, ale uważam, że jest to rzadki przypadek, gdy instrukcja print nie potrzebuje więcej znaków. Ponieważ printjest tam funkcja, byłbym w stanie zrównoważyć potrzebę nawiasów za pomocą *operatora rozpakowywania listy w celu zastąpienia ostatniego [0].

feersum
źródło
2
Można by również być w stanie zastąpić inputna raw_inputi zapisać 4 znaki w Pythonie 3.
comperendinous
„Podoba mi się Pythonowi pisanie wielu zagnieżdżonych pętli dla jednej linii”: ale czy czerpiesz radość z ich czytania?
Caleb Paul,
@Wideshanks Oczywiście, że nie ... chodzi tylko o kod tylko do zapisu!
feersum
3

Rubin - 132

12.times{|i|$*[0].split(?,)-(g=(0..6).map{|j|%w{C C# D D# E F F# G G# A A# B}[-i+=~(58>>j&1)]})==[]?(puts"Dorain scale in "+g[0]):g}

Dane wejściowe z argumentów wiersza poleceń.
na przykładruby dorianscale.rb B,B,D,E,D,B,A,G#,A,G#,E,D,F#,E,F#,E,F#,G#,A

Spróbuj na: ideone

Vectorized
źródło
3

Haskell - 140

Skorzystaj z właściwości Circle of Fifths wprowadzonej przez @steveverrill. Jeśli pozwolimy circle0 = words "C G D A E B F# C# G# D# A# F"i circle = circle0 ++ circle0, możemy skonstruować wszystkie skale, biorąc kolejne 7 notatek circle.

scales = [take 7 . drop i $ circle | i <- [0..11]]

W każdej skonstruowanej w ten sposób scale !! 3skali czwartym elementem jest nazwa skali.

Kod

w=words
n=w"C G D A E B F# C# G# D# A# F"
f s="Dorian scale in "++[x!!3|x<-[take 7.drop i$n++n|i<-[0..]],all(`elem`x)s]!!0
main=interact$f.w

Nie golfił

type Note = String
type Scale = [Note]

notes :: [Note]
notes = words "C G D A E B F# C# G# D# A# F"

scales :: [Scale]
scales = [take 7 . drop i $ notes ++ notes | i <- [0..11]]

findScale :: [Note] -> Note
findScale xs = head [scale !! 3 | scale <- scales, all (`elem` scale) xs]

main = interact (("Dorian scale in "++) . findScale . words)
Promień
źródło
2

Scala, 130 128 127

print("Dorian scale in "+(".#?".r findAllIn "FCGDAEBF#C#G#D#A#"*2 sliding(7)find{l=>args(0)split','forall(l contains _)}get 3))

Korzystanie z metody koła piątego. Dane wejściowe z argumentów wiersza poleceń tj

scala dorianscale.scala B,B,D,E,D,B,A,G#,A,G#,E,D,F#,E,F#,E,F#,G#,A
paradigmsort
źródło