Skip to content

Commit 272bf8e

Browse files
authored
Merge pull request #1598 from jedgarpark/macropad-ableton
first commit Live launcher code
2 parents c6cdb94 + fa4375a commit 272bf8e

File tree

1 file changed

+252
-0
lines changed

1 file changed

+252
-0
lines changed

Macropad_Ableton/code.py

Lines changed: 252 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,252 @@
1+
# SPDX-FileCopyrightText: 2021 John Park for Adafruit Industries
2+
# SPDX-License-Identifier: MIT
3+
# Ableton Live Macropad Launcher
4+
# In Ableton, choose "Launchpad Mini Mk3" as controller with MacroPad 2040 as in and out
5+
import board
6+
from digitalio import DigitalInOut, Pull
7+
import keypad
8+
import displayio
9+
import terminalio
10+
import neopixel
11+
import rotaryio
12+
from adafruit_simplemath import constrain
13+
from adafruit_display_text import label
14+
from adafruit_debouncer import Debouncer
15+
import usb_midi
16+
import adafruit_midi
17+
from adafruit_midi.control_change import ControlChange
18+
from adafruit_midi.note_off import NoteOff
19+
from adafruit_midi.note_on import NoteOn
20+
from adafruit_midi.midi_message import MIDIUnknownEvent
21+
22+
TITLE_TEXT = "Live Launcher 2040"
23+
print(TITLE_TEXT)
24+
TRACK_NAMES = ["DRUM", "BASS", "SYNTH"] # Customize these
25+
LIVE_CC_NUMBER = 74 # CC number to send w encoder
26+
FADER_TEXT = "cutoff" # change for intended CC name
27+
28+
29+
midi = adafruit_midi.MIDI(
30+
midi_in=usb_midi.ports[0],
31+
in_channel=(0, 1, 2),
32+
midi_out=usb_midi.ports[1],
33+
out_channel=0
34+
)
35+
36+
# ---Specify key pins---
37+
key_pins = (
38+
board.KEY1, board.KEY2, board.KEY3,
39+
board.KEY4, board.KEY5, board.KEY6,
40+
board.KEY7, board.KEY8, board.KEY9,
41+
board.KEY10, board.KEY11, board.KEY12
42+
)
43+
# ---Create keypad object---
44+
keys = keypad.Keys(key_pins, value_when_pressed=False, pull=True)
45+
46+
# ---Official Launchpad colors---
47+
LP_COLORS = (
48+
0x000000, 0x101010, 0x202020, 0x3f3f3f, 0x3f0f0f, 0x3f0000, 0x200000, 0x100000,
49+
0x3f2e1a, 0x3f0f00, 0x200800, 0x100400, 0x3f2b0b, 0x3f3f00, 0x202000, 0x101000,
50+
0x213f0c, 0x143f00, 0x0a2000, 0x051000, 0x123f12, 0x003f00, 0x002000, 0x001000,
51+
0x123f17, 0x003f06, 0x002003, 0x001001, 0x123f16, 0x003f15, 0x00200b, 0x001006,
52+
0x123f2d, 0x003f25, 0x002012, 0x001009, 0x12303f, 0x00293f, 0x001520, 0x000b10,
53+
0x12213f, 0x00153f, 0x000b20, 0x000610, 0x0b093f, 0x00003f, 0x000020, 0x000010,
54+
0x1a0d3e, 0x0b003f, 0x060020, 0x030010, 0x3f0f3f, 0x3f003f, 0x200020, 0x100010,
55+
0x3f101b, 0x3f0014, 0x20000a, 0x100005, 0x3f0300, 0x250d00, 0x1d1400, 0x080d01,
56+
0x000e00, 0x001206, 0x00051b, 0x00003f, 0x001113, 0x040032, 0x1f1f1f, 0x070707,
57+
0x3f0000, 0x2e3f0b, 0x2b3a01, 0x183f02, 0x032200, 0x003f17, 0x00293f, 0x000a3f,
58+
0x06003f, 0x16003f, 0x2b061e, 0x0a0400, 0x3f0c00, 0x213701, 0x1c3f05, 0x003f00,
59+
0x0e3f09, 0x153f1b, 0x0d3f32, 0x16223f, 0x0c1430, 0x1a1439, 0x34073f, 0x3f0016,
60+
0x3f1100, 0x2d2900, 0x233f00, 0x201601, 0x0e0a00, 0x001203, 0x031308, 0x05050a,
61+
0x050716, 0x190e06, 0x200000, 0x36100a, 0x351204, 0x3f2f09, 0x27370b, 0x192c03,
62+
0x05050b, 0x36341a, 0x1f3a22, 0x26253f, 0x23193f, 0x0f0f0f, 0x1c1c1c, 0x373f3f,
63+
0x270000, 0x0d0000, 0x063300, 0x011000, 0x2d2b00, 0x0f0c00, 0x2c1400, 0x120500,
64+
)
65+
66+
LP_PADS = {
67+
81: 0, 82: 1, 83: 2,
68+
71: 3, 72: 4, 73: 5,
69+
61: 6, 62: 7, 63: 8,
70+
51: 9, 52: 10, 53: 11
71+
}
72+
73+
LIVE_NOTES = [81, 82, 83, 71, 72, 73, 61, 62, 63, 51, 52, 53]
74+
CC_OFFSET = 20
75+
modifier = False # use to add encoder switch modifier to keys for clip mute
76+
MODIFIER_NOTES = [41, 42, 43, 41, 42, 43, 41, 42, 43, 41, 42, 43] # blank row in Live
77+
78+
# ---Encoder setup---
79+
encoder = rotaryio.IncrementalEncoder(board.ENCODER_B, board.ENCODER_A)
80+
sw = DigitalInOut(board.ENCODER_SWITCH)
81+
sw.switch_to_input(pull=Pull.UP)
82+
switch = Debouncer(sw)
83+
last_position = 0 # encoder position state
84+
85+
# ---NeoPixel setup---
86+
BRIGHT = 0.125
87+
DIM = 0.0625
88+
pixels = neopixel.NeoPixel(board.NEOPIXEL, 12, brightness=BRIGHT, auto_write=False)
89+
90+
# ---Display setup---
91+
display = board.DISPLAY
92+
screen = displayio.Group(max_size=12)
93+
display.show(screen)
94+
WIDTH = 128
95+
HEIGHT = 64
96+
FONT = terminalio.FONT
97+
# Draw a title label
98+
title = TITLE_TEXT
99+
title_area = label.Label(FONT, text=title, color=0xFFFFFF,x=6, y=3)
100+
screen.append(title_area)
101+
102+
# --- create display strings and positions
103+
x1 = 5
104+
x2 = 35
105+
x3 = 65
106+
y1 = 17
107+
y2 = 27
108+
y3 = 37
109+
y4 = 47
110+
y5 = 57
111+
112+
# ---Push knob text setup
113+
push_text_area = label.Label(FONT, text="[o]", color=0xffffff, x=WIDTH-22, y=y2)
114+
screen.append(push_text_area)
115+
116+
# ---CC knob text setup
117+
fader_text_area = label.Label(FONT, text=FADER_TEXT, color=0xffffff, x=WIDTH - 42, y=y4)
118+
screen.append(fader_text_area)
119+
# --- cc value display
120+
cc_val_text = str(CC_OFFSET)
121+
cc_val_text_area = label.Label(FONT, text=cc_val_text, color=0xffffff, x=WIDTH - 20, y=y5)
122+
screen.append(cc_val_text_area)
123+
124+
label_data = (
125+
# text, x, y
126+
(TRACK_NAMES[0], x1, y1), (TRACK_NAMES[1], x2, y1), (TRACK_NAMES[2], x3, y1),
127+
(".", x1, y2), (".", x2, y2), (".", x3, y2),
128+
(".", x1, y3), (".", x2, y3), (".", x3, y3),
129+
(".", x1, y4), (".", x2, y4), (".", x3, y4),
130+
(".", x1, y5), (".", x2, y5), (".", x3, y5)
131+
)
132+
133+
labels = []
134+
135+
for data in label_data:
136+
text, x, y = data
137+
label_area = label.Label(FONT, text=text, color=0xffffff)
138+
group = displayio.Group(max_size=4, x=x, y=y)
139+
group.append(label_area)
140+
screen.append(group)
141+
labels.append(label_area) # these can be individually addressed later
142+
143+
num = 1
144+
145+
while True:
146+
msg_in = midi.receive()
147+
if isinstance(msg_in, NoteOn) and msg_in.velocity != 0:
148+
print(
149+
"received NoteOn",
150+
"from channel",
151+
msg_in.channel + 1,
152+
"MIDI note",
153+
msg_in.note,
154+
"velocity",
155+
msg_in.velocity,
156+
"\n"
157+
)
158+
# send neopixel lightup code to key, text to display
159+
if msg_in.note < 84 and msg_in.note > 50:
160+
pixels[LP_PADS[msg_in.note]] = LP_COLORS[msg_in.velocity]
161+
pixels.show()
162+
if msg_in.velocity == 21: # active pad is indicated by Live as vel 21
163+
labels[LP_PADS[msg_in.note]+3].text = "o"
164+
else:
165+
labels[LP_PADS[msg_in.note]+3].text = "."
166+
167+
elif (
168+
isinstance(msg_in, NoteOff)
169+
or isinstance(msg_in, NoteOn)
170+
and msg_in.velocity == 0
171+
):
172+
print(
173+
"received NoteOff",
174+
"from channel",
175+
msg_in.channel + 1,
176+
"MIDI note",
177+
msg_in.note,
178+
"velocity",
179+
msg_in.velocity,
180+
"\n"
181+
)
182+
183+
elif isinstance(msg_in, ControlChange):
184+
print(
185+
"received CC",
186+
"from channel",
187+
msg_in.channel + 1,
188+
"controller",
189+
msg_in.control,
190+
"value",
191+
msg_in.value,
192+
"\n"
193+
)
194+
195+
elif isinstance(msg_in, MIDIUnknownEvent):
196+
# Message are only known if they are imported
197+
print("Unknown MIDI event status ", msg_in.status)
198+
199+
elif msg_in is not None:
200+
midi.send(msg_in)
201+
202+
event = keys.events.get() # check for keypad events
203+
204+
if not event: # Event is None; no keypad event happened, do other stuff
205+
206+
position = encoder.position # store encoder position state
207+
cc_position = constrain((position + CC_OFFSET), 0, 127) # lock to cc range
208+
if last_position is None or position != last_position:
209+
210+
if position < last_position:
211+
midi.send(ControlChange(LIVE_CC_NUMBER, cc_position))
212+
print("CC", cc_position)
213+
cc_val_text_area.text = str(cc_position)
214+
215+
elif position > last_position:
216+
midi.send(ControlChange(LIVE_CC_NUMBER, cc_position))
217+
print("CC", cc_position)
218+
cc_val_text_area.text = str(cc_position)
219+
last_position = position
220+
221+
switch.update() # check the encoder switch w debouncer
222+
if switch.fell:
223+
print("Mod")
224+
push_text_area.text = "[.]"
225+
modifier = True
226+
pixels.brightness = DIM
227+
pixels.show()
228+
229+
if switch.rose:
230+
modifier = False
231+
push_text_area.text = "[o]"
232+
pixels.brightness = BRIGHT
233+
pixels.show()
234+
235+
continue
236+
237+
num = event.key_number
238+
239+
if event.pressed and not modifier:
240+
midi.send(NoteOn(LIVE_NOTES[num], 127))
241+
print("\nsent note", LIVE_NOTES[num], "\n")
242+
243+
if event.pressed and modifier:
244+
midi.send(NoteOn(MODIFIER_NOTES[num], 127))
245+
246+
if event.released and not modifier:
247+
midi.send(NoteOff(LIVE_NOTES[num], 0))
248+
249+
if event.released and modifier:
250+
midi.send(NoteOff(MODIFIER_NOTES[num], 0))
251+
252+
pixels.show()

0 commit comments

Comments
 (0)