diff --git a/phoenix/composition/colors.py b/phoenix/composition/colors.py new file mode 100644 index 0000000..63090c1 --- /dev/null +++ b/phoenix/composition/colors.py @@ -0,0 +1,18 @@ +def merge_colors(color_1, color2, method='average'): + if method == 'average': + return ( + (color_1[0] + color2[0]) / 2, + (color_1[1] + color2[1]) / 2, + (color_1[2] + color2[2]) / 2 + ) + +color_patterns = { + 'psychedelic': [ + (255, 235, 0), + (252, 0, 25), + (1, 255, 79), + (255, 1, 215), + (86, 0, 204), + (0, 237, 245) + ] +} \ No newline at end of file diff --git a/phoenix/composition/pattern.py b/phoenix/composition/pattern.py new file mode 100644 index 0000000..bce0c91 --- /dev/null +++ b/phoenix/composition/pattern.py @@ -0,0 +1,81 @@ +import math +from .triangle_patterns import triangle_patterns +from .colors import merge_colors + +TRIANGLE = 0 +GRID = 1 + +class Pattern: + def __init__( + self, + type = TRIANGLE, + pattern = 'outer_edge', + tail_length = 6, + colors = [(35, 76, 130)], + tail_colors = [(0, 0, 0)], + children = [] + ): + self.pattern = pattern + if type == TRIANGLE: + self.pattern_array = triangle_patterns[pattern] + else: + self.pattern_array = None + self.length = len(self.pattern_array) + self.current = 0 + self.tail_length = tail_length + self.colors = colors + self.colors_length = len(colors) + self.tail_colors = tail_colors + self.tail_colors_length = len(tail_colors) + self.children = children + self.epochs_elapsed = 0 + + def next_epoch(self): + self.epochs_elapsed += 1 + + if self.epochs_elapsed > self.length: + self.epochs_elapsed = 0 + + return self.epochs_elapsed + + def get_next_color(self, colors, colors_length): + first_color = colors[math.floor((self.epochs_elapsed / self.length) * colors_length)] + second_color = colors[math.ceil((self.epochs_elapsed / self.length) * colors_length)] + + return merge_colors(first_color, second_color) + + def get_next(self, inner_step = 1): + next = self.pattern_array[self.current] + self.current = (self.current+1) % self.length + changes = [(next, self.get_next_color(self.colors, self.colors_length))] + + # Get next for children and append to changes + if self.children is not None: + for child_pattern in self.children: + changes.extend(child_pattern.get_next()) + + self.next_epoch() + + return changes + + def get_tail(self): + tail = (self.current-self.tail_length) % self.length + tails = [(self.pattern_array[tail], self.get_next_color(self.tail_colors, self.tail_colors_length))] + + # Get tail for children + if self.children is not None: + for child_pattern in self.children: + tails.extend(child_pattern.get_tail()) + + self.next_epoch() + return tails + +## not needed? + # def get_range(self): + # range = [] + # for i in range(0, self.tail_length): + # address = self.pattern_array[(self.current-i) % self.length] + # range.append(address) + # + # self.current = (self.current+1) % self.length + # return range diff --git a/phoenix/interface/program.py b/phoenix/composition/program.py similarity index 100% rename from phoenix/interface/program.py rename to phoenix/composition/program.py diff --git a/phoenix/composition/triangle_patterns.py b/phoenix/composition/triangle_patterns.py new file mode 100644 index 0000000..7165f40 --- /dev/null +++ b/phoenix/composition/triangle_patterns.py @@ -0,0 +1,114 @@ +from phoenix.composition.pattern import Pattern +from phoenix.coordinates.triangles import get_addresses_from_edge + +def reverse(pattern): + return pattern[::-1] + + +def cross_pattern_1(): + pattern = [] + pattern.extend(get_addresses_from_edge(1, 1)) + pattern.extend(get_addresses_from_edge(5, 2)) + pattern.extend(get_addresses_from_edge(4, 3)) + return pattern + +def cross_pattern_2(): + pattern = [] + pattern.extend(get_addresses_from_edge(2, 1)) + pattern.extend(get_addresses_from_edge(6, 1)) + pattern.extend(get_addresses_from_edge(4, 1)) + return pattern + +def cross_pattern_3(): + pattern = [] + pattern.extend(get_addresses_from_edge(3, 2)) + pattern.extend(get_addresses_from_edge(7, 2)) + pattern.extend(get_addresses_from_edge(4, 2)) + return pattern + +def cross_pattern_4(): + pattern = [] + pattern.extend(get_addresses_from_edge(1, 3)) + pattern.extend(get_addresses_from_edge(5, 3)) + pattern.extend(get_addresses_from_edge(8, 1)) + return pattern + +def cross_pattern_5(): + pattern = [] + pattern.extend(get_addresses_from_edge(2, 2)) + pattern.extend(get_addresses_from_edge(6, 2)) + pattern.extend(get_addresses_from_edge(8, 2)) + return pattern + +def cross_pattern_6(): + pattern = [] + pattern.extend(get_addresses_from_edge(3, 3)) + pattern.extend(get_addresses_from_edge(7, 2)) + pattern.extend(get_addresses_from_edge(8, 3)) + return pattern + + +def flower_right(): + pattern = [] + pattern.extend(get_addresses_from_edge(3, 2)) + pattern.extend(get_addresses_from_edge(7, 1)) + pattern.extend(get_addresses_from_edge(8, 2)) + pattern.extend(get_addresses_from_edge(4, 1)) + pattern.extend(get_addresses_from_edge(6, 2)) + pattern.extend(get_addresses_from_edge(2, 1)) + return pattern + +def flower_left(): + pattern = [] + pattern.extend(get_addresses_from_edge(3, 3)) + pattern.extend(get_addresses_from_edge(7, 3)) + pattern.extend(get_addresses_from_edge(8, 3)) + pattern.extend(get_addresses_from_edge(4, 3)) + pattern.extend(get_addresses_from_edge(5, 3)) + pattern.extend(get_addresses_from_edge(1, 1)) + return pattern + +def outer_edge(): + pattern = [] + pattern.extend(get_addresses_from_edge(1, 1)) + pattern.extend(get_addresses_from_edge(2, 1)) + pattern.extend(get_addresses_from_edge(2, 2)) + pattern.extend(get_addresses_from_edge(3, 2)) + pattern.extend(get_addresses_from_edge(3, 3)) + pattern.extend(get_addresses_from_edge(1, 3)) + return pattern + +def outer_counter_clockwise(): + pattern = [] + pattern.extend(get_addresses_from_edge(1, 1)) + pattern.extend(get_addresses_from_edge(2, 1)) + pattern.extend(get_addresses_from_edge(2, 2)) + pattern.extend(get_addresses_from_edge(4, 2)) + pattern.extend(get_addresses_from_edge(4, 3)) + pattern.extend(get_addresses_from_edge(4, 1)) + pattern.extend(get_addresses_from_edge(3, 2)) + pattern.extend(get_addresses_from_edge(3, 3)) + pattern.extend(get_addresses_from_edge(1, 3)) + return pattern + +def inner_clockwise(): + pattern = [] + pattern.extend(reverse(get_addresses_from_edge(5, 1))) + pattern.extend(reverse(get_addresses_from_edge(5, 3))) + pattern.extend(reverse(get_addresses_from_edge(5, 2))) + pattern.extend(get_addresses_from_edge(8, 1)) + pattern.extend(reverse(get_addresses_from_edge(6, 2))) + pattern.extend(reverse(get_addresses_from_edge(6, 1))) + pattern.extend(reverse(get_addresses_from_edge(6, 3))) + pattern.extend(get_addresses_from_edge(8, 2)) + pattern.extend(reverse(get_addresses_from_edge(7, 3))) + pattern.extend(reverse(get_addresses_from_edge(7, 2))) + pattern.extend(reverse(get_addresses_from_edge(7, 1))) + pattern.extend(get_addresses_from_edge(8, 3)) + return pattern + +triangle_patterns = { + 'outer_edge': outer_edge(), + 'outer_counter_clockwise': outer_counter_clockwise(), + 'inner_clockwise': inner_clockwise() +} diff --git a/phoenix/coordinates/grid.py b/phoenix/coordinates/grid.py index e69de29..0252fcc 100644 --- a/phoenix/coordinates/grid.py +++ b/phoenix/coordinates/grid.py @@ -0,0 +1,68 @@ +# Grid is for edge-neighbor based or pixel-neighbor based animations + +# x axis is along base of triangle +# y axis is along left edge +# z axis is along right edge, it can be derived from x and y +from .triangles import get_addresses_from_edge + +X = 0 +Y = 1 +Z = 2 + +class Pixel: + def __init__( + self, + edge, + address + ): + self.edge = edge + self.address = address + +class Edge: + def __init__( + self, + axis = X, + index = 1, + ): + self.axis = axis + self.index = index + self.pixels = self.calc_pixel_addresses() + + def calc_pixel_addresses(): + pixels = [] + + return pixels + + def get_x_neighbor(self, dir=1): + + return self.edge_x_wrap(x, y) + + def get_y_neighbor(self, dir=1): + + return self.edge_y_wrap(x, y) + + def get_z_neighbor(self, dir=1): + + return self.edge_z_wrap(x, y) + + +class Grid: + def __init__( + self, + ): + self.edges = [] + for i in range(0, Z+1): + axis_edges = [] + for j in range(0, 10): + axis_edges.append[Edge(i, j)] + self.edges.append(axis_edges) + + +class AutomataGrid(Grid): + def __init__( + self, + ): + self.super.__init__() + + def evolve(): + pass diff --git a/phoenix/coordinates/triangles.py b/phoenix/coordinates/triangles.py index 2925524..eb83597 100644 --- a/phoenix/coordinates/triangles.py +++ b/phoenix/coordinates/triangles.py @@ -1,18 +1,21 @@ +# Triangles is for outer and inner triangle based patterns + # Triangle order: ## Outer: Left, right, top, center -## STRIP_INDEX_INNER: Left, right, top, center +## Inner: Left, right, top, center # Edge order: ## Base down: Bottom, right, top ## Base up: Right, top, left +from phoenix.lights.strip import get_strip_index_from_address, get_address_from_strip_index + STRIP_INDEX_OUTER_LEFT = 1 STRIP_INDEX_OUTER_RIGHT = 2 STRIP_INDEX_INNER = 0 LEDS_PER_OUTER_EDGE = 12 LEDS_PER_INNER_EDGE = 6 -LEDS_PER_STRIP = 72 triangles = { 1: { @@ -67,18 +70,6 @@ def get_strip_index_from_edge(triangle, edge, index): strip_index = tri['index'][(edge-1)*tri['edge_length']+index] return {'strip': strip, 'index': strip_index} -def get_address_from_strip_index(strip, index): - return strip*LEDS_PER_STRIP+index - -def get_strip_index_from_address(address): - index = address % LEDS_PER_STRIP - strip = (address - index) / LEDS_PER_STRIP - address = { - 'strip': int(strip), - 'index': index - } - return address - def get_addresses_from_edge(triangle, edge): addresses = [] edge_length = triangles[triangle]['edge_length'] diff --git a/phoenix/interface/knobs.py b/phoenix/interface/knobs.py index dc262e2..1e746ed 100644 --- a/phoenix/interface/knobs.py +++ b/phoenix/interface/knobs.py @@ -2,7 +2,7 @@ import board from rainbowio import colorwheel from adafruit_seesaw import seesaw, neopixel, rotaryio, digitalio -from phoenix.lights.lights import Strip +from phoenix.lights.strip import Strip addresses = [0x37, 0x36, 0x3A] diff --git a/phoenix/lights/dimmer.py b/phoenix/lights/dimmer.py new file mode 100644 index 0000000..e69de29 diff --git a/phoenix/lights/lights.py b/phoenix/lights/lights.py index 2ce95c6..73c8b2e 100644 --- a/phoenix/lights/lights.py +++ b/phoenix/lights/lights.py @@ -1,21 +1,8 @@ -import board -import neopixel - from phoenix.coordinates.triangles import get_strip_index_from_address -from .pattern import Pattern +from phoenix.composition.pattern import Pattern, TRIANGLE, GRID +from phoenix.composition.colors import color_patterns, merge_colors from rainbowio import colorwheel - -addresses = [board.D10, board.D11, board.D13] - -class Strip: - def __init__( - self, - num, - ): - self.pixel = neopixel.NeoPixel(addresses[num-1], 72) - - def set_color(self, index, color): - self.pixel[index] = color +from .strip import Strip class Lights: def __init__( @@ -23,8 +10,22 @@ def __init__( ): self.strips = [Strip(1), Strip(2), Strip(3)] self.patterns = [ - Pattern('outer_counter_clockwise', 12, (35, 76, 130), (0, 0, 0)), - Pattern('inner_clockwise', 12, (130, 30, 70), (0, 0, 0)), + Pattern(TRIANGLE, 'outer_counter_clockwise', 12, color_patterns['psychedelic'], [(0, 0, 0)]), + Pattern(TRIANGLE, 'flower_left', 12, color_patterns['psychedelic'], [(0, 0, 0)], [ + Pattern(TRIANGLE, 'flower_right', 12, color_patterns['psychedelic'], [(0, 0, 0)]) + ]), + Pattern(TRIANGLE, 'cross_pattern_1', 12, color_patterns['psychedelic'], [(0, 0, 0)], [ + Pattern(TRIANGLE, 'cross_pattern_2', 12, color_patterns['psychedelic'], [(0, 0, 0)]), + Pattern(TRIANGLE, 'cross_pattern_3', 12, color_patterns['psychedelic'], [(0, 0, 0)]), + Pattern(TRIANGLE, 'cross_pattern_4', 12, color_patterns['psychedelic'], [(0, 0, 0)]), + Pattern(TRIANGLE, 'cross_pattern_5', 12, color_patterns['psychedelic'], [(0, 0, 0)]), + Pattern(TRIANGLE, 'cross_pattern_6', 12, color_patterns['psychedelic'], [(0, 0, 0)]) + ]), + Pattern(TRIANGLE, 'outer_counter_clockwise', 12, color_patterns['psychedelic'], [(0, 0, 0)], [ + Pattern(TRIANGLE, 'inner_clockwise', 12, [(35, 76, 130)], [(0, 0, 0)]), + ]), + Pattern(TRIANGLE, 'outer_counter_clockwise', 12, [(35, 76, 130)], [(0, 0, 0)]), + Pattern(TRIANGLE, 'inner_clockwise', 12, [(130, 30, 70)], [(0, 0, 0)]), ] self.clear_lights() @@ -34,9 +35,22 @@ def clear_lights(self): l.pixel[i] = (0, 0, 0) def handler(self): + changes = {} for pattern in self.patterns: - self.set_color(pattern.get_next(), pattern.color) - self.set_color(pattern.get_tail(), pattern.tail_color) + for next_address, next_color in pattern.get_next(): + # Another pattern already wanted to modify this address + # We have a conflict. Let's resolve: + if next_address in changes: + changes[next_address] = merge_colors(changes[next_address], next_color) + else: + changes[next_address] = next_color + + self.set_color(next_address, changes[next_address]) + + for tail_address, tail_color in pattern.get_tail(): + # As long as some other pattern hasn't decided to write on our pixel then clean up: + if tail_address not in changes: + self.set_color(tail_address, tail_color) def set_color(self, address, color): strip_index = get_strip_index_from_address(address) diff --git a/phoenix/lights/pattern.py b/phoenix/lights/pattern.py deleted file mode 100644 index f701b91..0000000 --- a/phoenix/lights/pattern.py +++ /dev/null @@ -1,35 +0,0 @@ -from .pattern_constructor import patterns - -class Pattern: - def __init__( - self, - pattern = 'outer_edge', - tail_length = 6, - color = (35, 76, 130), - tail_color = (0, 0, 0) - ): - self.pattern = pattern - self.pattern_array = patterns[pattern] - self.length = len(self.pattern_array) - self.current = 0 - self.tail_length = tail_length - self.color = color - self.tail_color = tail_color - - def get_range(self): - range = [] - for i in range(0, self.tail_length): - address = self.pattern_array[(self.current-i) % self.length] - range.append(address) - - self.current = (self.current+1) % self.length - return range - - def get_next(self): - next = self.pattern_array[self.current] - self.current = (self.current+1) % self.length - return next - - def get_tail(self): - tail = (self.current-self.tail_length) % self.length - return self.pattern_array[tail] diff --git a/phoenix/lights/pattern_constructor.py b/phoenix/lights/pattern_constructor.py deleted file mode 100644 index 14d9386..0000000 --- a/phoenix/lights/pattern_constructor.py +++ /dev/null @@ -1,49 +0,0 @@ -from phoenix.coordinates.triangles import get_addresses_from_edge - -def reverse(pattern): - return pattern[::-1] - -def outer_edge(): - pattern = [] - pattern.extend(get_addresses_from_edge(1, 1)) - pattern.extend(get_addresses_from_edge(2, 1)) - pattern.extend(get_addresses_from_edge(2, 2)) - pattern.extend(get_addresses_from_edge(3, 2)) - pattern.extend(get_addresses_from_edge(3, 3)) - pattern.extend(get_addresses_from_edge(1, 3)) - return pattern - -def outer_counter_clockwise(): - pattern = [] - pattern.extend(get_addresses_from_edge(1, 1)) - pattern.extend(get_addresses_from_edge(2, 1)) - pattern.extend(get_addresses_from_edge(2, 2)) - pattern.extend(get_addresses_from_edge(4, 2)) - pattern.extend(get_addresses_from_edge(4, 3)) - pattern.extend(get_addresses_from_edge(4, 1)) - pattern.extend(get_addresses_from_edge(3, 2)) - pattern.extend(get_addresses_from_edge(3, 3)) - pattern.extend(get_addresses_from_edge(1, 3)) - return pattern - -def inner_clockwise(): - pattern = [] - pattern.extend(reverse(get_addresses_from_edge(5, 1))) - pattern.extend(reverse(get_addresses_from_edge(5, 3))) - pattern.extend(reverse(get_addresses_from_edge(5, 2))) - pattern.extend(get_addresses_from_edge(8, 1)) - pattern.extend(reverse(get_addresses_from_edge(6, 2))) - pattern.extend(reverse(get_addresses_from_edge(6, 1))) - pattern.extend(reverse(get_addresses_from_edge(6, 3))) - pattern.extend(get_addresses_from_edge(8, 2)) - pattern.extend(reverse(get_addresses_from_edge(7, 3))) - pattern.extend(reverse(get_addresses_from_edge(7, 2))) - pattern.extend(reverse(get_addresses_from_edge(7, 1))) - pattern.extend(get_addresses_from_edge(8, 3)) - return pattern - -patterns = { - 'outer_edge': outer_edge(), - 'outer_counter_clockwise': outer_counter_clockwise(), - 'inner_clockwise': inner_clockwise() -} diff --git a/phoenix/lights/reactive.py b/phoenix/lights/reactive.py new file mode 100644 index 0000000..e69de29 diff --git a/phoenix/lights/strip.py b/phoenix/lights/strip.py new file mode 100644 index 0000000..91a261e --- /dev/null +++ b/phoenix/lights/strip.py @@ -0,0 +1,27 @@ +import board +import neopixel + +LEDS_PER_STRIP = 72 +addresses = [board.D10, board.D11, board.D13] + +class Strip: + def __init__( + self, + num, + ): + self.pixel = neopixel.NeoPixel(addresses[num-1], LEDS_PER_STRIP) + + def set_color(self, index, color): + self.pixel[index] = color + +def get_address_from_strip_index(strip, index): + return strip*LEDS_PER_STRIP+index + +def get_strip_index_from_address(address): + index = address % LEDS_PER_STRIP + strip = (address - index) / LEDS_PER_STRIP + address = { + 'strip': int(strip), + 'index': index + } + return address