Organise the Gregorian Church's Music

19

1

The year is 930, and the Gregorian Church is having a problem. They have thousands of pages of chant music, but the problem is that all the sheet music was simply thrown onto a pile instead of having any real organisation system:

Image of sheet music
Image by user gamerprinter at Cartographers' Guild.

The Church needs to organise all the sheet music, so they have hired a medieval software engineer to write a program to organise it for them. You are the software engineer that has been hired. However, the compilation process in medieval times involves the program being written onto paper by a team of slow biblical scribes. To decrease the time it takes for the team of scribes to compile your code, you must make the program as small as possible.

The Church wants the chant music to be organised based off the musical scale they are written in. All of the Church's chant music is written in Dorian scales. Given the notes of a certain piece of music, your program will output the Dorian scale that it is in. Here, I will explain exactly what a Dorian scale is. If you already know, you may skip this section.

There are 12 possible notes in any melody. Here they are in order:

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

A semitone (represented using a S) is incrementing one step to the right, wrapping around (so a semitone up from B would be back to C). A tone (represented using a T) is two semitones. For instance, a semitone up from F# would be G. A tone up from F# would be G#.

To create a Dorian scale, we start from any note in the list, and then move up in the following pattern, listing the notes that we encounter:

T, S, T, T, T, S

An example. I start from A. The notes of my Dorian scale becomes:

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)

The scale has the notes A, B, C, D, E, F#, and G. Because I started from A, we will call this the Dorian scale in A. There are therefore 12 different Dorian scales, each of which are named after the note that they started from. Each of them use the same pattern of tones and semitones, just starting from a different position. If my explanation is not coherent you may also consult Wikipedia.

The input of the program can be given from whatever is appropriate for your program (e.g. STDIN, command line argument, raw_input()). It may be not pre-initialised in a variable. The input will be a list of comma seperated notes, representing the melody of the piece. There may be repeated notes. There will always be enough different notes in the input to be able to decisively deduce the scale of the piece. An example input:

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

The output of the program should be the string Dorian scale in X, where X is the starting note of the scale. The output of the example input:

Dorian scale in B

