1 | #!/usr/bin/python |
---|
2 | |
---|
3 | import time |
---|
4 | import random |
---|
5 | import math |
---|
6 | |
---|
7 | from core import ColorPattern, MotionPattern, EventHandler |
---|
8 | from color import Color, Colors |
---|
9 | from geometry import key_positions, KeyMatrix, Keys |
---|
10 | |
---|
11 | |
---|
12 | class AlternatingColorPattern(ColorPattern): |
---|
13 | """Steps through a cycle of colors""" |
---|
14 | def color(self, t): |
---|
15 | index = int(round(len(self.colors)*self.phase(t))) - 1 |
---|
16 | return self.colors[index] |
---|
17 | |
---|
18 | |
---|
19 | class BeatColorPattern(ColorPattern): |
---|
20 | """Linearly fades through a cycle of colors""" |
---|
21 | def color(self, t): |
---|
22 | index = int(len(self.colors)*self.phase(t)) % len(self.colors) |
---|
23 | foreground = self.colors[index] |
---|
24 | background = self.colors[(index+1)%len(self.colors)] |
---|
25 | phase = (self.phase(t) * len(self.colors)) % 1 |
---|
26 | return Color.blend(foreground, background, transparency=phase) |
---|
27 | |
---|
28 | |
---|
29 | class ThrobColorPattern(ColorPattern): |
---|
30 | """Non-linearly (sin wave) fades through a cycle of colors""" |
---|
31 | def color(self, t): |
---|
32 | index = (2*int(len(self.colors)*self.phase(t))) % len(self.colors) |
---|
33 | foreground = self.colors[index] |
---|
34 | background = self.colors[(index+1)%len(self.colors)] |
---|
35 | phase = (self.phase(t) * len(self.colors)) % 1 |
---|
36 | intensity = (1 + math.sin(2 * math.pi * phase)) / 2 |
---|
37 | return Color.blend(foreground, background, transparency=1-intensity) |
---|
38 | |
---|
39 | |
---|
40 | class PulseColorPattern(ColorPattern): |
---|
41 | """Pulses a color on, then fades to the next color, through a cycle of |
---|
42 | colors |
---|
43 | """ |
---|
44 | def color(self, t): |
---|
45 | index = (2 * int(len(self.colors)*self.phase(t))) % len(self.colors) |
---|
46 | foreground = self.colors[index] |
---|
47 | background = self.colors[(index+1)%len(self.colors)] |
---|
48 | phase = (self.phase(t) * len(self.colors)) % 1 |
---|
49 | return Color.blend(foreground, background, |
---|
50 | transparency=phase) |
---|
51 | |
---|
52 | |
---|
53 | class AngleMotionPattern(MotionPattern): |
---|
54 | """Applies a phase offset across the keyboard at a specified angle and |
---|
55 | stretched a specified distance. (Makes for something of a wave effect.) |
---|
56 | """ |
---|
57 | def __init__(self, angle=0.0, size=50, *args, **kwargs): |
---|
58 | super(AngleMotionPattern, self).__init__(*args, **kwargs) |
---|
59 | self.angle = angle |
---|
60 | self.size = size |
---|
61 | |
---|
62 | def phase_offset(self, x, y): |
---|
63 | # The pixels for the keyboard geometry are not square; so account for |
---|
64 | # their shape by doubling the Y value. |
---|
65 | return (- x * math.cos(self.angle) - 2 * y * math.sin(self.angle)) / self.size |
---|
66 | |
---|
67 | |
---|
68 | # Lighting animations |
---|
69 | class RandomAnimation(EventHandler): |
---|
70 | """Light up a randomly selected subset of keys""" |
---|
71 | tick_rate = 0.1 |
---|
72 | |
---|
73 | def __init__(self, foreground=Colors.WHITE, background=Colors.BLACK, |
---|
74 | quantity=40, *args, **kwargs): |
---|
75 | super(RandomAnimation, self).__init__(*args, **kwargs) |
---|
76 | self.foreground = foreground |
---|
77 | self.background = background |
---|
78 | self.quantity = quantity |
---|
79 | self.lit_keys = [] |
---|
80 | |
---|
81 | def tick(self): |
---|
82 | """Handler for the periodic events""" |
---|
83 | # We don't enforce a strict rate here. If you are typing, the random |
---|
84 | # updating of keys will occur more frequently. If this is getting |
---|
85 | # called as part of a group of event handlers where another event |
---|
86 | # handler has a lower value for tick_rate, this animation will toggle |
---|
87 | # keys faster. Given the intent of this animation, that isn't a |
---|
88 | # problem. |
---|
89 | if len(self.lit_keys) > self.quantity: |
---|
90 | key = self.lit_keys.pop(0) |
---|
91 | self.keyboard.set_keys(key, self.background) |
---|
92 | key = random.choice(list(set(Keys.ALL.split(',')) - set(self.lit_keys))) |
---|
93 | self.lit_keys.append(key) |
---|
94 | self.keyboard.set_keys(key, self.foreground) |
---|
95 | |
---|
96 | |
---|
97 | class ColorAnimation(EventHandler): |
---|
98 | """Given a color pattern, apply that pattern to the keyboard""" |
---|
99 | def __init__(self, color_pattern=None, keys=Keys.ALL, *args, **kwargs): |
---|
100 | super(ColorAnimation, self).__init__(*args, **kwargs) |
---|
101 | self.keys = keys |
---|
102 | self.color_pattern = color_pattern |
---|
103 | |
---|
104 | def tick(self): |
---|
105 | color = self.color_pattern.color(time.time()) |
---|
106 | self.keyboard.set_keys(self.keys, color) |
---|
107 | |
---|
108 | |
---|
109 | class MotionAnimation(EventHandler): |
---|
110 | def __init__(self, color_pattern=None, motion_pattern=None, keys=Keys.ALL, |
---|
111 | *args, **kwargs): |
---|
112 | super(MotionAnimation, self).__init__(*args, **kwargs) |
---|
113 | self.keys = keys.split(',') |
---|
114 | self.color_pattern = color_pattern |
---|
115 | self.motion_pattern = motion_pattern |
---|
116 | |
---|
117 | # pre-calculate the points we will have to color |
---|
118 | self.points = [] |
---|
119 | for key in self.keys: |
---|
120 | self.points.extend(key_positions[key]) |
---|
121 | |
---|
122 | def tick(self): |
---|
123 | matrix = KeyMatrix() |
---|
124 | for x,y in self.points: |
---|
125 | color = self.color_pattern.color(time.time() + self.motion_pattern.t_offset(x, y)) |
---|
126 | matrix.set_pixel(x, y, color) |
---|
127 | colors = {} |
---|
128 | for key in self.keys: |
---|
129 | color = matrix.get_key(key) |
---|
130 | colors.setdefault(color, []).append(key) |
---|
131 | key_colors = [] |
---|
132 | for color, keys in colors.items(): |
---|
133 | key_colors.append((','.join(keys), color)) |
---|
134 | self.keyboard.set_many_keys(key_colors) |
---|