Piano Chords on White Keys
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)
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 #&@@@
.
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