From 3faa041fc5b1c346664540d091a2837597921068 Mon Sep 17 00:00:00 2001 From: Tristan Rowley Date: Sun, 19 Jan 2020 16:38:10 +0000 Subject: [PATCH] Rough Launchpad custom controls with feedback --- .../midi_action_mapping_Launchpad.json | 266 ++++++++++++++++++ plugins/MidiFeedbackAPCKey25Plugin.py | 18 +- plugins/MidiFeedbackLaunchpadPlugin.py | 48 ++++ 3 files changed, 327 insertions(+), 5 deletions(-) create mode 100644 json_objects/midi_action_mapping_Launchpad.json create mode 100644 plugins/MidiFeedbackLaunchpadPlugin.py diff --git a/json_objects/midi_action_mapping_Launchpad.json b/json_objects/midi_action_mapping_Launchpad.json new file mode 100644 index 0000000..fc49402 --- /dev/null +++ b/json_objects/midi_action_mapping_Launchpad.json @@ -0,0 +1,266 @@ +{ + "control_change 0": { + "DEFAULT": ["set_the_shader_param_0_layer_offset_0_continuous"], + "NAV_DETOUR": ["set_detour_mix_continuous"] + }, + "control_change 1": { + "DEFAULT": ["set_the_shader_param_1_layer_offset_0_continuous"], + "NAV_DETOUR": ["set_detour_speed_position_continuous"] + }, + "control_change 2": { + "DEFAULT": ["set_the_shader_param_2_layer_offset_0_continuous"], + "NAV_DETOUR": ["set_detour_start_continuous"] + }, + "control_change 3": { + "DEFAULT": ["set_the_shader_param_3_layer_offset_0_continuous"], + "NAV_DETOUR": ["set_detour_end_continuous"] + }, + "control_change 4": { + "DEFAULT": ["set_the_shader_param_0_layer_offset_1_continuous"] + }, + "control_change 5": { + "DEFAULT": ["set_the_shader_param_1_layer_offset_1_continuous"] + }, + "control_change 6": { + "DEFAULT": ["set_the_shader_param_2_layer_offset_1_continuous"] + }, + "control_change 7": { + "DEFAULT": ["set_the_shader_param_3_layer_offset_1_continuous"] + }, + "control_change 8": { + "DEFAULT": ["set_the_shader_param_0_layer_offset_2_continuous"] + }, + "control_change 9": { + "DEFAULT": ["set_the_shader_param_1_layer_offset_2_continuous"] + }, + "control_change 10": { + "DEFAULT": ["set_the_shader_param_2_layer_offset_2_continuous"] + }, + "control_change 11": { + "DEFAULT": ["set_the_shader_param_3_layer_offset_2_continuous"] + }, + "control_change 48": { + "DEFAULT": ["set_the_shader_param_0_layer_offset_0_continuous","set_strobe_amount_continuous"], + "NAV_DETOUR": ["set_detour_speed_position_continuous"] + }, + "control_change 49": { + "DEFAULT": ["set_the_shader_param_1_layer_offset_0_continuous","set_shader_speed_layer_0_amount"], + "NAV_DETOUR": ["set_detour_start_continuous"] + }, + "control_change 50": { + "DEFAULT": ["set_the_shader_param_2_layer_offset_0_continuous","set_shader_speed_layer_1_amount"], + "NAV_DETOUR": ["set_detour_end_continuous"] + }, + "control_change 51": { + "DEFAULT": ["set_the_shader_param_3_layer_offset_0_continuous","set_shader_speed_layer_2_amount"], + "NAV_DETOUR": ["set_detour_end_continuous"] + }, + "control_change 52": { + "DEFAULT": ["set_the_shader_param_0_layer_offset_1_continuous","set_param_0_layer_offset_0_modulation_level_continuous"], + "NAV_DETOUR": ["set_detour_speed_position_continuous"] + }, + "control_change 53": { + "DEFAULT": ["set_the_shader_param_1_layer_offset_1_continuous","set_param_1_layer_offset_0_modulation_level_continuous"], + "NAV_DETOUR": ["set_detour_start_continuous"] + }, + "control_change 54": { + "DEFAULT": ["set_the_shader_param_2_layer_offset_1_continuous","set_param_2_layer_offset_0_modulation_level_continuous"], + "NAV_DETOUR": ["set_detour_end_continuous"] + }, + "control_change 55": { + "DEFAULT": ["set_the_shader_param_3_layer_offset_1_continuous","set_param_3_layer_offset_0_modulation_level_continuous"], + "NAV_DETOUR": ["set_detour_end_continuous"] + }, + "control_change 56": { + "DEFAULT": ["set_the_shader_param_0_layer_offset_2_continuous"], + "NAV_DETOUR": ["set_detour_speed_position_continuous"] + }, + "control_change 57": { + "DEFAULT": ["set_the_shader_param_1_layer_offset_2_continuous"], + "NAV_DETOUR": ["set_detour_start_continuous"] + }, + "control_change 58": { + "DEFAULT": ["set_the_shader_param_2_layer_offset_2_continuous"], + "NAV_DETOUR": ["set_detour_end_continuous"] + }, + "control_change 59": { + "DEFAULT": ["set_the_shader_param_3_layer_offset_2_continuous"], + "NAV_DETOUR": ["set_detour_end_continuous"] + }, + "note_on 64": { + "DEFAULT": ["clear_automation"] + }, + "note_on 65": { + "DEFAULT": ["toggle_pause_automation"] + }, + "note_on 66": { + "DEFAULT": ["toggle_record_automation"] + }, + "note_on 67": { + "DEFAULT": ["toggle_overdub_automation"] + }, + "note_on 68": { + "DEFAULT": ["store_next_preset"] + }, + "note_on 69": { + "DEFAULT": ["store_current_preset","clear_current_preset"] + }, + "note_on 112": { + "DEFAULT": ["switch_to_preset_0","select_preset_0"] + }, + "note_on 113": { + "DEFAULT": ["switch_to_preset_1","select_preset_1"] + }, + "note_on 114": { + "DEFAULT": ["switch_to_preset_2","select_preset_2"] + }, + "note_on 115": { + "DEFAULT": ["switch_to_preset_3","select_preset_3"] + }, + "note_on 116": { + "DEFAULT": ["switch_to_preset_4","select_preset_4"] + }, + "note_on 117": { + "DEFAULT": ["switch_to_preset_5","select_preset_5"] + }, + "note_on 118": { + "DEFAULT": ["switch_to_preset_6","select_preset_6"] + }, + "note_on 119": { + "DEFAULT": ["switch_to_preset_7","select_preset_7"] + }, + "note_on 81": { + "DEFAULT": ["","reset_selected_modulation"] + }, + "note_on 8": { + "DEFAULT": ["toggle_shader_layer_0","select_shader_modulation_slot_0"] + }, + "note_on 24": { + "DEFAULT": ["toggle_shader_layer_1","select_shader_modulation_slot_1"] + }, + "note_on 40": { + "DEFAULT": ["toggle_shader_layer_2","select_shader_modulation_slot_2"] + }, + "note_on 85": { + "DEFAULT": ["toggle_feedback","select_shader_modulation_slot_3"] + }, + "note_on 86": { + "DEFAULT": ["toggle_capture_preview"] + }, + "note_on 70": { + "DEFAULT": ["previous_shader_layer"] + }, + "note_on 71": { + "DEFAULT": ["next_shader_layer"] + }, + "note_on 96": { + "DEFAULT": ["toggle_automation_clip_0","select_automation_clip_0"] + }, + "note_on 97": { + "DEFAULT": ["toggle_automation_clip_1","select_automation_clip_1"] + }, + "note_on 98": { + "DEFAULT": ["toggle_automation_clip_2","select_automation_clip_2"] + }, + "note_on 99": { + "DEFAULT": ["toggle_automation_clip_3","select_automation_clip_3"] + }, + "note_on 100": { + "DEFAULT": ["toggle_automation_clip_4","select_automation_clip_4"] + }, + "note_on 101": { + "DEFAULT": ["toggle_automation_clip_5","select_automation_clip_5"] + }, + "note_on 102": { + "DEFAULT": ["toggle_automation_clip_6","select_automation_clip_6"] + }, + "note_on 103": { + "DEFAULT": ["toggle_automation_clip_7","select_automation_clip_7"] + }, + + "note_on 0": { + "DEFAULT": ["play_shader_0_0"] + }, + "note_on 1": { + "DEFAULT": ["play_shader_0_1"] + }, + "note_on 2": { + "DEFAULT": ["play_shader_0_2"] + }, + "note_on 3": { + "DEFAULT": ["play_shader_0_3"] + }, + "note_on 4": { + "DEFAULT": ["play_shader_0_4"] + }, + "note_on 5": { + "DEFAULT": ["play_shader_0_5"] + }, + "note_on 6": { + "DEFAULT": ["play_shader_0_6"] + }, + "note_on 7": { + "DEFAULT": ["play_shader_0_7"] + }, + "note_on 16": { + "DEFAULT": ["play_shader_1_0"] + }, + "note_on 17": { + "DEFAULT": ["play_shader_1_1"] + }, + "note_on 18": { + "DEFAULT": ["play_shader_1_2"] + }, + "note_on 19": { + "DEFAULT": ["play_shader_1_3"] + }, + "note_on 20": { + "DEFAULT": ["play_shader_1_4"] + }, + "note_on 21": { + "DEFAULT": ["play_shader_1_5"] + }, + "note_on 22": { + "DEFAULT": ["play_shader_1_6"] + }, + "note_on 23": { + "DEFAULT": ["play_shader_1_7"] + }, + "note_on 32": { + "DEFAULT": ["play_shader_2_0"] + }, + "note_on 33": { + "DEFAULT": ["play_shader_2_1"] + }, + "note_on 34": { + "DEFAULT": ["play_shader_2_2"] + }, + "note_on 35": { + "DEFAULT": ["play_shader_2_3"] + }, + "note_on 36": { + "DEFAULT": ["play_shader_2_4"] + }, + "note_on 37": { + "DEFAULT": ["play_shader_2_5"] + }, + "note_on 38": { + "DEFAULT": ["play_shader_2_6"] + }, + "note_on 39": { + "DEFAULT": ["play_shader_2_7"] + }, + "note_on 120": { + "DEFAULT": ["function_on"] + }, + "note_off 120": { + "DEFAULT": ["function_off"] + }, + "note_on 91": { + "DEFAULT": ["send_serial_macro_0","send_serial_macro_1"] + }, + "note_on 93": { + "DEFAULT": ["send_serial_string_hellO","send_random_settings"] + } +} + diff --git a/plugins/MidiFeedbackAPCKey25Plugin.py b/plugins/MidiFeedbackAPCKey25Plugin.py index 2b85114..f393eb4 100644 --- a/plugins/MidiFeedbackAPCKey25Plugin.py +++ b/plugins/MidiFeedbackAPCKey25Plugin.py @@ -13,21 +13,29 @@ class MidiFeedbackAPCKey25Plugin(MidiFeedbackPlugin): def get_note(self,action,default): bind = self.pc.midi_input.find_binding_for_action(action) - if bind and 'note ' in bind: - return int(play_bind.split(' ')[1]) + if bind is not None and 'note_on' in bind: + return int(bind.split(' ')[1]) else: + print ("bind is %s, returning default" % bind) return default def set_midi_device(self, device): super().set_midi_device(device) self.last_state = None + self.init_notes() + + def init_notes(self): self.NOTE_PLAY_SHADER = self.get_note('play_shader_0_0',32) self.NOTE_SHADER_FEEDBACK = self.get_note('toggle_feedback',85) self.NOTE_SCENE_LAUNCH_COLUMN = self.get_note('toggle_shader_layer_0',82) self.NOTE_MODULATION_COLUMN = self.get_note('select_shader_modulation_slot_0', self.NOTE_SCENE_LAUNCH_COLUMN) self.NOTE_CAPTURE_PREVIEW = self.get_note('toggle_capture_preview', 86) self.NOTE_CLIP_STATUS_ROW = self.get_note('toggle_automation_clip_0', 8) + self.NOTE_SHADER_PRESET_ROW = self.get_note('select_preset_0', 0) + self.NOTE_SHADER_LAYER_ON = [ + self.get_note('toggle_shader_layer_%i',8+(i*16)) for i in range(0,3) + ] def supports_midi_feedback(self, device_name): supported_devices = ['APC Key 25'] @@ -63,10 +71,10 @@ class MidiFeedbackAPCKey25Plugin(MidiFeedbackPlugin): self.set_status(note=(self.NOTE_PLAY_SHADER-(layer)*8)+slot, velocity=self.COLOUR_OFF) def feedback_shader_layer_on(self, layer): - self.set_status(note=self.NOTE_SCENE_LAUNCH_COLUMN+layer, velocity=self.COLOUR_GREEN) + self.set_status(note=self.NOTE_SHADER_LAYER_ON[layer], velocity=self.COLOUR_GREEN) def feedback_shader_layer_off(self, layer): - self.set_status(note=self.NOTE_SCENE_LAUNCH_COLUMN+layer, velocity=self.COLOUR_OFF) + self.set_status(note=self.NOTE_SHADER_LAYER_ON[layer], velocity=self.COLOUR_OFF) def feedback_show_layer(self, layer): self.set_status(note=70, velocity=layer) @@ -147,7 +155,7 @@ class MidiFeedbackAPCKey25Plugin(MidiFeedbackPlugin): if plugin.presets[pad] is None: colour = self.COLOUR_RED colour += self.BLINK - self.set_status(command='note_on', note=pad, velocity=colour) + self.set_status(command='note_on', note=self.NOTE_SHADER_PRESET_ROW+pad, velocity=colour) except Exception as e: pass #print ("Warning: Failed when running plugin feedback for ShaderQuickPresetPlugin:\t%s" % str(e)) diff --git a/plugins/MidiFeedbackLaunchpadPlugin.py b/plugins/MidiFeedbackLaunchpadPlugin.py new file mode 100644 index 0000000..f8a2674 --- /dev/null +++ b/plugins/MidiFeedbackLaunchpadPlugin.py @@ -0,0 +1,48 @@ +from data_centre import plugin_collection +from data_centre.plugin_collection import MidiFeedbackPlugin +import mido +from plugins.MidiFeedbackAPCKey25Plugin import MidiFeedbackAPCKey25Plugin + +class MidiFeedbackLaunchpadPlugin(MidiFeedbackAPCKey25Plugin): + disabled = False + + status = {} + + def __init__(self, plugin_collection): + super().__init__(plugin_collection) + self.description = 'Outputs feedback to Novation Launchpad' + + def init_notes(self): + self.NOTE_PLAY_SHADER = self.get_note('play_shader_0_0',0) + self.NOTE_SHADER_FEEDBACK = self.get_note('toggle_feedback',85) + self.NOTE_SCENE_LAUNCH_COLUMN = self.get_note('toggle_shader_layer_0',82) + self.NOTE_MODULATION_COLUMN = self.get_note('select_shader_modulation_slot_0', self.NOTE_SCENE_LAUNCH_COLUMN) + self.NOTE_CAPTURE_PREVIEW = self.get_note('toggle_capture_preview', 86) + self.NOTE_CLIP_STATUS_ROW = self.get_note('toggle_automation_clip_0', 8) + self.NOTE_SHADER_PRESET_ROW = self.get_note('select_preset_0', 112) + self.NOTE_SHADER_LAYER_ON = [ + self.get_note('toggle_shader_layer_%i',8+(i*16)) for i in range(0,3) + ] + + def supports_midi_feedback(self, device_name): + supported_devices = ['Launchpad'] + for supported_device in supported_devices: + if device_name.startswith(supported_device): + return True + + def feedback_shader_on(self, layer, slot, colour=None): + if colour is None: colour = self.COLOUR_GREEN + self.set_status(note=(self.NOTE_PLAY_SHADER+(layer)*16)+slot, velocity=int(colour)) + + def feedback_shader_off(self, layer, slot): + self.set_status(note=(self.NOTE_PLAY_SHADER+(layer)*16)+slot, velocity=self.COLOUR_OFF) + + # TODO: make these colours correct+sensible + BLINK = 1 + COLOUR_OFF = 0 + COLOUR_GREEN = 8 #1 + COLOUR_GREEN_BLINK = 15 + COLOUR_RED = 32 + COLOUR_RED_BLINK = 47 + COLOUR_AMBER = 64 + COLOUR_AMBER_BLINK = 80