Comparing this with the Dorian scale in B (B C# D E F# G# A) we see that all the notes of the melody are within this scale. The note C# is unused in this case. However there are sufficient notes to unambiguously identify B Dorian as the correct key. No other Dorian scale fits, because whatever other scale we try, there is always at least one note of the melody that does not belong to the scale.

This is code golf, so the entry with the shortest number of characters wins. Ask in the comments if you have questions.

absinthe

Posted 2014-08-20T06:47:07.570

Reputation: 8 359

So, what we should do is to only interpret the first tone/semitone? – avall – 2014-08-20T06:58:59.280

​​​​​​​​​​​​​​​@avall I'm sorry, I don't understand your question. The input won't always start with the tonic, if that's what you're asking. – absinthe – 2014-08-20T07:02:22.110

Please provide us more examples. Especially those, which don't start with the tonic. – avall – 2014-08-20T07:03:21.163

​​​​​​​​​​​​​​​@avall C#,D#,A,B,A,F#,E ==> Dorian scale in F# – absinthe – 2014-08-20T07:06:23.640

​​​​​​​​​​​​​​​@avall F,A,C,B,E ==> Dorian scale in D – absinthe – 2014-08-20T07:07:42.703

I still don't understand how the sequence of notes could even hint at the scale. F, A are two tones apart. B, E are 2.5 tones apart in one direction, 3.5 in the other one. Should we assume the tone never changes by more than 5 semitones, spans exactly 12 semitones, and output the lowest of them? – John Dvorak – 2014-08-20T07:23:57.633

​​​​​​​​​@JanDvorak We know that there are 12 dorian scales, the notes of which may be obtained using the method described in the question. The only Dorian scale that contains the notes F, A, C, B, E within it is the one that starts from D. I have a feeling I wasn't clear at all in my explanation. – absinthe – 2014-08-20T07:26:18.467

1

The inverse problem is Scale from key and mode

– Peter Taylor – 2014-08-20T07:35:13.903

I see nothing unclear about this question. For interest, the min number of notes needed to define a scale unambiguously is 3. For D Dorian B,F and either C or E suffice. On the other hand it's possible to give 6 notes and still be ambiguous: C,G,D,A,E,B could be either D or A dorian, depending if the F is natural or sharp. @VonIlya, what's the flaw you refer to? I notice if you name all scales as sharps you get into some horrible double-sharp situations, but non-musicians will just use the corresponding natural and not notice a problem. I'm willing to vote to reopen and help if you explain. – Level River St – 2014-08-20T19:57:15.400

Sounds like a problem with your generator, not the question. Both examples in your comments have a tritone in them (B-F and A-D#) and a semitone in them, either of which on its own is sufficient to narrow it down to 2 possibilities. Some of these diagrams would really help: http://en.wikipedia.org/wiki/Pitch_constellation. If you delete your own negative comments I'll open vote and possibly edit. Musically it's better to say Bb dorian than A# dorian (the latter contains E# B# and F## if written correctly) but it's best to stick with the sharps now. I guess B C C# is undefined behaviour

– Level River St – 2014-08-20T21:02:01.390

​​​​​​​​​​​I suppose so. I've deleted the comments now. – absinthe – 2014-08-20T21:02:50.567

Reopen vote cast. It's too late here for me to help you with an edit, but I may do so tomorrow. – Level River St – 2014-08-20T21:05:22.990

@steveverrill, I think the flaw is that this is modern Dorian, not mediaeval. – Peter Taylor – 2014-08-20T22:23:53.113

​​​​​​​​​​​​​​​@Peter Taylor Hmm, that's true as well. Although I already threw historical accuracy out the window with the medieval software engineering part... – absinthe – 2014-08-20T22:24:46.847

Can I use space separated list of notes as input? – Ray – 2014-08-21T14:35:25.213

@Ray Yes. aaaaaaa – absinthe – 2014-08-21T20:33:07.590

By awarding a win so quickly, you discourage others from posting. You also commit to an answer that may well not be the best (in this case, the shortest). – DavidC – 2014-09-01T11:37:29.963

1

​​​​​​​​​​​​​​​@David As per this meta question, I awarded the accept to the shortest answer after a waiting period of 12 days since I started the challenge. It just happened the CJam answer was posted right when I was going to accept the next shortest one.

– absinthe – 2014-09-01T11:39:23.497

​​​​​​​​​​​​​​​@David Also, I considered not giving accepts for any of my questions, but I would think that would also discourage people from posting ("have you heard of that Ilya person that doesn't give +15 accepts out...?") Waiting for 12 days to let posts come in before accepting seemed like the best solution. – absinthe – 2014-09-01T11:42:54.697

Answers

2

CJam - 61

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

Try it at http://cjam.aditsu.net/

aditsu quit because SE is EVIL

Posted 2014-08-20T06:47:07.570

Reputation: 22 326

Wow, this must be my quickest win.. less than 1 minute :) – aditsu quit because SE is EVIL – 2014-09-01T10:45:55.740

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

Parsing strings in C is not that easy, so I went for a more mathematical approach.

I take advantage of the Circle of Fifths. If we arrange the notes in the following order based on counting up 7 semitones at a time (known as a "fifth"), we find that all the notes permitted in any given scale form a consecutive block of 7 notes and all the prohibited notes form a consecutive block of 5 notes.

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

(it's a circle, it wraps back around to F at the end.)

The position of a natural note in the above sequence can be calculated as (ASCII code) * 2 % 7. Then if the next character is odd (applies to # but not comma, space or zero byte) we add 7 to make it sharp. We store a bitmap of the notes that have been used.

The number 243 (binary 11111000) corresponds to the notes prohibited in the scale of A# Dorian. I multiplied this by (1<<12)+1=4097 to give the magic number 1016056. This is rightshifted to check (by ANDing) if the melody contains prohibited notes for each of the 12 scales in turn. If the melody does not contain prohibited notes, the scale is printed.

For the output we need to print the scale name encoded in the reverse order to cycle of fifths above, remember we are going backwards because we are rightshifting.) The ASCII sequence ADGCFBEADGCF is generated by 65+i*3%7. For the first five of these a sharp must also be printed.

Ungolfed code

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

Invalid input behaviour: If insufficient notes are supplied to unambiguously determine the scale, it will output all possible scales. If an impossible combination of notes is supplied, it will output nothing. Notes must be delimited by a comma (or other non-whitespace character with an even ASCII code <= 64.) Spaces cannot be used as everything after the first space would be considered a different argument. ASCII codes >64 will be interpreted as notes in the manner described.

Level River St

Posted 2014-08-20T06:47:07.570

Reputation: 22 049

It shocked me that the circle of fifths has this property! Maybe I can use it to golf a bit more. – Ray – 2014-08-21T21:37:45.683

1@Ray This is actually why we have the set of notes that we have. The octave has a frequency ratio 2:1. The fifth as defined by Pythagoras has a ratio of 3:2 and is the most important musical interval after the octave. Because 1.5^12 is close to but not equal to 2^7, the modern equal tempered fifth is squeezed down to 1.4983 so that exactly 12 fifths fit in 7 octaves. The old fashioned solution was to only use 7 notes out of the available 12 from the circle. That's why we have a scale based on 7 unevenly spaced notes. It's not some random convention, there's some solid maths behind it. – Level River St – 2014-08-21T22:23:31.927

There are a number of instruments that arrange notes in fifths for convenience reasons (the violin is tuned in this way, and the bass guitar is tuned in fourths, which is a ratio of 4:3). The most striking example (and the only instrument I know of that has notes layed out in a circle of fifths for good acoustic design) is the steelpan: http://www.google.es/patents/US7696421 . With this layout it doesn't matter if the note next to the one you are hitting rings a bit.

– Level River St – 2014-08-21T22:45:04.150

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

Ungolfed

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)

Ray

Posted 2014-08-20T06:47:07.570

Reputation: 1 946

3

Python 2 - 177 characters

It's not that short, but I find it the joy of Python to write multiple nested for loops in one line, even when not golfing. Unfortunately, I had to put the input statement on a separate line so that it would not execute more than once.

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]

