9
\$\begingroup\$

Backstory [which is not true]

A piano is set up like this:

![http://www.piano-lessons-made-simple.com/images/2-Octave-Labled.gif

However, on my piano, all of the black keys are broken!

I still want to be able to play some chords on my broken piano though.

In music, a chord is a group of notes that are played together. To allow for input of chords, I will first define what a semitone is.

What is a semitone?

A semitone is the smallest distance in Western music. If you look at the top part of the piano, you see that you can usually move from a black key to a white key, or vice versa; however, between B and C and E and F there is no black key.

What is a chord?

For the purposes of this challenge, we define a chord as a bunch of notes with a certain number of semitones between them. For example, let's take a took at a 4-3-3 chord starting on C (for music people, this is a V7 chord in F major). We start at C. We count up 4 semitones: C#, D, D#, E. The next note is E, and we count 3 semitones up after that: F, F#, G. The next note is G, and we count 3 semitones up after that: G#, A, Bb. So, we get C-E-G-Bb. Yay! But wait... Bb is a black key and those are broken... However, if we start from G, we get G-B-D-F! Yay!

Input

Input is given as a list of integers in any reasonable format. This represents the chord as described above.

Output

Output should be a list of notes on which I can start to only need to use white keys. This can also just be a string of all of the up to 7 notes because all of the keynames will be one character. You must be able to handle having an empty output as well.

Test Cases

input -> output // comments
4 3 -> C F G // this is a major triad
3 4 -> D E A // this is a minor triad
4 3 3 -> G // this is the major-minor seventh chord
3 3 3 -> [empty output] // this is the diminished-diminished seventh chord. All of them use black keys
4 4 -> [empty output] // this is an augmented triad
3 3 -> B // this is a diminished triad
1 -> B E // this is just a minor second
11 -> C F // this is just a major seventh

Other specs

  • Standard Loopholes forbidden
  • You may assume that the input has at least one integer
  • You may assume that all of the integers are non-negative and less than 12 (because the piano repeats every 12 notes)
  • Output may be in any order

Winning Criteria

The shortest valid submission as of April 15th will be accepted.

\$\endgroup\$
2
  • \$\begingroup\$ We may assume "non-negative and less than 12" - should that not be "positive and less than or equal to 12"? \$\endgroup\$ Commented Apr 11, 2017 at 4:18
  • \$\begingroup\$ @JonathanAllan Fundamentally there is no difference; my method allows for a Perfect Unison but not a Perfect Octave; yours vice-versa. Theoretically, your restriction might make more sense but I think I probably shouldn't change it because there are already answers and it doesn't change the challenge fundamentally. \$\endgroup\$
    – hyper-neutrino
    Commented Apr 11, 2017 at 4:45

5 Answers 5

6
\$\begingroup\$

MATL, 31 bytes

Thanks to Jonathan Allan for a correction.

'BAGFEDC'"GYs12X\110BQX@YSYsm?@

Try it online! Or verify all test cases.

Explanation

The pattern 2 2 1 2 2 2 1 specifies the intervals between consecutive white keys. The program uses a loop that applies all cyclic shifts to this basic pattern, in order to test each key as a potential lowest note of the input chord. For each shift, the cumulative sum of the pattern is obtained. For example, for B as potential lowest note, the pattern has been shifted to 1 2 2 1 2 2 2 and its cumulative sum is 1 3 5 6 8 10 12.

Now, to see if this can support a 4 3 3 chord we compute the cumulative sum of the chord intervals, which is 4 7 10; reduce it via 1-based modulo 12 (an interval of 14 would give 2); and check if those numbers are all members of the allowed values 1 3 5 6 8 10 12. That's not the case in this example. Had it been the case, we would output the letter B.

The correspondence between cyclic shifts and output letters is defined by the string 'BAGFEDC'. This indicates that 'B' (first character) corresponds to a cyclic shift by 1; 'A' (second character) corresponds to a cyclic shift by 2 etc.

'BAGFEDC'  % Push this string
"          % For each character from the string
  G        %   Push input array
  Ys       %   Cumulative sum
  12X\     %   1-based modulo 12, element-wise (1,12,13,14 respectively give 1,12,1,2)
  110BQ    %   Push 110, convert to binary, add 1 element-wise: gives [2 2 1 2 2 2 1]
  X@       %   Push current iteration index, starting at 1
  YS       %   Cyclic shift to the right by that amount
  Ys       %   Cumulative sum
  m        %   Ismember. Gives an array of true of false entries
  ?        %   If all true
    @      %     Push current character
           %   End (implicit)
           % End (implicit)
           % Display (implicit)
\$\endgroup\$
0
5
\$\begingroup\$

Mathematica, 110 bytes (ISO 8859-1 encoding)

±i_:=#&@@@Select["A#BC#D#EF#G#"~StringTake~{Mod[#,12,1]}&/@#&/@(Accumulate[i~Prepend~#]&/@Range@12),FreeQ@"#"]

Defines a unary function ± taking a list of integers as input (no restrictions on the size or signs of the integers, actually) and returns a list of one-character strings. For example, ±{3,4} returns {"A","D","E"}.

"A#BC#D#EF#G#"~StringTake~{Mod[#,12,1]}&/@# is a function that turns a list of integers into the corresponding note names, except that # stands for any black key. This is applied to each element of Accumulate[i~Prepend~#]&/@Range@12, which builds up a list of note values from the list input list of note intervals, starting with each possible note from 1 to 12. We filter out all such note-name lists that contain "#" using Select[...,FreeQ@"#"], and then return the first note in each remaining list using #&@@@.

\$\endgroup\$
6
  • \$\begingroup\$ Nice submission! \$\endgroup\$
    – hyper-neutrino
    Commented Apr 9, 2017 at 18:45
  • \$\begingroup\$ Question: Does Mathematica use its own byte system? This is 110 characters but in UTF-8 it's 111 bytes because of the +/- symbol. \$\endgroup\$
    – hyper-neutrino
    Commented Apr 9, 2017 at 19:06
  • \$\begingroup\$ You can completely strip the assignment and just "implicitly return" a function. \$\endgroup\$
    – wizzwizz4
    Commented Apr 9, 2017 at 19:50
  • \$\begingroup\$ @wizzwizz4: I found I had to name the variable in Accumulate[i~Prepend~#]& because otherwise there'd be a currying clash. Feel free to find a workaround though! \$\endgroup\$ Commented Apr 10, 2017 at 2:16
  • \$\begingroup\$ @HyperNeutrino: you're right that UTF-8 is the standard encoding, but Mathematica can (usually) function in ISO 8859-1 encoding as well. I've noted that in the post. \$\endgroup\$ Commented Apr 10, 2017 at 2:21
3
\$\begingroup\$

Jelly, 25 bytes

236ḃ2ṙЀ7+\€Ṭ
+\ịþ¢Ạ€TịØA

Try it online! or see a test suite

How?

236ḃ2ṙЀ7+\€Ṭ - Link 1, white-note-offsets: no arguments
236ḃ2         - 236 in bijective base 2 [2, 2, 1, 2, 2, 1, 2] - semitones G->A, A->B ...
     ṙЀ7     - rotate left by, mapped over [1,2,3,4,5,6,7] - i.e. as above for each
                    starting white key (1st one A->B,B->C,...; 2nd B->C,C->D,...; etc)
         +\€  - reduce €ach with addition - i.e. absolute number of semitones: [[2,3,5,7,8,10,12],[1,3,5,6,8,10,12],[2,4,5,7,9,11,12],[2,3,5,7,9,10,12],[1,3,5,7,8,10,12],[2,4,6,7,9,11,12],[2,4,5,7,9,10,12]]
            Ṭ - untruth (vectorises) - make lists with 1s at those indexes: [[0,1,1,0,1,0,1,1,0,1,0,1],[1,0,1,0,1,1,0,1,0,1,0,1],[0,1,0,1,1,0,1,0,1,0,1,1],[0,1,1,0,1,0,1,0,1,1,0,1],[1,0,1,0,1,0,1,1,0,1,0,1],[0,1,0,1,0,1,1,0,1,0,1,1],[0,1,0,1,1,0,1,0,1,1,0,1]]

+\ịþ¢Ạ€TịØA - Main link: list of semitone gap integers (even negatives will work)
+\          - reduce by addition - gets the absolute semitone offsets needed
    ¢       - last link (1) as a nilad
   þ        - outer product with:
  ị         -     index into - 7 lists, each with 1s for white and 0s for black keys hit
                      note that indexing is modular and all the lists are length 12
                      so any integer is a valid absolute offset, not just 0-11 inclusive
     Ạ€     - all truthy for €ach - for each get a 1 if all keys are white ones, else 0
       T    - truthy indexes - get the valid starting white keys as numbers from 1 to 7
        ị   - index into:
         ØA -     the uppercase alphabet
\$\endgroup\$
0
3
\$\begingroup\$

Python 2, 159 155 bytes

(Posting this after making sure there's a valid submission that's shorter than this one)

import numpy
s='C.D.EF.G.A.B'
def k(y):return lambda x:s[(x+y)%12]
for i in range(12):
    if s[i]!='.'and'.'not in map(k(i),numpy.cumsum(input())):print s[i]

Pretty much just the trivial solution. Inputs as a list of integers and outputs with each character on an individual line.

-4 bytes by removing an unnecessary variable

\$\endgroup\$
3
\$\begingroup\$

JavaScript (ES6), 72 71 68 bytes

a=>[..."C1D1EF1G1A1B"].filter((c,i,b)=>!+c>a.some(e=>+b[i+=e,i%12]))

Loops through each key omitting black keys, then checking that the cumulative sum of semitones never lands on a black key.

Edit: Saved 3 bytes thanks to @Arnauld.

\$\endgroup\$
1
  • 4
    \$\begingroup\$ Readability?! Are you sure you're on the right site? :-) \$\endgroup\$
    – wizzwizz4
    Commented Apr 9, 2017 at 19:48

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.