Piano Keyboard

Identifying Key Colors Through Science!
Piano Keyboard Diagram

The goal with this notebook is to create an algorithm for determining whether a piano key should be white or black based on it's position within the set. While initially it seems like a trivial task, there are a few hiccups that make a simple if-then method misidentify several key colors.

In [1]:
import pandas as pd

First, we need three variables. These will hold:

1. The "step" between groups.

2. The start of a two-key group.

3. The start of a three-key group.

Looking at the diagram above, we can see that there's a single black key to the left at the 2 spot. Keys are numbered 1 - 88, which runs from lowest to highest notes. The first two keys will be handled separately in the main function.

In [2]:
first_two_group = 5
first_three_group = 10
group_step = 12

Next, we have an incrementer class.

This will take in a numeric value and increment those by a given step size and count. The output includes the initial starting point from which the calculaitons will be made.

For example, if we start at 10 and want just the next two even numbers, we can call

a = incrementer(10, 3)
print(list(a))

And the result will be:

[10, 12, 14]
In [3]:
def incrementer(num, n_values=3, step=2):
    n = 0
    output = num
    while n < n_values:
        yield output
        output += step
        n += 1

Finally, we have our main function. This is supposed to be a simple script, but we could probably break this into a few smaller chunks or even a class if if was going into production somehow.

Annotations will be placed inside the function, where necessary.

In [4]:
def create_key_color_dict(verbose=False, key_dict = None):
    # Create a list of 88 keys starting at 1.
    keys = [i for i in range(1, 89)]
    
    # Create an empty dictionary for outputting results.
    if key_dict is None:
        ddict = {}

    # "Seed" our initial values with the first numbers for our two- and three-key sets.
    two_start = first_two_group
    three_start = first_three_group

    # We'll also seed our first two results with "W" for white and "B" for block
    # You can confirm placement from the provided diagram above.
    ddict[1] = "W"
    ddict[2] = "B"
    
    # We also have a "skip" list.  This is handy for when we generate incremental lists
    # and want to skip over certain values from our main iterator.
    to_skip = [1, 2]

    for k in keys:
        if not k in to_skip:
            # If the key equals the start of a three-key set
            if k == three_start:
                # Get a list of the three key numeric values with our incrementor function
                three_group = list(incrementer(three_start, 3))
                # Iterate over those numbers and set a dictionary entry to "B", for black key.
                for i in three_group:
                    ddict[i] = "B"
                    to_skip.append(i)
                
                # We'll update our three-key starting position by "stepping" ahead by our
                # aforementioned step amonut.  In this case, that would be 12 keys.
                three_start += group_step
    
            # The same process is repeated for the two-key groups.
            elif k == two_start:
                two_group = list(incrementer(two_start, 2))
                for i in two_group:
                    ddict[i] = "B"
                    to_skip.append(i)
    
                two_start += group_step
    
            else:
                # Otherwise, the dictionary entry should be "W", for white key
                ddict[k] = "W"
                
    # Before returning our dictionary, we will sort the keys in ascending order.
    return dict(sorted(ddict.items(), key=lambda x: x[0]))
In [5]:
key_color_dict = create_key_color_dict()

# Create a dataframe-friendly dictionary
df_dict = {"key_number":[], "color_id":[],}
for k, v in key_color_dict.items():
    df_dict["key_number"].append(k)
    df_dict["color_id"].append(v)


# create a dataframe from the dictionary
df = pd.DataFrame(df_dict)

def color_selector(col):
    piano_black = "#100c08"
    piano_white = "#fffafa"
    bg_color = piano_white if col == "W" else piano_black
    font_color = piano_black if col == "W" else piano_white
    return f"background-color: {bg_color};color: {font_color}"

def row_color_selector(row):
    piano_black = "#100c08"
    piano_white = "#fffafa"
    bg_color = piano_white if col == "W" else piano_black
    font_color = piano_black if col == "W" else piano_white
    return f"background-color: {bg_color};color: {font_color}"
In [6]:
# Apply to dataframe
df.style.applymap(color_selector, subset=["color_id"])
Out[6]:
key_number color_id
0 1 W
1 2 B
2 3 W
3 4 W
4 5 B
5 6 W
6 7 B
7 8 W
8 9 W
9 10 B
10 11 W
11 12 B
12 13 W
13 14 B
14 15 W
15 16 W
16 17 B
17 18 W
18 19 B
19 20 W
20 21 W
21 22 B
22 23 W
23 24 B
24 25 W
25 26 B
26 27 W
27 28 W
28 29 B
29 30 W
30 31 B
31 32 W
32 33 W
33 34 B
34 35 W
35 36 B
36 37 W
37 38 B
38 39 W
39 40 W
40 41 B
41 42 W
42 43 B
43 44 W
44 45 W
45 46 B
46 47 W
47 48 B
48 49 W
49 50 B
50 51 W
51 52 W
52 53 B
53 54 W
54 55 B
55 56 W
56 57 W
57 58 B
58 59 W
59 60 B
60 61 W
61 62 B
62 63 W
63 64 W
64 65 B
65 66 W
66 67 B
67 68 W
68 69 W
69 70 B
70 71 W
71 72 B
72 73 W
73 74 B
74 75 W
75 76 W
76 77 B
77 78 W
78 79 B
79 80 W
80 81 W
81 82 B
82 83 W
83 84 B
84 85 W
85 86 B
86 87 W
87 88 W