Adafruit_Learning_System_Gu.../Macropad_Ableton/code.py
2021-07-14 12:43:26 -07:00

239 lines
7.9 KiB
Python

# SPDX-FileCopyrightText: 2021 John Park for Adafruit Industries
# SPDX-License-Identifier: MIT
# Ableton Live Macropad Launcher
# In Ableton, choose "Launchpad Mini Mk3" as controller with MacroPad 2040 as in and out
# Use empty fifth scene to allow "unlaunching" of tracks with encoder modifier
import board
from adafruit_macropad import MacroPad
import displayio
import terminalio
from adafruit_simplemath import constrain
from adafruit_display_text import label
import usb_midi
import adafruit_midi
from adafruit_midi.control_change import ControlChange
from adafruit_midi.note_off import NoteOff
from adafruit_midi.note_on import NoteOn
from adafruit_midi.midi_message import MIDIUnknownEvent
macropad = MacroPad()
TITLE_TEXT = "Live Launcher 2040"
print(TITLE_TEXT)
TRACK_NAMES = ["DRUM", "BASS", "SYNTH"] # Customize these
LIVE_CC_NUMBER = 74 # CC number to send w encoder
FADER_TEXT = "cutoff" # change for intended CC name
# --- MIDI recieve is complex, so not using macropad.midi
midi = adafruit_midi.MIDI(
midi_in=usb_midi.ports[0],
in_channel=(0, 1, 2),
midi_out=usb_midi.ports[1],
out_channel=0
)
# ---Official Launchpad colors---
LP_COLORS = (
0x000000, 0x101010, 0x202020, 0x3f3f3f, 0x3f0f0f, 0x3f0000, 0x200000, 0x100000,
0x3f2e1a, 0x3f0f00, 0x200800, 0x100400, 0x3f2b0b, 0x3f3f00, 0x202000, 0x101000,
0x213f0c, 0x143f00, 0x0a2000, 0x051000, 0x123f12, 0x003f00, 0x002000, 0x001000,
0x123f17, 0x003f06, 0x002003, 0x001001, 0x123f16, 0x003f15, 0x00200b, 0x001006,
0x123f2d, 0x003f25, 0x002012, 0x001009, 0x12303f, 0x00293f, 0x001520, 0x000b10,
0x12213f, 0x00153f, 0x000b20, 0x000610, 0x0b093f, 0x00003f, 0x000020, 0x000010,
0x1a0d3e, 0x0b003f, 0x060020, 0x030010, 0x3f0f3f, 0x3f003f, 0x200020, 0x100010,
0x3f101b, 0x3f0014, 0x20000a, 0x100005, 0x3f0300, 0x250d00, 0x1d1400, 0x080d01,
0x000e00, 0x001206, 0x00051b, 0x00003f, 0x001113, 0x040032, 0x1f1f1f, 0x070707,
0x3f0000, 0x2e3f0b, 0x2b3a01, 0x183f02, 0x032200, 0x003f17, 0x00293f, 0x000a3f,
0x06003f, 0x16003f, 0x2b061e, 0x0a0400, 0x3f0c00, 0x213701, 0x1c3f05, 0x003f00,
0x0e3f09, 0x153f1b, 0x0d3f32, 0x16223f, 0x0c1430, 0x1a1439, 0x34073f, 0x3f0016,
0x3f1100, 0x2d2900, 0x233f00, 0x201601, 0x0e0a00, 0x001203, 0x031308, 0x05050a,
0x050716, 0x190e06, 0x200000, 0x36100a, 0x351204, 0x3f2f09, 0x27370b, 0x192c03,
0x05050b, 0x36341a, 0x1f3a22, 0x26253f, 0x23193f, 0x0f0f0f, 0x1c1c1c, 0x373f3f,
0x270000, 0x0d0000, 0x063300, 0x011000, 0x2d2b00, 0x0f0c00, 0x2c1400, 0x120500,
)
LP_PADS = {
81: 0, 82: 1, 83: 2,
71: 3, 72: 4, 73: 5,
61: 6, 62: 7, 63: 8,
51: 9, 52: 10, 53: 11
}
LIVE_NOTES = [81, 82, 83, 71, 72, 73, 61, 62, 63, 51, 52, 53]
CC_OFFSET = 20
modifier = False # use to add encoder switch modifier to keys for clip mute
MODIFIER_NOTES = [41, 42, 43, 41, 42, 43, 41, 42, 43, 41, 42, 43] # blank row in Live
last_position = 0 # encoder position state
# ---NeoPixel setup---
BRIGHT = 0.125
DIM = 0.0625
macropad.pixels.brightness = BRIGHT
# ---Display setup---
display = board.DISPLAY
screen = displayio.Group(max_size=12)
display.show(screen)
WIDTH = 128
HEIGHT = 64
FONT = terminalio.FONT
# Draw a title label
title = TITLE_TEXT
title_area = label.Label(FONT, text=title, color=0xFFFFFF, x=6, y=3)
screen.append(title_area)
# --- create display strings and positions
x1 = 5
x2 = 35
x3 = 65
y1 = 17
y2 = 27
y3 = 37
y4 = 47
y5 = 57
# ---Push knob text setup
push_text_area = label.Label(FONT, text="[o]", color=0xffffff, x=WIDTH-22, y=y2)
screen.append(push_text_area)
# ---CC knob text setup
fader_text_area = label.Label(FONT, text=FADER_TEXT, color=0xffffff, x=WIDTH - 42, y=y4)
screen.append(fader_text_area)
# --- cc value display
cc_val_text = str(CC_OFFSET)
cc_val_text_area = label.Label(FONT, text=cc_val_text, color=0xffffff, x=WIDTH - 20, y=y5)
screen.append(cc_val_text_area)
label_data = (
# text, x, y
(TRACK_NAMES[0], x1, y1), (TRACK_NAMES[1], x2, y1), (TRACK_NAMES[2], x3, y1),
(".", x1, y2), (".", x2, y2), (".", x3, y2),
(".", x1, y3), (".", x2, y3), (".", x3, y3),
(".", x1, y4), (".", x2, y4), (".", x3, y4),
(".", x1, y5), (".", x2, y5), (".", x3, y5)
)
labels = []
for data in label_data:
text, x, y = data
label_area = label.Label(FONT, text=text, color=0xffffff)
group = displayio.Group(max_size=4, x=x, y=y)
group.append(label_area)
screen.append(group)
labels.append(label_area) # these are individually addressed later
num = 1
while True:
msg_in = midi.receive()
if isinstance(msg_in, NoteOn) and msg_in.velocity != 0:
print(
"received NoteOn",
"from channel",
msg_in.channel + 1,
"MIDI note",
msg_in.note,
"velocity",
msg_in.velocity,
"\n"
)
# send neopixel lightup code to key, text to display
if msg_in.note in LP_PADS:
macropad.pixels[LP_PADS[msg_in.note]] = LP_COLORS[msg_in.velocity]
macropad.pixels.show()
if msg_in.velocity == 21: # active pad is indicated by Live as vel 21
labels[LP_PADS[msg_in.note]+3].text = "o"
else:
labels[LP_PADS[msg_in.note]+3].text = "."
elif isinstance(msg_in, NoteOff):
print(
"received NoteOff",
"from channel",
msg_in.channel + 1,
"\n"
)
elif isinstance(msg_in, NoteOn) and msg_in.velocity == 0:
print(
"received NoteOff",
"from channel",
msg_in.channel + 1,
"MIDI note",
msg_in.note,
"velocity",
msg_in.velocity,
"\n"
)
elif isinstance(msg_in, ControlChange):
print(
"received CC",
"from channel",
msg_in.channel + 1,
"controller",
msg_in.control,
"value",
msg_in.value,
"\n"
)
elif isinstance(msg_in, MIDIUnknownEvent):
# Message are only known if they are imported
print("Unknown MIDI event status ", msg_in.status)
elif msg_in is not None:
midi.send(msg_in)
key_event = macropad.keys.events.get() # check for keypad events
if not key_event: # Event is None; no keypad event happened, do other stuff
position = macropad.encoder # store encoder position state
cc_position = constrain((position + CC_OFFSET), 0, 127) # lock to cc range
if last_position is None or position != last_position:
if position < last_position:
midi.send(ControlChange(LIVE_CC_NUMBER, cc_position))
print("CC", cc_position)
cc_val_text_area.text = str(cc_position)
elif position > last_position:
midi.send(ControlChange(LIVE_CC_NUMBER, cc_position))
print("CC", cc_position)
cc_val_text_area.text = str(cc_position)
last_position = position
macropad.encoder_switch_debounced.update() # check the encoder switch w debouncer
if macropad.encoder_switch_debounced.pressed:
print("Mod")
push_text_area.text = "[.]"
modifier = True
macropad.pixels.brightness = DIM
if macropad.encoder_switch_debounced.released:
modifier = False
push_text_area.text = "[o]"
macropad.pixels.brightness = BRIGHT
continue
num = key_event.key_number
if key_event.pressed and not modifier:
midi.send(NoteOn(LIVE_NOTES[num], 127))
print("\nsent note", LIVE_NOTES[num], "\n")
if key_event.pressed and modifier:
midi.send(NoteOn(MODIFIER_NOTES[num], 127))
if key_event.released and not modifier:
midi.send(NoteOff(LIVE_NOTES[num], 0))
if key_event.released and modifier:
midi.send(NoteOff(MODIFIER_NOTES[num], 0))
macropad.pixels.show()