254 lines
7.9 KiB
Python
254 lines
7.9 KiB
Python
import board
|
|
import digitalio
|
|
import time
|
|
import usb_midi
|
|
import neopixel
|
|
from adafruit_midi import MIDI
|
|
from adafruit_midi.control_change import ControlChange
|
|
from adafruit_midi.note_on import NoteOn
|
|
from adafruit_midi.note_off import NoteOff
|
|
from digitalio import DigitalInOut, Direction, Pull
|
|
from adafruit_debouncer import Debouncer
|
|
|
|
midi = MIDI(midi_out=usb_midi.ports[1], out_channel=0)
|
|
led = neopixel.NeoPixel(board.GP5, 7, brightness=0.5, auto_write=True)
|
|
|
|
encoder_colors = [
|
|
(255, 0, 0), # Red
|
|
(0, 255, 0), # Green
|
|
(0, 0, 255), # Blue
|
|
(0, 255, 255), # Cyan
|
|
(255, 0, 255) # Magenta
|
|
]
|
|
|
|
# Encoder Pins
|
|
clk = DigitalInOut(board.GP20)
|
|
clk.direction = Direction.INPUT
|
|
clk.pull = Pull.UP
|
|
|
|
dt = DigitalInOut(board.GP18)
|
|
dt.direction = Direction.INPUT
|
|
dt.pull = Pull.UP
|
|
|
|
last_clk = clk.value
|
|
|
|
# Buttons
|
|
enc_button_pin = DigitalInOut(board.GP17)
|
|
enc_button_pin.switch_to_input(pull=Pull.UP)
|
|
enc_button = Debouncer(enc_button_pin)
|
|
|
|
next_button_pin = DigitalInOut(board.GP15)
|
|
next_button_pin.switch_to_input(pull=Pull.UP)
|
|
next_button = Debouncer(next_button_pin)
|
|
|
|
back_button_pin = DigitalInOut(board.GP14)
|
|
back_button_pin.switch_to_input(pull=Pull.UP)
|
|
back_button = Debouncer(back_button_pin)
|
|
|
|
mode_toggle_button_pin = DigitalInOut(board.GP13)
|
|
mode_toggle_button_pin.switch_to_input(pull=Pull.UP)
|
|
mode_toggle_button = Debouncer(mode_toggle_button_pin)
|
|
|
|
# States
|
|
current_encoder = 0
|
|
encoder_cc_notes = [1, 2, 3, 4, 5]
|
|
encoder_bttn_notes = [11, 12, 13, 14, 15]
|
|
mode_toggle_note = 21
|
|
|
|
# Functions
|
|
def encoder_flash():
|
|
steps = 10
|
|
delay = 0.15 / (steps * 2) # Half for fade-in, half for fade-out
|
|
leds = [3, 4, 5, 6]
|
|
base_color = encoder_colors[current_encoder]
|
|
white = (255, 255, 255)
|
|
|
|
# Fade to white
|
|
for i in range(steps):
|
|
ratio = (i + 1) / steps # 0.1 to 1.0
|
|
blended = tuple(
|
|
int(base_color[k] * (1 - ratio) + white[k] * ratio)
|
|
for k in range(3)
|
|
)
|
|
for led_index in leds:
|
|
led[led_index] = blended
|
|
time.sleep(delay)
|
|
|
|
# Fade back to base color
|
|
for i in range(steps):
|
|
ratio = (i + 1) / steps # 0.1 to 1.0
|
|
blended = tuple(
|
|
int(white[k] * (1 - ratio) + base_color[k] * ratio)
|
|
for k in range(3)
|
|
)
|
|
for led_index in leds:
|
|
led[led_index] = blended
|
|
time.sleep(delay)
|
|
|
|
# Final color correction
|
|
for led_index in leds:
|
|
led[led_index] = base_color
|
|
|
|
def fade_leds_parallel(led_targets, steps=10, delay=0.01):
|
|
for step in range(steps + 1):
|
|
for index, start_color, end_color in led_targets:
|
|
blended = tuple(
|
|
int(start_color[i] + (end_color[i] - start_color[i]) * step / steps)
|
|
for i in range(3)
|
|
)
|
|
led[index] = blended
|
|
time.sleep(delay)
|
|
|
|
|
|
def startup_animation():
|
|
print("Running startup LED test animation...")
|
|
start_time = time.monotonic()
|
|
button_indices = [0, 1, 2]
|
|
encoder_indices = [3, 4, 5, 6]
|
|
|
|
while time.monotonic() - start_time < 5:
|
|
for i in range(len(encoder_indices)):
|
|
led[encoder_indices[i]] = (255, 255, 255)
|
|
if i > 0:
|
|
led[encoder_indices[i - 1]] = (0, 0, 0)
|
|
time.sleep(0.05)
|
|
led[encoder_indices[-1]] = (0, 0, 0)
|
|
|
|
# Fade up and down the button LEDs
|
|
for b in range(0, 256, 10):
|
|
for i in button_indices:
|
|
led[i] = (b, b, b)
|
|
time.sleep(0.01)
|
|
for b in range(255, -1, -10):
|
|
for i in button_indices:
|
|
led[i] = (b, b, b)
|
|
time.sleep(0.01)
|
|
|
|
# Fade in to start colors
|
|
target_colors = [
|
|
encoder_colors[(current_encoder + 1) % len(encoder_colors)], # LED 0
|
|
encoder_colors[(current_encoder - 1) % len(encoder_colors)], # LED 1
|
|
(63, 63, 63), # LED 2 at 25% white
|
|
]
|
|
for step in range(0, 101, 5):
|
|
for i in range(3):
|
|
led[i] = tuple(int(c * step / 100) for c in target_colors[i])
|
|
for i in range(3, 7):
|
|
led[i] = tuple(int(c * step / 100) for c in encoder_colors[current_encoder])
|
|
time.sleep(0.02)
|
|
|
|
# Startup
|
|
print("MIDI Encoder Controller started.")
|
|
startup_animation()
|
|
led[3:7] = [encoder_colors[current_encoder]] * 4
|
|
led[0] = encoder_colors[(current_encoder + 1) % len(encoder_colors)]
|
|
led[1] = encoder_colors[(current_encoder - 1) % len(encoder_colors)]
|
|
led[2] = (63, 63, 63) # Mode toggle LED default 25% white
|
|
mode_button_state_note = 0
|
|
|
|
should_interrupt = False
|
|
|
|
def animation_interrupt():
|
|
global should_interrupt
|
|
if should_interrupt:
|
|
should_interrupt = False
|
|
return True
|
|
return False
|
|
|
|
# Main Loop
|
|
while True:
|
|
enc_button.update()
|
|
next_button.update()
|
|
back_button.update()
|
|
mode_toggle_button.update()
|
|
|
|
# Rotary Encoder Logic
|
|
if clk.value != last_clk and clk.value == False:
|
|
direction = None
|
|
if dt.value != clk.value:
|
|
direction = "CW"
|
|
midi.send(ControlChange(encoder_cc_notes[current_encoder], 1))
|
|
else:
|
|
direction = "CCW"
|
|
midi.send(ControlChange(encoder_cc_notes[current_encoder], 127))
|
|
encoder_flash()
|
|
last_clk = clk.value
|
|
|
|
# Encoder Button
|
|
if enc_button.fell:
|
|
midi.send(NoteOn(encoder_bttn_notes[current_encoder], 127))
|
|
encoder_flash()
|
|
if enc_button.rose:
|
|
midi.send(NoteOff(encoder_bttn_notes[current_encoder], 0))
|
|
|
|
# Next Encoder Button
|
|
if next_button.fell:
|
|
current_encoder = (current_encoder + 1) % len(encoder_cc_notes)
|
|
|
|
# Prepare fade targets
|
|
led_targets = []
|
|
|
|
# Fade main encoder LEDs (3 to 6)
|
|
for i in range(3, 7):
|
|
led_targets.append((i, led[i], encoder_colors[current_encoder]))
|
|
|
|
# Fade forward/backward indicators (0 and 1)
|
|
forward_color = encoder_colors[(current_encoder + 1) % len(encoder_colors)]
|
|
backward_color = encoder_colors[(current_encoder - 1) % len(encoder_colors)]
|
|
led_targets.append((0, led[0], forward_color))
|
|
led_targets.append((1, led[1], backward_color))
|
|
|
|
# Run the simultaneous fade
|
|
fade_leds_parallel(led_targets)
|
|
|
|
forward_color = encoder_colors[(current_encoder + 1) % len(encoder_colors)]
|
|
backward_color = encoder_colors[(current_encoder - 1) % len(encoder_colors)]
|
|
led[3:7] = [encoder_colors[current_encoder]] * 4
|
|
led[0] = forward_color
|
|
led[1] = backward_color
|
|
|
|
# Back Encoder Button
|
|
if back_button.fell:
|
|
current_encoder = (current_encoder - 1) % len(encoder_cc_notes)
|
|
|
|
# Prepare fade targets
|
|
led_targets = []
|
|
|
|
# Fade main encoder LEDs (3 to 6)
|
|
for i in range(3, 7):
|
|
led_targets.append((i, led[i], encoder_colors[current_encoder]))
|
|
|
|
# Fade forward/backward indicators (0 and 1)
|
|
forward_color = encoder_colors[(current_encoder + 1) % len(encoder_colors)]
|
|
backward_color = encoder_colors[(current_encoder - 1) % len(encoder_colors)]
|
|
led_targets.append((0, led[0], forward_color))
|
|
led_targets.append((1, led[1], backward_color))
|
|
|
|
# Run the simultaneous fade
|
|
fade_leds_parallel(led_targets)
|
|
|
|
forward_color = encoder_colors[(current_encoder + 1) % len(encoder_colors)]
|
|
backward_color = encoder_colors[(current_encoder - 1) % len(encoder_colors)]
|
|
led[3:7] = [encoder_colors[current_encoder]] * 4
|
|
led[0] = forward_color
|
|
led[1] = backward_color
|
|
|
|
# Mode Toggle Button
|
|
if mode_toggle_button.fell:
|
|
if mode_button_state_note == 0:
|
|
mode_button_state_note = 127
|
|
elif mode_button_state_note == 127:
|
|
mode_button_state_note = 0
|
|
else:
|
|
mode_button_state_note = 0
|
|
|
|
midi.send(NoteOn(mode_toggle_note, mode_button_state_note))
|
|
|
|
for b in range(100, 24, -5):
|
|
level = int(255 * b / 100)
|
|
led[2] = (level, level, level)
|
|
time.sleep(0.02)
|
|
led[2] = (63, 63, 63)
|
|
|
|
time.sleep(0.001)
|