-
Notifications
You must be signed in to change notification settings - Fork 12
/
Copy pathdevice_arturia_keylab_mkii_midi.py
223 lines (187 loc) · 8.76 KB
/
device_arturia_keylab_mkii_midi.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
# name=Arturia Keylab mkII (MIDI)
# url=https://github.com/rjuang/flstudio-arturia-keylab-mk2
# receiveFrom=Arturia Keylab mkII DAW (MIDIIN2/MIDIOUT2)
import channels
import config
import device
import arturia_leds
import arturia_midi
import version
from arturia_leds import ArturiaLights
from arturia_scheduler import Scheduler
from arturia_recorder import Recorder
from arturia_savedata import SaveData
from debug import log
# --------------------[ MIDI Script Integration Events for FL Studio ]---------------------------
# When "Analog Lab" mode is selected, MIDI commands for keys related to Analog Lab are sent here.
# All buttons have status code 176. These just need to be re-routed to the Analog Lab plugin
# Unfortunately, the only way to properly do this is to have the user manually configure the plugin to a free midiIn
# port number and forward the notes there.
MIDI_DRUM_PAD_STATUS_ON = 169
MIDI_DRUM_PAD_STATUS_OFF = 137
MIDI_DRUM_PAD_DATA1_MIN = 36
MIDI_DRUM_PAD_DATA1_MAX = 51
REC_BUTTON_ID = 95
STOP_BUTTON_ID = 93
def dispatch_to_other_scripts(payload):
arturia_midi.dispatch_message_to_other_scripts(
arturia_midi.INTER_SCRIPT_STATUS_BYTE, 0, 0, payload=payload)
_scheduler = Scheduler()
_savedata = SaveData()
_recorder = Recorder(_scheduler, _savedata)
_lights = ArturiaLights(send_fn=dispatch_to_other_scripts)
_pad_recording_led = False
_pad_recording_task = None
_sustain_enabled = False
_buttons_held = set()
_fallback_pad_values = {}
_longpress_status = {}
# Drop notes that match the specified critiria
_drop_note = None
_interscript_idle_disabled = False
def OnInit():
print('Loaded MIDI script for Arturia Keylab mkII MIDI (ver %d)' % version.CHANGE_DATE)
def OnRefresh(flags):
_savedata.Load()
def OnShortPressDrumPad(event):
global _pad_recording_task
note = event.data1
velocity = event.data2
if _recorder.IsRecording():
log('midi', 'Stop Recording. short press detected for %s' % str(note))
_recorder.StopRecording()
_scheduler.CancelTask(_pad_recording_task)
_pad_recording_task = None
elif event.status == 153:
global _sustain_enabled
log('midi', 'Play. short press detected for %s. Sustain=%s' % (str(note), _sustain_enabled))
if not _recorder.Play(note, loop=_sustain_enabled):
if config.ENABLE_MPC_STYLE_PADS:
index = note - 0x24
if index < channels.channelCount():
# 0x3C corresponds to middle C
channels.midiNoteOn(index, 0x3C, velocity)
else:
channels.midiNoteOn(channels.selectedChannel(), note, _fallback_pad_values[note])
elif event.status == 137:
if not _recorder.HasRecording(note):
if config.ENABLE_MPC_STYLE_PADS:
index = note - 0x24
if index < channels.channelCount():
# 0x3C corresponds to middle C
channels.midiNoteOn(index, 0x3C, 0)
else:
channels.midiNoteOn(channels.selectedChannel(), note, 0)
def OnLongPressDrumPad(note):
global _pad_recording_led, _drop_note
if _recorder.IsRecording():
log('midi', 'Stop Recording. Long press detected for %s' % str(note))
_recorder.StopRecording()
else:
log('midi', 'Start Recording. Long press detected for %s' % str(note))
if config.ENABLE_MPC_STYLE_PADS:
index = note - 0x24
if index < channels.channelCount():
# 0x3C corresponds to middle C
channels.midiNoteOn(index, 0x3C, 0)
else:
channels.midiNoteOn(channels.selectedChannel(), note, 0)
_drop_note = note
_recorder.StartRecording(note)
_pad_recording_led = False
BlinkLight(note)
def BlinkLight(note):
global _pad_recording_led, _pad_recording_task
if _recorder.IsRecording():
led_id = ArturiaLights.getPadLedId(note)
_pad_recording_led = not _pad_recording_led
_lights.SetLights({led_id: ArturiaLights.AsOnOffByte(_pad_recording_led)})
_pad_recording_task = _scheduler.ScheduleTask(lambda: BlinkLight(note), delay=500)
def OnIdle():
processIdle(disable_interscript_idle=True)
def processIdle(disable_interscript_idle=False):
global _interscript_idle_disabled
_scheduler.Idle()
if disable_interscript_idle and not _interscript_idle_disabled:
log('midi', 'Disabling interscript idle.')
_interscript_idle_disabled = True
arturia_midi.dispatch_message_to_other_scripts(
arturia_midi.INTER_SCRIPT_STATUS_BYTE,
arturia_midi.INTER_SCRIPT_DATA1_UPDATE_STATE,
arturia_midi.INTER_SCRIPT_DATA2_STATE_IDLE_AVAILABLE)
def OnMidiMsg(event):
global _drop_note, _buttons_held, _recorder
note = event.data1
log_msg = True
should_color_pads = ((not arturia_leds.ESSENTIAL_KEYBOARD and config.ENABLE_MK2_COLORIZE_PAD_LIGHTS) or
(arturia_leds.ESSENTIAL_KEYBOARD and config.ENABLE_COLORIZE_BANK_LIGHTS))
if (event.status == MIDI_DRUM_PAD_STATUS_ON and _drop_note != event.data1) or event.status == 153:
if MIDI_DRUM_PAD_DATA1_MIN <= note <= MIDI_DRUM_PAD_DATA1_MAX:
# Make sure to trigger short press event on the event down so as to avoid delay.
event.handled = True
if event.status == 153:
_fallback_pad_values[event.data1] = event.data2
if (note not in _longpress_status
and REC_BUTTON_ID not in _buttons_held
and not config.ENABLE_LONG_PRESS_SUSTAIN_ON_PADS):
log('midi', 'Schedule long press detection for %s' % str(note))
_longpress_status[note] = _scheduler.ScheduleTask(lambda: OnLongPressDrumPad(note), delay=1000)
if REC_BUTTON_ID in _buttons_held:
OnLongPressDrumPad(note)
else:
OnShortPressDrumPad(event)
if should_color_pads:
channel_idx = channels.selectedChannel()
color_val = ArturiaLights.fullColor(channels.getChannelColor(channel_idx))
led_id = ArturiaLights.getPadLedId(note)
_lights.SetLights({led_id: color_val}, rgb=True)
elif event.status == MIDI_DRUM_PAD_STATUS_OFF:
event.handled = True
if MIDI_DRUM_PAD_DATA1_MIN <= event.data1 <= MIDI_DRUM_PAD_DATA1_MAX:
if event.data1 == _drop_note:
_drop_note = None
else:
OnShortPressDrumPad(event)
if event.data1 in _longpress_status:
if _scheduler.CancelTask(_longpress_status[event.data1]):
log('midi', 'Long press canceled for %s' % str(event.data1))
del _longpress_status[event.data1]
if should_color_pads:
channel_idx = channels.selectedChannel()
color_val = ArturiaLights.fadedColor(channels.getChannelColor(channel_idx))
led_id = ArturiaLights.getPadLedId(note)
_lights.SetLights({led_id: color_val}, rgb=True)
elif 128 <= event.status <= 159: # Midi note on
_recorder.OnMidiNote(event)
elif event.status == arturia_midi.INTER_SCRIPT_STATUS_BYTE:
if event.data1 == arturia_midi.INTER_SCRIPT_DATA1_BTN_DOWN_CMD:
_buttons_held.add(event.data2)
if event.data2 == STOP_BUTTON_ID:
if _recorder.IsRecording():
_recorder.StopRecording()
else:
_recorder.StopPlaying()
elif event.data1 == arturia_midi.INTER_SCRIPT_DATA1_BTN_UP_CMD and event.data2 in _buttons_held:
_buttons_held.remove(event.data2)
elif event.data1 == arturia_midi.INTER_SCRIPT_DATA1_IDLE_CMD:
processIdle()
# All inter-cmd messages should be marked handled to ensure they do not contribute to influencing FL Studio
# state
event.handled = True
else:
if 0xB0 <= event.status <= 0xBF:
if event.data1 == 118 and event.data2 == 127:
# User switched to Analog Lab mode.
log('analoglab', 'Switched to Analog Lab.')
if event.data1 == 64:
global _sustain_enabled
_sustain_enabled = (event.data2 == 127)
log('sustain', 'enabled' if _sustain_enabled else 'disabled')
# Forward all remaining events to plugin
port_num = config.PLUGIN_FORWARDING_MIDI_IN_PORT
message = event.status + (event.data1 << 8) + (event.data2 << 16) + (port_num << 24)
device.forwardMIDICC(message, 2)
if log_msg:
log('midi', 'status: %d, data1: %d, data2: %d handled: %s' % (event.status, event.data1, event.data2, str(event.handled)))
def OnDeInit():
print('Unloading plugin...')