I don't use Python 3, but I believe this is a rare instance when the print statement would not need more characters. Since print is a function there, I would be able to offset the need for parentheses with the use of the * list unpacking operator to replace the last [0].

feersum

Posted 2014-08-20T06:47:07.570

Reputation: 29 566

2You'd also be able to substitute input for raw_input and save 4 characters in Python 3. – comperendinous – 2014-08-20T20:00:39.797

"I find it the joy of Python to write multiple nested for loops in one line": but do you find joy in reading them? – Caleb Paul – 2014-08-22T01:10:02.550

@Wideshanks Of course not... it's all about the write-only code! – feersum – 2014-08-22T03:25:06.667

3

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

Input from command line args.
e.g. ruby dorianscale.rb B,B,D,E,D,B,A,G#,A,G#,E,D,F#,E,F#,E,F#,G#,A

Try it at: ideone

Vectorized

Posted 2014-08-20T06:47:07.570

Reputation: 3 486

3

Haskell - 140

Make use of the Circle of Fifths property introduced by @steveverrill. If we let circle0 = words "C G D A E B F# C# G# D# A# F" and circle = circle0 ++ circle0, then we can construct all scales by taking consecutive 7 notes in circle.

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

In each scale constructed by this way, scale !! 3, the 4th element is the scale name.

Code

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

Ungolfed

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)

Ray

Posted 2014-08-20T06:47:07.570

Reputation: 1 946

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

Using the circle of fifths method. Input from command line args ie

scala dorianscale.scala B,B,D,E,D,B,A,G#,A,G#,E,D,F#,E,F#,E,F#,G#,A

paradigmsort

Posted 2014-08-20T06:47:07.570

Reputation: 61