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.5 / (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)