-
-
Notifications
You must be signed in to change notification settings - Fork 21
/
SynthesiaKontrol.py
252 lines (222 loc) · 7.79 KB
/
SynthesiaKontrol.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
# The MIT License
#
# Copyright (c) 2018-2021 Olivier Jacques
#
# Synthesia Kontrol: an app to light the keys of Native Instruments
# Komplete Kontrol MK2 keyboard, driven by Synthesia
import time
import hid
import mido
NATIVE_INSTRUMENTS = 0x17cc
INSTR_ADDR = 0x1620
NB_KEYS = 61
MODE = "MK2"
def init():
"""Connect to the keyboard, switch all lights off"""
global bufferC # Buffer with the full key/lights mapping
global device
print("Opening Keyboard device...")
device = hid.device()
try:
device.open(NATIVE_INSTRUMENTS, INSTR_ADDR)
except Exception as e:
print("Error: " + str(e))
quit()
# Write 3 bytes to the device: 0xa0, 0x00, 0x00
# Thanks to @xample for investigating this
device.write([0xa0, 0x00, 0x00])
bufferC = [0x00] * 249
notes_off()
return True
def notes_off():
"""Turn off lights for all notes"""
print("Turn off lights for all notes")
bufferC = [0x00] * 249
if (MODE == "MK2"):
device.write([0x81] + bufferC)
elif (MODE == "MK1"):
device.write([0x82] + bufferC)
else:
print("Error: unsupported mode - should be MK1 or MK2")
quit()
def accept_notes(port):
"""Only let note_on and note_off messages through."""
for message in port:
if message.type in ('note_on', 'note_off'):
yield message
if message.type == 'control_change' and message.channel == 0 and message.control == 16:
if (message.value & 4):
print("User is playing")
if (message.value & 1):
print("Playing Right Hand")
if (message.value & 2):
print("Playing Left Hand")
notes_off()
def CoolDemoSweep(loopcount):
speed = 0.01
for loop in range(0, loopcount):
# Forward
for x in range(0, NB_KEYS-3):
if (MODE == "MK2"):
bufferC = [0x00] * NB_KEYS
bufferC[0] = 0x81
bufferC[x] = (0x04 + x % 4)
bufferC[x+1] = (0x08 + x % 4)
bufferC[x+2] = (0x0c + x % 4)
bufferC[x+3] = (0x10 + x % 4)
else:
bufferC = [0x00] * 3 * NB_KEYS
bufferC[0] = 0x82
bufferC[x*3-2] = 0xFF
device.write(bufferC)
time.sleep(speed)
# Backward
for x in range(NB_KEYS - 1, 0, -1):
if (MODE == "MK2"):
bufferC = [0x00] * NB_KEYS
bufferC[0] = 0x81
bufferC[x] = (0x2c + x % 4)
bufferC[x-1] = (0x2d + x % 4)
bufferC[x-2] = (0x2e + x % 4)
bufferC[x-3] = (0x2f + x % 4)
else:
bufferC = [0x00] * 3 * NB_KEYS
bufferC[0] = 0x82
bufferC[x*3-2] = 0xFF
device.write(bufferC)
time.sleep(speed)
notes_off()
def LightNote(note, status, channel, velocity):
"""Light a note ON or OFF"""
# bufferC[0] = 0x81 # For Komplete Kontrol MK2
offset = OFFSET
key = (note + offset)
if key < 0 or key >= NB_KEYS:
return
# Determine color
if (MODE == "MK2"):
left = 0x2d # Blue
left_thumb = 0x2f # Lighter Blue
right = 0x1d # Green
right_thumb = 0x1f # Lighter Green
elif (MODE == "MK1"):
left = [0x00] + [0x00] + [0xFF] # Blue
left_thumb = [0x00] + [0x00] + [0x80] # Lighter Blue
right = [0x00] + [0xFF] + [0x00] # Green
right_thumb = [0x00] + [0x80] + [0x00] # Lighter Green
else:
print("Error: unsupported mode - should be MK1 or MK2")
quit()
default = right
color = default
# Finger based channel protocol from Synthesia
# Reference: https://www.synthesiagame.com/forum/viewtopic.php?p=43585#p43585
if channel == 0:
# we don't know who or what this note belongs to, but light something up anyway
color = default
if channel >= 1 and channel <= 5:
# left hand fingers, thumb through pinky
if channel == 1:
color = left_thumb
else:
color = left
if channel >= 6 and channel <= 10:
# right hand fingers, thumb through pinky
if channel == 6:
color = right_thumb
else:
color = right
if channel == 11:
# left hand, unknown finger
color = left
if channel == 12:
# right hand, unknown finger
color = right
if status == 'note_on' and velocity != 0:
if (MODE == "MK2"):
bufferC[key] = color # Set color
else:
bufferC[3*key:3*key+3] = color
if (status == 'note_off' or velocity == 0):
# Note off or velocity 0 (equals note off)
if (MODE == "MK2"):
bufferC[key] = 0x00 # Switch key light off
else:
bufferC[3*key:3*key+3] = [0x00] * 3
if (MODE == "MK2"):
device.write([0x81] + bufferC)
else:
device.write([0x82] + bufferC)
if __name__ == '__main__':
"""Main: connect to keyboard, open midi input port, listen to midi"""
print("Select your keyboard (1, 2, 3, ...) and press 'Enter':")
print(" 1-Komplete Kontrol S61 MK2")
print(" 2-Komplete Kontrol S88 MK2")
print(" 3-Komplete Kontrol S49 MK2")
print(" 4-Komplete Kontrol S61 MK1")
print(" 5-Komplete Kontrol S88 MK1")
print(" 6-Komplete Kontrol S49 MK1")
print(" 7-Komplete Kontrol S25 MK1")
keyboard = input()
# Customize here for new keyboards
# Pull requests welcome!
if keyboard == "1":
MODE = "MK2"
INSTR_ADDR = 0x1620 # KK S61 MK2
NB_KEYS = 61
OFFSET = -36
elif keyboard == "2":
MODE = "MK2"
INSTR_ADDR = 0x1630 # KK S88 MK2
NB_KEYS = 88
OFFSET = -21
elif keyboard == "3":
MODE = "MK2"
INSTR_ADDR = 0x1610 # KK S49 MK2
NB_KEYS = 49
OFFSET = -36
elif keyboard == "4":
MODE = "MK1"
INSTR_ADDR = 0x1360 # KK S61 MK1
NB_KEYS = 61
OFFSET = -36
elif keyboard == "5":
MODE = "MK1"
INSTR_ADDR = 0x1410 # KK S88 MK1
NB_KEYS = 88
OFFSET = -21
elif keyboard == "6":
MODE = "MK1"
INSTR_ADDR = 0x1350 # KK S49 MK1
NB_KEYS = 49
OFFSET = -36
elif keyboard == "7":
MODE = "MK1"
INSTR_ADDR = 0x1340 # KK S25 MK1
NB_KEYS = 25
OFFSET = -21
else:
print("Got '" + keyboard +
"' - please type a number which corresponds to your keyboard, then Enter")
quit()
print("Connecting to Komplete Kontrol Keyboard")
connected = init()
portName = ""
if connected:
print("Connected to Komplete Kontrol!")
CoolDemoSweep(2) # Happy dance
print("Opening LoopBe input port")
ports = mido.get_input_names()
for port in ports:
print(" Found MIDI port " + port + "...")
if "LoopBe" in port:
portName = port
if portName == "":
print("Error: can't find 'LoopBe' midi port. Please install LoopBe1 from http://www.nerds.de/en/download.html (Windows) or name your IAC midi device 'LoopBe' (on Mac).")
exit(1)
print("Listening to Midi on LoopBe midi port")
with mido.open_input(portName) as midiPort:
for message in accept_notes(midiPort):
print('Received {}'.format(message))
LightNote(message.note, message.type,
message.channel, message.velocity)