diff --git a/nanoKONTROL2/ChannelStrip.py b/nanoKONTROL2/ChannelStrip.py new file mode 100644 index 00000000..25e4bf70 --- /dev/null +++ b/nanoKONTROL2/ChannelStrip.py @@ -0,0 +1,353 @@ +#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/MackieControl/ChannelStrip.py +from MackieControlComponent import * +from itertools import chain + +class ChannelStrip(MackieControlComponent): + """Represets a Channel Strip of the Mackie Control, which consists out of the""" + + def __init__(self, main_script, strip_index): + MackieControlComponent.__init__(self, main_script) + self.__channel_strip_controller = None + self.__is_touched = False + self.__strip_index = strip_index + self.__stack_offset = 0 + self.__bank_and_channel_offset = 0 + self.__assigned_track = None + self.__v_pot_parameter = None + self.__v_pot_display_mode = VPOT_DISPLAY_SINGLE_DOT + self.__fader_parameter = None + self.__meters_enabled = False + self.__last_meter_value = -1 + self.__send_meter_mode() + self.__within_track_added_or_deleted = False + self.__within_destroy = False + self.set_bank_and_channel_offset(offset=0, show_return_tracks=False, within_track_added_or_deleted=False) + + def destroy(self): + self.__within_destroy = True + if self.__assigned_track: + self.__remove_listeners() + self.__assigned_track = None + self.send_midi((208, 0 + (self.__strip_index << 4))) + self.__meters_enabled = False + self.__send_meter_mode() + self.refresh_state() + MackieControlComponent.destroy(self) + self.__within_destroy = False + + def set_channel_strip_controller(self, channel_strip_controller): + self.__channel_strip_controller = channel_strip_controller + + def strip_index(self): + return self.__strip_index + + def assigned_track(self): + return self.__assigned_track + + def is_touched(self): + return self.__is_touched + + def set_is_touched(self, touched): + self.__is_touched = touched + + def stack_offset(self): + return self.__stack_offset + + def set_stack_offset(self, offset): + """This is the offset that one gets by 'stacking' several MackieControl XTs: + the first is at index 0, the second at 8, etc ... + """ + self.__stack_offset = offset + + def set_bank_and_channel_offset(self, offset, show_return_tracks, within_track_added_or_deleted): + final_track_index = self.__strip_index + self.__stack_offset + offset + self.__within_track_added_or_deleted = within_track_added_or_deleted + if show_return_tracks: + tracks = self.song().return_tracks + else: + tracks = self.song().visible_tracks + if final_track_index < len(tracks): + new_track = tracks[final_track_index] + else: + new_track = None + if new_track != self.__assigned_track: + if self.__assigned_track: + self.__remove_listeners() + self.__assigned_track = new_track + if self.__assigned_track: + self.__add_listeners() + self.refresh_state() + self.__within_track_added_or_deleted = False + + def v_pot_parameter(self): + return self.__v_pot_parameter + + def set_v_pot_parameter(self, parameter, display_mode = VPOT_DISPLAY_SINGLE_DOT): + self.__v_pot_display_mode = display_mode + self.__v_pot_parameter = parameter + if not parameter: + self.unlight_vpot_leds() + + def fader_parameter(self): + return self.__fader_parameter + + def set_fader_parameter(self, parameter): + self.__fader_parameter = parameter + if not parameter: + self.reset_fader() + + def enable_meter_mode(self, Enabled, needs_to_send_meter_mode = True): + self.__meters_enabled = Enabled + if needs_to_send_meter_mode or Enabled: + self.__send_meter_mode() + + def reset_fader(self): + self.send_midi((PB_STATUS + self.__strip_index, 0, 0)) + + def unlight_vpot_leds(self): + self.send_midi((CC_STATUS + 0, 48 + self.__strip_index, 32)) + + def show_full_enlighted_poti(self): + self.send_midi((CC_STATUS + 0, 48 + self.__strip_index, VPOT_DISPLAY_WRAP * 16 + 11)) + + def handle_channel_strip_switch_ids(self, sw_id, value): + if sw_id in range(SID_RECORD_ARM_BASE, SID_RECORD_ARM_BASE + NUM_CHANNEL_STRIPS): + if sw_id - SID_RECORD_ARM_BASE is self.__strip_index: + if value == BUTTON_PRESSED: + if self.song().exclusive_arm: + exclusive = not self.control_is_pressed() + else: + exclusive = self.control_is_pressed() + self.__toggle_arm_track(exclusive) + elif sw_id in range(SID_SOLO_BASE, SID_SOLO_BASE + NUM_CHANNEL_STRIPS): + if sw_id - SID_SOLO_BASE is self.__strip_index: + if value == BUTTON_PRESSED: + if self.song().exclusive_solo: + exclusive = not self.control_is_pressed() + else: + exclusive = self.control_is_pressed() + self.__toggle_solo_track(exclusive) + elif sw_id in range(SID_MUTE_BASE, SID_MUTE_BASE + NUM_CHANNEL_STRIPS): + if sw_id - SID_MUTE_BASE is self.__strip_index: + if value == BUTTON_PRESSED: + self.__toggle_mute_track() + # Commented out to avoid track focus change on fader movement + # Otherwise can cause glitching when moving multiple faders at once + #elif sw_id in range(SID_SELECT_BASE, SID_SELECT_BASE + NUM_CHANNEL_STRIPS): + # if sw_id - SID_SELECT_BASE is self.__strip_index: + # if value == BUTTON_PRESSED: + # self.__select_track() + elif sw_id in range(SID_VPOD_PUSH_BASE, SID_VPOD_PUSH_BASE + NUM_CHANNEL_STRIPS): + if sw_id - SID_VPOD_PUSH_BASE is self.__strip_index: + if value == BUTTON_PRESSED and self.__channel_strip_controller != None: + self.__channel_strip_controller.handle_pressed_v_pot(self.__strip_index, self.__stack_offset) + elif sw_id in fader_touch_switch_ids: + if sw_id - SID_FADER_TOUCH_SENSE_BASE is self.__strip_index: + if value == BUTTON_PRESSED or value == BUTTON_RELEASED: + if self.__channel_strip_controller != None: + touched = value == BUTTON_PRESSED + self.set_is_touched(touched) + self.__channel_strip_controller.handle_fader_touch(self.__strip_index, self.__stack_offset, touched) + + def handle_vpot_rotation(self, strip_index, cc_value): + if strip_index is self.__strip_index and self.__channel_strip_controller != None: + self.__channel_strip_controller.handle_vpot_rotation(self.__strip_index, self.__stack_offset, cc_value) + + def refresh_state(self): + if not self.__within_track_added_or_deleted: + self.__update_track_is_selected_led() + self.__update_solo_led() + self.__update_mute_led() + self.__update_arm_led() + if not self.__within_destroy and self.__assigned_track != None: + self.__send_meter_mode() + self.__last_meter_value = -1 + if not self.__assigned_track: + self.reset_fader() + self.unlight_vpot_leds() + + def on_update_display_timer(self): + if not self.main_script().is_pro_version or self.__meters_enabled and self.__channel_strip_controller.assignment_mode() == CSM_VOLPAN: + if self.__assigned_track: + if self.__assigned_track.can_be_armed and self.__assigned_track.arm: + meter_value = self.__assigned_track.input_meter_level + else: + meter_value = self.__assigned_track.output_meter_level + else: + meter_value = 0.0 + meter_byte = int(meter_value * 12.0) + (self.__strip_index << 4) + if self.__last_meter_value != meter_value or meter_value != 0.0: + self.__last_meter_value = meter_value + self.send_midi((208, meter_byte)) + + def build_midi_map(self, midi_map_handle): + needs_takeover = False + if self.__fader_parameter: + feeback_rule = Live.MidiMap.PitchBendFeedbackRule() + feeback_rule.channel = self.__strip_index + feeback_rule.value_pair_map = tuple() + feeback_rule.delay_in_ms = 200.0 + Live.MidiMap.map_midi_pitchbend_with_feedback_map(midi_map_handle, self.__fader_parameter, self.__strip_index, feeback_rule, not needs_takeover) + Live.MidiMap.send_feedback_for_parameter(midi_map_handle, self.__fader_parameter) + else: + channel = self.__strip_index + Live.MidiMap.forward_midi_pitchbend(self.script_handle(), midi_map_handle, channel) + if self.__v_pot_parameter: + if self.__v_pot_display_mode == VPOT_DISPLAY_SPREAD: + range_end = 7 + else: + range_end = 12 + feeback_rule = Live.MidiMap.CCFeedbackRule() + feeback_rule.channel = 0 + feeback_rule.cc_no = 48 + self.__strip_index + feeback_rule.cc_value_map = tuple([ self.__v_pot_display_mode * 16 + x for x in range(1, range_end) ]) + feeback_rule.delay_in_ms = -1.0 + Live.MidiMap.map_midi_cc_with_feedback_map(midi_map_handle, self.__v_pot_parameter, 0, FID_PANNING_BASE + self.__strip_index, Live.MidiMap.MapMode.relative_signed_bit, feeback_rule, needs_takeover) + Live.MidiMap.send_feedback_for_parameter(midi_map_handle, self.__v_pot_parameter) + else: + channel = 0 + cc_no = FID_PANNING_BASE + self.__strip_index + Live.MidiMap.forward_midi_cc(self.script_handle(), midi_map_handle, channel, cc_no) + + def __assigned_track_index(self): + index = 0 + for t in chain(self.song().visible_tracks, self.song().return_tracks): + if t == self.__assigned_track: + return index + index += 1 + + if not (self.__assigned_track and 0): + raise AssertionError + + def __add_listeners(self): + if self.__assigned_track.can_be_armed: + self.__assigned_track.add_arm_listener(self.__update_arm_led) + self.__assigned_track.add_mute_listener(self.__update_mute_led) + self.__assigned_track.add_solo_listener(self.__update_solo_led) + if not self.song().view.selected_track_has_listener(self.__update_track_is_selected_led): + self.song().view.add_selected_track_listener(self.__update_track_is_selected_led) + + def __remove_listeners(self): + if self.__assigned_track.can_be_armed: + self.__assigned_track.remove_arm_listener(self.__update_arm_led) + self.__assigned_track.remove_mute_listener(self.__update_mute_led) + self.__assigned_track.remove_solo_listener(self.__update_solo_led) + self.song().view.remove_selected_track_listener(self.__update_track_is_selected_led) + + def __send_meter_mode(self): + on_mode = 1 + off_mode = 0 + if self.__meters_enabled: + on_mode = on_mode | 2 + if self.__assigned_track: + mode = on_mode + else: + mode = off_mode + if self.main_script().is_extension(): + device_type = SYSEX_DEVICE_TYPE_XT + else: + device_type = SYSEX_DEVICE_TYPE + self.send_midi((240, + 0, + 0, + 102, + device_type, + 32, + self.__strip_index, + mode, + 247)) + + def __toggle_arm_track(self, exclusive): + if self.__assigned_track and self.__assigned_track.can_be_armed: + self.__assigned_track.arm = not self.__assigned_track.arm + if exclusive: + for t in self.song().tracks: + if t != self.__assigned_track: + t.arm = False + + def __toggle_mute_track(self): + if self.__assigned_track: + self.__assigned_track.mute = not self.__assigned_track.mute + + def __toggle_solo_track(self, exclusive): + if self.__assigned_track: + self.__assigned_track.solo = not self.__assigned_track.solo + if exclusive: + for t in chain(self.song().tracks, self.song().return_tracks): + if t != self.__assigned_track: + t.solo = False + + def __select_track(self): + if self.__assigned_track: + all_tracks = tuple(self.song().visible_tracks) + tuple(self.song().return_tracks) + if self.song().view.selected_track != all_tracks[self.__assigned_track_index()]: + self.song().view.selected_track = all_tracks[self.__assigned_track_index()] + elif self.application().view.is_view_visible('Arranger'): + if self.__assigned_track: + self.__assigned_track.view.is_collapsed = not self.__assigned_track.view.is_collapsed + + def __update_arm_led(self): + track = self.__assigned_track + if track and track.can_be_armed and track.arm: + self.send_midi((NOTE_ON_STATUS, SID_RECORD_ARM_BASE + self.__strip_index, BUTTON_STATE_ON)) + else: + self.send_midi((NOTE_ON_STATUS, SID_RECORD_ARM_BASE + self.__strip_index, BUTTON_STATE_OFF)) + + def __update_mute_led(self): + if self.__assigned_track and self.__assigned_track.mute: + self.send_midi((NOTE_ON_STATUS, SID_MUTE_BASE + self.__strip_index, BUTTON_STATE_ON)) + else: + self.send_midi((NOTE_ON_STATUS, SID_MUTE_BASE + self.__strip_index, BUTTON_STATE_OFF)) + + def __update_solo_led(self): + if self.__assigned_track and self.__assigned_track.solo: + self.send_midi((NOTE_ON_STATUS, SID_SOLO_BASE + self.__strip_index, BUTTON_STATE_ON)) + else: + self.send_midi((NOTE_ON_STATUS, SID_SOLO_BASE + self.__strip_index, BUTTON_STATE_OFF)) + + def __update_track_is_selected_led(self): + if self.song().view.selected_track == self.__assigned_track: + self.send_midi((NOTE_ON_STATUS, SID_SELECT_BASE + self.__strip_index, BUTTON_STATE_ON)) + else: + self.send_midi((NOTE_ON_STATUS, SID_SELECT_BASE + self.__strip_index, BUTTON_STATE_OFF)) + + +class MasterChannelStrip(MackieControlComponent): + + def __init__(self, main_script): + MackieControlComponent.__init__(self, main_script) + self.__strip_index = MASTER_CHANNEL_STRIP_INDEX + self.__assigned_track = self.song().master_track + + def destroy(self): + self.reset_fader() + MackieControlComponent.destroy(self) + + def set_channel_strip_controller(self, channel_strip_controller): + pass + + def handle_channel_strip_switch_ids(self, sw_id, value): + pass + + def refresh_state(self): + pass + + def on_update_display_timer(self): + pass + + def enable_meter_mode(self, Enabled): + pass + + def reset_fader(self): + self.send_midi((PB_STATUS + self.__strip_index, 0, 0)) + + def build_midi_map(self, midi_map_handle): + if self.__assigned_track: + needs_takeover = False + volume = self.__assigned_track.mixer_device.volume + feeback_rule = Live.MidiMap.PitchBendFeedbackRule() + feeback_rule.channel = self.__strip_index + feeback_rule.value_pair_map = tuple() + feeback_rule.delay_in_ms = 200.0 + Live.MidiMap.map_midi_pitchbend_with_feedback_map(midi_map_handle, volume, self.__strip_index, feeback_rule, not needs_takeover) + Live.MidiMap.send_feedback_for_parameter(midi_map_handle, volume) diff --git a/nanoKONTROL2/ChannelStripController.py b/nanoKONTROL2/ChannelStripController.py new file mode 100644 index 00000000..56dc1fe3 --- /dev/null +++ b/nanoKONTROL2/ChannelStripController.py @@ -0,0 +1,862 @@ +#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/MackieControl/ChannelStripController.py +from MackieControlComponent import * +from _Generic.Devices import * +from itertools import chain + +class ChannelStripController(MackieControlComponent): + """ + Controls all channel-strips of the Mackie Control and controller extensions + (Mackie Control XTs) if available: Maps and controls the faders, VPots and the + displays depending on the assignemnt modes (Vol_Pan, PlugIn, IO, Send) and + edit and flip mode. + + stack_offset vs. strip_index vs. bank_channel_offset: + + When using multiple sets of channel strips (stacking them), we will still only + have one ChannelStripController which rules them all. + To identify and seperate them, the implementation uses 3 different kind of + indices or offsets: + + - strip_index: is the index of a channel_strip within its controller box, + so strip no 1 on an extension (XT) and strip number one on the 'main' Mackie + will both have a strip_index of 1. + We need to preserve this index, because every device (extension or main controller + will use a unique MIDI port to send out its MIDI messages which uses the + strip_index, encoded into the MIDI messages channel, to tell the hardware which + channel on the controller is meant. + + - stack_offset: descibes how many channels are left to the device that a + channel_strip belongs to. For example: You have 3 Mackies: First, a XT, then + the main Mackie, then another XT. + The first XT will have the stack_index 0, the main Mackie, the stack_index 8, + because 8 faders are on present before it. The second XT has a stack_index of 16 + + - bank_cha_offset: this shifts all available channel strips within all the tracks + that should be controlled. For example: If you have a song with 32 tracks, and + a main Mackie Control + a XT on the right, then you want to shift the first fader + of the main Mackie to Track 16, to be able to control Track 16 to 32. + + The master channel strip is hardcoded and not in the list of "normal" channel_strips, + because its always mapped to the master_volume. + """ + + def __init__(self, main_script, channel_strips, master_strip, main_display_controller): + MackieControlComponent.__init__(self, main_script) + self.__left_extensions = [] + self.__right_extensions = [] + self.__own_channel_strips = channel_strips + self.__master_strip = master_strip + self.__channel_strips = channel_strips + self.__main_display_controller = main_display_controller + self.__meters_enabled = False + self.__assignment_mode = CSM_VOLPAN + self.__sub_mode_in_io_mode = CSM_IO_FIRST_MODE + self.__plugin_mode = PCM_DEVICES + self.__plugin_mode_offsets = [ 0 for x in range(PCM_NUMMODES) ] + self.__chosen_plugin = None + self.__ordered_plugin_parameters = [] + self.__displayed_plugins = [] + self.__last_attached_selected_track = None + self.__send_mode_offset = 0 + self.__flip = False + self.__view_returns = False + self.__bank_cha_offset = 0 + self.__bank_cha_offset_returns = 0 + self.__within_track_added_or_deleted = False + self.song().add_visible_tracks_listener(self.__on_tracks_added_or_deleted) + self.song().view.add_selected_track_listener(self.__on_selected_track_changed) + for t in chain(self.song().visible_tracks, self.song().return_tracks): + if not t.solo_has_listener(self.__update_rude_solo_led): + t.add_solo_listener(self.__update_rude_solo_led) + if not t.has_audio_output_has_listener(self.__on_any_tracks_output_type_changed): + t.add_has_audio_output_listener(self.__on_any_tracks_output_type_changed) + + self.__on_selected_track_changed() + for s in self.__own_channel_strips: + s.set_channel_strip_controller(self) + + self.__reassign_channel_strip_offsets() + self.__reassign_channel_strip_parameters(for_display_only=False) + self._last_assignment_mode = self.__assignment_mode + + def destroy(self): + self.song().remove_visible_tracks_listener(self.__on_tracks_added_or_deleted) + self.song().view.remove_selected_track_listener(self.__on_selected_track_changed) + for t in chain(self.song().visible_tracks, self.song().return_tracks): + if t.solo_has_listener(self.__update_rude_solo_led): + t.remove_solo_listener(self.__update_rude_solo_led) + if t.has_audio_output_has_listener(self.__on_any_tracks_output_type_changed): + t.remove_has_audio_output_listener(self.__on_any_tracks_output_type_changed) + + st = self.__last_attached_selected_track + if st and st.devices_has_listener(self.__on_selected_device_chain_changed): + st.remove_devices_listener(self.__on_selected_device_chain_changed) + for note in channel_strip_assignment_switch_ids: + self.send_midi((NOTE_ON_STATUS, note, BUTTON_STATE_OFF)) + + for note in channel_strip_control_switch_ids: + self.send_midi((NOTE_ON_STATUS, note, BUTTON_STATE_OFF)) + + self.send_midi((NOTE_ON_STATUS, SELECT_RUDE_SOLO, BUTTON_STATE_OFF)) + self.send_midi((CC_STATUS, 75, g7_seg_led_conv_table[' '])) + self.send_midi((CC_STATUS, 74, g7_seg_led_conv_table[' '])) + MackieControlComponent.destroy(self) + + def set_controller_extensions(self, left_extensions, right_extensions): + """ Called from the main script (after all scripts where initialized), to let us + know where and how many MackieControlXT are installed. + There exists only one ChannelStripController, so we will take care about the + extensions channel strips + """ + self.__left_extensions = left_extensions + self.__right_extensions = right_extensions + self.__channel_strips = [] + stack_offset = 0 + for le in left_extensions: + for s in le.channel_strips(): + self.__channel_strips.append(s) + s.set_stack_offset(stack_offset) + + stack_offset += NUM_CHANNEL_STRIPS + + for s in self.__own_channel_strips: + self.__channel_strips.append(s) + s.set_stack_offset(stack_offset) + + stack_offset += NUM_CHANNEL_STRIPS + for re in right_extensions: + for s in re.channel_strips(): + self.__channel_strips.append(s) + s.set_stack_offset(stack_offset) + + stack_offset += NUM_CHANNEL_STRIPS + + for s in self.__channel_strips: + s.set_channel_strip_controller(self) + + self.refresh_state() + + def refresh_state(self): + self.__update_assignment_mode_leds() + self.__update_assignment_display() + self.__update_rude_solo_led() + self.__reassign_channel_strip_offsets() + self.__on_flip_changed() + self.__update_view_returns_mode() + + def request_rebuild_midi_map(self): + """ Overridden to call also the extensions request_rebuild_midi_map""" + MackieControlComponent.request_rebuild_midi_map(self) + for ex in self.__left_extensions + self.__right_extensions: + ex.request_rebuild_midi_map() + + def on_update_display_timer(self): + self.__update_channel_strip_strings() + + def toggle_meter_mode(self): + """ called from the main script when the display toggle button was pressed """ + self.__meters_enabled = not self.__meters_enabled + self.__apply_meter_mode(meter_state_changed=True) + + def handle_assignment_switch_ids(self, switch_id, value): + if switch_id == SID_ASSIGNMENT_IO: + if value == BUTTON_PRESSED: + self.__set_assignment_mode(CSM_IO) + elif switch_id == SID_ASSIGNMENT_SENDS: + if value == BUTTON_PRESSED: + self.__set_assignment_mode(CSM_SENDS) + elif switch_id == SID_ASSIGNMENT_PAN: + if value == BUTTON_PRESSED: + self.__set_assignment_mode(CSM_VOLPAN) + elif switch_id == SID_ASSIGNMENT_PLUG_INS: + if value == BUTTON_PRESSED: + self.__set_assignment_mode(CSM_PLUGINS) + elif switch_id == SID_ASSIGNMENT_EQ: + if value == BUTTON_PRESSED: + self.__switch_to_prev_page() + elif switch_id == SID_ASSIGNMENT_DYNAMIC: + if value == BUTTON_PRESSED: + self.__switch_to_next_page() + elif switch_id == SID_FADERBANK_PREV_BANK: + if value == BUTTON_PRESSED: + if self.shift_is_pressed(): + self.__set_channel_offset(0) + else: + self.__set_channel_offset(self.__strip_offset() - len(self.__channel_strips)) + elif switch_id == SID_FADERBANK_NEXT_BANK: + if value == BUTTON_PRESSED: + if self.shift_is_pressed(): + last_possible_offset = (self.__controlled_num_of_tracks() - self.__strip_offset()) / len(self.__channel_strips) * len(self.__channel_strips) + self.__strip_offset() + if last_possible_offset == self.__controlled_num_of_tracks(): + last_possible_offset -= len(self.__channel_strips) + self.__set_channel_offset(last_possible_offset) + elif self.__strip_offset() < self.__controlled_num_of_tracks() - len(self.__channel_strips): + self.__set_channel_offset(self.__strip_offset() + len(self.__channel_strips)) + elif switch_id == SID_FADERBANK_PREV_CH: + if value == BUTTON_PRESSED: + if self.shift_is_pressed(): + self.__set_channel_offset(0) + else: + self.__set_channel_offset(self.__strip_offset() - 1) + elif switch_id == SID_FADERBANK_NEXT_CH: + if value == BUTTON_PRESSED: + if self.shift_is_pressed(): + self.__set_channel_offset(self.__controlled_num_of_tracks() - len(self.__channel_strips)) + elif self.__strip_offset() < self.__controlled_num_of_tracks() - len(self.__channel_strips): + self.__set_channel_offset(self.__strip_offset() + 1) + elif switch_id == SID_FADERBANK_FLIP: + if value == BUTTON_PRESSED: + self.__toggle_flip() + elif switch_id == SID_FADERBANK_EDIT: + if value == BUTTON_PRESSED: + self.__toggle_view_returns() + + def handle_vpot_rotation(self, strip_index, stack_offset, cc_value): + """ forwarded to us by the channel_strips """ + if self.__assignment_mode == CSM_IO: + if cc_value >= 64: + direction = -1 + else: + direction = 1 + channel_strip = self.__channel_strips[stack_offset + strip_index] + current_routing = self.__routing_target(channel_strip) + available_routings = self.__available_routing_targets(channel_strip) + if current_routing and available_routings: + if current_routing in available_routings: + i = list(available_routings).index(current_routing) + if direction == 1: + new_i = min(len(available_routings) - 1, i + direction) + else: + new_i = max(0, i + direction) + new_routing = available_routings[new_i] + elif len(available_routings): + new_routing = available_routings[0] + self.__set_routing_target(channel_strip, new_routing) + elif self.__assignment_mode == CSM_PLUGINS: + pass + else: + channel_strip = self.__channel_strips[stack_offset + strip_index] + raise not channel_strip.assigned_track() or not channel_strip.assigned_track().has_audio_output or AssertionError('in every other mode, the midimap should handle the messages') + + def handle_fader_touch(self, strip_offset, stack_offset, touched): + """ forwarded to us by the channel_strips """ + self.__reassign_channel_strip_parameters(for_display_only=True) + + def handle_pressed_v_pot(self, strip_index, stack_offset): + """ forwarded to us by the channel_strips """ + if self.__assignment_mode == CSM_VOLPAN or self.__assignment_mode == CSM_SENDS or self.__assignment_mode == CSM_PLUGINS and self.__plugin_mode == PCM_PARAMETERS: + if stack_offset + strip_index in range(0, len(self.__channel_strips)): + param = self.__channel_strips[stack_offset + strip_index].v_pot_parameter() + if param and param.is_enabled: + if param.is_quantized: + if param.value + 1 > param.max: + param.value = param.min + else: + param.value = param.value + 1 + else: + param.value = param.default_value + elif self.__assignment_mode == CSM_PLUGINS and self.__plugin_mode == PCM_DEVICES: + device_index = strip_index + stack_offset + self.__plugin_mode_offsets[PCM_DEVICES] + if device_index >= 0 and device_index < len(self.song().view.selected_track.devices): + if self.__chosen_plugin != None: + self.__chosen_plugin.remove_parameters_listener(self.__on_parameter_list_of_chosen_plugin_changed) + self.__chosen_plugin = self.song().view.selected_track.devices[device_index] + if self.__chosen_plugin != None: + self.__chosen_plugin.add_parameters_listener(self.__on_parameter_list_of_chosen_plugin_changed) + self.__reorder_parameters() + self.__plugin_mode_offsets[PCM_PARAMETERS] = 0 + self.__set_plugin_mode(PCM_PARAMETERS) + + def assignment_mode(self): + return self.__assignment_mode + + def __strip_offset(self): + """ return the bank_channel offset depending if we are in return mode or not + """ + if self.__view_returns: + return self.__bank_cha_offset_returns + else: + return self.__bank_cha_offset + + def __controlled_num_of_tracks(self): + """ return the number of tracks, depending on if we are in send_track + mode or normal track mode + """ + if self.__view_returns: + return len(self.song().return_tracks) + else: + return len(self.song().visible_tracks) + + def __send_parameter(self, strip_index, stack_index): + """ Return the send parameter that is assigned to the given channel strip + """ + if not self.__assignment_mode == CSM_SENDS: + raise AssertionError + send_index = strip_index + stack_index + self.__send_mode_offset + p = send_index < len(self.song().view.selected_track.mixer_device.sends) and self.song().view.selected_track.mixer_device.sends[send_index] + return (p, p.name) + return (None, None) + + def __plugin_parameter(self, strip_index, stack_index): + """ Return the parameter that is assigned to the given channel strip + """ + if not self.__assignment_mode == CSM_PLUGINS: + raise AssertionError + if self.__plugin_mode == PCM_DEVICES: + return (None, None) + elif not (self.__plugin_mode == PCM_PARAMETERS and self.__chosen_plugin): + raise AssertionError + parameters = self.__ordered_plugin_parameters + parameter_index = strip_index + stack_index + self.__plugin_mode_offsets[PCM_PARAMETERS] + return parameter_index >= 0 and parameter_index < len(parameters) and parameters[parameter_index] + else: + return (None, None) + else: + raise 0 or AssertionError + + def __any_slider_is_touched(self): + for s in self.__channel_strips: + if s.is_touched(): + return True + + return False + + def __can_flip(self): + if self.__assignment_mode == CSM_PLUGINS and self.__plugin_mode == PCM_DEVICES: + return False + if self.__assignment_mode == CSM_IO: + return False + return True + + def __can_switch_to_prev_page(self): + """ return true if pressing the "next" button will have any effect """ + if self.__assignment_mode == CSM_PLUGINS: + return self.__plugin_mode_offsets[self.__plugin_mode] > 0 + elif self.__assignment_mode == CSM_SENDS: + return self.__send_mode_offset > 0 + else: + return False + + def __can_switch_to_next_page(self): + """ return true if pressing the "prev" button will have any effect """ + if self.__assignment_mode == CSM_PLUGINS: + sel_track = self.song().view.selected_track + if self.__plugin_mode == PCM_DEVICES: + return self.__plugin_mode_offsets[PCM_DEVICES] + len(self.__channel_strips) < len(sel_track.devices) + raise self.__plugin_mode == PCM_PARAMETERS and (self.__chosen_plugin or AssertionError) + parameters = self.__ordered_plugin_parameters + return self.__plugin_mode_offsets[PCM_PARAMETERS] + len(self.__channel_strips) < len(parameters) + if not 0: + raise AssertionError + else: + if self.__assignment_mode == CSM_SENDS: + return self.__send_mode_offset + len(self.__channel_strips) < len(self.song().return_tracks) + return False + + def __available_routing_targets(self, channel_strip): + if not self.__assignment_mode == CSM_IO: + raise AssertionError + t = channel_strip.assigned_track() + if t: + if self.__sub_mode_in_io_mode == CSM_IO_MODE_INPUT_MAIN: + return t.input_routings + if self.__sub_mode_in_io_mode == CSM_IO_MODE_INPUT_SUB: + return t.input_sub_routings + if self.__sub_mode_in_io_mode == CSM_IO_MODE_OUTPUT_MAIN: + return t.output_routings + return self.__sub_mode_in_io_mode == CSM_IO_MODE_OUTPUT_SUB and t.output_sub_routings + raise 0 or AssertionError + else: + return None + + def __routing_target(self, channel_strip): + if not self.__assignment_mode == CSM_IO: + raise AssertionError + t = channel_strip.assigned_track() + if t: + if self.__sub_mode_in_io_mode == CSM_IO_MODE_INPUT_MAIN: + return t.current_input_routing + if self.__sub_mode_in_io_mode == CSM_IO_MODE_INPUT_SUB: + return t.current_input_sub_routing + if self.__sub_mode_in_io_mode == CSM_IO_MODE_OUTPUT_MAIN: + return t.current_output_routing + return self.__sub_mode_in_io_mode == CSM_IO_MODE_OUTPUT_SUB and t.current_output_sub_routing + raise 0 or AssertionError + else: + return None + + def __set_routing_target(self, channel_strip, target_string): + raise self.__assignment_mode == CSM_IO or AssertionError + t = channel_strip.assigned_track() + if t: + if self.__sub_mode_in_io_mode == CSM_IO_MODE_INPUT_MAIN: + t.current_input_routing = target_string + elif self.__sub_mode_in_io_mode == CSM_IO_MODE_INPUT_SUB: + t.current_input_sub_routing = target_string + elif self.__sub_mode_in_io_mode == CSM_IO_MODE_OUTPUT_MAIN: + t.current_output_routing = target_string + elif self.__sub_mode_in_io_mode == CSM_IO_MODE_OUTPUT_SUB: + t.current_output_sub_routing = target_string + else: + raise 0 or AssertionError + + def __set_channel_offset(self, new_offset): + """ Set and validate a new channel_strip offset, which shifts all available channel + strips within all the available tracks or reutrn tracks + """ + if new_offset < 0: + new_offset = 0 + elif new_offset >= self.__controlled_num_of_tracks(): + new_offset = self.__controlled_num_of_tracks() - 1 + if self.__view_returns: + self.__bank_cha_offset_returns = new_offset + else: + self.__bank_cha_offset = new_offset + self.__main_display_controller.set_channel_offset(new_offset) + self.__reassign_channel_strip_offsets() + self.__reassign_channel_strip_parameters(for_display_only=False) + self.__update_channel_strip_strings() + self.request_rebuild_midi_map() + + def __set_assignment_mode(self, mode): + for plugin in self.__displayed_plugins: + if plugin != None: + plugin.remove_name_listener(self.__update_plugin_names) + + self.__displayed_plugins = [] + if mode == CSM_PLUGINS: + self.__assignment_mode = mode + self.__main_display_controller.set_show_parameter_names(True) + self.__set_plugin_mode(PCM_DEVICES) + elif mode == CSM_SENDS: + self.__main_display_controller.set_show_parameter_names(True) + self.__assignment_mode = mode + else: + if mode == CSM_IO: + for s in self.__channel_strips: + s.unlight_vpot_leds() + + self.__main_display_controller.set_show_parameter_names(False) + if self.__assignment_mode != mode: + self.__assignment_mode = mode + elif self.__assignment_mode == CSM_IO: + self.__switch_to_next_io_mode() + self.__update_assignment_mode_leds() + self.__update_assignment_display() + self.__apply_meter_mode() + self.__reassign_channel_strip_parameters(for_display_only=False) + self.__update_channel_strip_strings() + self.__update_page_switch_leds() + if mode == CSM_PLUGINS: + self.__update_vpot_leds_in_plugins_device_choose_mode() + self.__update_flip_led() + self.request_rebuild_midi_map() + + def __set_plugin_mode(self, new_mode): + """ Set a new plugin sub-mode, which can be: + 1. Choosing the device to control (PCM_DEVICES) + 2. Controlling the chosen devices parameters (PCM_PARAMETERS) + """ + if not (new_mode >= 0 and new_mode < PCM_NUMMODES): + raise AssertionError + if self.__plugin_mode != new_mode: + self.__plugin_mode = new_mode + self.__reassign_channel_strip_parameters(for_display_only=False) + self.request_rebuild_midi_map() + self.__plugin_mode == PCM_DEVICES and self.__update_vpot_leds_in_plugins_device_choose_mode() + else: + for plugin in self.__displayed_plugins: + if plugin != None: + plugin.remove_name_listener(self.__update_plugin_names) + + self.__displayed_plugins = [] + self.__update_page_switch_leds() + self.__update_flip_led() + self.__update_page_switch_leds() + + def __switch_to_prev_page(self): + """ Switch to the previous page in the non track strip modes (choosing plugs, or + controlling devices) + """ + if self.__can_switch_to_prev_page(): + if self.__assignment_mode == CSM_PLUGINS: + self.__plugin_mode_offsets[self.__plugin_mode] -= len(self.__channel_strips) + if self.__plugin_mode == PCM_DEVICES: + self.__update_vpot_leds_in_plugins_device_choose_mode() + elif self.__assignment_mode == CSM_SENDS: + self.__send_mode_offset -= len(self.__channel_strips) + self.__reassign_channel_strip_parameters(for_display_only=False) + self.__update_channel_strip_strings() + self.__update_page_switch_leds() + self.request_rebuild_midi_map() + + def __switch_to_next_page(self): + """ Switch to the next page in the non track strip modes (choosing plugs, or + controlling devices) + """ + if self.__can_switch_to_next_page(): + if self.__assignment_mode == CSM_PLUGINS: + self.__plugin_mode_offsets[self.__plugin_mode] += len(self.__channel_strips) + if self.__plugin_mode == PCM_DEVICES: + self.__update_vpot_leds_in_plugins_device_choose_mode() + elif self.__assignment_mode == CSM_SENDS: + self.__send_mode_offset += len(self.__channel_strips) + else: + raise 0 or AssertionError + self.__reassign_channel_strip_parameters(for_display_only=False) + self.__update_channel_strip_strings() + self.__update_page_switch_leds() + self.request_rebuild_midi_map() + + def __switch_to_next_io_mode(self): + """ Step through the available IO modes (In/OutPut//Main/Sub) + """ + self.__sub_mode_in_io_mode += 1 + if self.__sub_mode_in_io_mode > CSM_IO_LAST_MODE: + self.__sub_mode_in_io_mode = CSM_IO_FIRST_MODE + + def __reassign_channel_strip_offsets(self): + """ Update the channel strips bank_channel offset + """ + for s in self.__channel_strips: + s.set_bank_and_channel_offset(self.__strip_offset(), self.__view_returns, self.__within_track_added_or_deleted) + + def __reassign_channel_strip_parameters(self, for_display_only): + """ Reevaluate all v-pot/fader -> parameter assignments + """ + display_parameters = [] + for s in self.__channel_strips: + vpot_param = (None, None) + slider_param = (None, None) + vpot_display_mode = VPOT_DISPLAY_SINGLE_DOT + slider_display_mode = VPOT_DISPLAY_SINGLE_DOT + if self.__assignment_mode == CSM_VOLPAN: + if s.assigned_track() and s.assigned_track().has_audio_output: + vpot_param = (s.assigned_track().mixer_device.panning, 'Pan') + vpot_display_mode = VPOT_DISPLAY_BOOST_CUT + slider_param = (s.assigned_track().mixer_device.volume, 'Volume') + slider_display_mode = VPOT_DISPLAY_WRAP + elif self.__assignment_mode == CSM_PLUGINS: + vpot_param = self.__plugin_parameter(s.strip_index(), s.stack_offset()) + vpot_display_mode = VPOT_DISPLAY_WRAP + if s.assigned_track() and s.assigned_track().has_audio_output: + slider_param = (s.assigned_track().mixer_device.volume, 'Volume') + slider_display_mode = VPOT_DISPLAY_WRAP + elif self.__assignment_mode == CSM_SENDS: + vpot_param = self.__send_parameter(s.strip_index(), s.stack_offset()) + vpot_display_mode = VPOT_DISPLAY_WRAP + if s.assigned_track() and s.assigned_track().has_audio_output: + slider_param = (s.assigned_track().mixer_device.volume, 'Volume') + slider_display_mode = VPOT_DISPLAY_WRAP + elif self.__assignment_mode == CSM_IO: + if s.assigned_track() and s.assigned_track().has_audio_output: + slider_param = (s.assigned_track().mixer_device.volume, 'Volume') + if self.__flip and self.__can_flip(): + if self.__any_slider_is_touched(): + display_parameters.append(vpot_param) + else: + display_parameters.append(slider_param) + if not for_display_only: + s.set_v_pot_parameter(slider_param[0], slider_display_mode) + s.set_fader_parameter(vpot_param[0]) + else: + if self.__any_slider_is_touched(): + display_parameters.append(slider_param) + else: + display_parameters.append(vpot_param) + if not for_display_only: + s.set_v_pot_parameter(vpot_param[0], vpot_display_mode) + s.set_fader_parameter(slider_param[0]) + + self.__main_display_controller.set_channel_offset(self.__strip_offset()) + if len(display_parameters): + self.__main_display_controller.set_parameters(display_parameters) + + def _need_to_update_meter(self, meter_state_changed): + return meter_state_changed and self.__assignment_mode == CSM_VOLPAN + + def __apply_meter_mode(self, meter_state_changed = False): + """ Update the meter mode in the displays and channel strips """ + enabled = self.__meters_enabled and self.__assignment_mode is CSM_VOLPAN + send_meter_mode = self._last_assignment_mode != self.__assignment_mode or self._need_to_update_meter(meter_state_changed) + for s in self.__channel_strips: + s.enable_meter_mode(enabled, needs_to_send_meter_mode=send_meter_mode) + + self.__main_display_controller.enable_meters(enabled) + self._last_assignment_mode = self.__assignment_mode + + def __toggle_flip(self): + """ En/Disable V-Pot / Fader flipping + """ + if self.__can_flip(): + self.__flip = not self.__flip + self.__on_flip_changed() + + def __toggle_view_returns(self): + """ Toggle if we want to control the return tracks or normal tracks + """ + self.__view_returns = not self.__view_returns + self.__update_view_returns_mode() + + def __update_assignment_mode_leds(self): + """ Show which assignment mode is currently active """ + if self.__assignment_mode == CSM_IO: + sid_on_switch = SID_ASSIGNMENT_IO + elif self.__assignment_mode == CSM_SENDS: + sid_on_switch = SID_ASSIGNMENT_SENDS + elif self.__assignment_mode == CSM_VOLPAN: + sid_on_switch = SID_ASSIGNMENT_PAN + elif self.__assignment_mode == CSM_PLUGINS: + sid_on_switch = SID_ASSIGNMENT_PLUG_INS + else: + raise 0 or AssertionError + sid_on_switch = None + for s in (SID_ASSIGNMENT_IO, + SID_ASSIGNMENT_SENDS, + SID_ASSIGNMENT_PAN, + SID_ASSIGNMENT_PLUG_INS): + if s == sid_on_switch: + self.send_midi((NOTE_ON_STATUS, s, BUTTON_STATE_ON)) + else: + self.send_midi((NOTE_ON_STATUS, s, BUTTON_STATE_OFF)) + + def __update_assignment_display(self): + """ Cryptically label the current assignment mode in the 2char display above + the assignment buttons + """ + if self.__assignment_mode == CSM_VOLPAN: + ass_string = ['P', 'N'] + else: + if self.__assignment_mode == CSM_PLUGINS or self.__assignment_mode == CSM_SENDS: + ass_string = self.__last_attached_selected_track == self.song().master_track and ['M', 'A'] + for t in self.song().return_tracks: + if t == self.__last_attached_selected_track: + ass_string = ['R', chr(ord('A') + list(self.song().return_tracks).index(t))] + break + + for t in self.song().visible_tracks: + if t == self.__last_attached_selected_track: + ass_string = list('%.2d' % min(99, list(self.song().visible_tracks).index(t) + 1)) + break + + if not ass_string: + raise AssertionError + elif self.__assignment_mode == CSM_IO: + if self.__sub_mode_in_io_mode == CSM_IO_MODE_INPUT_MAIN: + ass_string = ['I', "'"] + elif self.__sub_mode_in_io_mode == CSM_IO_MODE_INPUT_SUB: + ass_string = ['I', ','] + elif self.__sub_mode_in_io_mode == CSM_IO_MODE_OUTPUT_MAIN: + ass_string = ['0', "'"] + elif self.__sub_mode_in_io_mode == CSM_IO_MODE_OUTPUT_SUB: + ass_string = ['0', ','] + else: + raise 0 or AssertionError + else: + raise 0 or AssertionError + self.send_midi((CC_STATUS, 75, g7_seg_led_conv_table[ass_string[0]])) + self.send_midi((CC_STATUS, 74, g7_seg_led_conv_table[ass_string[1]])) + + def __update_rude_solo_led(self): + any_track_soloed = False + for t in chain(self.song().tracks, self.song().return_tracks): + if t.solo: + any_track_soloed = True + break + + if any_track_soloed: + self.send_midi((NOTE_ON_STATUS, SELECT_RUDE_SOLO, BUTTON_STATE_ON)) + else: + self.send_midi((NOTE_ON_STATUS, SELECT_RUDE_SOLO, BUTTON_STATE_OFF)) + + def __update_page_switch_leds(self): + """ visualize if the "prev" an "next" buttons can be pressed """ + if self.__can_switch_to_prev_page(): + self.send_midi((NOTE_ON_STATUS, SID_ASSIGNMENT_EQ, BUTTON_STATE_ON)) + else: + self.send_midi((NOTE_ON_STATUS, SID_ASSIGNMENT_EQ, BUTTON_STATE_OFF)) + if self.__can_switch_to_next_page(): + self.send_midi((NOTE_ON_STATUS, SID_ASSIGNMENT_DYNAMIC, BUTTON_STATE_ON)) + else: + self.send_midi((NOTE_ON_STATUS, SID_ASSIGNMENT_DYNAMIC, BUTTON_STATE_OFF)) + + def __update_flip_led(self): + if self.__flip and self.__can_flip(): + self.send_midi((NOTE_ON_STATUS, SID_FADERBANK_FLIP, BUTTON_STATE_ON)) + else: + self.send_midi((NOTE_ON_STATUS, SID_FADERBANK_FLIP, BUTTON_STATE_OFF)) + + def __update_vpot_leds_in_plugins_device_choose_mode(self): + """ To be called in assignment mode CSM_PLUGINS, submode PCM_DEVICES only: + This will enlighten all poties which can be pressed to choose a device + for editing, and unlight all poties where pressing will have no effect + """ + raise self.__assignment_mode == CSM_PLUGINS or AssertionError + raise self.__plugin_mode == PCM_DEVICES or AssertionError + sel_track = self.song().view.selected_track + count = 0 + for s in self.__channel_strips: + offset = self.__plugin_mode_offsets[self.__plugin_mode] + if sel_track and offset + count >= 0 and offset + count < len(sel_track.devices): + s.show_full_enlighted_poti() + else: + s.unlight_vpot_leds() + count += 1 + + def __update_channel_strip_strings(self): + """ In IO mode, collect all strings that will be visible in the main display manually + """ + if not self.__any_slider_is_touched(): + if self.__assignment_mode == CSM_IO: + targets = [] + for s in self.__channel_strips: + if self.__routing_target(s): + targets.append(self.__routing_target(s)) + else: + targets.append('') + + self.__main_display_controller.set_channel_strip_strings(targets) + elif self.__assignment_mode == CSM_PLUGINS and self.__plugin_mode == PCM_DEVICES: + for plugin in self.__displayed_plugins: + if plugin != None: + plugin.remove_name_listener(self.__update_plugin_names) + + self.__displayed_plugins = [] + sel_track = self.song().view.selected_track + for i in range(len(self.__channel_strips)): + device_index = i + self.__plugin_mode_offsets[PCM_DEVICES] + if device_index >= 0 and device_index < len(sel_track.devices): + sel_track.devices[device_index].add_name_listener(self.__update_plugin_names) + self.__displayed_plugins.append(sel_track.devices[device_index]) + else: + self.__displayed_plugins.append(None) + + self.__update_plugin_names() + + def __update_plugin_names(self): + raise self.__assignment_mode == CSM_PLUGINS and self.__plugin_mode == PCM_DEVICES or AssertionError + device_strings = [] + for plugin in self.__displayed_plugins: + if plugin != None: + device_strings.append(plugin.name) + else: + device_strings.append('') + + self.__main_display_controller.set_channel_strip_strings(device_strings) + + def __update_view_returns_mode(self): + """ Update the control return tracks LED + """ + if self.__view_returns: + self.send_midi((NOTE_ON_STATUS, SID_FADERBANK_EDIT, BUTTON_STATE_ON)) + else: + self.send_midi((NOTE_ON_STATUS, SID_FADERBANK_EDIT, BUTTON_STATE_OFF)) + self.__main_display_controller.set_show_return_track_names(self.__view_returns) + self.__reassign_channel_strip_offsets() + self.__reassign_channel_strip_parameters(for_display_only=False) + self.request_rebuild_midi_map() + + def __on_selected_track_changed(self): + """ Notifier, called as soon as the selected track has changed + """ + st = self.__last_attached_selected_track + if st and st.devices_has_listener(self.__on_selected_device_chain_changed): + st.remove_devices_listener(self.__on_selected_device_chain_changed) + self.__last_attached_selected_track = self.song().view.selected_track + st = self.__last_attached_selected_track + if st: + st.add_devices_listener(self.__on_selected_device_chain_changed) + if self.__assignment_mode == CSM_PLUGINS: + self.__plugin_mode_offsets = [ 0 for x in range(PCM_NUMMODES) ] + if self.__chosen_plugin != None: + self.__chosen_plugin.remove_parameters_listener(self.__on_parameter_list_of_chosen_plugin_changed) + self.__chosen_plugin = None + self.__ordered_plugin_parameters = [] + self.__update_assignment_display() + if self.__plugin_mode == PCM_DEVICES: + self.__update_vpot_leds_in_plugins_device_choose_mode() + else: + self.__set_plugin_mode(PCM_DEVICES) + elif self.__assignment_mode == CSM_SENDS: + self.__reassign_channel_strip_parameters(for_display_only=False) + self.__update_assignment_display() + self.request_rebuild_midi_map() + + def __on_flip_changed(self): + """ Update the flip button LED when the flip mode changed + """ + self.__update_flip_led() + if self.__can_flip(): + self.__update_assignment_display() + self.__reassign_channel_strip_parameters(for_display_only=False) + self.request_rebuild_midi_map() + + def __on_selected_device_chain_changed(self): + if self.__assignment_mode == CSM_PLUGINS: + if self.__plugin_mode == PCM_DEVICES: + self.__update_vpot_leds_in_plugins_device_choose_mode() + self.__update_page_switch_leds() + elif self.__plugin_mode == PCM_PARAMETERS: + if not self.__chosen_plugin: + self.__set_plugin_mode(PCM_DEVICES) + elif self.__chosen_plugin not in self.__last_attached_selected_track.devices: + if self.__chosen_plugin != None: + self.__chosen_plugin.remove_parameters_listener(self.__on_parameter_list_of_chosen_plugin_changed) + self.__chosen_plugin = None + self.__set_plugin_mode(PCM_DEVICES) + + def __on_tracks_added_or_deleted(self): + """ Notifier, called as soon as tracks where added, removed or moved + """ + self.__within_track_added_or_deleted = True + for t in chain(self.song().visible_tracks, self.song().return_tracks): + if not t.solo_has_listener(self.__update_rude_solo_led): + t.add_solo_listener(self.__update_rude_solo_led) + if not t.has_audio_output_has_listener(self.__on_any_tracks_output_type_changed): + t.add_has_audio_output_listener(self.__on_any_tracks_output_type_changed) + + if self.__send_mode_offset >= len(self.song().return_tracks): + self.__send_mode_offset = 0 + self.__reassign_channel_strip_parameters(for_display_only=False) + self.__update_channel_strip_strings() + if self.__strip_offset() + len(self.__channel_strips) >= self.__controlled_num_of_tracks(): + self.__set_channel_offset(max(0, self.__controlled_num_of_tracks() - len(self.__channel_strips))) + self.__reassign_channel_strip_parameters(for_display_only=False) + self.__update_channel_strip_strings() + if self.__assignment_mode == CSM_SENDS: + self.__update_page_switch_leds() + self.refresh_state() + self.__main_display_controller.refresh_state() + self.__within_track_added_or_deleted = False + self.request_rebuild_midi_map() + + def __on_any_tracks_output_type_changed(self): + """ called as soon as any device chain has changed (devices where + added/removed/swapped...) + """ + self.__reassign_channel_strip_parameters(for_display_only=False) + self.request_rebuild_midi_map() + + def __on_parameter_list_of_chosen_plugin_changed(self): + raise self.__chosen_plugin != None or AssertionError + raise self.__plugin_mode == PCM_PARAMETERS or AssertionError + self.__reorder_parameters() + self.__reassign_channel_strip_parameters(for_display_only=False) + self.request_rebuild_midi_map() + + def __reorder_parameters(self): + result = [] + if self.__chosen_plugin: + if self.__chosen_plugin.class_name in DEVICE_DICT.keys(): + device_banks = DEVICE_DICT[self.__chosen_plugin.class_name] + for bank in device_banks: + for param_name in bank: + parameter_name = '' + parameter = get_parameter_by_name(self.__chosen_plugin, param_name) + if parameter: + parameter_name = parameter.name + result.append((parameter, parameter_name)) + + else: + result = [ (p, p.name) for p in self.__chosen_plugin.parameters[1:] ] + self.__ordered_plugin_parameters = result diff --git a/nanoKONTROL2/MackieControl.py b/nanoKONTROL2/MackieControl.py new file mode 100644 index 00000000..0f041586 --- /dev/null +++ b/nanoKONTROL2/MackieControl.py @@ -0,0 +1,240 @@ +#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/MackieControl/MackieControl.py +from consts import * +from MainDisplay import MainDisplay +from MainDisplayController import MainDisplayController +from TimeDisplay import TimeDisplay +from ChannelStrip import ChannelStrip, MasterChannelStrip +from ChannelStripController import ChannelStripController +from SoftwareController import SoftwareController +from Transport import Transport +import Live +import MidiRemoteScript + +class MackieControl: + """Main class that establishes the Mackie Control <-> Live interaction. It acts + as a container/manager for all the Mackie Control sub-components like ChannelStrips, + Displays and so on. + Futher it is glued to Lives MidiRemoteScript C instance, which will forward some + notifications to us, and lets us forward some requests that are needed beside the + general Live API (see 'send_midi' or 'request_rebuild_midi_map'). + """ + + def __init__(self, c_instance): + self.__c_instance = c_instance + self.__components = [] + self.__main_display = MainDisplay(self) + self.__components.append(self.__main_display) + self.__main_display_controller = MainDisplayController(self, self.__main_display) + self.__components.append(self.__main_display_controller) + self.__time_display = TimeDisplay(self) + self.__components.append(self.__time_display) + self.__software_controller = SoftwareController(self) + self.__components.append(self.__software_controller) + self.__transport = Transport(self) + self.__components.append(self.__transport) + self.__channel_strips = [ ChannelStrip(self, i) for i in range(NUM_CHANNEL_STRIPS) ] + for s in self.__channel_strips: + self.__components.append(s) + + self.__master_strip = MasterChannelStrip(self) + self.__components.append(self.__master_strip) + self.__channel_strip_controller = ChannelStripController(self, self.__channel_strips, self.__master_strip, self.__main_display_controller) + self.__components.append(self.__channel_strip_controller) + self.__shift_is_pressed = False + self.__option_is_pressed = False + self.__ctrl_is_pressed = False + self.__alt_is_pressed = False + self.is_pro_version = False + self._received_firmware_version = False + self._refresh_state_next_time = 0 + + def disconnect(self): + for c in self.__components: + c.destroy() + + def connect_script_instances(self, instanciated_scripts): + """Called by the Application as soon as all scripts are initialized. + You can connect yourself to other running scripts here, as we do it + connect the extension modules (MackieControlXTs). + """ + try: + from MackieControlXT.MackieControlXT import MackieControlXT + except: + print 'failed to load the MackieControl XT script (might not be installed)' + + found_self = False + right_extensions = [] + left_extensions = [] + for s in instanciated_scripts: + if s is self: + found_self = True + elif isinstance(s, MackieControlXT): + s.set_mackie_control_main(self) + if found_self: + right_extensions.append(s) + else: + left_extensions.append(s) + + raise found_self or AssertionError + self.__main_display_controller.set_controller_extensions(left_extensions, right_extensions) + self.__channel_strip_controller.set_controller_extensions(left_extensions, right_extensions) + + def request_firmware_version(self): + if not self._received_firmware_version: + self.send_midi((240, + 0, + 0, + 102, + SYSEX_DEVICE_TYPE, + 19, + 0, + 247)) + + def application(self): + """returns a reference to the application that we are running in""" + return Live.Application.get_application() + + def song(self): + """returns a reference to the Live Song that we do interact with""" + return self.__c_instance.song() + + def handle(self): + """returns a handle to the c_interface that is needed when forwarding MIDI events + via the MIDI map + """ + return self.__c_instance.handle() + + def refresh_state(self): + for c in self.__components: + c.refresh_state() + + self.request_firmware_version() + self._refresh_state_next_time = 30 + + def is_extension(self): + return False + + def request_rebuild_midi_map(self): + """ To be called from any components, as soon as their internal state changed in a + way, that we do need to remap the mappings that are processed directly by the + Live engine. + Dont assume that the request will immediately result in a call to + your build_midi_map function. For performance reasons this is only + called once per GUI frame.""" + self.__c_instance.request_rebuild_midi_map() + + def build_midi_map(self, midi_map_handle): + """New MIDI mappings can only be set when the scripts 'build_midi_map' function + is invoked by our C instance sibling. Its either invoked when we have requested it + (see 'request_rebuild_midi_map' above) or when due to a change in Lives internal state, + a rebuild is needed.""" + for s in self.__channel_strips: + s.build_midi_map(midi_map_handle) + + self.__master_strip.build_midi_map(midi_map_handle) + for i in range(SID_FIRST, SID_LAST + 1): + if i not in function_key_control_switch_ids: + Live.MidiMap.forward_midi_note(self.handle(), midi_map_handle, 0, i) + + Live.MidiMap.forward_midi_cc(self.handle(), midi_map_handle, 0, JOG_WHEEL_CC_NO) + + def update_display(self): + if self._refresh_state_next_time > 0: + self._refresh_state_next_time -= 1 + if self._refresh_state_next_time == 0: + for c in self.__components: + c.refresh_state() + + self.request_firmware_version() + for c in self.__components: + c.on_update_display_timer() + + def send_midi(self, midi_event_bytes): + """Use this function to send MIDI events through Live to the _real_ MIDI devices + that this script is assigned to.""" + self.__c_instance.send_midi(midi_event_bytes) + + def receive_midi(self, midi_bytes): + if midi_bytes[0] & 240 == NOTE_ON_STATUS or midi_bytes[0] & 240 == NOTE_OFF_STATUS: + note = midi_bytes[1] + value = BUTTON_PRESSED if midi_bytes[2] > 0 else BUTTON_RELEASED + if note in range(SID_FIRST, SID_LAST + 1): + if note in display_switch_ids: + self.__handle_display_switch_ids(note, value) + if note in channel_strip_switch_ids + fader_touch_switch_ids: + for s in self.__channel_strips: + s.handle_channel_strip_switch_ids(note, value) + + if note in channel_strip_control_switch_ids: + self.__channel_strip_controller.handle_assignment_switch_ids(note, value) + if note in function_key_control_switch_ids: + self.__software_controller.handle_function_key_switch_ids(note, value) + if note in software_controls_switch_ids: + self.__software_controller.handle_software_controls_switch_ids(note, value) + if note in transport_control_switch_ids: + self.__transport.handle_transport_switch_ids(note, value) + if note in marker_control_switch_ids: + self.__transport.handle_marker_switch_ids(note, value) + if note in jog_wheel_switch_ids: + self.__transport.handle_jog_wheel_switch_ids(note, value) + elif midi_bytes[0] & 240 == CC_STATUS: + cc_no = midi_bytes[1] + cc_value = midi_bytes[2] + if cc_no == JOG_WHEEL_CC_NO: + self.__transport.handle_jog_wheel_rotation(cc_value) + elif cc_no in range(FID_PANNING_BASE, FID_PANNING_BASE + NUM_CHANNEL_STRIPS): + for s in self.__channel_strips: + s.handle_vpot_rotation(cc_no - FID_PANNING_BASE, cc_value) + + elif midi_bytes[0] == 240 and len(midi_bytes) == 12 and midi_bytes[5] == 20: + version_bytes = midi_bytes[6:-2] + major_version = version_bytes[1] + self.is_pro_version = major_version > 50 + self._received_firmware_version = True + + def can_lock_to_devices(self): + return False + + def suggest_input_port(self): + return '' + + def suggest_output_port(self): + return '' + + def suggest_map_mode(self, cc_no, channel): + result = Live.MidiMap.MapMode.absolute + if cc_no in range(FID_PANNING_BASE, FID_PANNING_BASE + NUM_CHANNEL_STRIPS): + result = Live.MidiMap.MapMode.relative_signed_bit + return result + + def shift_is_pressed(self): + return self.__shift_is_pressed + + def set_shift_is_pressed(self, pressed): + self.__shift_is_pressed = pressed + + def option_is_pressed(self): + return self.__option_is_pressed + + def set_option_is_pressed(self, pressed): + self.__option_is_pressed = pressed + + def control_is_pressed(self): + return self.__control_is_pressed + + def set_control_is_pressed(self, pressed): + self.__control_is_pressed = pressed + + def alt_is_pressed(self): + return self.__alt_is_pressed + + def set_alt_is_pressed(self, pressed): + self.__alt_is_pressed = pressed + + def __handle_display_switch_ids(self, switch_id, value): + if switch_id == SID_DISPLAY_NAME_VALUE: + if value == BUTTON_PRESSED: + self.__channel_strip_controller.toggle_meter_mode() + elif switch_id == SID_DISPLAY_SMPTE_BEATS: + if value == BUTTON_PRESSED: + self.__time_display.toggle_mode() diff --git a/nanoKONTROL2/MackieControlComponent.py b/nanoKONTROL2/MackieControlComponent.py new file mode 100644 index 00000000..a90ceb4b --- /dev/null +++ b/nanoKONTROL2/MackieControlComponent.py @@ -0,0 +1,42 @@ +#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/MackieControl/MackieControlComponent.py +from consts import * +import Live + +class MackieControlComponent: + """Baseclass for every 'sub component' of the Mackie Control. Just offers some """ + + def __init__(self, main_script): + self.__main_script = main_script + + def destroy(self): + self.__main_script = None + + def main_script(self): + return self.__main_script + + def shift_is_pressed(self): + return self.__main_script.shift_is_pressed() + + def option_is_pressed(self): + return self.__main_script.option_is_pressed() + + def control_is_pressed(self): + return self.__main_script.control_is_pressed() + + def alt_is_pressed(self): + return self.__main_script.alt_is_pressed() + + def song(self): + return self.__main_script.song() + + def script_handle(self): + return self.__main_script.handle() + + def application(self): + return self.__main_script.application() + + def send_midi(self, bytes): + self.__main_script.send_midi(bytes) + + def request_rebuild_midi_map(self): + self.__main_script.request_rebuild_midi_map() diff --git a/nanoKONTROL2/MainDisplay.py b/nanoKONTROL2/MainDisplay.py new file mode 100644 index 00000000..c597a81f --- /dev/null +++ b/nanoKONTROL2/MainDisplay.py @@ -0,0 +1,61 @@ +#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/MackieControl/MainDisplay.py +from MackieControlComponent import * + +class MainDisplay(MackieControlComponent): + """ Representing one main 2 row display of a Mackie Control or Extension + """ + + def __init__(self, main_script): + MackieControlComponent.__init__(self, main_script) + self.__stack_offset = 0 + self.__last_send_messages = [[], []] + + def destroy(self): + NUM_CHARS_PER_DISPLAY_LINE = 54 + upper_message = 'Ableton Live'.center(NUM_CHARS_PER_DISPLAY_LINE) + self.send_display_string(upper_message, 0, 0) + lower_message = 'Device is offline'.center(NUM_CHARS_PER_DISPLAY_LINE) + self.send_display_string(lower_message, 1, 0) + MackieControlComponent.destroy(self) + + def stack_offset(self): + return self.__stack_offset + + def set_stack_offset(self, offset): + """This is the offset that one gets by 'stacking' several MackieControl XTs: + the first is at index 0, the second at 8, etc ... + """ + self.__stack_offset = offset + + def send_display_string(self, display_string, display_row, cursor_offset): + if display_row == 0: + offset = cursor_offset + elif display_row == 1: + offset = NUM_CHARS_PER_DISPLAY_LINE + 2 + cursor_offset + else: + raise 0 or AssertionError + message_string = [ ord(c) for c in display_string ] + for i in range(len(message_string)): + if message_string[i] >= 128: + message_string[i] = 0 + + if self.__last_send_messages[display_row] != message_string: + self.__last_send_messages[display_row] = message_string + if self.main_script().is_extension(): + device_type = SYSEX_DEVICE_TYPE_XT + else: + device_type = SYSEX_DEVICE_TYPE + display_sysex = (240, + 0, + 0, + 102, + device_type, + 18, + offset) + tuple(message_string) + (247,) + self.send_midi(display_sysex) + + def refresh_state(self): + self.__last_send_messages = [[], []] + + def on_update_display_timer(self): + pass \ No newline at end of file diff --git a/nanoKONTROL2/MainDisplayController.py b/nanoKONTROL2/MainDisplayController.py new file mode 100644 index 00000000..8fb45afe --- /dev/null +++ b/nanoKONTROL2/MainDisplayController.py @@ -0,0 +1,164 @@ +#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/MackieControl/MainDisplayController.py +from MackieControlComponent import * + +class MainDisplayController(MackieControlComponent): + """ Controlling all available main displays (the display above the channel strips), + which will be only one when only using the 'main' Mackie Control, and severals + when using at least one Mackie Control XT, attached to the main Mackie Control + + The Displays can be run in two modes: Channel and Global mode: + - In channel mode 2*6 characters can be shown for each channel strip + - In global mode, you can setup the two 54 charchter lines to whatever you want + + See 'class ChannelStripController' for descriptions of the stack_index or details + about the different assignment modes. + """ + + def __init__(self, main_script, display): + MackieControlComponent.__init__(self, main_script) + self.__left_extensions = [] + self.__right_extensions = [] + self.__displays = [display] + self.__own_display = display + self.__parameters = [ [] for x in range(NUM_CHANNEL_STRIPS) ] + self.__channel_strip_strings = [ '' for x in range(NUM_CHANNEL_STRIPS) ] + self.__channel_strip_mode = True + self.__show_parameter_names = False + self.__bank_channel_offset = 0 + self.__meters_enabled = False + self.__show_return_tracks = False + + def destroy(self): + self.enable_meters(False) + MackieControlComponent.destroy(self) + + def set_controller_extensions(self, left_extensions, right_extensions): + """ Called from the main script (after all scripts where initialized), to let us + know where and how many MackieControlXT are installed. + """ + self.__left_extensions = left_extensions + self.__right_extensions = right_extensions + self.__displays = [] + stack_offset = 0 + for le in left_extensions: + self.__displays.append(le.main_display()) + le.main_display().set_stack_offset(stack_offset) + stack_offset += NUM_CHANNEL_STRIPS + + self.__displays.append(self.__own_display) + self.__own_display.set_stack_offset(stack_offset) + stack_offset += NUM_CHANNEL_STRIPS + for re in right_extensions: + self.__displays.append(re.main_display()) + re.main_display().set_stack_offset(stack_offset) + stack_offset += NUM_CHANNEL_STRIPS + + self.__parameters = [ [] for x in range(len(self.__displays) * NUM_CHANNEL_STRIPS) ] + self.__channel_strip_strings = [ '' for x in range(len(self.__displays) * NUM_CHANNEL_STRIPS) ] + self.refresh_state() + + def enable_meters(self, enabled): + if self.__meters_enabled != enabled: + self.__meters_enabled = enabled + self.refresh_state() + + def set_show_parameter_names(self, enable): + self.__show_parameter_names = enable + + def set_channel_offset(self, channel_offset): + self.__bank_channel_offset = channel_offset + + def parameters(self): + return self.__parameters + + def set_parameters(self, parameters): + if parameters: + self.set_channel_strip_strings(None) + for d in self.__displays: + self.__parameters = parameters + + def channel_strip_strings(self): + return self.__channel_strip_strings + + def set_channel_strip_strings(self, channel_strip_strings): + if channel_strip_strings: + self.set_parameters(None) + self.__channel_strip_strings = channel_strip_strings + + def set_show_return_track_names(self, show_returns): + self.__show_return_tracks = show_returns + + def refresh_state(self): + for d in self.__displays: + d.refresh_state() + + def on_update_display_timer(self): + strip_index = 0 + for display in self.__displays: + if self.__channel_strip_mode: + upper_string = u'' + lower_string = u'' + track_index_range = range(self.__bank_channel_offset + display.stack_offset(), self.__bank_channel_offset + display.stack_offset() + NUM_CHANNEL_STRIPS) + if self.__show_return_tracks: + tracks = self.song().return_tracks + else: + tracks = self.song().visible_tracks + for t in track_index_range: + if self.__parameters and self.__show_parameter_names: + if self.__parameters[strip_index]: + upper_string += self.__generate_6_char_string(self.__parameters[strip_index][1]) + else: + upper_string += self.__generate_6_char_string('') + elif t < len(tracks): + upper_string += self.__generate_6_char_string(tracks[t].name) + else: + upper_string += self.__generate_6_char_string('') + upper_string += ' ' + if self.__parameters and self.__parameters[strip_index]: + if self.__parameters[strip_index][0]: + lower_string += self.__generate_6_char_string(unicode(self.__parameters[strip_index][0])) + else: + lower_string += self.__generate_6_char_string('') + elif self.__channel_strip_strings and self.__channel_strip_strings[strip_index]: + lower_string += self.__generate_6_char_string(self.__channel_strip_strings[strip_index]) + else: + lower_string += self.__generate_6_char_string('') + lower_string += ' ' + strip_index += 1 + + display.send_display_string(upper_string, 0, 0) + if not self.__meters_enabled: + display.send_display_string(lower_string, 1, 0) + else: + ascii_message = '< _1234 guck ma #!?:;_ >' + if not self.__test: + self.__test = 0 + self.__test = self.__test + 1 + if self.__test > NUM_CHARS_PER_DISPLAY_LINE - len(ascii_message): + self.__test = 0 + self.send_display_string(ascii_message, 0, self.__test) + + def __generate_6_char_string(self, display_string): + if not display_string: + return ' ' + if len(display_string.strip()) > 6 and display_string.endswith('dB') and display_string.find('.') != -1: + display_string = display_string[:-2] + if len(display_string) > 6: + for um in [' ', + 'i', + 'o', + 'u', + 'e', + 'a']: + while len(display_string) > 6 and display_string.rfind(um, 1) != -1: + um_pos = display_string.rfind(um, 1) + display_string = display_string[:um_pos] + display_string[um_pos + 1:] + + else: + display_string = display_string.center(6) + ret = u'' + for i in range(6): + ret += display_string[i] + + raise len(ret) == 6 or AssertionError + return ret diff --git a/nanoKONTROL2/SoftwareController.py b/nanoKONTROL2/SoftwareController.py new file mode 100644 index 00000000..bc3d22fd --- /dev/null +++ b/nanoKONTROL2/SoftwareController.py @@ -0,0 +1,196 @@ +#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/MackieControl/SoftwareController.py +from MackieControlComponent import * + +class SoftwareController(MackieControlComponent): + """Representing the buttons above the transport, including the basic: """ + + def __init__(self, main_script): + MackieControlComponent.__init__(self, main_script) + self.__last_can_undo_state = False + self.__last_can_redo_state = False + av = self.application().view + av.add_is_view_visible_listener('Session', self.__update_session_arranger_button_led) + av.add_is_view_visible_listener('Detail/Clip', self.__update_detail_sub_view_button_led) + av.add_is_view_visible_listener('Browser', self.__update_browser_button_led) + av.add_is_view_visible_listener('Detail', self.__update_detail_button_led) + self.song().view.add_draw_mode_listener(self.__update_draw_mode_button_led) + self.song().add_back_to_arranger_listener(self.__update_back_to_arranger_button_led) + + def destroy(self): + av = self.application().view + av.remove_is_view_visible_listener('Session', self.__update_session_arranger_button_led) + av.remove_is_view_visible_listener('Detail/Clip', self.__update_detail_sub_view_button_led) + av.remove_is_view_visible_listener('Browser', self.__update_browser_button_led) + av.remove_is_view_visible_listener('Detail', self.__update_detail_button_led) + self.song().view.remove_draw_mode_listener(self.__update_draw_mode_button_led) + self.song().remove_back_to_arranger_listener(self.__update_back_to_arranger_button_led) + for note in software_controls_switch_ids: + self.send_midi((NOTE_ON_STATUS, note, BUTTON_STATE_OFF)) + + for note in function_key_control_switch_ids: + self.send_midi((NOTE_ON_STATUS, note, BUTTON_STATE_OFF)) + + MackieControlComponent.destroy(self) + + def handle_function_key_switch_ids(self, switch_id, value): + raise 0 or AssertionError + + def handle_software_controls_switch_ids(self, switch_id, value): + if switch_id == SID_MOD_SHIFT: + self.main_script().set_shift_is_pressed(value == BUTTON_PRESSED) + elif switch_id == SID_MOD_OPTION: + self.main_script().set_option_is_pressed(value == BUTTON_PRESSED) + elif switch_id == SID_MOD_CTRL: + self.main_script().set_control_is_pressed(value == BUTTON_PRESSED) + elif switch_id == SID_MOD_ALT: + self.main_script().set_alt_is_pressed(value == BUTTON_PRESSED) + elif switch_id == SID_AUTOMATION_ON: + if value == BUTTON_PRESSED: + self.__toggle_session_arranger_is_visible() + elif switch_id == SID_AUTOMATION_RECORD: + if value == BUTTON_PRESSED: + self.__toggle_detail_sub_view() + elif switch_id == SID_AUTOMATION_SNAPSHOT: + if value == BUTTON_PRESSED: + self.__toggle_browser_is_visible() + elif switch_id == SID_AUTOMATION_TOUCH: + if value == BUTTON_PRESSED: + self.__toggle_detail_is_visible() + elif switch_id == SID_FUNC_UNDO: + if value == BUTTON_PRESSED: + self.song().undo() + elif switch_id == SID_FUNC_REDO: + if value == BUTTON_PRESSED: + self.song().redo() + elif switch_id == SID_FUNC_CANCEL: + if value == BUTTON_PRESSED: + self.__toggle_back_to_arranger() + elif switch_id == SID_FUNC_ENTER: + if value == BUTTON_PRESSED: + self.__toggle_draw_mode() + elif switch_id == SID_FUNC_MARKER: + if value == BUTTON_PRESSED: + self.song().set_or_delete_cue() + elif switch_id == SID_FUNC_MIXER: + if value == BUTTON_PRESSED: + self.__toggle_follow_song() + + def refresh_state(self): + self.main_script().set_shift_is_pressed(False) + self.main_script().set_option_is_pressed(False) + self.main_script().set_control_is_pressed(False) + self.main_script().set_alt_is_pressed(False) + self.__update_session_arranger_button_led() + self.__update_detail_sub_view_button_led() + self.__update_browser_button_led() + self.__update_detail_button_led() + self.__update_undo_button_led() + self.__update_redo_button_led() + self.__update_draw_mode_button_led() + self.__update_back_to_arranger_button_led() + + def on_update_display_timer(self): + if self.__last_can_undo_state != self.song().can_undo: + self.__last_can_undo_state = self.song().can_undo + self.__update_undo_button_led() + if self.__last_can_redo_state != self.song().can_redo: + self.__last_can_redo_state = self.song().can_redo + self.__update_redo_button_led() + + def __toggle_session_arranger_is_visible(self): + if self.application().view.is_view_visible('Session'): + if self.shift_is_pressed(): + self.application().view.focus_view('Session') + else: + self.application().view.hide_view('Session') + elif not self.application().view.is_view_visible('Arranger'): + raise AssertionError + self.shift_is_pressed() and self.application().view.focus_view('Arranger') + else: + self.application().view.hide_view('Arranger') + + def __toggle_detail_sub_view(self): + if self.application().view.is_view_visible('Detail/Clip'): + if self.shift_is_pressed(): + self.application().view.focus_view('Detail/Clip') + else: + self.application().view.show_view('Detail/DeviceChain') + elif self.shift_is_pressed(): + self.application().view.focus_view('Detail/DeviceChain') + else: + self.application().view.show_view('Detail/Clip') + + def __toggle_browser_is_visible(self): + if self.application().view.is_view_visible('Browser'): + if self.shift_is_pressed(): + self.application().view.focus_view('Browser') + else: + self.application().view.hide_view('Browser') + else: + self.application().view.show_view('Browser') + + def __toggle_detail_is_visible(self): + if self.application().view.is_view_visible('Detail'): + if self.shift_is_pressed(): + self.application().view.focus_view('Detail') + else: + self.application().view.hide_view('Detail') + else: + self.application().view.show_view('Detail') + + def __toggle_back_to_arranger(self): + self.song().back_to_arranger = not self.song().back_to_arranger + + def __toggle_draw_mode(self): + self.song().view.draw_mode = not self.song().view.draw_mode + + def __toggle_follow_song(self): + self.song().view.follow_song = not self.song().view.follow_song + + def __update_session_arranger_button_led(self): + if self.application().view.is_view_visible('Session'): + self.send_midi((NOTE_ON_STATUS, SID_AUTOMATION_ON, BUTTON_STATE_ON)) + else: + self.send_midi((NOTE_ON_STATUS, SID_AUTOMATION_ON, BUTTON_STATE_OFF)) + + def __update_detail_sub_view_button_led(self): + if self.application().view.is_view_visible('Detail/Clip'): + self.send_midi((NOTE_ON_STATUS, SID_AUTOMATION_RECORD, BUTTON_STATE_ON)) + else: + self.send_midi((NOTE_ON_STATUS, SID_AUTOMATION_RECORD, BUTTON_STATE_OFF)) + + def __update_browser_button_led(self): + if self.application().view.is_view_visible('Browser'): + self.send_midi((NOTE_ON_STATUS, SID_AUTOMATION_SNAPSHOT, BUTTON_STATE_ON)) + else: + self.send_midi((NOTE_ON_STATUS, SID_AUTOMATION_SNAPSHOT, BUTTON_STATE_OFF)) + + def __update_detail_button_led(self): + if self.application().view.is_view_visible('Detail'): + self.send_midi((NOTE_ON_STATUS, SID_AUTOMATION_TOUCH, BUTTON_STATE_ON)) + else: + self.send_midi((NOTE_ON_STATUS, SID_AUTOMATION_TOUCH, BUTTON_STATE_OFF)) + + def __update_undo_button_led(self): + if self.song().can_undo: + self.send_midi((NOTE_ON_STATUS, SID_FUNC_UNDO, BUTTON_STATE_ON)) + else: + self.send_midi((NOTE_ON_STATUS, SID_FUNC_UNDO, BUTTON_STATE_OFF)) + + def __update_redo_button_led(self): + if self.song().can_redo: + self.send_midi((NOTE_ON_STATUS, SID_FUNC_REDO, BUTTON_STATE_ON)) + else: + self.send_midi((NOTE_ON_STATUS, SID_FUNC_REDO, BUTTON_STATE_OFF)) + + def __update_back_to_arranger_button_led(self): + if self.song().back_to_arranger: + self.send_midi((NOTE_ON_STATUS, SID_FUNC_CANCEL, BUTTON_STATE_ON)) + else: + self.send_midi((NOTE_ON_STATUS, SID_FUNC_CANCEL, BUTTON_STATE_OFF)) + + def __update_draw_mode_button_led(self): + if self.song().view.draw_mode: + self.send_midi((NOTE_ON_STATUS, SID_FUNC_ENTER, BUTTON_STATE_ON)) + else: + self.send_midi((NOTE_ON_STATUS, SID_FUNC_ENTER, BUTTON_STATE_OFF)) diff --git a/nanoKONTROL2/TimeDisplay.py b/nanoKONTROL2/TimeDisplay.py new file mode 100644 index 00000000..558980c1 --- /dev/null +++ b/nanoKONTROL2/TimeDisplay.py @@ -0,0 +1,64 @@ +#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/MackieControl/TimeDisplay.py +from MackieControlComponent import * + +class TimeDisplay(MackieControlComponent): + """Represents the Mackie Controls Time-Display, plus the two LED's that show the""" + + def __init__(self, main_script): + MackieControlComponent.__init__(self, main_script) + self.__main_script = main_script + self.__show_beat_time = False + self.__smpt_format = Live.Song.TimeFormat.smpte_25 + self.__last_send_time = [] + self.show_beats() + + def destroy(self): + self.clear_display() + MackieControlComponent.destroy(self) + + def show_beats(self): + self.__show_beat_time = True + self.send_midi((NOTE_ON_STATUS, SELECT_BEATS_NOTE, BUTTON_STATE_ON)) + self.send_midi((NOTE_ON_STATUS, SELECT_SMPTE_NOTE, BUTTON_STATE_OFF)) + + def show_smpte(self, smpte_mode): + self.__show_beat_time = False + self.__smpt_format = smpte_mode + self.send_midi((NOTE_ON_STATUS, SELECT_BEATS_NOTE, BUTTON_STATE_OFF)) + self.send_midi((NOTE_ON_STATUS, SELECT_SMPTE_NOTE, BUTTON_STATE_ON)) + + def toggle_mode(self): + if self.__show_beat_time: + self.show_smpte(self.__smpt_format) + else: + self.show_beats() + + def clear_display(self): + time_string = [ ' ' for i in range(10) ] + self.__send_time_string(time_string, show_points=False) + self.send_midi((NOTE_ON_STATUS, SELECT_BEATS_NOTE, BUTTON_STATE_OFF)) + self.send_midi((NOTE_ON_STATUS, SELECT_SMPTE_NOTE, BUTTON_STATE_OFF)) + + def refresh_state(self): + self.show_beats() + self.__last_send_time = [] + + def on_update_display_timer(self): + """Called by a timer which gets called every 100 ms. We will simply check if the""" + if self.__show_beat_time: + time_string = str(self.song().get_current_beats_song_time()) + else: + time_string = str(self.song().get_current_smpte_song_time(self.__smpt_format)) + time_string = [ c for c in time_string if c not in ('.', ':') ] + if self.__last_send_time != time_string: + self.__last_send_time = time_string + self.__send_time_string(time_string, show_points=True) + + def __send_time_string(self, time_string, show_points): + raise len(time_string) >= 10 or AssertionError + for c in range(0, 10): + char = time_string[9 - c].upper() + char_code = g7_seg_led_conv_table[char] + if show_points and c in (3, 5, 7): + char_code += 64 + self.send_midi((176, 64 + c, char_code)) diff --git a/nanoKONTROL2/Transport.py b/nanoKONTROL2/Transport.py new file mode 100644 index 00000000..25ab5f87 --- /dev/null +++ b/nanoKONTROL2/Transport.py @@ -0,0 +1,471 @@ +#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/MackieControl/Transport.py +from MackieControlComponent import * + +class Transport(MackieControlComponent): + """Representing the transport section of the Mackie Control: """ + + def __init__(self, main_script): + MackieControlComponent.__init__(self, main_script) + self.__forward_button_down = False + self.____rewind_button_down = False + self.__zoom_button_down = False + self.__scrub_button_down = False + self.__cursor_left_is_down = False + self.__cursor_right_is_down = False + self.__cursor_up_is_down = False + self.__cursor_down_is_down = False + self.__cursor_repeat_delay = 0 + self.__transport_repeat_delay = 0 + self.____fast_forward_counter = 0 + self.__fast___rewind_counter = 0 + self.__jog_step_count_forward = 0 + self.__jog_step_count_backwards = 0 + self.__last_focussed_clip_play_state = CLIP_STATE_INVALID + self.song().add_record_mode_listener(self.__update_record_button_led) + self.song().add_is_playing_listener(self.__update_play_button_led) + self.song().add_loop_listener(self.__update_loop_button_led) + self.song().add_punch_out_listener(self.__update_punch_out_button_led) + self.song().add_punch_in_listener(self.__update_punch_in_button_led) + self.song().add_can_jump_to_prev_cue_listener(self.__update_prev_cue_button_led) + self.song().add_can_jump_to_next_cue_listener(self.__update_next_cue_button_led) + self.application().view.add_is_view_visible_listener('Session', self.__on_session_is_visible_changed) + self.refresh_state() + + def destroy(self): + self.song().remove_record_mode_listener(self.__update_record_button_led) + self.song().remove_is_playing_listener(self.__update_play_button_led) + self.song().remove_loop_listener(self.__update_loop_button_led) + self.song().remove_punch_out_listener(self.__update_punch_out_button_led) + self.song().remove_punch_in_listener(self.__update_punch_in_button_led) + self.song().remove_can_jump_to_prev_cue_listener(self.__update_prev_cue_button_led) + self.song().remove_can_jump_to_next_cue_listener(self.__update_next_cue_button_led) + self.application().view.remove_is_view_visible_listener('Session', self.__on_session_is_visible_changed) + for note in transport_control_switch_ids: + self.send_midi((NOTE_ON_STATUS, note, BUTTON_STATE_OFF)) + + for note in jog_wheel_switch_ids: + self.send_midi((NOTE_ON_STATUS, note, BUTTON_STATE_OFF)) + + for note in marker_control_switch_ids: + self.send_midi((NOTE_ON_STATUS, note, BUTTON_STATE_OFF)) + + MackieControlComponent.destroy(self) + + def refresh_state(self): + self.__update_play_button_led() + self.__update_record_button_led() + self.__update_prev_cue_button_led() + self.__update_next_cue_button_led() + self.__update_loop_button_led() + self.__update_punch_in_button_led() + self.__update_punch_out_button_led() + self.__forward_button_down = False + self.____rewind_button_down = False + self.__zoom_button_down = False + self.__scrub_button_down = False + self.__cursor_left_is_down = False + self.__cursor_right_is_down = False + self.__cursor_up_is_down = False + self.__cursor_down_is_down = False + self.__cursor_repeat_delay = 0 + self.__transport_repeat_delay = 0 + self.____fast_forward_counter = 0 + self.__fast___rewind_counter = 0 + self.__jog_step_count_forward = 0 + self.__jog_step_count_backwards = 0 + self.__last_focussed_clip_play_state = CLIP_STATE_INVALID + self.__update_forward_rewind_leds() + self.__update_zoom_button_led() + self.__update_scrub_button_led() + + def session_is_visible(self): + return self.application().view.is_view_visible('Session') + + def selected_clip_slot(self): + return self.song().view.highlighted_clip_slot + + def on_update_display_timer(self): + if self.__transport_repeat_delay > 2: + if self.alt_is_pressed(): + base_acceleration = 1 + else: + base_acceleration = self.song().signature_numerator + if self.song().is_playing: + base_acceleration *= 4 + if not (self.__forward_button_down and self.____rewind_button_down): + if self.__forward_button_down: + self.____fast_forward_counter += 1 + self.__fast___rewind_counter -= 4 + if not self.alt_is_pressed(): + self.__fast_forward(base_acceleration + max(1, self.____fast_forward_counter / 4)) + else: + self.__fast_forward(base_acceleration) + if self.____rewind_button_down: + self.__fast___rewind_counter += 1 + self.____fast_forward_counter -= 4 + if not self.alt_is_pressed(): + self.__rewind(base_acceleration + max(1, self.__fast___rewind_counter / 4)) + else: + self.__rewind(base_acceleration) + else: + self.__transport_repeat_delay += 1 + if self.__cursor_repeat_delay > 2: + if self.__cursor_left_is_down: + self.__on_cursor_left_pressed() + if self.__cursor_right_is_down: + self.__on_cursor_right_pressed() + if self.__cursor_up_is_down: + self.__on_cursor_up_pressed() + if self.__cursor_down_is_down: + self.__on_cursor_down_pressed() + else: + self.__cursor_repeat_delay += 1 + if self.session_is_visible(): + self.__update_zoom_led_in_session() + + def handle_marker_switch_ids(self, switch_id, value): + if switch_id == SID_MARKER_FROM_PREV: + if value == BUTTON_PRESSED: + self.__jump_to_prev_cue() + elif switch_id == SID_MARKER_FROM_NEXT: + if value == BUTTON_PRESSED: + self.__jump_to_next_cue() + elif switch_id == SID_MARKER_LOOP: + if value == BUTTON_PRESSED: + self.__toggle_loop() + elif switch_id == SID_MARKER_PI: + if value == BUTTON_PRESSED: + if self.control_is_pressed(): + self.__set_loopstart_from_cur_position() + else: + self.__toggle_punch_in() + elif switch_id == SID_MARKER_PO: + if value == BUTTON_PRESSED: + if self.control_is_pressed(): + self.__set_loopend_from_cur_position() + else: + self.__toggle_punch_out() + elif switch_id == SID_MARKER_HOME: + if value == BUTTON_PRESSED: + self.__goto_home() + elif switch_id == SID_MARKER_END: + if value == BUTTON_PRESSED: + self.__goto_end() + + def handle_transport_switch_ids(self, switch_id, value): + if switch_id == SID_TRANSPORT_REWIND: + if value == BUTTON_PRESSED: + self.__rewind() + self.____rewind_button_down = True + elif value == BUTTON_RELEASED: + self.____rewind_button_down = False + self.__fast___rewind_counter = 0 + self.__update_forward_rewind_leds() + elif switch_id == SID_TRANSPORT_FAST_FORWARD: + if value == BUTTON_PRESSED: + self.__fast_forward() + self.__forward_button_down = True + elif value == BUTTON_RELEASED: + self.__forward_button_down = False + self.____fast_forward_counter = 0 + self.__update_forward_rewind_leds() + elif switch_id == SID_TRANSPORT_STOP: + if value == BUTTON_PRESSED: + self.__stop_song() + elif switch_id == SID_TRANSPORT_PLAY: + if value == BUTTON_PRESSED: + self.__start_song() + elif switch_id == SID_TRANSPORT_RECORD: + if value == BUTTON_PRESSED: + self.__toggle_record() + + def handle_jog_wheel_rotation(self, value): + backwards = value >= 64 + if self.control_is_pressed(): + if self.alt_is_pressed(): + step = 0.1 + else: + step = 1.0 + if backwards: + amount = -(value - 64) + else: + amount = value + tempo = max(20, min(999, self.song().tempo + amount * step)) + self.song().tempo = tempo + elif self.session_is_visible(): + num_steps_per_session_scroll = 4 + if backwards: + self.__jog_step_count_backwards += 1 + if self.__jog_step_count_backwards >= num_steps_per_session_scroll: + self.__jog_step_count_backwards = 0 + step = -1 + else: + step = 0 + else: + self.__jog_step_count_forward += 1 + if self.__jog_step_count_forward >= num_steps_per_session_scroll: + self.__jog_step_count_forward = 0 + step = 1 + else: + step = 0 + if step: + new_index = list(self.song().scenes).index(self.song().view.selected_scene) + step + new_index = min(len(self.song().scenes) - 1, max(0, new_index)) + self.song().view.selected_scene = self.song().scenes[new_index] + else: + if backwards: + step = max(1.0, (value - 64) / 2.0) + else: + step = max(1.0, value / 2.0) + if self.song().is_playing: + step *= 4.0 + if self.alt_is_pressed(): + step /= 4.0 + if self.__scrub_button_down: + if backwards: + self.song().scrub_by(-step) + else: + self.song().scrub_by(step) + elif backwards: + self.song().jump_by(-step) + else: + self.song().jump_by(step) + + def handle_jog_wheel_switch_ids(self, switch_id, value): + if switch_id == SID_JOG_CURSOR_UP: + if value == BUTTON_PRESSED: + self.__cursor_up_is_down = True + self.__cursor_repeat_delay = 0 + self.__on_cursor_up_pressed() + elif value == BUTTON_RELEASED: + self.__cursor_up_is_down = False + elif switch_id == SID_JOG_CURSOR_DOWN: + if value == BUTTON_PRESSED: + self.__cursor_down_is_down = True + self.__cursor_repeat_delay = 0 + self.__on_cursor_down_pressed() + elif value == BUTTON_RELEASED: + self.__cursor_down_is_down = False + elif switch_id == SID_JOG_CURSOR_LEFT: + if value == BUTTON_PRESSED: + self.__cursor_left_is_down = True + self.__cursor_repeat_delay = 0 + self.__on_cursor_left_pressed() + elif value == BUTTON_RELEASED: + self.__cursor_left_is_down = False + elif switch_id == SID_JOG_CURSOR_RIGHT: + if value == BUTTON_PRESSED: + self.__cursor_right_is_down = True + self.__cursor_repeat_delay = 0 + self.__on_cursor_right_pressed() + elif value == BUTTON_RELEASED: + self.__cursor_right_is_down = False + elif switch_id == SID_JOG_ZOOM: + if value == BUTTON_PRESSED: + if self.session_is_visible(): + if self.selected_clip_slot(): + if self.alt_is_pressed(): + self.selected_clip_slot().has_stop_button = not self.selected_clip_slot().has_stop_button + elif self.option_is_pressed(): + self.selected_clip_slot().stop() + else: + self.selected_clip_slot().fire() + else: + self.__zoom_button_down = not self.__zoom_button_down + self.__update_zoom_button_led() + elif switch_id == SID_JOG_SCRUB: + if value == BUTTON_PRESSED: + if self.session_is_visible(): + if self.option_is_pressed(): + self.song().stop_all_clips() + else: + self.song().view.selected_scene.fire_as_selected() + else: + self.__scrub_button_down = not self.__scrub_button_down + self.__update_scrub_button_led() + + def __on_cursor_up_pressed(self): + nav = Live.Application.Application.View.NavDirection + if self.__zoom_button_down: + self.application().view.zoom_view(nav.up, '', self.alt_is_pressed()) + else: + self.application().view.scroll_view(nav.up, '', self.alt_is_pressed()) + + def __on_cursor_down_pressed(self): + nav = Live.Application.Application.View.NavDirection + if self.__zoom_button_down: + self.application().view.zoom_view(nav.down, '', self.alt_is_pressed()) + else: + self.application().view.scroll_view(nav.down, '', self.alt_is_pressed()) + + def __on_cursor_left_pressed(self): + nav = Live.Application.Application.View.NavDirection + if self.__zoom_button_down: + self.application().view.zoom_view(nav.left, '', self.alt_is_pressed()) + else: + self.application().view.scroll_view(nav.left, '', self.alt_is_pressed()) + + def __on_cursor_right_pressed(self): + nav = Live.Application.Application.View.NavDirection + if self.__zoom_button_down: + self.application().view.zoom_view(nav.right, '', self.alt_is_pressed()) + else: + self.application().view.scroll_view(nav.right, '', self.alt_is_pressed()) + + def __toggle_record(self): + self.song().record_mode = not self.song().record_mode + + def __rewind(self, acceleration = 1): + beats = acceleration + self.song().jump_by(-beats) + + def __fast_forward(self, acceleration = 1): + beats = acceleration + self.song().jump_by(beats) + + def __stop_song(self): + self.song().stop_playing() + + def __start_song(self): + if self.shift_is_pressed(): + if not self.song().is_playing: + self.song().continue_playing() + else: + self.song().stop_playing() + elif self.control_is_pressed(): + self.song().play_selection() + else: + self.song().start_playing() + + def __toggle_follow(self): + self.song().follow_song = not self.song().follow_song + + def __toggle_loop(self): + self.song().loop = not self.song().loop + + def __toggle_punch_in(self): + self.song().punch_in = not self.song().punch_in + + def __toggle_punch_out(self): + self.song().punch_out = not self.song().punch_out + + def __jump_to_prev_cue(self): + self.song().jump_to_prev_cue() + + def __jump_to_next_cue(self): + self.song().jump_to_next_cue() + + def __set_loopstart_from_cur_position(self): + if self.song().current_song_time < self.song().loop_start + self.song().loop_length: + old_loop_start = self.song().loop_start + self.song().loop_start = self.song().current_song_time + self.song().loop_length += old_loop_start - self.song().loop_start + + def __set_loopend_from_cur_position(self): + if self.song().current_song_time > self.song().loop_start: + self.song().loop_length = self.song().current_song_time - self.song().loop_start + + def __goto_home(self): + self.song().current_song_time = 0 + + def __goto_end(self): + self.song().current_song_time = self.song().last_event_time + + def __on_session_is_visible_changed(self): + if not self.session_is_visible(): + self.__update_zoom_button_led() + + def __update_zoom_led_in_session(self): + if self.session_is_visible(): + clip_slot = self.selected_clip_slot() + if clip_slot and clip_slot.clip: + if clip_slot.clip.is_triggered: + state = CLIP_TRIGGERED + elif clip_slot.clip.is_playing: + state = CLIP_PLAYING + else: + state = CLIP_STOPPED + else: + state = CLIP_STOPPED + if state != self.__last_focussed_clip_play_state: + self.__last_focussed_clip_play_state = state + if state == CLIP_PLAYING: + self.send_midi((NOTE_ON_STATUS, SID_JOG_ZOOM, BUTTON_STATE_ON)) + elif state == CLIP_TRIGGERED: + self.send_midi((NOTE_ON_STATUS, SID_JOG_ZOOM, BUTTON_STATE_BLINKING)) + else: + self.send_midi((NOTE_ON_STATUS, SID_JOG_ZOOM, BUTTON_STATE_OFF)) + + def __update_forward_rewind_leds(self): + if self.__forward_button_down: + self.send_midi((NOTE_ON_STATUS, SID_TRANSPORT_FAST_FORWARD, BUTTON_STATE_ON)) + self.__transport_repeat_delay = 0 + else: + self.send_midi((NOTE_ON_STATUS, SID_TRANSPORT_FAST_FORWARD, BUTTON_STATE_OFF)) + if self.____rewind_button_down: + self.send_midi((NOTE_ON_STATUS, SID_TRANSPORT_REWIND, BUTTON_STATE_ON)) + self.__transport_repeat_delay = 0 + else: + self.send_midi((NOTE_ON_STATUS, SID_TRANSPORT_REWIND, BUTTON_STATE_OFF)) + + def __update_zoom_button_led(self): + if self.__zoom_button_down: + self.send_midi((NOTE_ON_STATUS, SID_JOG_ZOOM, BUTTON_STATE_ON)) + else: + self.send_midi((NOTE_ON_STATUS, SID_JOG_ZOOM, BUTTON_STATE_OFF)) + + def __update_scrub_button_led(self): + if self.__scrub_button_down: + self.send_midi((NOTE_ON_STATUS, SID_JOG_SCRUB, BUTTON_STATE_ON)) + else: + self.send_midi((NOTE_ON_STATUS, SID_JOG_SCRUB, BUTTON_STATE_OFF)) + + def __update_play_button_led(self): + if self.song().is_playing: + self.send_midi((NOTE_ON_STATUS, SID_TRANSPORT_PLAY, BUTTON_STATE_ON)) + self.send_midi((NOTE_ON_STATUS, SID_TRANSPORT_STOP, BUTTON_STATE_OFF)) + else: + self.send_midi((NOTE_ON_STATUS, SID_TRANSPORT_PLAY, BUTTON_STATE_OFF)) + self.send_midi((NOTE_ON_STATUS, SID_TRANSPORT_STOP, BUTTON_STATE_ON)) + + def __update_record_button_led(self): + if self.song().record_mode: + self.send_midi((NOTE_ON_STATUS, SID_TRANSPORT_RECORD, BUTTON_STATE_ON)) + else: + self.send_midi((NOTE_ON_STATUS, SID_TRANSPORT_RECORD, BUTTON_STATE_OFF)) + + def __update_follow_song_button_led(self): + if self.song().follow_song: + self.send_midi((NOTE_ON_STATUS, SID_MARKER_FROM_PREV, BUTTON_STATE_ON)) + else: + self.send_midi((NOTE_ON_STATUS, SID_MARKER_FROM_PREV, BUTTON_STATE_OFF)) + + def __update_prev_cue_button_led(self): + if self.song().can_jump_to_prev_cue: + self.send_midi((NOTE_ON_STATUS, SID_MARKER_FROM_PREV, BUTTON_STATE_ON)) + else: + self.send_midi((NOTE_ON_STATUS, SID_MARKER_FROM_PREV, BUTTON_STATE_OFF)) + + def __update_next_cue_button_led(self): + if self.song().can_jump_to_next_cue: + self.send_midi((NOTE_ON_STATUS, SID_MARKER_FROM_NEXT, BUTTON_STATE_ON)) + else: + self.send_midi((NOTE_ON_STATUS, SID_MARKER_FROM_NEXT, BUTTON_STATE_OFF)) + + def __update_loop_button_led(self): + if self.song().loop: + self.send_midi((NOTE_ON_STATUS, SID_MARKER_LOOP, BUTTON_STATE_ON)) + else: + self.send_midi((NOTE_ON_STATUS, SID_MARKER_LOOP, BUTTON_STATE_OFF)) + + def __update_punch_in_button_led(self): + if self.song().punch_in: + self.send_midi((NOTE_ON_STATUS, SID_MARKER_PI, BUTTON_STATE_ON)) + else: + self.send_midi((NOTE_ON_STATUS, SID_MARKER_PI, BUTTON_STATE_OFF)) + + def __update_punch_out_button_led(self): + if self.song().punch_out: + self.send_midi((NOTE_ON_STATUS, SID_MARKER_PO, BUTTON_STATE_ON)) + else: + self.send_midi((NOTE_ON_STATUS, SID_MARKER_PO, BUTTON_STATE_OFF)) \ No newline at end of file diff --git a/nanoKONTROL2/__init__.py b/nanoKONTROL2/__init__.py new file mode 100644 index 00000000..1032bcc6 --- /dev/null +++ b/nanoKONTROL2/__init__.py @@ -0,0 +1,19 @@ +#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/MackieControl/__init__.py +from MackieControl import MackieControl + +def create_instance(c_instance): + return MackieControl(c_instance) + + +from _Framework.Capabilities import * + +def get_capabilities(): + return {CONTROLLER_ID_KEY: controller_id(vendor_id=2675, product_ids=[6], model_name='MCU Pro USB v3.1'), + PORTS_KEY: [inport(props=[SCRIPT, REMOTE]), + inport(props=[]), + inport(props=[]), + inport(props=[]), + outport(props=[SCRIPT, REMOTE]), + outport(props=[]), + outport(props=[]), + outport(props=[])]} \ No newline at end of file diff --git a/nanoKONTROL2/consts.py b/nanoKONTROL2/consts.py new file mode 100644 index 00000000..dbf0988f --- /dev/null +++ b/nanoKONTROL2/consts.py @@ -0,0 +1,241 @@ +#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/MackieControl/consts.py +NOTE_OFF_STATUS = 128 +NOTE_ON_STATUS = 144 +CC_STATUS = 176 +PB_STATUS = 224 +SYSEX_DEVICE_TYPE = 20 +SYSEX_DEVICE_TYPE_XT = 21 +NUM_CHANNEL_STRIPS = 8 +MASTER_CHANNEL_STRIP_INDEX = 8 +BUTTON_STATE_OFF = 0 +BUTTON_STATE_ON = 127 +BUTTON_STATE_BLINKING = 1 +BUTTON_PRESSED = 1 +BUTTON_RELEASED = 0 +NUM_CHARS_PER_DISPLAY_LINE = 54 +SELECT_SMPTE_NOTE = 113 +SELECT_BEATS_NOTE = 114 +SELECT_RUDE_SOLO = 115 +FID_PANNING_BASE = 16 +JOG_WHEEL_CC_NO = 60 +VPOT_DISPLAY_SINGLE_DOT = 0 +VPOT_DISPLAY_BOOST_CUT = 1 +VPOT_DISPLAY_WRAP = 2 +VPOT_DISPLAY_SPREAD = 3 +CSM_VOLPAN = 0 +CSM_PLUGINS = 1 +CSM_IO = 2 +CSM_SENDS = 3 +CSM_IO_MODE_INPUT_MAIN = 0 +CSM_IO_MODE_INPUT_SUB = 1 +CSM_IO_MODE_OUTPUT_MAIN = 2 +CSM_IO_MODE_OUTPUT_SUB = 3 +CSM_IO_FIRST_MODE = CSM_IO_MODE_INPUT_MAIN +CSM_IO_LAST_MODE = CSM_IO_MODE_OUTPUT_SUB +PCM_DEVICES = 0 +PCM_PARAMETERS = 1 +PCM_NUMMODES = 2 +CLIP_STATE_INVALID = -1 +CLIP_STOPPED = 0 +CLIP_TRIGGERED = 1 +CLIP_PLAYING = 2 +g7_seg_led_conv_table = {' ': 0, + 'A': 1, + 'B': 2, + 'C': 3, + 'D': 4, + 'E': 5, + 'F': 6, + 'G': 7, + 'H': 8, + 'I': 9, + 'J': 10, + 'K': 11, + 'L': 12, + 'M': 13, + 'N': 14, + 'O': 15, + 'P': 16, + 'Q': 17, + 'R': 18, + 'S': 19, + 'T': 20, + 'U': 21, + 'V': 22, + 'W': 23, + 'X': 24, + 'Y': 25, + 'Z': 26, + '\\': 34, + '#': 35, + '$': 36, + '%': 37, + '&': 38, + "'": 39, + '(': 40, + ')': 41, + '*': 42, + '+': 43, + ',': 44, + '0': 48, + '1': 49, + '2': 50, + '3': 51, + '4': 52, + '5': 53, + '6': 54, + '7': 55, + '8': 56, + '9': 57, + ';': 59, + '<': 60} +SID_FIRST = 0 +SID_RECORD_ARM_BASE = 0 +SID_RECORD_ARM_CH1 = 0 +SID_RECORD_ARM_CH2 = 1 +SID_RECORD_ARM_CH3 = 2 +SID_RECORD_ARM_CH4 = 3 +SID_RECORD_ARM_CH5 = 4 +SID_RECORD_ARM_CH6 = 5 +SID_RECORD_ARM_CH7 = 6 +SID_RECORD_ARM_CH8 = 7 +SID_SOLO_BASE = 8 +SID_SOLO_CH1 = 8 +SID_SOLO_CH2 = 9 +SID_SOLO_CH3 = 10 +SID_SOLO_CH4 = 11 +SID_SOLO_CH5 = 12 +SID_SOLO_CH6 = 13 +SID_SOLO_CH7 = 14 +SID_SOLO_CH8 = 15 +SID_MUTE_BASE = 16 +SID_MUTE_CH1 = 16 +SID_MUTE_CH2 = 17 +SID_MUTE_CH3 = 18 +SID_MUTE_CH4 = 19 +SID_MUTE_CH5 = 20 +SID_MUTE_CH6 = 21 +SID_MUTE_CH7 = 22 +SID_MUTE_CH8 = 23 +SID_SELECT_BASE = 24 +SID_SELECT_CH1 = 24 +SID_SELECT_CH2 = 25 +SID_SELECT_CH3 = 26 +SID_SELECT_CH4 = 27 +SID_SELECT_CH5 = 28 +SID_SELECT_CH6 = 29 +SID_SELECT_CH7 = 30 +SID_SELECT_CH8 = 31 +SID_VPOD_PUSH_BASE = 32 +SID_VPOD_PUSH_CH1 = 32 +SID_VPOD_PUSH_CH2 = 33 +SID_VPOD_PUSH_CH3 = 34 +SID_VPOD_PUSH_CH4 = 35 +SID_VPOD_PUSH_CH5 = 36 +SID_VPOD_PUSH_CH6 = 37 +SID_VPOD_PUSH_CH7 = 38 +SID_VPOD_PUSH_CH8 = 39 +channel_strip_switch_ids = range(SID_RECORD_ARM_BASE, SID_VPOD_PUSH_CH8 + 1) +SID_ASSIGNMENT_IO = 40 +SID_ASSIGNMENT_SENDS = 41 +SID_ASSIGNMENT_PAN = 42 +SID_ASSIGNMENT_PLUG_INS = 43 +SID_ASSIGNMENT_EQ = 44 +SID_ASSIGNMENT_DYNAMIC = 45 +channel_strip_assignment_switch_ids = range(SID_ASSIGNMENT_IO, SID_ASSIGNMENT_DYNAMIC + 1) +SID_FADERBANK_PREV_BANK = 46 +SID_FADERBANK_NEXT_BANK = 47 +SID_FADERBANK_PREV_CH = 48 +SID_FADERBANK_NEXT_CH = 49 +SID_FADERBANK_FLIP = 50 +SID_FADERBANK_EDIT = 51 +channel_strip_control_switch_ids = range(SID_ASSIGNMENT_IO, SID_FADERBANK_EDIT + 1) +SID_DISPLAY_NAME_VALUE = 52 +SID_DISPLAY_SMPTE_BEATS = 53 +display_switch_ids = range(SID_DISPLAY_NAME_VALUE, SID_DISPLAY_SMPTE_BEATS + 1) +SID_SOFTWARE_F1 = 54 +SID_SOFTWARE_F2 = 55 +SID_SOFTWARE_F3 = 56 +SID_SOFTWARE_F4 = 57 +SID_SOFTWARE_F5 = 58 +SID_SOFTWARE_F6 = 59 +SID_SOFTWARE_F7 = 60 +SID_SOFTWARE_F8 = 61 +SID_SOFTWARE_F9 = 62 +SID_SOFTWARE_F10 = 63 +SID_SOFTWARE_F11 = 64 +SID_SOFTWARE_F12 = 65 +SID_SOFTWARE_F13 = 66 +SID_SOFTWARE_F14 = 67 +SID_SOFTWARE_F15 = 68 +SID_SOFTWARE_F16 = 69 +function_key_control_switch_ids = range(SID_SOFTWARE_F1, SID_SOFTWARE_F16 + 1) +SID_MOD_SHIFT = 70 +SID_MOD_OPTION = 71 +SID_MOD_CTRL = 72 +SID_MOD_ALT = 73 +SID_AUTOMATION_ON = 74 +SID_AUTOMATION_RECORD = 75 +SID_AUTOMATION_SNAPSHOT = 77 +SID_AUTOMATION_TOUCH = 78 +SID_FUNC_UNDO = 76 +SID_FUNC_CANCEL = 80 +SID_FUNC_ENTER = 81 +SID_FUNC_REDO = 79 +SID_FUNC_MARKER = 82 +SID_FUNC_MIXER = 83 +software_controls_switch_ids = (SID_MOD_SHIFT, + SID_MOD_OPTION, + SID_MOD_CTRL, + SID_MOD_ALT, + SID_AUTOMATION_ON, + SID_AUTOMATION_RECORD, + SID_AUTOMATION_SNAPSHOT, + SID_AUTOMATION_TOUCH, + SID_FUNC_UNDO, + SID_FUNC_CANCEL, + SID_FUNC_ENTER, + SID_FUNC_REDO, + SID_FUNC_MARKER, + SID_FUNC_MIXER) +SID_TRANSPORT_REWIND = 91 +SID_TRANSPORT_FAST_FORWARD = 92 +SID_TRANSPORT_STOP = 93 +SID_TRANSPORT_PLAY = 94 +SID_TRANSPORT_RECORD = 95 +transport_control_switch_ids = range(SID_TRANSPORT_REWIND, SID_TRANSPORT_RECORD + 1) +SID_MARKER_FROM_PREV = 84 +SID_MARKER_FROM_NEXT = 85 +SID_MARKER_LOOP = 86 +SID_MARKER_PI = 87 +SID_MARKER_PO = 88 +SID_MARKER_HOME = 89 +SID_MARKER_END = 90 +marker_control_switch_ids = (SID_MARKER_FROM_PREV, + SID_MARKER_FROM_NEXT, + SID_MARKER_LOOP, + SID_MARKER_PI, + SID_MARKER_PO, + SID_MARKER_HOME, + SID_MARKER_END) +SID_JOG_CURSOR_UP = 96 +SID_JOG_CURSOR_DOWN = 97 +SID_JOG_CURSOR_LEFT = 98 +SID_JOG_CURSOR_RIGHT = 99 +SID_JOG_ZOOM = 100 +SID_JOG_SCRUB = 101 +jog_wheel_switch_ids = range(SID_JOG_CURSOR_UP, SID_JOG_SCRUB + 1) +SID_USER_FOOT_SWITCHA = 102 +SID_USER_FOOT_SWITCHB = 103 +SID_FADER_TOUCH_SENSE_BASE = 104 +SID_FADER_TOUCH_SENSE_CH1 = 104 +SID_FADER_TOUCH_SENSE_CH2 = 105 +SID_FADER_TOUCH_SENSE_CH3 = 106 +SID_FADER_TOUCH_SENSE_CH4 = 107 +SID_FADER_TOUCH_SENSE_CH5 = 108 +SID_FADER_TOUCH_SENSE_CH6 = 109 +SID_FADER_TOUCH_SENSE_CH7 = 110 +SID_FADER_TOUCH_SENSE_CH8 = 111 +SID_FADER_TOUCH_SENSE_MASTER = 112 +fader_touch_switch_ids = range(SID_FADER_TOUCH_SENSE_CH1, SID_FADER_TOUCH_SENSE_MASTER + 1) +SID_LAST = 112 \ No newline at end of file