Skip to content

Instantly share code, notes, and snippets.

@amb
Created October 10, 2024 10:54
Show Gist options
  • Save amb/d243633de14b99f0e409008223f71e1a to your computer and use it in GitHub Desktop.
Save amb/d243633de14b99f0e409008223f71e1a to your computer and use it in GitHub Desktop.
Launchpad X midi Game of Life
import time
import mido
import random
import numpy as np
# List all available MIDI ports
print("Available MIDI ports:")
for port in mido.get_input_names():
print(port)
for port in mido.get_output_names():
print(port)
# Create a MIDI output port
outport = mido.open_output("Launchpad X LPX MIDI In")
inport = mido.open_input("Launchpad X LPX MIDI Out")
# Open virtual MIDI ports
# This is Mac OS specific, on Windows you can use the loopMIDI software
virtual_out = mido.open_output("IAC Driver Bus 1")
previous_color_grid = np.ones((8, 8, 3), dtype=np.uint8)
def set_colors(grid, full_refresh=False):
difference = grid != previous_color_grid
if not np.any(difference) and not full_refresh:
return
previous_color_grid[:] = grid
sysex_data = [0x00, 0x20, 0x29, 0x02, 0x0C, 0x03]
for row in range(1, 9):
for col in range(1, 9):
if not full_refresh and not np.any(difference[row - 1, col - 1]):
continue
led_index = row * 10 + col
C = grid[row - 1, col - 1]
sysex_data.extend([0x03, led_index, *C])
outport.send(mido.Message("sysex", data=sysex_data))
def value_to_color(value):
c = (float(value) / 127.0) ** 1.5 * 127
return np.array([c, c, c], dtype=np.uint8)
def set_colors_mono(grid):
stacked_pixels = np.stack([grid, grid, grid], axis=-1) * 127
set_colors(stacked_pixels)
return stacked_pixels
pixels = np.random.randint(0, 2, (8, 8), dtype=np.uint8)
stacked_pixels = np.stack([pixels, pixels, pixels], axis=-1) * 127
color_grid = stacked_pixels[:]
set_colors(stacked_pixels, full_refresh=True)
try:
while True:
# Run Game of Life on pixels
new_pixels = np.zeros_like(pixels)
pixels = np.where(color_grid[:, :, 0] > 0, 1, 0)
for i in range(8):
for j in range(8):
neighbors = pixels[i - 1 : i + 2, j - 1 : j + 2].sum() - pixels[i, j]
if pixels[i, j] == 1 and neighbors in [2, 3]:
new_pixels[i, j] = 1
elif pixels[i, j] == 0 and neighbors == 3:
new_pixels[i, j] = 1
pixels = new_pixels
color_grid = set_colors_mono(pixels)
# Output pixel grid as MIDI notes
for i in range(8):
for j in range(8):
note = i * 10 + j + 1
velocity = 127 if pixels[i, j] else 0
virtual_out.send(mido.Message("note_on", note=note, velocity=velocity))
current_time = time.time()
while time.time() - current_time < 0.1:
midi_message = inport.poll()
if midi_message:
print(midi_message)
if not hasattr(midi_message, "note"):
continue
row = midi_message.note // 10 - 1
col = midi_message.note % 10 - 1
# Polyphonic aftertouch
if midi_message.type == "polytouch":
color_grid[row, col] = value_to_color(midi_message.value)
set_colors(color_grid)
# Note on
if midi_message.type == "note_on" and midi_message.velocity > 0:
color_grid[row, col] = value_to_color(midi_message.velocity)
set_colors(color_grid)
# Note off
if midi_message.type == "note_on" and midi_message.velocity == 0:
color_grid[row, col] = np.array([0, 0, 0], dtype=np.uint8)
set_colors(color_grid)
except KeyboardInterrupt:
pass
# Close the MIDI port
print("Closing MIDI ports")
outport.close()
inport.close()
virtual_out.close()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment