From c5602c3220caf1e0be34f350e35d8c3520872993 Mon Sep 17 00:00:00 2001 From: jzgdev Date: Mon, 20 Mar 2017 00:12:05 -0700 Subject: [PATCH] Added push2 and pushbase for 9.7.1 Added decompyled MIDI remote scripts for push2 and pushbase folders for Ableton Live version 9.7.1. --- Push2/.DS_Store_failed | 0 Push2/__init__.py | 19 +- Push2/actions.py | 37 +- Push2/automation.py | 37 +- Push2/bank_definitions.py | 902 ------- Push2/bank_selection_component.py | 58 +- Push2/banking_util.py | 106 - Push2/browser_component.py | 294 ++- Push2/browser_item.py | 14 +- Push2/browser_list.py | 32 +- Push2/browser_modes.py | 23 +- Push2/chain_selection_component.py | 41 +- Push2/clip_control.py | 177 +- Push2/clip_decoration.py | 141 +- Push2/color_chooser.py | 76 + Push2/colors.py | 321 ++- Push2/convert.py | 252 +- Push2/custom_bank_definitions.py | 2091 +++++++++++------ Push2/decoration.py | 5 +- Push2/device_component.py | 93 +- Push2/device_decoration.py | 359 ++- Push2/device_enabling.py | 75 - Push2/device_navigation.py | 121 +- Push2/device_options.py | 28 +- Push2/device_parameter_bank.py | 148 -- Push2/device_parameter_bank_with_options.py | 9 +- Push2/device_parameter_component.py | 20 - Push2/device_util.py | 36 + Push2/device_view_component.py | 57 +- Push2/drum_group_component.py | 145 +- Push2/drum_pad_parameter_component.py | 127 +- Push2/elements.py | 8 +- Push2/firmware.py | 161 +- Push2/firmware/app_push2_1.0.44.upgrade | Bin 58112 -> 0 bytes Push2/firmware/app_push2_1.0.47.upgrade | Bin 58624 -> 0 bytes Push2/hardware_settings_component.py | 19 +- Push2/internal_parameter.py | 276 --- Push2/item_lister_component.py | 66 +- Push2/mapped_control.py | 63 - Push2/master_track.py | 8 +- Push2/mixable_utilities.py | 3 +- Push2/mixer_component.py | 207 -- Push2/mixer_control_component.py | 88 +- Push2/mode_collector.py | 9 +- Push2/model/__init__.py | 244 +- Push2/model/declaration.py | 27 +- Push2/model/generation.py | 96 +- Push2/model/repr.py | 238 +- Push2/model/uniqueid.py | 3 +- Push2/mute_solo_stop.py | 60 +- Push2/note_editor.py | 9 +- Push2/note_settings.py | 40 +- Push2/notification_component.py | 50 +- Push2/observable_property_alias.py | 10 +- Push2/pad_sensitivity.py | 33 + Push2/pad_velocity_curve.py | 113 +- Push2/parameter_mapping_sensitivities.py | 336 ++- Push2/parameter_slot_description.py | 109 - Push2/push2.py | 298 ++- Push2/push2_model.py | 28 +- Push2/real_time_channel.py | 22 +- Push2/routing.py | 896 +++++++ Push2/scales_component.py | 62 +- Push2/selected_track_parameter_provider.py | 12 + Push2/session_component.py | 101 +- Push2/session_recording.py | 21 +- Push2/session_ring_selection_linking.py | 16 +- Push2/settings.py | 9 +- Push2/setup_component.py | 115 +- Push2/simpler_slice_nudging.py | 91 - Push2/simpler_zoom.py | 134 -- Push2/skin_default.py | 80 +- Push2/sliced_simpler.py | 16 + Push2/stop_clip_component.py | 53 - Push2/sysex.py | 59 +- Push2/track_list.py | 163 +- Push2/track_mixer_control_component.py | 17 +- Push2/track_selection.py | 72 +- Push2/transport_state.py | 73 + Push2/user_component.py | 13 +- Push2/waveform_navigation.py | 939 ++++++-- pushbase/__init__.py | 7 +- pushbase/accent_component.py | 12 +- pushbase/action_with_options_component.py | 16 +- pushbase/actions.py | 137 +- pushbase/auto_arm_component.py | 20 +- pushbase/automation_component.py | 20 +- pushbase/banking_util.py | 14 +- pushbase/browser_modes.py | 18 +- pushbase/browser_util.py | 11 +- pushbase/clip_control_component.py | 171 +- pushbase/colors.py | 170 +- pushbase/configurable_button_element.py | 24 +- pushbase/consts.py | 69 +- pushbase/control_element_factory.py | 13 +- pushbase/decoration.py | 17 +- pushbase/device_chain_utils.py | 34 +- pushbase/device_component.py | 33 +- pushbase/device_parameter_bank.py | 34 +- pushbase/device_parameter_component.py | 33 +- pushbase/drum_group_component.py | 69 +- pushbase/elements.py | 74 +- pushbase/fixed_length.py | 80 +- pushbase/grid_resolution.py | 27 +- pushbase/instrument_component.py | 101 +- pushbase/internal_parameter.py | 71 +- pushbase/live_util.py | 26 + pushbase/loop_selector_component.py | 73 +- pushbase/mapped_control.py | 10 +- pushbase/matrix_maps.py | 28 +- pushbase/melodic_component.py | 51 +- pushbase/melodic_pattern.py | 220 +- pushbase/message_box_component.py | 40 +- pushbase/messenger_mode_component.py | 34 + pushbase/note_editor_component.py | 478 ++-- pushbase/note_editor_paginator.py | 13 +- pushbase/note_repeat_component.py | 20 +- pushbase/note_settings_component.py | 66 +- pushbase/pad_control.py | 11 +- pushbase/pad_sensitivity.py | 83 + pushbase/parameter_provider.py | 41 +- pushbase/parameter_slot_description.py | 33 +- ...ent.py => percussion_instrument_finder.py} | 41 +- pushbase/playhead_component.py | 29 +- pushbase/playhead_element.py | 26 +- pushbase/provider_device_component.py | 40 - pushbase/proxy_element.py | 17 + pushbase/push_base.py | 215 +- pushbase/quantization_component.py | 26 +- pushbase/scrollable_list.py | 44 +- pushbase/scrollable_list_component.py | 83 +- pushbase/select_playing_clip_component.py | 33 +- pushbase/selected_track_parameter_provider.py | 39 +- pushbase/selection.py | 9 +- pushbase/session_recording_component.py | 171 +- pushbase/setting.py | 32 +- pushbase/simpler_decoration.py | 100 +- pushbase/simpler_slice_nudging.py | 13 +- pushbase/skin_default.py | 17 +- pushbase/sliced_simpler_component.py | 119 +- pushbase/slideable_touch_strip_component.py | 18 +- pushbase/song_utils.py | 20 + pushbase/special_chan_strip_component.py | 275 --- pushbase/special_mixer_component.py | 160 -- pushbase/special_session_component.py | 143 +- pushbase/step_duplicator.py | 72 + pushbase/step_seq_component.py | 84 +- pushbase/sysex.py | 4 - pushbase/touch_encoder_element.py | 16 +- pushbase/touch_strip_controller.py | 17 +- pushbase/touch_strip_element.py | 37 +- pushbase/track_frozen_mode.py | 24 +- pushbase/transport_component.py | 10 +- pushbase/user_component.py | 13 +- pushbase/value_component.py | 24 +- pushbase/velocity_levels_component.py | 145 ++ pushbase/velocity_levels_element.py | 34 + 157 files changed, 9560 insertions(+), 6502 deletions(-) delete mode 100644 Push2/.DS_Store_failed delete mode 100644 Push2/bank_definitions.py delete mode 100644 Push2/banking_util.py create mode 100644 Push2/color_chooser.py delete mode 100644 Push2/device_enabling.py delete mode 100644 Push2/device_parameter_bank.py delete mode 100644 Push2/device_parameter_component.py create mode 100644 Push2/device_util.py delete mode 100644 Push2/firmware/app_push2_1.0.44.upgrade delete mode 100644 Push2/firmware/app_push2_1.0.47.upgrade delete mode 100644 Push2/internal_parameter.py delete mode 100644 Push2/mapped_control.py delete mode 100644 Push2/mixer_component.py create mode 100644 Push2/pad_sensitivity.py delete mode 100644 Push2/parameter_slot_description.py create mode 100644 Push2/routing.py create mode 100644 Push2/selected_track_parameter_provider.py delete mode 100644 Push2/simpler_slice_nudging.py delete mode 100644 Push2/simpler_zoom.py create mode 100644 Push2/sliced_simpler.py delete mode 100644 Push2/stop_clip_component.py create mode 100644 Push2/transport_state.py create mode 100644 pushbase/live_util.py create mode 100644 pushbase/messenger_mode_component.py create mode 100644 pushbase/pad_sensitivity.py rename pushbase/{percussion_instrument_finder_component.py => percussion_instrument_finder.py} (72%) delete mode 100644 pushbase/provider_device_component.py create mode 100644 pushbase/proxy_element.py create mode 100644 pushbase/song_utils.py delete mode 100644 pushbase/special_chan_strip_component.py delete mode 100644 pushbase/special_mixer_component.py create mode 100644 pushbase/step_duplicator.py delete mode 100644 pushbase/sysex.py create mode 100644 pushbase/velocity_levels_component.py create mode 100644 pushbase/velocity_levels_element.py diff --git a/Push2/.DS_Store_failed b/Push2/.DS_Store_failed deleted file mode 100644 index e69de29b..00000000 diff --git a/Push2/__init__.py b/Push2/__init__.py index e444948e..33c2d8f8 100644 --- a/Push2/__init__.py +++ b/Push2/__init__.py @@ -1,18 +1,19 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/Push2/__init__.py +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/Push2/__init__.py +# Compiled at: 2016-05-20 03:43:52 from __future__ import absolute_import, print_function def get_capabilities(): from ableton.v2.control_surface import capabilities as caps - return {caps.CONTROLLER_ID_KEY: caps.controller_id(vendor_id=10626, product_ids=[6503], model_name='Ableton Push 2'), - caps.PORTS_KEY: [caps.inport(props=[caps.HIDDEN, caps.NOTES_CC, caps.SCRIPT]), + return {caps.CONTROLLER_ID_KEY: caps.controller_id(vendor_id=10626, product_ids=[ + 6503], model_name='Ableton Push 2'), + caps.PORTS_KEY: [ + caps.inport(props=[caps.HIDDEN, caps.NOTES_CC, caps.SCRIPT]), caps.inport(props=[]), - caps.outport(props=[caps.HIDDEN, - caps.NOTES_CC, - caps.SYNC, - caps.SCRIPT]), + caps.outport(props=[caps.HIDDEN, caps.NOTES_CC, caps.SYNC, caps.SCRIPT]), caps.outport(props=[])], - caps.TYPE_KEY: 'push2', - caps.AUTO_LOAD_KEY: True} + caps.TYPE_KEY: 'push2', + caps.AUTO_LOAD_KEY: True + } def create_instance(c_instance): diff --git a/Push2/actions.py b/Push2/actions.py index 63a2d5c6..4a119a65 100644 --- a/Push2/actions.py +++ b/Push2/actions.py @@ -1,24 +1,37 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/Push2/actions.py +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/Push2/actions.py +# Compiled at: 2016-09-29 19:13:24 from __future__ import absolute_import, print_function +from ableton.v2.base import liveobj_valid from pushbase.actions import CaptureAndInsertSceneComponent as CaptureAndInsertSceneComponentBase from .clip_decoration import ClipDecoratedPropertiesCopier class CaptureAndInsertSceneComponent(CaptureAndInsertSceneComponentBase): - def __init__(self, name = None, decorator_factory = None, *a, **k): + def __init__(self, name=None, decorator_factory=None, *a, **k): super(CaptureAndInsertSceneComponent, self).__init__(name, *a, **k) self._decorator_factory = decorator_factory + def _copy_decorated_properties(self, source_clip, destination_clip): + ClipDecoratedPropertiesCopier(source_clip=source_clip, destination_clip=destination_clip, decorator_factory=self._decorator_factory).post_duplication_action() + def post_trigger_action(self): + view = self.song.view + if liveobj_valid(view.detail_clip) and view.detail_clip.is_arrangement_clip: + previous_detail_clip = view.detail_clip + super(CaptureAndInsertSceneComponent, self).post_trigger_action() + self._copy_decorated_properties(previous_detail_clip, view.detail_clip) + else: - def get_playing_clip(track): - slot_ix = track.playing_slot_index - if slot_ix > -1: - return track.clip_slots[slot_ix].clip + def get_playing_clip(track): + slot_ix = track.playing_slot_index + if slot_ix > -1: + return track.clip_slots[slot_ix].clip + else: + return None - played_clips = [ get_playing_clip(track) for track in self.song.tracks ] - super(CaptureAndInsertSceneComponent, self).post_trigger_action() - new_slots = self.song.view.selected_scene.clip_slots - for ix, clip in enumerate(played_clips): - if clip: - ClipDecoratedPropertiesCopier(target_clip=clip, destination_clip=new_slots[ix].clip, decorator_factory=self._decorator_factory).post_duplication_action() \ No newline at end of file + played_clips = [ get_playing_clip(track) for track in self.song.tracks ] + super(CaptureAndInsertSceneComponent, self).post_trigger_action() + new_slots = view.selected_scene.clip_slots + for ix, clip in enumerate(played_clips): + if liveobj_valid(clip): + self._copy_decorated_properties(clip, new_slots[ix].clip) \ No newline at end of file diff --git a/Push2/automation.py b/Push2/automation.py index 8588dbae..d01afe22 100644 --- a/Push2/automation.py +++ b/Push2/automation.py @@ -1,15 +1,16 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/Push2/automation.py +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/Push2/automation.py +# Compiled at: 2016-05-20 03:43:52 from __future__ import absolute_import, print_function from itertools import ifilter -from ableton.v2.base import liveobj_valid, listenable_property +from ableton.v2.base import liveobj_valid, listenable_property, listens from pushbase.automation_component import AutomationComponent as AutomationComponentBase from pushbase.internal_parameter import InternalParameterBase from pushbase.parameter_provider import ParameterInfo class StepAutomationParameter(InternalParameterBase): - def __init__(self, parameter = None, *a, **k): - raise liveobj_valid(parameter) or AssertionError + def __init__(self, parameter=None, *a, **k): + assert liveobj_valid(parameter) super(StepAutomationParameter, self).__init__(name=parameter.name, *a, **k) self._parameter = parameter self._value = self._parameter.value @@ -65,7 +66,7 @@ def make_automation_parameter(parameter_info): class AutomationComponent(AutomationComponentBase): ENCODER_SENSITIVITY_FACTOR = 0.5 - __events__ = ('parameters',) + __events__ = ('parameters', ) def __init__(self, *a, **k): self._parameter_infos = [] @@ -75,14 +76,28 @@ def __init__(self, *a, **k): @property def deviceType(self): device_type = 'default' - if hasattr(self.parameter_provider, 'device'): + device = self.device + if liveobj_valid(device): device = self.parameter_provider.device() device_type = device.class_name if liveobj_valid(device) else device_type return device_type + @listenable_property + def device(self): + device = None + if hasattr(self.parameter_provider, 'device'): + device = self.parameter_provider.device() + return device + + def _on_parameter_provider_changed(self, provider): + self.notify_device() + self.__on_device_changed.subject = provider if getattr(self.parameter_provider, 'device', None) is not None else None + return + @property def parameters(self): - return map(lambda info: (info.parameter if info else None), self._parameter_infos) + return map(lambda info: if info: +info.parameterNone, self._parameter_infos) @property def parameter_infos(self): @@ -123,4 +138,10 @@ def _update_parameter_values(self): def _parameter_for_index(self, parameters, index): if parameters[index]: - return parameters[index].original_parameter \ No newline at end of file + return parameters[index].original_parameter + else: + return None + + @listens('device') + def __on_device_changed(self): + self.notify_device() \ No newline at end of file diff --git a/Push2/bank_definitions.py b/Push2/bank_definitions.py deleted file mode 100644 index 54a0a512..00000000 --- a/Push2/bank_definitions.py +++ /dev/null @@ -1,902 +0,0 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/midi-remote-scripts/Push2/bank_definitions.py -from ableton.v2.base.collection import IndexedDict -from .parameter_slot_description import use -PARAMETERS_KEY = 'Parameters' -MAIN_KEY = 'Main' -RACK_BANKS = IndexedDict((('Macros', {PARAMETERS_KEY: ('Macro 1', 'Macro 2', 'Macro 3', 'Macro 4', 'Macro 5', 'Macro 6', 'Macro 7', 'Macro 8')}),)) -BANK_DEFINITIONS = {'AudioEffectGroupDevice': RACK_BANKS, - 'MidiEffectGroupDevice': RACK_BANKS, - 'InstrumentGroupDevice': RACK_BANKS, - 'DrumGroupDevice': RACK_BANKS, - 'UltraAnalog': IndexedDict(((MAIN_KEY, {PARAMETERS_KEY: (use('OSC1 Shape').if_parameter('OSC1 On/Off').has_value('1').else_use('OSC2 Shape').if_parameter('OSC2 On/Off').has_value('1'), - use('OSC1 Octave').if_parameter('OSC1 On/Off').has_value('1').else_use('OSC2 Octave').if_parameter('OSC2 On/Off').has_value('1'), - use('OSC2 Shape').if_parameter('OSC1 On/Off').has_value('1').and_parameter('OSC2 On/Off').has_value('1').else_use('OSC1 Semi').if_parameter('OSC1 On/Off').has_value('1').else_use('OSC2 Semi').if_parameter('OSC2 On/Off').has_value('1'), - use('OSC2 Octave').if_parameter('OSC1 On/Off').has_value('1').and_parameter('OSC2 On/Off').has_value('1').else_use('OSC1 Detune').if_parameter('OSC1 On/Off').has_value('1').else_use('OSC2 Detune').if_parameter('OSC2 On/Off').has_value('1'), - use('F1 Type').if_parameter('F1 On/Off').has_value('1').else_use('F2 Type').if_parameter('F2 On/Off').has_value('1'), - use('F1 Freq').if_parameter('F1 On/Off').has_value('1').else_use('F2 Freq').if_parameter('F2 On/Off').has_value('1'), - use('F1 Resonance').if_parameter('F1 On/Off').has_value('1').else_use('F2 Resonance').if_parameter('F2 On/Off').has_value('1'), - 'Volume')}), - ('Osc. 1 Shape', {PARAMETERS_KEY: ('OSC1 On/Off', - use('OSC1 Shape').if_parameter('OSC1 On/Off').has_value('1'), - use('').if_parameter('OSC1 On/Off').has_value('0').else_use('OSC1 PW').if_parameter('OSC1 Shape').has_value('Rect'), - use('').if_parameter('OSC1 On/Off').has_value('0').else_use('O1 PW < LFO').if_parameter('OSC1 Shape').has_value('Rect').else_use('').if_parameter('LFO1 On/Off').has_value('0'), - use('').if_parameter('OSC1 On/Off').has_value('0').else_use('').if_parameter('OSC1 Shape').has_value('Noise').else_use('').if_parameter('OSC1 Shape').has_value('Sine').else_use('OSC1 Mode'), - use('').if_parameter('OSC1 On/Off').has_value('0').else_use('').if_parameter('OSC1 Shape').has_value('Noise').else_use('O1 Sub/Sync'), - use('OSC1 Balance').if_parameter('OSC1 On/Off').has_value('1'), - use('OSC1 Level').if_parameter('OSC1 On/Off').has_value('1'))}), - ('Osc. 1 Pitch', {PARAMETERS_KEY: ('OSC1 On/Off', - use('OSC1 Octave').if_parameter('OSC1 On/Off').has_value('1'), - use('OSC1 Semi').if_parameter('OSC1 On/Off').has_value('1'), - use('OSC1 Detune').if_parameter('OSC1 On/Off').has_value('1'), - use('PEG1 Amount').if_parameter('OSC1 On/Off').has_value('1'), - use('PEG1 Time').if_parameter('OSC1 On/Off').has_value('1'), - use('O1 Keytrack').if_parameter('OSC1 On/Off').has_value('1'), - use('').if_parameter('OSC1 On/Off').has_value('0').else_use('').if_parameter('LFO1 On/Off').has_value('0').else_use('OSC1 < LFO'))}), - ('Osc. 2 Shape', {PARAMETERS_KEY: ('OSC2 On/Off', - use('OSC2 Shape').if_parameter('OSC2 On/Off').has_value('1'), - use('').if_parameter('OSC2 On/Off').has_value('0').else_use('OSC2 PW').if_parameter('OSC2 Shape').has_value('Rect'), - use('').if_parameter('OSC2 On/Off').has_value('0').else_use('O2 PW < LFO').if_parameter('OSC2 Shape').has_value('Rect').else_use('').if_parameter('LFO2 On/Off').has_value('0'), - use('').if_parameter('OSC2 On/Off').has_value('0').else_use('').if_parameter('OSC2 Shape').has_value('Noise').else_use('').if_parameter('OSC2 Shape').has_value('Sine').else_use('OSC2 Mode'), - use('').if_parameter('OSC2 On/Off').has_value('0').else_use('').if_parameter('OSC2 Shape').has_value('Noise').else_use('O2 Sub/Sync'), - use('OSC2 Balance').if_parameter('OSC2 On/Off').has_value('1'), - use('OSC2 Level').if_parameter('OSC2 On/Off').has_value('1'))}), - ('Osc. 2 Pitch', {PARAMETERS_KEY: ('OSC2 On/Off', - use('OSC2 Octave').if_parameter('OSC2 On/Off').has_value('1'), - use('OSC2 Semi').if_parameter('OSC2 On/Off').has_value('1'), - use('OSC2 Detune').if_parameter('OSC2 On/Off').has_value('1'), - use('PEG2 Amount').if_parameter('OSC2 On/Off').has_value('1'), - use('PEG2 Time').if_parameter('OSC2 On/Off').has_value('1'), - use('O2 Keytrack').if_parameter('OSC2 On/Off').has_value('1'), - use('').if_parameter('OSC2 On/Off').has_value('0').else_use('').if_parameter('LFO2 On/Off').has_value('0').else_use('OSC2 < LFO'))}), - ('Filters', {PARAMETERS_KEY: ('F1 On/Off', - use('F1 Type').if_parameter('F1 On/Off').has_value('1'), - use('F1 Freq').if_parameter('F1 On/Off').has_value('1'), - use('F1 Resonance').if_parameter('F1 On/Off').has_value('1'), - 'F2 On/Off', - use('F2 Type').if_parameter('F2 On/Off').has_value('1'), - use('F2 Freq').if_parameter('F2 On/Off').has_value('1'), - use('F2 Resonance').if_parameter('F2 On/Off').has_value('1'))}), - ('Filt. 1 Env.', {PARAMETERS_KEY: ('F1 On/Off', - use('FEG1 < Vel').if_parameter('F1 On/Off').has_value('1'), - use('FEG1 A < Vel').if_parameter('F1 On/Off').has_value('1'), - use('FEG1 Attack').if_parameter('F1 On/Off').has_value('1'), - use('FEG1 Decay').if_parameter('F1 On/Off').has_value('1'), - use('FEG1 Sustain').if_parameter('F1 On/Off').has_value('1'), - use('FEG1 S Time').if_parameter('F1 On/Off').has_value('1'), - use('FEG1 Rel').if_parameter('F1 On/Off').has_value('1'))}), - ('Filt. 2 Env.', {PARAMETERS_KEY: ('F2 On/Off', - use('FEG2 < Vel').if_parameter('F1 On/Off').has_value('1'), - use('FEG2 A < Vel').if_parameter('F1 On/Off').has_value('1'), - use('FEG2 Attack').if_parameter('F1 On/Off').has_value('1'), - use('FEG2 Decay').if_parameter('F1 On/Off').has_value('1'), - use('FEG2 Sustain').if_parameter('F1 On/Off').has_value('1'), - use('FEG2 S Time').if_parameter('F1 On/Off').has_value('1'), - use('FEG2 Rel').if_parameter('F1 On/Off').has_value('1'))}), - ('Filt. Modulation', {PARAMETERS_KEY: ('F1 On/Off', - use('F1 Freq < LFO').if_parameter('F1 On/Off').has_value('1'), - use('F1 Freq < Env').if_parameter('F1 On/Off').has_value('1'), - use('F1 Res < LFO').if_parameter('F1 On/Off').has_value('1'), - 'F2 On/Off', - use('F2 Freq < LFO').if_parameter('F2 On/Off').has_value('1'), - use('F2 Freq < Env').if_parameter('F2 On/Off').has_value('1'), - use('F2 Res < LFO').if_parameter('F2 On/Off').has_value('1'))}), - ('Amp', {PARAMETERS_KEY: (use('AMP1 Level').if_parameter('AMP1 On/Off').has_value('1'), - use('AMP1 Pan').if_parameter('AMP1 On/Off').has_value('1'), - use('AMP1 < LFO').if_parameter('AMP1 On/Off').has_value('1'), - use('').if_parameter('LFO1 On/Off').has_value('0').else_use('LFO1 Speed').if_parameter('LFO1 Sync').has_value('Hertz').else_use('LFO1 SncRate'), - use('AMP2 Level').if_parameter('AMP2 On/Off').has_value('1'), - use('AMP2 Pan').if_parameter('AMP2 On/Off').has_value('1'), - use('AMP2 < LFO').if_parameter('AMP2 On/Off').has_value('1'), - use('').if_parameter('LFO2 On/Off').has_value('0').else_use('LFO2 Speed').if_parameter('LFO2 Sync').has_value('Hertz').else_use('LFO2 SncRate'))}), - ('Amp 1 Envelope', {PARAMETERS_KEY: ('AMP1 On/Off', - use('AEG1 < Vel').if_parameter('AMP1 On/Off').has_value('1'), - use('AEG1 A < Vel').if_parameter('AMP1 On/Off').has_value('1'), - use('AEG1 Attack').if_parameter('AMP1 On/Off').has_value('1'), - use('AEG1 Decay').if_parameter('AMP1 On/Off').has_value('1'), - use('AEG1 Sustain').if_parameter('AMP1 On/Off').has_value('1'), - use('AEG1 S Time').if_parameter('AMP1 On/Off').has_value('1'), - use('AEG1 Rel').if_parameter('AMP1 On/Off').has_value('1'))}), - ('Amp 2 Envelope', {PARAMETERS_KEY: ('AMP2 On/Off', - use('AEG2 < Vel').if_parameter('AMP2 On/Off').has_value('1'), - use('AEG2 A < Vel').if_parameter('AMP2 On/Off').has_value('1'), - use('AEG2 Attack').if_parameter('AMP2 On/Off').has_value('1'), - use('AEG2 Decay').if_parameter('AMP2 On/Off').has_value('1'), - use('AEG2 Sustain').if_parameter('AMP2 On/Off').has_value('1'), - use('AEG2 S Time').if_parameter('AMP2 On/Off').has_value('1'), - use('AEG2 Rel').if_parameter('AMP2 On/Off').has_value('1'))}), - ('Noise & Unison', {PARAMETERS_KEY: ('Noise On/Off', - use('Noise Level').if_parameter('Noise On/Off').has_value('1'), - use('Noise Color').if_parameter('Noise On/Off').has_value('1'), - use('Noise Balance').if_parameter('Noise On/Off').has_value('1'), - 'Unison On/Off', - use('Unison Detune').if_parameter('Unison On/Off').has_value('1'), - use('Unison Delay').if_parameter('Unison On/Off').has_value('1'), - '')}), - ('Performance', {PARAMETERS_KEY: ('Glide On/Off', - use('Glide Time').if_parameter('Glide On/Off').has_value('1'), - use('Glide Mode').if_parameter('Glide On/Off').has_value('1'), - use('Glide Legato').if_parameter('Glide On/Off').has_value('1'), - 'PB Range', - 'Key Stretch', - 'Key Error', - 'Voices')}), - ('LFO 1', {PARAMETERS_KEY: ('LFO1 On/Off', - use('LFO1 Sync').if_parameter('LFO1 On/Off').has_value('1'), - use('').if_parameter('LFO1 On/Off').has_value('0').else_use('LFO1 Speed').if_parameter('LFO1 Sync').has_value('Hertz').else_use('LFO1 SncRate'), - use('LFO1 Shape').if_parameter('LFO1 On/Off').has_value('1'), - use('').if_parameter('LFO1 On/Off').has_value('0').else_use('LFO1 PW').if_parameter('LFO1 Shape').has_value('Rect').else_use('LFO1 PW').if_parameter('LFO1 Shape').has_value('Tri'), - use('LFO1 Phase').if_parameter('LFO1 On/Off').has_value('1'), - use('LFO1 Delay').if_parameter('LFO1 On/Off').has_value('1'), - use('LFO1 Fade In').if_parameter('LFO1 On/Off').has_value('1'))}), - ('LFO 2', {PARAMETERS_KEY: ('LFO2 On/Off', - use('LFO2 Sync').if_parameter('LFO2 On/Off').has_value('1'), - use('').if_parameter('LFO2 On/Off').has_value('0').else_use('LFO2 Speed').if_parameter('LFO2 Sync').has_value('Hertz').else_use('LFO2 SncRate'), - use('LFO2 Shape').if_parameter('LFO2 On/Off').has_value('1'), - use('').if_parameter('LFO2 On/Off').has_value('0').else_use('LFO2 PW').if_parameter('LFO2 Shape').has_value('Rect').else_use('LFO2 PW').if_parameter('LFO2 Shape').has_value('Tri'), - use('LFO2 Phase').if_parameter('LFO2 On/Off').has_value('1'), - use('LFO2 Delay').if_parameter('LFO2 On/Off').has_value('1'), - use('LFO2 Fade In').if_parameter('LFO2 On/Off').has_value('1'))}), - ('Vibrato', {PARAMETERS_KEY: ('Vib On/Off', - use('Vib Amount').if_parameter('Vib On/Off').has_value('1'), - use('Vib Speed').if_parameter('Vib On/Off').has_value('1'), - use('Vib Delay').if_parameter('Vib On/Off').has_value('1'), - use('Vib Fade-In').if_parameter('Vib On/Off').has_value('1'), - use('Vib Error').if_parameter('Vib On/Off').has_value('1'), - use('Vib < ModWh').if_parameter('Vib On/Off').has_value('1'), - '')}))), - 'Collision': IndexedDict(((MAIN_KEY, {PARAMETERS_KEY: (use('Res 1 Type').if_parameter('Res 1 On/Off').has_value('on'), - use('').if_parameter('Res 1 On/Off').has_value('off').else_use('').if_parameter('Res 1 Type').has_value('Tube').else_use('').if_parameter('Res 1 Type').has_value('Pipe').else_use('Res 1 Brightness'), - use('').if_parameter('Res 1 On/Off').has_value('off').else_use('').if_parameter('Res 1 Type').has_value('Tube').else_use('Res 1 Opening').if_parameter('Res 1 Type').has_value('Pipe').else_use('Res 1 Inharmonics'), - use('Res 1 Decay').if_parameter('Res 1 On/Off').has_value('on'), - use('').if_parameter('Res 1 On/Off').has_value('off').else_use('Res 1 Radius').if_parameter('Res 1 Type').has_value('Tube').else_use('Res 1 Radius').if_parameter('Res 1 Type').has_value('Pipe').else_use('Res 1 Material'), - use('Mallet Stiffness').if_parameter('Mallet On/Off').has_value('on'), - use('Mallet Noise Amount').if_parameter('Mallet On/Off').has_value('on'), - 'Volume')}), - ('Mix', {PARAMETERS_KEY: (use('Res 1 Volume').if_parameter('Res 1 On/Off').has_value('on'), - use('Panorama').if_parameter('Res 1 On/Off').has_value('on'), - use('Res 1 Bleed').if_parameter('Res 1 On/Off').has_value('on'), - use('Res 2 Volume').if_parameter('Res 2 On/Off').has_value('on'), - use('Res 2 Panorama').if_parameter('Res 2 On/Off').has_value('on'), - use('Res 2 Bleed').if_parameter('Res 2 On/Off').has_value('on'), - 'Structure', - 'Volume')}), - ('Mallet', {PARAMETERS_KEY: ('Mallet On/Off', - use('Mallet Volume').if_parameter('Mallet On/Off').has_value('on'), - use('Mallet Noise Amount').if_parameter('Mallet On/Off').has_value('on'), - use('Mallet Stiffness').if_parameter('Mallet On/Off').has_value('on'), - use('Mallet Noise Color').if_parameter('Mallet On/Off').has_value('on'), - use('Mallet Modulation').if_parameter('Mallet On/Off').has_value('on'), - use('Mallet Volume < Vel').if_parameter('Mallet On/Off').has_value('on'), - use('Mallet Noise Amount < Vel').if_parameter('Mallet On/Off').has_value('on'))}), - ('Noise Envelope', {PARAMETERS_KEY: ('Noise On/Off', - use('Noise Volume').if_parameter('Noise On/Off').has_value('on'), - use('Noise Volume < Key').if_parameter('Noise On/Off').has_value('on'), - use('Noise Volume < Vel').if_parameter('Noise On/Off').has_value('on'), - use('Noise Attack').if_parameter('Noise On/Off').has_value('on'), - use('Noise Sustain').if_parameter('Noise On/Off').has_value('on'), - use('Noise Decay').if_parameter('Noise On/Off').has_value('on'), - use('Noise Release').if_parameter('Noise On/Off').has_value('on'))}), - ('Noise Filter', {PARAMETERS_KEY: ('Noise On/Off', - use('Noise Volume').if_parameter('Noise On/Off').has_value('on'), - use('Noise Filter Type').if_parameter('Noise On/Off').has_value('on'), - use('Noise Filter Freq').if_parameter('Noise On/Off').has_value('on'), - use('Noise Filter Q').if_parameter('Noise On/Off').has_value('on'), - use('Noise Freq < Key').if_parameter('Noise On/Off').has_value('on'), - use('Noise Freq < Vel').if_parameter('Noise On/Off').has_value('on'), - use('Noise Freq < Env').if_parameter('Noise On/Off').has_value('on'))}), - ('Res. 1 Body', {PARAMETERS_KEY: ('Res 1 On/Off', - use('Res 1 Type').if_parameter('Res 1 On/Off').has_value('on'), - use('').if_parameter('Res 1 On/Off').has_value('off').else_use('Res 1 Ratio').if_parameter('Res 1 Type').has_value('Plate').else_use('Res 1 Ratio').if_parameter('Res 1 Type').has_value('Membrane'), - use('Res 1 Decay').if_parameter('Res 1 On/Off').has_value('on'), - use('').if_parameter('Res 1 On/Off').has_value('off').else_use('Res 1 Radius').if_parameter('Res 1 Type').has_value('Tube').else_use('Res 1 Radius').if_parameter('Res 1 Type').has_value('Pipe').else_use('Res 1 Material'), - use('').if_parameter('Res 1 On/Off').has_value('off').else_use('').if_parameter('Res 1 Type').has_value('Tube').else_use('').if_parameter('Res 1 Type').has_value('Pipe').else_use('Res 1 Listening L'), - use('').if_parameter('Res 1 On/Off').has_value('off').else_use('').if_parameter('Res 1 Type').has_value('Tube').else_use('').if_parameter('Res 1 Type').has_value('Pipe').else_use('Res 1 Listening R'), - use('').if_parameter('Res 1 On/Off').has_value('off').else_use('').if_parameter('Res 1 Type').has_value('Tube').else_use('').if_parameter('Res 1 Type').has_value('Pipe').else_use('Res 1 Hit'))}), - ('Res. 1 Tune', {PARAMETERS_KEY: ('Res 1 On/Off', - use('').if_parameter('Res 1 On/Off').has_value('off').else_use('').if_parameter('Res 1 Type').has_value('Tube').else_use('').if_parameter('Res 1 Type').has_value('Pipe').else_use('Res 1 Brightness'), - use('Res 1 Quality').if_parameter('Res 1 On/Off').has_value('on'), - use('').if_parameter('Res 1 On/Off').has_value('off').else_use('').if_parameter('Res 1 Type').has_value('Tube').else_use('Res 1 Opening').if_parameter('Res 1 Type').has_value('Pipe').else_use('Res 1 Inharmonics'), - use('Res 1 Tune').if_parameter('Res 1 On/Off').has_value('on'), - use('Res 1 Fine Tune').if_parameter('Res 1 On/Off').has_value('on'), - use('Res 1 Pitch Env.').if_parameter('Res 1 On/Off').has_value('on'), - use('Res 1 Pitch Env. Time').if_parameter('Res 1 On/Off').has_value('on'))}), - ('Res. 2 Body', {PARAMETERS_KEY: ('Res 2 On/Off', - use('Res 2 Type').if_parameter('Res 2 On/Off').has_value('on'), - use('').if_parameter('Res 2 On/Off').has_value('off').else_use('Res 2 Ratio').if_parameter('Res 2 Type').has_value('Plate').else_use('Res 2 Ratio').if_parameter('Res 2 Type').has_value('Membrane'), - use('Res 2 Decay').if_parameter('Res 2 On/Off').has_value('on'), - use('').if_parameter('Res 2 On/Off').has_value('off').else_use('Res 2 Radius').if_parameter('Res 2 Type').has_value('Tube').else_use('Res 2 Radius').if_parameter('Res 2 Type').has_value('Pipe').else_use('Res 2 Material'), - use('').if_parameter('Res 2 On/Off').has_value('off').else_use('').if_parameter('Res 2 Type').has_value('Tube').else_use('').if_parameter('Res 2 Type').has_value('Pipe').else_use('Res 2 Listening L'), - use('').if_parameter('Res 2 On/Off').has_value('off').else_use('').if_parameter('Res 2 Type').has_value('Tube').else_use('').if_parameter('Res 2 Type').has_value('Pipe').else_use('Res 2 Listening R'), - use('').if_parameter('Res 2 On/Off').has_value('off').else_use('').if_parameter('Res 2 Type').has_value('Tube').else_use('').if_parameter('Res 2 Type').has_value('Pipe').else_use('Res 2 Hit'))}), - ('Res. 2 Tune', {PARAMETERS_KEY: ('Res 2 On/Off', - use('').if_parameter('Res 2 On/Off').has_value('off').else_use('').if_parameter('Res 2 Type').has_value('Tube').else_use('').if_parameter('Res 2 Type').has_value('Pipe').else_use('Res 2 Brightness'), - use('Res 2 Quality').if_parameter('Res 2 On/Off').has_value('on'), - use('').if_parameter('Res 2 On/Off').has_value('off').else_use('').if_parameter('Res 2 Type').has_value('Tube').else_use('Res 2 Opening').if_parameter('Res 2 Type').has_value('Pipe').else_use('Res 2 Inharmonics'), - use('Res 2 Tune').if_parameter('Res 2 On/Off').has_value('on'), - use('Res 2 Fine Tune').if_parameter('Res 2 On/Off').has_value('on'), - use('Res 2 Pitch Env.').if_parameter('Res 2 On/Off').has_value('on'), - use('Res 2 Pitch Env. Time').if_parameter('Res 2 On/Off').has_value('on'))}), - ('LFO 1', {PARAMETERS_KEY: ('LFO 1 On/Off', - use('LFO 1 Depth').if_parameter('LFO 1 On/Off').has_value('on'), - use('LFO 1 Shape').if_parameter('LFO 1 On/Off').has_value('on'), - use('LFO 1 Sync').if_parameter('LFO 1 On/Off').has_value('on'), - use('').if_parameter('LFO 1 On/Off').has_value('off').else_use('LFO 1 Sync Rate').if_parameter('LFO 1 Sync').has_value('Sync').else_use('LFO 1 Rate'), - use('LFO 1 Offset').if_parameter('LFO 1 On/Off').has_value('on'), - use('LFO 1 Destination A').if_parameter('LFO 1 On/Off').has_value('on'), - use('LFO 1 Destination A Amount').if_parameter('LFO 1 On/Off').has_value('on'))}), - ('LFO 2', {PARAMETERS_KEY: ('LFO 2 On/Off', - use('LFO 2 Depth').if_parameter('LFO 2 On/Off').has_value('on'), - use('LFO 2 Shape').if_parameter('LFO 2 On/Off').has_value('on'), - use('LFO 2 Sync').if_parameter('LFO 2 On/Off').has_value('on'), - use('').if_parameter('LFO 2 On/Off').has_value('off').else_use('LFO 2 Sync Rate').if_parameter('LFO 2 Sync').has_value('Sync').else_use('LFO 2 Rate'), - use('LFO 2 Offset').if_parameter('LFO 2 On/Off').has_value('on'), - use('LFO 2 Destination A').if_parameter('LFO 2 On/Off').has_value('on'), - use('LFO 2 Destination A Amount').if_parameter('LFO 2 On/Off').has_value('on'))}), - ('Mallet Mod.', {PARAMETERS_KEY: ('Mallet On/Off', - use('Mallet Volume < Key').if_parameter('Mallet On/Off').has_value('on'), - use('Mallet Volume < Vel').if_parameter('Mallet On/Off').has_value('on'), - use('Mallet Noise Amount < Key').if_parameter('Mallet On/Off').has_value('on'), - use('Mallet Noise Amount < Vel').if_parameter('Mallet On/Off').has_value('on'), - use('Mallet Stiffness < Key').if_parameter('Mallet On/Off').has_value('on'), - use('Mallet Stiffness < Vel').if_parameter('Mallet On/Off').has_value('on'), - '')}))), - 'LoungeLizard': IndexedDict(((MAIN_KEY, {PARAMETERS_KEY: ('M Stiffness', 'M Force', 'Noise Amount', 'F Tine Vol', 'F Tone Vol', 'F Release', 'P Symmetry', 'Volume')}), - ('Mallet', {PARAMETERS_KEY: ('M Stiffness', 'M Force', 'Noise Pitch', 'Noise Decay', 'Noise Amount', 'M Stiff < Vel', 'M Force < Vel', 'Volume')}), - ('Fork', {PARAMETERS_KEY: ('F Tine Color', 'F Tine Decay', 'F Tine Vol', 'F Tone Vol', 'F Tone Decay', 'F Release', 'F Tine < Key', 'Volume')}), - ('Damper', {PARAMETERS_KEY: ('Damp Tone', 'Damp Balance', 'Damp Amount', '', '', '', '', 'Volume')}), - ('Pickup', {PARAMETERS_KEY: ('P Symmetry', 'P Distance', 'P Amp In', 'P Amp Out', 'Pickup Model', 'P Amp < Key', '', 'Volume')}), - ('Modulation', {PARAMETERS_KEY: ('M Stiff < Vel', 'M Stiff < Key', 'M Force < Vel', 'M Force < Key', 'Noise < Key', 'F Tine < Key', 'P Amp < Key', 'Volume')}), - ('Global', {PARAMETERS_KEY: ('KB Stretch', 'PB Range', '', '', 'Voices', 'Semitone', 'Detune', 'Volume')}))), - 'InstrumentImpulse': IndexedDict(((MAIN_KEY, {PARAMETERS_KEY: ('1 Transpose', '1 Volume', '3 Transpose', '3 Volume', '7 Transpose', '7 Volume', '8 Transpose', '8 Volume')}), - ('Pad 1', {PARAMETERS_KEY: ('1 Start', '1 Envelope Decay', '1 Stretch Factor', '1 Saturator Drive', '1 Envelope Type', '1 Transpose', '1 Volume <- Vel', '1 Volume')}), - ('1 Filt/Mod/Pan', {PARAMETERS_KEY: ('1 Filter Type', '1 Filter Freq', '1 Filter Res', '1 Filter <- Vel', '1 Filter <- Random', '1 Pan', '1 Pan <- Vel', '1 Pan <- Random')}), - ('Pad 2', {PARAMETERS_KEY: ('2 Start', '2 Envelope Decay', '2 Stretch Factor', '2 Saturator Drive', '2 Envelope Type', '2 Transpose', '2 Volume <- Vel', '2 Volume')}), - ('2 Filt/Mod/Pan', {PARAMETERS_KEY: ('2 Filter Type', '2 Filter Freq', '2 Filter Res', '2 Filter <- Vel', '2 Filter <- Random', '2 Pan', '2 Pan <- Vel', '2 Pan <- Random')}), - ('Pad 3', {PARAMETERS_KEY: ('3 Start', '3 Envelope Decay', '3 Stretch Factor', '3 Saturator Drive', '3 Envelope Type', '3 Transpose', '3 Volume <- Vel', '3 Volume')}), - ('3 Filt/Mod/Pan', {PARAMETERS_KEY: ('3 Filter Type', '3 Filter Freq', '3 Filter Res', '3 Filter <- Vel', '3 Filter <- Random', '3 Pan', '3 Pan <- Vel', '3 Pan <- Random')}), - ('Pad 4', {PARAMETERS_KEY: ('4 Start', '4 Envelope Decay', '4 Stretch Factor', '4 Saturator Drive', '4 Envelope Type', '4 Transpose', '4 Volume <- Vel', '4 Volume')}), - ('4 Filt/Mod/Pan', {PARAMETERS_KEY: ('4 Filter Type', '4 Filter Freq', '4 Filter Res', '4 Filter <- Vel', '4 Filter <- Random', '4 Pan', '4 Pan <- Vel', '4 Pan <- Random')}), - ('Pad 5', {PARAMETERS_KEY: ('5 Start', '5 Envelope Decay', '5 Stretch Factor', '5 Saturator Drive', '5 Envelope Type', '5 Transpose', '5 Volume <- Vel', '5 Volume')}), - ('5 Filt/Mod/Pan', {PARAMETERS_KEY: ('5 Filter Type', '5 Filter Freq', '5 Filter Res', '5 Filter <- Vel', '5 Filter <- Random', '5 Pan', '5 Pan <- Vel', '5 Pan <- Random')}), - ('Pad 6', {PARAMETERS_KEY: ('6 Start', '6 Envelope Decay', '6 Stretch Factor', '6 Saturator Drive', '6 Envelope Type', '6 Transpose', '6 Volume <- Vel', '6 Volume')}), - ('6 Filt/Mod/Pan', {PARAMETERS_KEY: ('6 Filter Type', '6 Filter Freq', '6 Filter Res', '6 Filter <- Vel', '6 Filter <- Random', '6 Pan', '6 Pan <- Vel', '6 Pan <- Random')}), - ('Pad 7', {PARAMETERS_KEY: ('7 Start', '7 Envelope Decay', '7 Stretch Factor', '7 Saturator Drive', '7 Envelope Type', '7 Transpose', '7 Volume <- Vel', '7 Volume')}), - ('7 Filt/Mod/Pan', {PARAMETERS_KEY: ('7 Filter Type', '7 Filter Freq', '7 Filter Res', '7 Filter <- Vel', '7 Filter <- Random', '7 Pan', '7 Pan <- Vel', '7 Pan <- Random')}), - ('Pad 8', {PARAMETERS_KEY: ('8 Start', '8 Envelope Decay', '8 Stretch Factor', '8 Saturator Drive', '8 Envelope Type', '8 Transpose', '8 Volume <- Vel', '8 Volume')}), - ('8 Filt/Mod/Pan', {PARAMETERS_KEY: ('8 Filter Type', '8 Filter Freq', '8 Filter Res', '8 Filter <- Vel', '8 Filter <- Random', '8 Pan', '8 Pan <- Vel', '8 Pan <- Random')}), - ('Stretch Modes', {PARAMETERS_KEY: ('1 Stretch Mode', '2 Stretch Mode', '3 Stretch Mode', '4 Stretch Mode', '5 Stretch Mode', '6 Stretch Mode', '7 Stretch Mode', '8 Stretch Mode')}), - ('Global', {PARAMETERS_KEY: ('Global Time', 'Global Transpose', 'Global Volume', '', '', '', '', '')}))), - 'Operator': IndexedDict(((MAIN_KEY, {PARAMETERS_KEY: (use('Algorithm').if_parameter('Filter On').has_value('off').else_use('Filter Freq'), - use('Tone').if_parameter('Filter On').has_value('off').else_use('Filter Res'), - use('A Coarse').if_parameter('A Fix On ').has_value('off').else_use('A Fix Freq'), - use('A Fine').if_parameter('A Fix On ').has_value('off').else_use('A Fix Freq Mul'), - use('B Coarse').if_parameter('B Fix On ').has_value('off').else_use('B Fix Freq'), - use('B Fine').if_parameter('B Fix On ').has_value('off').else_use('B Fix Freq Mul'), - 'Osc-B Level', - 'Volume')}), - ('Global', {PARAMETERS_KEY: ('Time', 'Time < Key', 'Tone', 'Algorithm', 'Panorama', 'Pan < Key', 'Pan < Rnd', 'Volume')}), - ('Osc. A', {PARAMETERS_KEY: ('Osc-A On', - 'Osc-A Level', - 'A Fix On ', - use('A Coarse').if_parameter('A Fix On ').has_value('off').else_use('A Fix Freq'), - use('A Fine').if_parameter('A Fix On ').has_value('off').else_use('A Fix Freq Mul'), - 'Osc-A Wave', - 'Osc-A Retrig', - 'Osc-A Phase')}), - ('Osc. A Env.', {PARAMETERS_KEY: ('Osc-A On', 'Ae Init', 'Ae Attack', 'Ae Peak', 'Ae Decay', 'Ae Sustain', 'Ae Release', 'Osc-A Lev < Vel')}), - ('Osc. B', {PARAMETERS_KEY: ('Osc-B On', - 'Osc-B Level', - 'B Fix On ', - use('B Coarse').if_parameter('B Fix On ').has_value('off').else_use('B Fix Freq'), - use('B Fine').if_parameter('B Fix On ').has_value('off').else_use('B Fix Freq Mul'), - 'Osc-B Wave', - 'Osc-B Retrig', - 'Osc-B Phase')}), - ('Osc. B Env.', {PARAMETERS_KEY: ('Osc-B On', 'Be Init', 'Be Attack', 'Be Peak', 'Be Decay', 'Be Sustain', 'Be Release', 'Osc-B Lev < Vel')}), - ('Osc. C', {PARAMETERS_KEY: ('Osc-C On', - 'Osc-C Level', - 'C Fix On ', - use('C Coarse').if_parameter('C Fix On ').has_value('off').else_use('C Fix Freq'), - use('C Fine').if_parameter('C Fix On ').has_value('off').else_use('C Fix Freq Mul'), - 'Osc-C Wave', - 'Osc-C Retrig', - 'Osc-C Phase')}), - ('Osc. C Env.', {PARAMETERS_KEY: ('Osc-C On', 'Osc-C On', 'Ce Attack', 'Ce Peak', 'Ce Decay', 'Ce Sustain', 'Ce Release', 'Osc-C Lev < Vel')}), - ('Osc. D', {PARAMETERS_KEY: ('Osc-D On', - 'Osc-D Level', - 'D Fix On ', - use('D Coarse').if_parameter('D Fix On ').has_value('off').else_use('D Fix Freq'), - use('D Fine').if_parameter('D Fix On ').has_value('off').else_use('D Fix Freq Mul'), - 'Osc-D Wave', - 'Osc-D Retrig', - 'Osc-D Phase')}), - ('Osc. D Env.', {PARAMETERS_KEY: ('Osc-D On', 'De Init', 'De Attack', 'De Peak', 'De Decay', 'De Sustain', 'De Release', 'Osc-D Lev < Vel')}), - ('Filter', {PARAMETERS_KEY: ('Filter On', - use('Filter Type').if_parameter('Filter Type').is_available(True).else_use('Filter Type (Legacy)'), - 'Filter Freq', - use('Filter Res').if_parameter('Filter Res').is_available(True).else_use('Filter Res (Legacy)'), - use('Filter Circuit - LP/HP').if_parameter('Filter Type').has_value('Lowpass').else_use('Filter Circuit - LP/HP').if_parameter('Filter Type').has_value('Highpass').else_use('Filter Circuit - BP/NO/Morph'), - use('Filter Morph').if_parameter('Filter Type').has_value('Morph').else_use('').if_parameter('Filter Type').has_value('Lowpass').and_parameter('Filter Circuit - LP/HP').has_value('Clean').else_use('').if_parameter('Filter Type').has_value('Highpass').and_parameter('Filter Circuit - LP/HP').has_value('Clean').else_use('').if_parameter('Filter Type').has_value('Bandpass').and_parameter('Filter Circuit - BP/NO/Morph').has_value('Clean').else_use('').if_parameter('Filter Type').has_value('Notch').and_parameter('Filter Circuit - BP/NO/Morph').has_value('Clean').else_use('Filter Drive'), - 'Filt < Vel', - 'Filt < Key')}), - ('Filt. Env.', {PARAMETERS_KEY: ('Fe Amount', 'Fe Init', 'Fe Attack', 'Fe Peak', 'Fe Decay', 'Fe Sustain', 'Fe Release', 'Fe End')}), - ('LFO', {PARAMETERS_KEY: ('LFO On', - 'LFO Type', - 'LFO Range', - use('LFO Sync').if_parameter('LFO Range').has_value('Sync').else_use('LFO Rate'), - 'LFO Retrigger', - 'LFO Amt', - '', - '')}), - ('LFO Env.', {PARAMETERS_KEY: ('LFO On', 'Le Init', 'Le Attack', 'Le Peak', 'Le Decay', 'Le Sustain', 'Le Release', 'Le End')}), - ('Pitch', {PARAMETERS_KEY: ('Transpose', 'Spread', 'Glide On', 'Glide Time', 'Pe On', 'Pe Amount', 'LFO < Pe', 'Pe Dst B')}), - ('Pitch Env.', {PARAMETERS_KEY: ('Pe On', 'Pe Attack', 'Pe Peak', 'Pe Decay', 'Pe Sustain', 'Pe Release', 'Pe End', 'Pe R < Vel')}))), - 'MultiSampler': IndexedDict(((MAIN_KEY, {PARAMETERS_KEY: (use('Filter Type').if_parameter('Filter Type').is_available(True).else_use('Filter Type (Legacy)'), - use('Filter Freq'), - use('Filter Res').if_parameter('Filter Res').is_available(True).else_use('Filter Res (Legacy)'), - 'Ve Attack', - 'Ve Decay', - 'Ve Release', - 'Transpose', - 'Volume')}), - ('Volume Env.', {PARAMETERS_KEY: ('Ve Init', 'Ve Attack', 'Ve Peak', 'Ve Decay', 'Ve Sustain', 'Ve Release', 'Vol < Vel', 'Volume')}), - ('Env. Loop & Pan', {PARAMETERS_KEY: ('Ve Mode', - use('Ve Loop').if_parameter('Ve Mode').has_value('Loop').else_use('Ve Retrig').if_parameter('Ve Mode').has_value('Beat').else_use('Ve Retrig').if_parameter('Ve Mode').has_value('Sync').else_use(''), - 'Ve R < Vel', - '', - 'Pan', - 'Pan < Rnd', - 'Time', - 'Time < Key')}), - ('Filter', {PARAMETERS_KEY: ('F On', - use('Filter Type').if_parameter('Filter Type').is_available(True).else_use('Filter Type (Legacy)'), - use('Filter Freq'), - use('Filter Res').if_parameter('Filter Res').is_available(True).else_use('Filter Res (Legacy)'), - use('Filter Circuit - LP/HP').if_parameter('Filter Type').has_value('Lowpass').else_use('Filter Circuit - LP/HP').if_parameter('Filter Type').has_value('Highpass').else_use('Filter Circuit - BP/NO/Morph'), - use('Filter Morph').if_parameter('Filter Type').has_value('Morph').else_use('').if_parameter('Filter Type').has_value('Lowpass').and_parameter('Filter Circuit - LP/HP').has_value('Clean').else_use('').if_parameter('Filter Type').has_value('Highpass').and_parameter('Filter Circuit - LP/HP').has_value('Clean').else_use('').if_parameter('Filter Type').has_value('Bandpass').and_parameter('Filter Circuit - BP/NO/Morph').has_value('Clean').else_use('').if_parameter('Filter Type').has_value('Notch').and_parameter('Filter Circuit - BP/NO/Morph').has_value('Clean').else_use('Filter Drive'), - 'Filt < Vel', - 'Filt < Key')}), - ('Filt. Env', {PARAMETERS_KEY: ('Fe On', 'Fe < Env', 'Fe Init', 'Fe Attack', 'Fe Decay', 'Fe Peak', 'Fe Sustain', 'Fe Release')}), - ('Shaper', {PARAMETERS_KEY: ('Fe On', - 'Fe End', - 'Fe Mode', - use('Fe Loop').if_parameter('Fe Mode').has_value('Loop').else_use('Fe Retrig').if_parameter('Fe Mode').has_value('Beat').else_use('Fe Retrig').if_parameter('Fe Mode').has_value('Sync').else_use(''), - 'Fe R < Vel', - 'Shaper On', - 'Shaper Type', - 'Shaper Amt')}), - ('Osc. pg. 1', {PARAMETERS_KEY: ('Osc On', 'O Mode', 'Oe Init', 'Oe Attack', 'Oe Peak', 'Oe Decay', 'Oe Sustain', 'Oe Release')}), - ('Osc. pg. 2', {PARAMETERS_KEY: ('Oe End', - 'Oe Mode', - use('Oe Loop').if_parameter('Oe Mode').has_value('Loop').else_use('Oe Retrig').if_parameter('Oe Mode').has_value('Beat').else_use('Oe Retrig').if_parameter('Oe Mode').has_value('Sync').else_use(''), - 'O Type', - 'O Volume', - 'O Fix On', - use('O Coarse').if_parameter('O Fix On').has_value('off').else_use('O Fix Freq'), - use('O Fine').if_parameter('O Fix On').has_value('off').else_use('O Fix Freq Mul'))}), - ('Pitch Env.', {PARAMETERS_KEY: ('Pe On', 'Pe < Env', 'Pe Init', 'Pe Attack', 'Pe Peak', 'Pe Decay', 'Pe Sustain', 'Pe Release')}), - ('Pitch Env. 2', {PARAMETERS_KEY: ('Pe On', - 'Pe End', - 'Pe R < Vel', - 'Pe Mode', - use('Pe Loop').if_parameter('Pe Mode').has_value('Loop').else_use('Pe Retrig').if_parameter('Pe Mode').has_value('Beat').else_use('Pe Retrig').if_parameter('Pe Mode').has_value('Sync').else_use(''), - '', - '', - '')}), - ('Pitch/Glide', {PARAMETERS_KEY: ('Pe On', - 'Spread', - 'Transpose', - 'Detune', - 'Key Zone Shift', - 'Glide Mode', - use('Glide Time').if_parameter('Glide Mode').has_value('On'), - '')}), - ('LFO1 pg. 1', {PARAMETERS_KEY: ('L 1 On', - 'L 1 Wave', - 'L 1 Sync', - use('L 1 Sync Rate').if_parameter('L 1 Sync').has_value('Sync').else_use('L 1 Rate'), - 'Vol < LFO', - 'Filt < LFO', - 'Pan < LFO', - 'Pitch < LFO')}), - ('LFO1 pg. 2', {PARAMETERS_KEY: ('L 1 On', 'L 1 Retrig', 'L 1 Offset', 'L 1 Attack', '', '', '', '')}), - ('LFO2 pg. 1', {PARAMETERS_KEY: ('L 2 On', - 'L 2 Wave', - 'L 2 Sync', - use('L 2 Sync Rate').if_parameter('L 2 Sync').has_value('Sync').else_use('L 2 Rate'), - 'L 2 Retrig', - 'L 2 Offset', - 'L 2 Attack', - '')}), - ('LFO2 pg. 2', {PARAMETERS_KEY: ('L 2 On', - 'L 2 St Mode', - use('L 2 Spin').if_parameter('L 2 St Mode').has_value('Spin').else_use('L 2 Phase'), - '', - '', - '', - '', - '')}), - ('LFO3 pg. 1', {PARAMETERS_KEY: ('L 3 On', - 'L 3 Wave', - 'L 3 Sync', - use('L 3 Sync Rate').if_parameter('L 3 Sync').has_value('Sync').else_use('L 3 Rate'), - 'L 3 Retrig', - 'L 3 Offset', - 'L 3 Attack', - '')}), - ('LFO3 pg. 2', {PARAMETERS_KEY: ('L 3 On', - 'L 3 St Mode', - use('L 3 Spin').if_parameter('L 3 St Mode').has_value('Spin').else_use('L 3 Phase'), - '', - '', - '', - '', - '')}), - ('Aux Env. 1', {PARAMETERS_KEY: ('Ae On', - 'Ae Init', - 'Ae Peak', - 'Ae Sustain', - 'Ae End', - 'Ae Mode', - use('Ae Loop').if_parameter('Ae Mode').has_value('Loop').else_use('Ae Retrig').if_parameter('Ae Mode').has_value('Beat').else_use('Ae Retrig').if_parameter('Ae Mode').has_value('Sync'), - '')}), - ('Aux Env. 2', {PARAMETERS_KEY: ('Ae On', 'Ae Attack', 'Ae Decay', 'Ae Release', 'Ae A Slope', 'Ae D Slope', 'Ae R Slope', '')}))), - 'OriginalSimpler': IndexedDict(((MAIN_KEY, {PARAMETERS_KEY: (use('Fade In').if_parameter('Mode').has_value('One-Shot').else_use('Nudge').if_parameter('Mode').has_value('Slicing').else_use('Ve Attack').if_parameter('Mode').has_value('Classic'), - use('Fade Out').if_parameter('Mode').has_value('One-Shot').else_use('Ve Decay').if_parameter('Mode').has_value('Classic').else_use('Playback').if_parameter('Mode').has_value('Slicing'), - use('Transpose').if_parameter('Mode').has_value('One-Shot').else_use('Transpose').if_parameter('Mode').has_value('Slicing').else_use('Ve Sustain').if_parameter('Mode').has_value('Classic'), - use('Detune').if_parameter('Mode').has_value('One-Shot').else_use('Sensitivity').if_parameter('Mode').has_value('Slicing').else_use('Ve Release').if_parameter('Mode').has_value('Classic'), - use('Filter Freq').if_parameter('F On').has_value('on').else_use('S Start'), - use('Filter Res').if_parameter('F On').has_value('on').else_use('S Length'), - use('Filter Type').if_parameter('F On').has_value('on'), - 'Volume')}), - ('Loop', {PARAMETERS_KEY: ('S Start', 'S Length', 'S Loop On', 'S Loop Length', 'S Loop Fade', 'Transpose', 'Detune', 'Volume')}), - ('Filter', {PARAMETERS_KEY: ('F On', - use('Filter Type').if_parameter('Filter Type').is_available(True).else_use('Filter Type (Legacy)'), - use('Filter Freq'), - use('Filter Res').if_parameter('Filter Res').is_available(True).else_use('Filter Res (Legacy)'), - use('Filter Circuit - LP/HP').if_parameter('Filter Type').has_value('Lowpass').else_use('Filter Circuit - LP/HP').if_parameter('Filter Type').has_value('Highpass').else_use('Filter Circuit - BP/NO/Morph'), - use('Filter Morph').if_parameter('Filter Type').has_value('Morph').else_use('').if_parameter('Filter Type').has_value('Lowpass').and_parameter('Filter Circuit - LP/HP').has_value('Clean').else_use('').if_parameter('Filter Type').has_value('Highpass').and_parameter('Filter Circuit - LP/HP').has_value('Clean').else_use('').if_parameter('Filter Type').has_value('Bandpass').and_parameter('Filter Circuit - BP/NO/Morph').has_value('Clean').else_use('').if_parameter('Filter Type').has_value('Notch').and_parameter('Filter Circuit - BP/NO/Morph').has_value('Clean').else_use('Filter Drive'), - 'Filt < Vel', - 'Filt < Key')}), - ('Filt. Env.', {PARAMETERS_KEY: ('Fe On', 'Fe Attack', 'Fe Decay', 'Fe Sustain', 'Fe Release', 'Fe < Env', 'Filter Freq', '')}), - ('LFO', {PARAMETERS_KEY: ('L On', 'L Wave', 'L Rate', 'L Attack', 'L R < Key', 'Pan < LFO', 'L Retrig', 'L Offset')}), - ('Pitch Env.', {PARAMETERS_KEY: ('Pe On', 'Pe Attack', 'Pe Decay', 'Pe Sustain', 'Pe Release', 'Pe < Env', '', '')}), - ('Pitch & Glide', {PARAMETERS_KEY: ('Glide Mode', 'Glide Time', 'Pitch < LFO', 'Pe < Env', 'Pitch < LFO', 'Transpose', 'Detune', 'Spread')}), - ('Global', {PARAMETERS_KEY: ('Pan', 'Spread', 'Pan < Rnd', 'Pan < LFO', '', '', 'Vol < Vel', 'Volume')}))), - 'StringStudio': IndexedDict(((MAIN_KEY, {PARAMETERS_KEY: (use('Excitator Type').if_parameter('Exc On/Off').has_value('1'), - use('Exc ForceMassProt').if_parameter('Exc On/Off').has_value('1'), - use('Exc FricStiff').if_parameter('Exc On/Off').has_value('1'), - use('Exc Velocity').if_parameter('Exc On/Off').has_value('1'), - use('E Pos').if_parameter('Exc On/Off').has_value('1'), - 'String Decay', - 'Str Damping', - 'Volume')}), - ('Excitator', {PARAMETERS_KEY: ('Exc On/Off', - use('Excitator Type').if_parameter('Exc On/Off').has_value('1'), - use('Exc ForceMassProt').if_parameter('Exc On/Off').has_value('1'), - use('Exc FricStiff').if_parameter('Exc On/Off').has_value('1'), - use('Exc Velocity').if_parameter('Exc On/Off').has_value('1'), - use('E Pos').if_parameter('Exc On/Off').has_value('1'), - use('E Pos Abs').if_parameter('Exc On/Off').has_value('1'), - 'Volume')}), - ('String & Pickup', {PARAMETERS_KEY: ('String Decay', - 'S Decay < Key', - 'S Decay Ratio', - 'Str Inharmon', - 'Str Damping', - 'S Damp < Key', - 'Pickup On/Off', - use('Pickup Pos').if_parameter('Pickup On/Off').has_value('1'))}), - ('Damper', {PARAMETERS_KEY: ('Damper On', - use('Damper Mass').if_parameter('Damper On').has_value('1'), - use('D Stiffness').if_parameter('Damper On').has_value('1'), - use('Damp Pos').if_parameter('Damper On').has_value('1'), - use('D Damping').if_parameter('Damper On').has_value('1'), - use('Damper Gated').if_parameter('Damper On').has_value('1'), - use('').if_parameter('Damper On').has_value('0').else_use('D Velocity').if_parameter('Damper Gated').has_value('1').else_use(''), - use('D Pos Abs').if_parameter('Damper On').has_value('1'))}), - ('Termination', {PARAMETERS_KEY: ('Term On/Off', - use('Term Mass').if_parameter('Term On/Off').has_value('1'), - use('Term Fng Stiff').if_parameter('Term On/Off').has_value('1'), - use('Term Fret Stiff').if_parameter('Term On/Off').has_value('1'), - use('T Mass < Vel').if_parameter('Term On/Off').has_value('1'), - use('T Mass < Key').if_parameter('Term On/Off').has_value('1'), - '', - 'Volume')}), - ('Body', {PARAMETERS_KEY: ('Body On/Off', - use('Body Type').if_parameter('Body On/Off').has_value('1'), - use('Body Size').if_parameter('Body On/Off').has_value('1'), - use('Body Decay').if_parameter('Body On/Off').has_value('1'), - use('Body Low-Cut').if_parameter('Body On/Off').has_value('1'), - use('Body High-Cut').if_parameter('Body On/Off').has_value('1'), - use('Body Mix').if_parameter('Body On/Off').has_value('1'), - 'Volume')}), - ('Filter', {PARAMETERS_KEY: ('Filter On/Off', - use('Filter Type').if_parameter('Filter On/Off').has_value('1'), - use('Filter Freq').if_parameter('Filter On/Off').has_value('1'), - use('Filter Reso').if_parameter('Filter On/Off').has_value('1'), - use('Freq < Env').if_parameter('Filter On/Off').has_value('1'), - use('Freq < LFO').if_parameter('Filter On/Off').has_value('1'), - use('Reso < Env').if_parameter('Filter On/Off').has_value('1'), - use('Reso < LFO').if_parameter('Filter On/Off').has_value('1'))}), - ('LFO', {PARAMETERS_KEY: ('LFO On/Off', - use('LFO Shape').if_parameter('LFO On/Off').has_value('1'), - use('LFO Sync On').if_parameter('LFO On/Off').has_value('1'), - use('').if_parameter('LFO On/Off').has_value('0').else_use('LFO SyncRate').if_parameter('LFO Sync On').has_value('Beat').else_use('LFO Speed'), - use('LFO Delay').if_parameter('LFO On/Off').has_value('1'), - use('LFO Fade In').if_parameter('LFO On/Off').has_value('1'), - '', - '')}), - ('Vibrato', {PARAMETERS_KEY: ('Vibrato On/Off', - use('Vib Delay').if_parameter('Vibrato On/Off').has_value('1'), - use('Vib Fade-In').if_parameter('Vibrato On/Off').has_value('1'), - use('Vib Speed').if_parameter('Vibrato On/Off').has_value('1'), - use('Vib Amount').if_parameter('Vibrato On/Off').has_value('1'), - use('Vib < ModWh').if_parameter('Vibrato On/Off').has_value('1'), - use('Vib Error').if_parameter('Vibrato On/Off').has_value('1'), - use('Vib Delay').if_parameter('Vibrato On/Off').has_value('1'))}), - ('Unison & Portamento', {PARAMETERS_KEY: ('Unison On/Off', - use('Unison Voices').if_parameter('Unison On/Off').has_value('1'), - use('Uni Delay').if_parameter('Unison On/Off').has_value('1'), - use('Uni Detune').if_parameter('Unison On/Off').has_value('1'), - 'Porta On/Off', - use('Porta Time').if_parameter('Porta On/Off').has_value('1'), - use('Porta Legato').if_parameter('Porta On/Off').has_value('1'), - use('Porta Prop').if_parameter('Porta On/Off').has_value('1'))}), - ('Global', {PARAMETERS_KEY: ('Octave', 'Semitone', 'Fine Tune', 'Voices', 'PB Depth', 'Stretch', 'Error', 'Key Priority')}), - ('Filt. Env.', {PARAMETERS_KEY: ('FEG On/Off', - use('FEG Attack').if_parameter('FEG On/Off').has_value('1'), - use('FEG Decay').if_parameter('FEG On/Off').has_value('1'), - use('FEG Sustain').if_parameter('FEG On/Off').has_value('1'), - use('FEG Release').if_parameter('FEG On/Off').has_value('1'), - use('FEG Att < Vel').if_parameter('FEG On/Off').has_value('1'), - use('FEG < Vel').if_parameter('FEG On/Off').has_value('1'), - '')}), - ('Excitator Mod.', {PARAMETERS_KEY: (use('Exc Prot < Vel').if_parameter('Exc On/Off').has_value('1'), - use('Exc Prot < Key').if_parameter('Exc On/Off').has_value('1'), - use('Exc Stiff < Vel').if_parameter('Exc On/Off').has_value('1'), - use('Exc Stiff < Key').if_parameter('Exc On/Off').has_value('1'), - use('Exc Vel < Vel').if_parameter('Exc On/Off').has_value('1'), - use('Exc Vel < Key').if_parameter('Exc On/Off').has_value('1'), - use('E Pos < Vel').if_parameter('Exc On/Off').has_value('1'), - use('E Pos < Key').if_parameter('Exc On/Off').has_value('1'))}), - ('Mass Mod.', {PARAMETERS_KEY: (use('D Mass < Key').if_parameter('Damper On').has_value('1'), - use('D Stiff < Key').if_parameter('Damper On').has_value('1'), - use('D Pos < Key').if_parameter('Damper On').has_value('1'), - use('D Pos < Vel').if_parameter('Damper On').has_value('1'), - use('Damper Gated').if_parameter('Damper On').has_value('1'), - use('').if_parameter('Damper On').has_value('0').else_use('D Velo < Key').if_parameter('Damper Gated').has_value('1').else_use(''), - '', - '')}))), - 'MidiArpeggiator': IndexedDict(((MAIN_KEY, {PARAMETERS_KEY: ('Style', - use('Synced Rate').if_parameter('Sync On').has_value('on').else_use('Free Rate'), - 'Gate', - 'Offset', - 'Hold On', - 'Tranpose Key', - 'Transp. Steps', - 'Transp. Dist.')}), ('Rhythm', {PARAMETERS_KEY: ('Sync On', - use('Synced Rate').if_parameter('Sync On').has_value('on').else_use('Free Rate'), - 'Groove', - 'Offset', - 'Repeats', - 'Gate', - 'Retrigger Mode', - use('Ret. Interval').if_parameter('Retrigger Mode').has_value('Beat'))}), ('Pitch/Vel.', {PARAMETERS_KEY: ('Tranpose Mode', 'Tranpose Key', 'Transp. Steps', 'Transp. Dist.', 'Velocity On', 'Vel. Retrigger', 'Velocity Decay', 'Velocity Target')}))), - 'MidiChord': IndexedDict((('Shift', {PARAMETERS_KEY: ('Shift1', 'Shift2', 'Shift3', 'Shift4', 'Shift5', 'Shift6', '', '')}), ('Velocity', {PARAMETERS_KEY: ('Velocity1', 'Velocity2', 'Velocity3', 'Velocity4', 'Velocity5', 'Velocity6', '', '')}))), - 'MidiNoteLength': IndexedDict(((MAIN_KEY, {PARAMETERS_KEY: ('Trigger Mode', - 'Sync On', - use('Synced Length').if_parameter('Sync On').has_value('on').else_use('Time Length'), - 'Gate', - 'On/Off-Balance', - 'Decay Time', - 'Decay Key Scale', - '')}),)), - 'MidiPitcher': IndexedDict(((MAIN_KEY, {PARAMETERS_KEY: ('Pitch', 'Range', 'Lowest', '', '', '', '', '')}),)), - 'MidiRandom': IndexedDict(((MAIN_KEY, {PARAMETERS_KEY: ('Chance', 'Choices', 'Mode', 'Scale', 'Sign', '', '', '')}),)), - 'MidiScale': IndexedDict(((MAIN_KEY, {PARAMETERS_KEY: ('Base', 'Transpose', 'Range', 'Lowest', 'Fold', '', '', '')}),)), - 'MidiVelocity': IndexedDict(((MAIN_KEY, {PARAMETERS_KEY: ('Mode', 'Drive', 'Compand', 'Out Hi', 'Out Low', 'Range', 'Lowest', 'Random')}),)), - 'Amp': IndexedDict((('Global', {PARAMETERS_KEY: ('Amp Type', 'Bass', 'Middle', 'Treble', 'Presence', 'Gain', 'Volume', 'Dry/Wet')}), ('Dual/Mono', {PARAMETERS_KEY: ('Dual Mono', '', '', '', '', '', '', '')}))), - 'AutoFilter': IndexedDict(((MAIN_KEY, {PARAMETERS_KEY: (use('Filter Type').if_parameter('Filter Type').is_available(True).else_use('Filter Type (Legacy)'), - use('Frequency'), - use('Resonance').if_parameter('Resonance').is_available(True).else_use('Resonance (Legacy)'), - use('Filter Circuit - LP/HP').if_parameter('Filter Type').has_value('Lowpass').else_use('Filter Circuit - LP/HP').if_parameter('Filter Type').has_value('Highpass').else_use('Filter Circuit - BP/NO/Morph'), - use('Morph').if_parameter('Filter Type').has_value('Morph').else_use('').if_parameter('Filter Type').has_value('Lowpass').and_parameter('Filter Circuit - LP/HP').has_value('Clean').else_use('').if_parameter('Filter Type').has_value('Highpass').and_parameter('Filter Circuit - LP/HP').has_value('Clean').else_use('').if_parameter('Filter Type').has_value('Bandpass').and_parameter('Filter Circuit - BP/NO/Morph').has_value('Clean').else_use('').if_parameter('Filter Type').has_value('Notch').and_parameter('Filter Circuit - BP/NO/Morph').has_value('Clean').else_use('Drive'), - 'LFO Amount', - 'LFO Sync', - use('LFO Frequency').if_parameter('LFO Sync').has_value('Free').else_use('LFO Sync Rate'))}), - ('Envelope', {PARAMETERS_KEY: (use('Filter Type').if_parameter('Filter Type').is_available(True).else_use('Filter Type (Legacy)'), - use('Frequency'), - use('Resonance').if_parameter('Resonance').is_available(True).else_use('Resonance (Legacy)'), - use('Morph').if_parameter('Filter Type').has_value('Morph SVF').else_use('Drive'), - 'Env. Attack', - 'Env. Release', - 'Env. Modulation', - '')}), - ('LFO', {PARAMETERS_KEY: ('LFO Amount', - 'LFO Waveform', - 'LFO Sync', - use('LFO Frequency').if_parameter('LFO Sync').has_value('Free').else_use('LFO Sync Rate'), - use('').if_parameter('LFO Waveform').has_value('S&H Mono').else_use('LFO Offset').if_parameter('LFO Sync').has_value('Sync').else_use('LFO Stereo Mode'), - use('').if_parameter('LFO Waveform').has_value('S&H Mono').else_use('LFO Phase').if_parameter('LFO Sync').has_value('Sync').else_use('LFO Phase').if_parameter('LFO Stereo Mode').has_value('Phase').else_use('LFO Spin'), - 'LFO Quantize On', - 'LFO Quantize Rate')}), - ('Sidechain', {PARAMETERS_KEY: ('Ext. In On', 'Ext. In Mix', 'Ext. In Gain', '', '', '', '', '')}))), - 'AutoPan': IndexedDict(((MAIN_KEY, {PARAMETERS_KEY: ('Amount', - 'Shape', - 'Invert', - 'Waveform', - 'LFO Type', - use('Sync Rate').if_parameter('LFO Type').has_value('Beats').else_use('Frequency'), - use('Width (Random)').if_parameter('Waveform').has_value('S&H Width').else_use('Stereo Mode').if_parameter('LFO Type').has_value('Frequency').else_use('Offset'), - use('').if_parameter('Waveform').has_value('S&H Width').else_use('Phase').if_parameter('LFO Type').has_value('Beats').else_use('Spin').if_parameter('Stereo Mode').has_value('Spin').else_use('Phase'))}),)), - 'BeatRepeat': IndexedDict(((MAIN_KEY, {PARAMETERS_KEY: ('Grid', 'Interval', 'Offset', 'Gate', 'Pitch', 'Pitch Decay', 'Variation', 'Chance')}), ('Filt/Mix', {PARAMETERS_KEY: ('Filter On', 'Filter Freq', 'Filter Width', '', 'Mix Type', 'Volume', 'Decay', 'Chance')}), ('Repeat Rate', {PARAMETERS_KEY: ('Repeat', 'Interval', 'Offset', 'Gate', 'Grid', 'Block Triplets', 'Variation', 'Variation Type')}))), - 'Cabinet': IndexedDict(((MAIN_KEY, {PARAMETERS_KEY: ('Cabinet Type', 'Microphone Type', 'Microphone Position', 'Dual Mono', '', '', '', 'Dry/Wet')}),)), - 'Chorus': IndexedDict((('Chorus', {PARAMETERS_KEY: ('LFO Amount', - 'LFO Rate', - 'Delay 1 Time', - 'Delay 1 HiPass', - 'Delay 2 Mode', - use('').if_parameter('Delay 2 Mode').has_value('off').else_use('Delay 2 Time'), - 'Feedback', - 'Dry/Wet')}), ('Other', {PARAMETERS_KEY: ('LFO Extend On', 'Polarity', 'Link On', '', '', '', '', '')}))), - 'Compressor2': IndexedDict(((MAIN_KEY, {PARAMETERS_KEY: ('Threshold', - use('Expansion Ratio').if_parameter('Model').has_value('Expand').else_use('Ratio'), - 'Model', - 'Knee', - 'Attack', - use('Release').if_parameter('Auto Release On/Off').has_value('off').else_use('Makeup').if_parameter('Ext. In On').has_value('off'), - 'Dry/Wet', - 'Output Gain')}), ('Sidechain', {PARAMETERS_KEY: ('Ext. In On', - 'Ext. In Gain', - 'Ext. In Mix', - 'Side Listen', - 'EQ On', - 'EQ Mode', - 'EQ Freq', - use('').if_parameter('EQ On').has_value('off').else_use('EQ Gain').if_parameter('EQ Mode').has_value('Low Shelf').else_use('EQ Gain').if_parameter('EQ Mode').has_value('High Shelf').else_use('EQ Gain').if_parameter('EQ Mode').has_value('Bell').else_use('EQ Q'))}), ('Global', {PARAMETERS_KEY: ('Auto Release On/Off', 'Env Mode', 'Makeup', 'LookAhead', '', '', 'Dry/Wet', 'Output Gain')}))), - 'Corpus': IndexedDict(((MAIN_KEY, {PARAMETERS_KEY: ('Resonance Type', - use('').if_parameter('Resonance Type').has_value('Tube').else_use('').if_parameter('Resonance Type').has_value('Pipe').else_use('Brightness'), - 'Decay', - use('Radius').if_parameter('Resonance Type').has_value('Tube').else_use('Radius').if_parameter('Resonance Type').has_value('Pipe').else_use('Material'), - use('').if_parameter('Resonance Type').has_value('Tube').else_use('Opening').if_parameter('Resonance Type').has_value('Pipe').else_use('Inharmonics'), - use('Ratio').if_parameter('Resonance Type').has_value('Plate').else_use('Ratio').if_parameter('Resonance Type').has_value('Membrane').else_use(''), - use('Transpose').if_parameter('MIDI Frequency').has_value('1').else_use('Tune'), - 'Dry Wet')}), - ('Body', {PARAMETERS_KEY: ('Resonance Type', - use('Ratio').if_parameter('Resonance Type').has_value('Plate').else_use('Ratio').if_parameter('Resonance Type').has_value('Membrane'), - 'Decay', - use('Radius').if_parameter('Resonance Type').has_value('Tube').else_use('Radius').if_parameter('Resonance Type').has_value('Pipe').else_use('Material'), - use('').if_parameter('Resonance Type').has_value('Tube').else_use('Opening').if_parameter('Resonance Type').has_value('Pipe').else_use('Inharmonics'), - use('').if_parameter('Resonance Type').has_value('Tube').else_use('').if_parameter('Resonance Type').has_value('Pipe').else_use('Listening L'), - use('').if_parameter('Resonance Type').has_value('Tube').else_use('').if_parameter('Resonance Type').has_value('Pipe').else_use('Listening R'), - use('').if_parameter('Resonance Type').has_value('Tube').else_use('').if_parameter('Resonance Type').has_value('Pipe').else_use('Hit'))}), - ('LFO', {PARAMETERS_KEY: ('LFO On/Off', - use('LFO Shape').if_parameter('LFO On/Off').has_value('1'), - use('LFO Amount').if_parameter('LFO On/Off').has_value('1'), - use('LFO Sync').if_parameter('LFO On/Off').has_value('1'), - use('').if_parameter('LFO On/Off').has_value('0').else_use('LFO Rate').if_parameter('LFO Sync').has_value('Free').else_use('LFO Sync Rate'), - use('').if_parameter('LFO On/Off').has_value('0').else_use('LFO Stereo Mode').if_parameter('LFO Sync').has_value('Free').else_use('Offset'), - use('').if_parameter('LFO On/Off').has_value('0').else_use('Phase').if_parameter('LFO Sync').has_value('Sync').else_use('Spin').if_parameter('LFO Stereo Mode').has_value('Spin'), - '')}), - ('Tune & Sidechain', {PARAMETERS_KEY: ('MIDI Frequency', - 'MIDI Mode', - use('Transpose').if_parameter('MIDI Frequency').has_value('1').else_use('Tune'), - use('Fine').if_parameter('MIDI Frequency').has_value('1'), - 'Spread', - use('').if_parameter('Resonance Type').has_value('Tube').else_use('').if_parameter('Resonance Type').has_value('Pipe').else_use('Brightness'), - 'Note Off', - use('Off Decay').if_parameter('Note Off').has_value('1'))}), - ('Filter & Mix', {PARAMETERS_KEY: ('Filter On/Off', - use('Mid Freq').if_parameter('Filter On/Off').has_value('1'), - use('Width').if_parameter('Filter On/Off').has_value('1'), - use('').if_parameter('Resonance Type').has_value('Tube').else_use('').if_parameter('Resonance Type').has_value('Pipe').else_use('Resonator Quality'), - 'Bleed', - 'Gain', - 'Dry Wet', - '')}))), - 'Tube': IndexedDict((('Character', {PARAMETERS_KEY: ('Drive', 'Tube Type', 'Bias', 'Tone', 'Attack', 'Release', 'Envelope', 'Dry/Wet')}), ('Output', {PARAMETERS_KEY: ('', '', '', '', '', '', 'Output', 'Dry/Wet')}))), - 'Eq8': IndexedDict(((MAIN_KEY, {PARAMETERS_KEY: ('1 Frequency A', - use('1 Gain A').if_parameter('1 Filter Type A').has_value('Low Shelf').else_use('1 Gain A').if_parameter('1 Filter Type A').has_value('Bell').else_use('1 Gain A').if_parameter('1 Filter Type A').has_value('High Shelf').else_use('1 Resonance A'), - '2 Frequency A', - use('2 Gain A').if_parameter('2 Filter Type A').has_value('Low Shelf').else_use('2 Gain A').if_parameter('2 Filter Type A').has_value('Bell').else_use('2 Gain A').if_parameter('2 Filter Type A').has_value('High Shelf').else_use('2 Resonance A'), - '3 Frequency A', - use('3 Gain A').if_parameter('3 Filter Type A').has_value('Low Shelf').else_use('3 Gain A').if_parameter('3 Filter Type A').has_value('Bell').else_use('3 Gain A').if_parameter('3 Filter Type A').has_value('High Shelf').else_use('3 Resonance A'), - '4 Frequency A', - use('4 Gain A').if_parameter('4 Filter Type A').has_value('Low Shelf').else_use('4 Gain A').if_parameter('4 Filter Type A').has_value('Bell').else_use('4 Gain A').if_parameter('4 Filter Type A').has_value('High Shelf').else_use('4 Resonance A'))}), - ('EQ Band 1', {PARAMETERS_KEY: ('1 Filter On A', '1 Filter Type A', '1 Frequency A', '1 Gain A', '1 Resonance A', 'Adaptive Q', 'Scale', 'Output Gain')}), - ('EQ Band 2', {PARAMETERS_KEY: ('2 Filter On A', '2 Filter Type A', '2 Frequency A', '2 Gain A', '2 Resonance A', 'Adaptive Q', 'Scale', 'Output Gain')}), - ('EQ Band 3', {PARAMETERS_KEY: ('3 Filter On A', '3 Filter Type A', '3 Frequency A', '3 Gain A', '3 Resonance A', 'Adaptive Q', 'Scale', 'Output Gain')}), - ('EQ Band 4', {PARAMETERS_KEY: ('4 Filter On A', '4 Filter Type A', '4 Frequency A', '4 Gain A', '4 Resonance A', 'Adaptive Q', 'Scale', 'Output Gain')}), - ('EQ Band 5', {PARAMETERS_KEY: ('5 Filter On A', '5 Filter Type A', '5 Frequency A', '5 Gain A', '5 Resonance A', 'Adaptive Q', 'Scale', 'Output Gain')}), - ('EQ Band 6', {PARAMETERS_KEY: ('6 Filter On A', '6 Filter Type A', '6 Frequency A', '6 Gain A', '6 Resonance A', 'Adaptive Q', 'Scale', 'Output Gain')}), - ('EQ Band 7', {PARAMETERS_KEY: ('7 Filter On A', '7 Filter Type A', '7 Frequency A', '7 Gain A', '7 Resonance A', 'Adaptive Q', 'Scale', 'Output Gain')}), - ('EQ Band 8', {PARAMETERS_KEY: ('8 Filter On A', '8 Filter Type A', '8 Frequency A', '8 Gain A', '8 Resonance A', 'Adaptive Q', 'Scale', 'Output Gain')}), - ('8 x Frequency', {PARAMETERS_KEY: ('1 Frequency A', '2 Frequency A', '3 Frequency A', '4 Frequency A', '5 Frequency A', '6 Frequency A', '7 Frequency A', '8 Frequency A')}), - ('8 x Gain', {PARAMETERS_KEY: ('1 Gain A', '2 Gain A', '3 Gain A', '4 Gain A', '5 Gain A', '6 Gain A', '7 Gain A', '8 Gain A')}), - ('8 x Resonance', {PARAMETERS_KEY: ('1 Resonance A', '2 Resonance A', '3 Resonance A', '4 Resonance A', '5 Resonance A', '6 Resonance A', '7 Resonance A', '8 Resonance A')}))), - 'FilterEQ3': IndexedDict((('EQ', {PARAMETERS_KEY: ('LowOn', 'MidOn', 'HighOn', 'GainLo', 'GainMid', 'GainHi', 'FreqLo', 'FreqHi')}), ('Slope', {PARAMETERS_KEY: ('Slope', '', '', '', '', '', '', '')}))), - 'Erosion': IndexedDict(((MAIN_KEY, {PARAMETERS_KEY: ('Mode', - 'Frequency', - use('').if_parameter('Mode').has_value('Sine').else_use('Width'), - 'Amount', - '', - '', - '', - '')}),)), - 'ProxyAudioEffectDevice': IndexedDict(((MAIN_KEY, {PARAMETERS_KEY: ('Input Gain', 'Output Gain', 'Dry/Wet', '', '', '', '', '')}),)), - 'FilterDelay': IndexedDict(((MAIN_KEY, {PARAMETERS_KEY: ('2 Filter Freq', - '2 Filter Width', - '2 Delay Mode', - use('2 Time Delay').if_parameter('2 Delay Mode').has_value('off').else_use('2 Beat Delay'), - '2 Feedback', - use('1 Volume').if_parameter('1 Input On').has_value('on').else_use('2 Pan'), - '2 Volume', - use('3 Volume').if_parameter('3 Input On').has_value('on').else_use('Dry'))}), - ('L Filter', {PARAMETERS_KEY: ('1 Input On', - '1 Filter Freq', - '1 Filter Width', - '1 Feedback', - '1 Delay Mode', - use('1 Time Delay').if_parameter('1 Delay Mode').has_value('off').else_use('1 Beat Delay'), - use('1 Beat Swing').if_parameter('1 Delay Mode').has_value('on').else_use(''), - '1 Volume')}), - ('L+R Filter', {PARAMETERS_KEY: ('2 Input On', - '2 Filter Freq', - '2 Filter Width', - '2 Feedback', - '2 Delay Mode', - use('2 Time Delay').if_parameter('2 Delay Mode').has_value('off').else_use('2 Beat Delay'), - use('2 Beat Swing').if_parameter('2 Delay Mode').has_value('on').else_use(''), - '2 Volume')}), - ('R Filter', {PARAMETERS_KEY: ('3 Input On', - '3 Filter Freq', - '3 Filter Width', - '3 Feedback', - '3 Delay Mode', - use('3 Time Delay').if_parameter('3 Delay Mode').has_value('off').else_use('3 Beat Delay'), - use('3 Beat Swing').if_parameter('3 Delay Mode').has_value('on').else_use(''), - '3 Volume')}), - ('Mix', {PARAMETERS_KEY: ('1 Pan', '2 Pan', '3 Pan', '', '1 Volume', '2 Volume', '3 Volume', 'Dry')}))), - 'Flanger': IndexedDict(((MAIN_KEY, {PARAMETERS_KEY: ('LFO Amount', - 'Sync', - use('Frequency').if_parameter('Sync').has_value('Free').else_use('Sync Rate'), - 'Delay Time', - 'Hi Pass', - 'Env. Modulation', - 'Feedback', - 'Dry/Wet')}), ('Envelope', {PARAMETERS_KEY: ('Env. Attack', 'Env. Release', 'Env. Modulation', 'Hi Pass', 'Delay Time', 'Feedback', 'Polarity', 'Dry/Wet')}), ('LFO / S&H', {PARAMETERS_KEY: ('LFO Amount', - 'LFO Waveform', - 'Sync', - use('Frequency').if_parameter('Sync').has_value('Free').else_use('Sync Rate'), - use('').if_parameter('LFO Waveform').has_value('S&H Width').else_use('LFO Stereo Mode').if_parameter('Sync').has_value('Free').else_use('LFO Offset'), - use('LFO Width (Random)').if_parameter('LFO Waveform').has_value('S&H Width').else_use('LFO Phase').if_parameter('Sync').has_value('Sync').else_use('LFO Phase').if_parameter('LFO Stereo Mode').has_value('Phase').else_use('LFO Spin'), - '', - '')}))), - 'FrequencyShifter': IndexedDict((('FreqDrive', {PARAMETERS_KEY: ('Mode', - use('Ring Mod Frequency').if_parameter('Mode').has_value('Ring Modulation').else_use('Coarse'), - 'Wide', - 'Fine', - use('Drive On/Off').if_parameter('Mode').has_value('Ring Modulation'), - use('Drive').if_parameter('Drive On/Off').has_value('1').and_parameter('Mode').has_value('Ring Modulation'), - 'LFO Amount', - 'Dry/Wet')}), ('LFO / S&H', {PARAMETERS_KEY: ('LFO Amount', - 'LFO Waveform', - 'Sync', - use('LFO Frequency').if_parameter('Sync').has_value('Free').else_use('Sync Rate'), - use('').if_parameter('LFO Waveform').has_value('S&H Width').else_use('LFO Stereo Mode').if_parameter('Sync').has_value('Free').else_use('LFO Offset'), - use('LFO Width (Random)').if_parameter('LFO Waveform').has_value('S&H Width').else_use('LFO Phase').if_parameter('Sync').has_value('Sync').else_use('LFO Phase').if_parameter('LFO Stereo Mode').has_value('Phase').else_use('LFO Spin'), - '', - '')}))), - 'Gate': IndexedDict((('Gate', {PARAMETERS_KEY: ('Threshold', 'Return', 'FlipMode', 'LookAhead', 'Attack', 'Hold', 'Release', 'Floor')}), ('Sidechain', {PARAMETERS_KEY: ('Ext. In On', - 'Ext. In Gain', - 'Ext. In Mix', - 'Side Listen', - 'EQ On', - 'EQ Mode', - 'EQ Freq', - use('EQ Gain').if_parameter('EQ Mode').has_value('Low Shelf').else_use('EQ Gain').if_parameter('EQ Mode').has_value('High Shelf').else_use('EQ Gain').if_parameter('EQ Mode').has_value('Bell').else_use('EQ Q'))}))), - 'GlueCompressor': IndexedDict((('Compression', {PARAMETERS_KEY: ('Threshold', 'Ratio', 'Attack', 'Release', 'Peak Clip In', 'Range', 'Makeup', 'Dry/Wet')}), ('Sidechain', {PARAMETERS_KEY: ('Ext. In On', - 'Ext. In Gain', - 'Ext. In Mix', - '', - 'EQ On', - 'EQ Mode', - 'EQ Freq', - use('EQ Gain').if_parameter('EQ Mode').has_value('Low Shelf').else_use('EQ Gain').if_parameter('EQ Mode').has_value('High Shelf').else_use('EQ Gain').if_parameter('EQ Mode').has_value('Bell').else_use('EQ Q'))}))), - 'GrainDelay': IndexedDict((('Pitch', {PARAMETERS_KEY: ('Frequency', - 'Pitch', - 'Delay Mode', - use('Time Delay').if_parameter('Delay Mode').has_value('off').else_use('Beat Delay'), - 'Random', - 'Spray', - 'Feedback', - 'DryWet')}), ('Time', {PARAMETERS_KEY: ('Delay Mode', - use('Time Delay').if_parameter('Delay Mode').has_value('off').else_use('Beat Delay'), - 'Beat Swing', - 'Feedback', - '', - '', - '', - 'DryWet')}))), - 'Limiter': IndexedDict(((MAIN_KEY, {PARAMETERS_KEY: ('Gain', 'Ceiling', 'Link Channels', 'Lookahead', 'Auto', 'Release time', '', '')}),)), - 'Looper': IndexedDict(((MAIN_KEY, {PARAMETERS_KEY: ('State', 'Speed', 'Reverse', 'Quantization', 'Monitor', 'Song Control', 'Tempo Control', 'Feedback')}),)), - 'MultibandDynamics': IndexedDict(((MAIN_KEY, {PARAMETERS_KEY: ('Above Threshold (Low)', 'Above Ratio (Low)', 'Above Threshold (Mid)', 'Above Ratio (Mid)', 'Above Threshold (High)', 'Above Ratio (High)', 'Master Output', 'Amount')}), - ('Low Band', {PARAMETERS_KEY: ('Band Activator (Low)', 'Input Gain (Low)', 'Below Threshold (Low)', 'Below Ratio (Low)', 'Above Threshold (Low)', 'Above Ratio (Low)', 'Attack Time (Low)', 'Release Time (Low)')}), - ('Mid Band', {PARAMETERS_KEY: ('Band Activator (Mid)', 'Input Gain (Mid)', 'Below Threshold (Mid)', 'Below Ratio (Mid)', 'Above Threshold (Mid)', 'Above Ratio (Mid)', 'Attack Time (Mid)', 'Release Time (Mid)')}), - ('High Band', {PARAMETERS_KEY: ('Band Activator (High)', 'Input Gain (High)', 'Below Threshold (High)', 'Below Ratio (High)', 'Above Threshold (High)', 'Above Ratio (High)', 'Attack Time (High)', 'Release Time (High)')}), - ('Mix & Split', {PARAMETERS_KEY: ('Output Gain (Low)', 'Low-Mid Crossover', 'Output Gain (Mid)', 'Mid-High Crossover', 'Output Gain (High)', 'Peak/RMS Mode', 'Amount', 'Master Output')}), - ('Sidechain', {PARAMETERS_KEY: ('Ext. In On', 'Ext. In Mix', 'Ext. In Gain', '', 'Time Scaling', 'Soft Knee On/Off', '', '')}))), - 'Overdrive': IndexedDict(((MAIN_KEY, {PARAMETERS_KEY: ('Filter Freq', 'Filter Width', 'Drive', 'Tone', 'Preserve Dynamics', '', '', 'Dry/Wet')}),)), - 'Phaser': IndexedDict(((MAIN_KEY, {PARAMETERS_KEY: ('Poles', - 'Frequency', - 'Feedback', - 'Env. Modulation', - 'LFO Amount', - 'LFO Sync', - use('LFO Frequency').if_parameter('LFO Sync').has_value('Free').else_use('LFO Sync Rate'), - 'Dry/Wet')}), ('Envelope', {PARAMETERS_KEY: ('Poles', 'Type', 'Color', 'Frequency', 'Feedback', 'Env. Modulation', 'Env. Attack', 'Env. Release')}), ('LFO', {PARAMETERS_KEY: ('LFO Amount', - 'LFO Waveform', - 'LFO Sync', - use('LFO Frequency').if_parameter('LFO Sync').has_value('Free').else_use('LFO Sync Rate'), - use('LFO Width (Random)').if_parameter('LFO Waveform').has_value('S&H Width').else_use('LFO Stereo Mode').if_parameter('LFO Sync').has_value('Free').else_use('LFO Offset'), - use('').if_parameter('LFO Waveform').has_value('S&H Width').else_use('LFO Phase').if_parameter('LFO Sync').has_value('Sync').else_use('LFO Phase').if_parameter('LFO Stereo Mode').has_value('Phase').else_use('LFO Spin'), - '', - '')}))), - 'PingPongDelay': IndexedDict(((MAIN_KEY, {PARAMETERS_KEY: ('Delay Mode', - use('Beat Delay').if_parameter('Delay Mode').has_value('Sync').else_use('Time Delay'), - use('Beat Swing').if_parameter('Delay Mode').has_value('Sync').else_use(''), - 'Freeze', - 'Filter Freq', - 'Filter Width', - 'Feedback', - 'Dry/Wet')}),)), - 'Redux': IndexedDict(((MAIN_KEY, {PARAMETERS_KEY: ('Bit On', - 'Bit Depth', - 'Sample Mode', - use('Sample Hard').if_parameter('Sample Mode').has_value('Hard').else_use('Sample Soft'), - '', - '', - '', - '')}),)), - 'Resonator': IndexedDict(((MAIN_KEY, {PARAMETERS_KEY: ('Frequency', 'Decay', 'Color', 'I Gain', 'II Gain', 'III Gain', 'Width', 'Dry/Wet')}), - ('Global', {PARAMETERS_KEY: ('Mode', 'Decay', 'Const', 'Color', '', 'Width', 'Global Gain', 'Dry/Wet')}), - ('Filter', {PARAMETERS_KEY: ('Filter On', 'Frequency', 'Filter Type', '', '', '', '', '')}), - ('Mode I & II', {PARAMETERS_KEY: ('I On', 'I Note', 'I Tune', 'I Gain', 'II On', 'II Pitch', 'II Tune', 'II Gain')}), - ('Mode III & IV', {PARAMETERS_KEY: ('III On', 'III Pitch', 'III Tune', 'III Gain', 'IV On', 'IV Pitch', 'IV Tune', 'IV Gain')}), - ('Mode V', {PARAMETERS_KEY: ('V On', 'V Pitch', 'V Tune', 'V Gain', '', '', '', '')}), - ('Mix', {PARAMETERS_KEY: ('I Gain', 'II Gain', 'III Gain', 'IV Gain', 'V Gain', '', '', '')}), - ('Pitch', {PARAMETERS_KEY: ('I Note', 'II Pitch', 'III Pitch', 'IV Pitch', 'V Pitch', '', '', '')}))), - 'Reverb': IndexedDict(((MAIN_KEY, {PARAMETERS_KEY: ('PreDelay', - use('In Filter Freq').if_parameter('In LowCut On').has_value('on').else_use('ER Shape').if_parameter('In HighCut On').has_value('off').else_use('In Filter Freq'), - use('Chorus Amount').if_parameter('Chorus On').has_value('on').else_use('ER Level'), - 'Stereo Image', - 'Room Size', - 'DecayTime', - use('HiShelf Gain').if_parameter('HiShelf On').has_value('on').else_use('Diffuse Level'), - 'Dry/Wet')}), - ('Global', {PARAMETERS_KEY: ('Chorus On', 'Chorus Rate', 'Chorus Amount', 'Quality', 'Freeze On', 'Flat On', 'ER Level', 'Diffuse Level')}), - ('Diffusion Network', {PARAMETERS_KEY: ('HiShelf On', 'HiShelf Freq', 'HiShelf Gain', 'LowShelf On', 'LowShelf Freq', 'LowShelf Gain', 'Density', 'Scale')}), - ('Input/Reflections', {PARAMETERS_KEY: ('In LowCut On', 'In HighCut On', 'In Filter Freq', 'In Filter Width', 'ER Spin On', 'ER Spin Rate', 'ER Spin Amount', 'ER Shape')}))), - 'Saturator': IndexedDict(((MAIN_KEY, {PARAMETERS_KEY: ('Drive', 'Type', 'Color', 'Base', 'Frequency', 'Width', 'Depth', 'Output')}), ('Waveshaper', {PARAMETERS_KEY: ('Type', - use('WS Drive').if_parameter('Type').has_value('Waveshaper'), - use('WS Curve').if_parameter('Type').has_value('Waveshaper'), - use('WS Depth').if_parameter('Type').has_value('Waveshaper'), - use('WS Lin').if_parameter('Type').has_value('Waveshaper'), - use('WS Damp').if_parameter('Type').has_value('Waveshaper'), - use('WS Period').if_parameter('Type').has_value('Waveshaper'), - 'Dry/Wet')}), ('Output', {PARAMETERS_KEY: ('', '', '', '', '', 'Soft Clip', 'Output', 'Dry/Wet')}))), - 'CrossDelay': IndexedDict(((MAIN_KEY, {PARAMETERS_KEY: ('L Delay Mode', - use('L Beat Delay').if_parameter('L Delay Mode').has_value('on').else_use('L Time Delay'), - use('L Beat Swing').if_parameter('L Delay Mode').has_value('on'), - 'R Delay Mode', - use('R Beat Delay').if_parameter('R Delay Mode').has_value('on').else_use('R Time Delay'), - use('R Beat Swing').if_parameter('R Delay Mode').has_value('on').else_use(''), - 'Feedback', - 'Dry/Wet')}), ('Global', {PARAMETERS_KEY: ('', '', '', '', '', 'Link On', 'Feedback', 'Dry/Wet')}))), - 'StereoGain': IndexedDict(((MAIN_KEY, {PARAMETERS_KEY: ('Mute', 'BlockDc', 'PhaseInvertL', 'PhaseInvertR', 'Signal Source', 'Panorama', 'StereoSeparation', 'Gain')}),)), - 'Vinyl': IndexedDict((('Global', {PARAMETERS_KEY: ('Tracing On', 'Tracing Drive', 'Tracing Freq.', 'Tracing Width', 'Pinch On', 'Global Drive', 'Crackle Density', 'Crackle Volume')}), ('Pinch', {PARAMETERS_KEY: ('Pinch On', 'Pinch Soft On', 'Pinch Mono On', 'Pinch Width', 'Pinch Drive', 'Pinch Freq.', 'Crackle Density', 'Crackle Volume')}))), - 'Vocoder': IndexedDict(((MAIN_KEY, {PARAMETERS_KEY: ('Formant Shift', 'Attack Time', 'Release Time', 'Unvoiced Level', 'Gate Threshold', 'Filter Bandwidth', 'Envelope Depth', 'Dry/Wet')}), - ('Carrier', {PARAMETERS_KEY: ('Noise Rate', 'Noise Crackle', 'Lower Pitch Detection', 'Upper Pitch Detection', 'Oscillator Pitch', 'Oscillator Waveform', 'Enhance', '')}), - ('Global', {PARAMETERS_KEY: ('Formant Shift', 'Attack Time', 'Release Time', 'Mono/Stereo', 'Output Level', 'Gate Threshold', 'Envelope Depth', 'Dry/Wet')}), - ('Filters/Voicing', {PARAMETERS_KEY: ('Filter Bandwidth', 'Upper Filter Band', 'Lower Filter Band', 'Precise/Retro', 'Unvoiced Level', 'Unvoiced Sensitivity', 'Unvoiced Speed', 'Enhance')})))} \ No newline at end of file diff --git a/Push2/bank_selection_component.py b/Push2/bank_selection_component.py index fcad4d42..2c129ad3 100644 --- a/Push2/bank_selection_component.py +++ b/Push2/bank_selection_component.py @@ -1,37 +1,44 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/Push2/bank_selection_component.py +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/Push2/bank_selection_component.py +# Compiled at: 2016-05-20 03:43:52 from __future__ import absolute_import, print_function -from ableton.v2.base import NamedTuple, listenable_property, listens, listens_group, liveobj_valid, SlotManager, nop +from ableton.v2.base import NamedTuple, listenable_property, listens, listens_group, liveobj_valid, nop from ableton.v2.control_surface import Component from ableton.v2.control_surface.control import control_list, ButtonControl from pushbase.banking_util import MAIN_KEY from .item_lister_component import ItemListerComponent, ItemProvider -class BankProvider(ItemProvider, SlotManager): +class BankProvider(ItemProvider): - def __init__(self, bank_registry = None, banking_info = None, *a, **k): - raise bank_registry is not None or AssertionError - raise banking_info is not None or AssertionError + def __init__(self, bank_registry=None, banking_info=None, *a, **k): + assert bank_registry is not None + assert banking_info is not None super(BankProvider, self).__init__(*a, **k) self._bank_registry = bank_registry self._banking_info = banking_info self._device = None + self._items = [] self._on_device_bank_changed.subject = bank_registry + return def set_device(self, device): if self._device != device: self._device = device + self._on_device_parameters_changed.subject = self._device + self._items = self._create_items() self.notify_items() self.notify_selected_item() + def _create_items(self): + bank_names = self.internal_bank_names(self._banking_info.device_bank_names(self._device)) + return [ (NamedTuple(name=b), 0) for b in bank_names ] + @property def device(self): return self._device @property def items(self): - nesting_level = 0 - bank_names = self.internal_bank_names(self._banking_info.device_bank_names(self._device)) - return [ (NamedTuple(name=b), nesting_level) for b in bank_names ] + return self._items @property def selected_item(self): @@ -51,6 +58,14 @@ def _on_device_bank_changed(self, device, _): if device == self._device: self.notify_selected_item() + @listens('parameters') + def _on_device_parameters_changed(self): + items = self._create_items() + if self._items != items: + self._items = items + self.select_item(items[-1][0] if items else 0) + self.notify_items() + def internal_bank_names(self, original_bank_names): num_banks = len(original_bank_names) if num_banks > 0: @@ -59,9 +74,10 @@ def internal_bank_names(self, original_bank_names): class EditModeOptionsComponent(Component): - option_buttons = control_list(ButtonControl, color='ItemNavigation.ItemSelected', control_count=8) + color_class_name = 'EditModeOptions' + option_buttons = control_list(ButtonControl, color=color_class_name + '.ItemSelected', control_count=8) - def __init__(self, back_callback = nop, device_options_provider = None, *a, **k): + def __init__(self, back_callback=nop, device_options_provider=None, *a, **k): super(EditModeOptionsComponent, self).__init__(*a, **k) self._device = None self._device_options_provider = device_options_provider @@ -69,11 +85,14 @@ def __init__(self, back_callback = nop, device_options_provider = None, *a, **k) self.__on_device_changed.subject = device_options_provider self.__on_options_changed.subject = device_options_provider self._update_button_feedback() + return def _option_for_button(self, button): options = self.options if len(options) > button.index - 1: return options[button.index - 1] + else: + return None @option_buttons.pressed def option_buttons(self, button): @@ -89,14 +108,11 @@ def option_buttons(self, button): def _set_device(self, device): self._device = device - self.__on_device_name_changed.subject = device self.notify_device() @listenable_property def device(self): - if liveobj_valid(self._device): - return self._device.name - return '' + return self._device @listenable_property def options(self): @@ -108,10 +124,6 @@ def options(self): def __on_device_changed(self): self._update_device() - @listens('name') - def __on_device_name_changed(self): - self.notify_device() - @listens('options') def __on_options_changed(self): self.__on_active_options_changed.replace_subjects(self.options) @@ -127,7 +139,7 @@ def _update_button_feedback(self): if button.index > 0: option = self._option_for_button(button) has_active_option = option and option.active - button.color = 'ItemNavigation.' + ('ItemNotSelected' if has_active_option else 'NoItem') + button.color = self.color_class_name + '.' + ('ItemNotSelected' if has_active_option else 'NoItem') def _update_device(self): self._set_device(self._device_options_provider.device()) @@ -139,9 +151,10 @@ def update(self): class BankSelectionComponent(ItemListerComponent): - __events__ = ('back',) + color_class_name = 'BankSelection' + __events__ = ('back', ) - def __init__(self, bank_registry = None, banking_info = None, device_options_provider = None, *a, **k): + def __init__(self, bank_registry=None, banking_info=None, device_options_provider=None, *a, **k): self._bank_provider = BankProvider(bank_registry=bank_registry, banking_info=banking_info) super(BankSelectionComponent, self).__init__(item_provider=self._bank_provider, *a, **k) self._options = self.register_component(EditModeOptionsComponent(back_callback=self.notify_back, device_options_provider=device_options_provider)) @@ -156,6 +169,7 @@ def set_option_buttons(self, buttons): def set_device(self, item): device = item if item != self._bank_provider.device else None self._bank_provider.set_device(device) + return @property def options(self): diff --git a/Push2/banking_util.py b/Push2/banking_util.py deleted file mode 100644 index bbb3b69a..00000000 --- a/Push2/banking_util.py +++ /dev/null @@ -1,106 +0,0 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/midi-remote-scripts/Push2/banking_util.py -from __future__ import absolute_import -from math import ceil -from copy import deepcopy -from ableton.v2.base import liveobj_valid -from .bank_definitions import BANK_DEFINITIONS, MAIN_KEY -MX_MAIN_BANK_INDEX = -1 -BANK_FORMAT = 'Bank %d' - -def has_bank_count(device): - if liveobj_valid(device): - try: - num_banks = device.get_bank_count() - return num_banks > 0 - except (AttributeError, RuntimeError): - pass - - return False - - -def has_main_bank(device, definitions = BANK_DEFINITIONS): - if has_bank_count(device): - try: - main_bank = device.get_bank_parameters(MX_MAIN_BANK_INDEX) - return bool(main_bank) - except (AttributeError, RuntimeError): - return False - - else: - return MAIN_KEY in definitions.get(device.class_name, {}) - - -def has_bank_names(device, definitions = BANK_DEFINITIONS): - if has_bank_count(device): - try: - name = device.get_bank_name(0) - return bool(name) - except (AttributeError, RuntimeError): - return False - - else: - return bool(definitions.get(device.class_name, {}).keys()) - - -def all_parameters(device): - return list(device.parameters[1:]) if liveobj_valid(device) else [] - - -def device_bank_count(device, bank_size = 8, definition = None, definitions = BANK_DEFINITIONS): - count = 0 - if not (liveobj_valid(device) and definition): - definition = definitions.get(device.class_name, {}) - if has_bank_count(device): - count = device.get_bank_count() + int(has_main_bank(device)) - elif definition.keys(): - count = len(definition.keys()) - else: - count = int(ceil(float(len(all_parameters(device))) / bank_size)) - return count - - -def device_bank_definition(device, definitions = BANK_DEFINITIONS): - original_definition = definitions.get(device.class_name, None) - definition = deepcopy(original_definition) if original_definition is not None else None - return definition - - -def device_bank_names(device, bank_size = 8, definitions = BANK_DEFINITIONS): - names = [] - if liveobj_valid(device): - class_name = device.class_name - if class_name in definitions: - names = definitions[class_name].keys() - elif has_bank_count(device) and has_bank_names(device): - offset = int(has_main_bank(device)) - names = [ device.get_bank_name(index - offset) for index in xrange(device_bank_count(device)) ] - if has_main_bank(device) and not names[0]: - names[0] = MAIN_KEY - else: - bank_count = device_bank_count(device, bank_size=bank_size) - names = [ BANK_FORMAT % (index + 1) for index in xrange(bank_count) ] - return names - - -class BankingInfo(object): - - def __init__(self, bank_definitions = BANK_DEFINITIONS): - self._bank_definitions = bank_definitions - - def has_bank_count(self, device): - return has_bank_count(device) - - def has_main_bank(self, device): - return has_main_bank(device, definitions=self._bank_definitions) - - def has_bank_names(self, device): - return has_bank_names(device, definitions=self._bank_definitions) - - def device_bank_count(self, device, **k): - return device_bank_count(device, definitions=self._bank_definitions, **k) - - def device_bank_definition(self, device): - return device_bank_definition(device, definitions=self._bank_definitions) - - def device_bank_names(self, device, **k): - return device_bank_names(device, definitions=self._bank_definitions, **k) \ No newline at end of file diff --git a/Push2/browser_component.py b/Push2/browser_component.py index d4548b29..e96e79d3 100644 --- a/Push2/browser_component.py +++ b/Push2/browser_component.py @@ -1,17 +1,53 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/Push2/browser_component.py +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/Push2/browser_component.py +# Compiled at: 2016-09-29 19:13:24 from __future__ import absolute_import, print_function from contextlib import contextmanager from itertools import imap from math import ceil import Live from ableton.v2.base import BooleanContext, depends, index_if, lazy_attribute, listenable_property, listens, liveobj_changed, liveobj_valid, nop, task -from ableton.v2.control_surface import Component +from ableton.v2.control_surface import Component, CompoundComponent from ableton.v2.control_surface.control import control_list, ButtonControl, StepEncoderControl, ToggleButtonControl from pushbase.browser_util import filter_type_for_hotswap_target, get_selection_for_new_device +from pushbase.consts import MessageBoxText +from pushbase.live_util import get_position_for_new_track from pushbase.message_box_component import Messenger -from .colors import translate_color_index +from .colors import DISPLAY_BUTTON_SHADE_LEVEL, IndexedColor from .browser_list import BrowserList from .browser_item import BrowserItem, ProxyBrowserItem +NAVIGATION_COLORS = dict(color='Browser.Navigation', disabled_color='Browser.NavigationDisabled') + +class LoadNeighbourOverlayComponent(Component): + __events__ = ('load_next', 'load_previous') + load_next_button = ButtonControl(repeat=False, **NAVIGATION_COLORS) + load_previous_button = ButtonControl(repeat=False, **NAVIGATION_COLORS) + + @load_next_button.pressed + def button(self, button): + self.notify_load_next() + + @load_previous_button.pressed + def button(self, button): + self.notify_load_previous() + + @listenable_property + def can_load_next(self): + return self.load_next_button.enabled + + @can_load_next.setter + def can_load_next(self, can_load_next): + self.load_next_button.enabled = can_load_next + self.notify_can_load_next() + + @listenable_property + def can_load_previous(self): + return self.load_previous_button.enabled + + @can_load_previous.setter + def can_load_previous(self, can_load_previous): + self.load_previous_button.enabled = can_load_previous + self.notify_can_load_previous() + class WrappedLoadableBrowserItem(BrowserItem): @@ -26,35 +62,40 @@ def is_selected(self): else: relation = self._browser.relation_to_hotswap_target(self._contained_item) return relation == Live.Browser.Relation.equal + return class FolderBrowserItem(BrowserItem): - def __init__(self, wrapped_loadable = None, *a, **k): - raise wrapped_loadable is not None or AssertionError + def __init__(self, wrapped_loadable=None, *a, **k): + assert wrapped_loadable is not None super(FolderBrowserItem, self).__init__(*a, **k) self._wrapped_loadable = wrapped_loadable + return @property def is_selected(self): if self._contained_item is None: return self._is_selected - return self._contained_item.is_selected + else: + return self._contained_item.is_selected @lazy_attribute def children(self): - return [self._wrapped_loadable] + list(self.contained_item.children) + return [ + self._wrapped_loadable] + list(self.contained_item.children) class PluginPresetBrowserItem(BrowserItem): - def __init__(self, preset_name = None, preset_index = None, vst_device = None, *a, **k): - raise preset_name is not None or AssertionError - raise preset_index is not None or AssertionError - raise vst_device is not None or AssertionError + def __init__(self, preset_name=None, preset_index=None, vst_device=None, *a, **k): + assert preset_name is not None + assert preset_index is not None + assert vst_device is not None super(PluginPresetBrowserItem, self).__init__(name=(preset_name if preset_name else '' % (preset_index + 1)), is_loadable=True, *a, **k) self.preset_index = preset_index self._vst_device = vst_device + return @property def is_selected(self): @@ -67,35 +108,40 @@ def uri(self): class PluginBrowserItem(BrowserItem): - def __init__(self, vst_device = None, *a, **k): + def __init__(self, vst_device=None, *a, **k): super(PluginBrowserItem, self).__init__(is_loadable=False, is_selected=True, *a, **k) - raise vst_device is not None or AssertionError + assert vst_device is not None self._vst_device = vst_device + return @property def children(self): - return [ PluginPresetBrowserItem(preset_name=preset, preset_index=i, vst_device=self._vst_device) for i, preset in enumerate(self._vst_device.presets) ] + return [ PluginPresetBrowserItem(preset_name=preset, preset_index=i, vst_device=self._vst_device) for i, preset in enumerate(self._vst_device.presets) + ] class CannotFocusListError(Exception): pass -class BrowserComponent(Component, Messenger): +class BrowserComponent(CompoundComponent, Messenger): __events__ = ('loaded', 'close') NUM_ITEMS_PER_COLUMN = 6 NUM_VISIBLE_BROWSER_LISTS = 7 NUM_COLUMNS_IN_EXPANDED_LIST = 3 EXPAND_LIST_TIME = 1.5 REVEAL_PREVIEW_LIST_TIME = 0.2 - navigation_colors = dict(color='Browser.Navigation', disabled_color='Browser.NavigationDisabled') + MIN_TIME = 0.6 + MAX_TIME = 1.4 + MIN_TIME_TEXT_LENGTH = 30 + MAX_TIME_TEXT_LENGTH = 70 up_button = ButtonControl(repeat=True) down_button = ButtonControl(repeat=True) - right_button = ButtonControl(repeat=True, **navigation_colors) - left_button = ButtonControl(repeat=True, **navigation_colors) - back_button = ButtonControl(**navigation_colors) - open_button = ButtonControl(**navigation_colors) - load_button = ButtonControl(**navigation_colors) + right_button = ButtonControl(repeat=True, **NAVIGATION_COLORS) + left_button = ButtonControl(repeat=True, **NAVIGATION_COLORS) + back_button = ButtonControl(**NAVIGATION_COLORS) + open_button = ButtonControl(**NAVIGATION_COLORS) + load_button = ButtonControl(**NAVIGATION_COLORS) close_button = ButtonControl() prehear_button = ToggleButtonControl(toggled_color='Browser.Option', untoggled_color='Browser.OptionDisabled') scroll_encoders = control_list(StepEncoderControl, num_steps=10, control_count=NUM_VISIBLE_BROWSER_LISTS) @@ -107,11 +153,10 @@ class BrowserComponent(Component, Messenger): can_exit = listenable_property.managed(False) context_color_index = listenable_property.managed(-1) context_text = listenable_property.managed('') - load_text = listenable_property.managed('') @depends(commit_model_changes=None, selection=None) - def __init__(self, preferences = dict(), commit_model_changes = None, selection = None, main_modes_ref = None, *a, **k): - raise commit_model_changes is not None or AssertionError + def __init__(self, preferences=dict(), commit_model_changes=None, selection=None, main_modes_ref=None, *a, **k): + assert commit_model_changes is not None super(BrowserComponent, self).__init__(*a, **k) self._lists = [] self._browser = Live.Application.get_application().browser @@ -124,20 +169,22 @@ def __init__(self, preferences = dict(), commit_model_changes = None, selection self._unexpand_with_scroll_encoder = False self._delay_preview_list = BooleanContext() self._selection = selection - self._load_next = False self._main_modes_ref = main_modes_ref if main_modes_ref is not None else nop + self._load_neighbour_overlay = self.register_component(LoadNeighbourOverlayComponent(is_enabled=False)) self._content_filter_type = None self._content_hotswap_target = None self._preview_list_task = self._tasks.add(task.sequence(task.wait(self.REVEAL_PREVIEW_LIST_TIME), task.run(self._replace_preview_list_by_task))).kill() self._update_root_items() self._update_navigation_buttons() - self._update_load_text() self._update_context() self.prehear_button.is_toggled = preferences.setdefault('browser_prehear', True) self._on_selected_track_color_index_changed.subject = self.song.view self._on_selected_track_name_changed.subject = self.song.view self._on_detail_clip_name_changed.subject = self.song.view self._on_hotswap_target_changed.subject = self._browser + self._on_load_next.subject = self._load_neighbour_overlay + self._on_load_previous.subject = self._load_neighbour_overlay + self._on_focused_item_changed.subject = self self.register_slot(self, self.notify_focused_item, 'focused_list_index') def auto_unexpand(): @@ -145,6 +192,7 @@ def auto_unexpand(): self._update_list_offset() self._unexpand_task = self._tasks.add(task.sequence(task.wait(self.EXPAND_LIST_TIME), task.run(auto_unexpand))).kill() + return @up_button.pressed def up_button(self, button): @@ -220,6 +268,8 @@ def scroll_encoders(self, encoder): except CannotFocusListError: pass + return + @scroll_encoders.released def scroll_encoders(self, encoders): self._on_encoder_released() @@ -235,6 +285,8 @@ def scroll_encoders(self, value, encoder): except CannotFocusListError: pass + return + @scroll_focused_encoder.value def scroll_focused_encoder(self, value, encoder): self._on_encoder_value(value) @@ -276,12 +328,13 @@ def _get_list_index_for_encoder(self, encoder): if encoder.index == 0: return self.list_offset return self.list_offset + 1 - index = self.list_offset + encoder.index - if self.focused_list_index + 1 == index and self.focused_list.selected_item.is_loadable: - index = self.focused_list_index - if 0 <= index < len(self._lists): - return index else: + index = self.list_offset + encoder.index + if self.focused_list_index + 1 == index and self.should_widen_focused_item: + index = self.focused_list_index + if 0 <= index < len(self._lists): + return index + return None return None @load_button.pressed @@ -325,10 +378,23 @@ def focused_item(self): def expanded(self): return self._expanded + @property + def load_neighbour_overlay(self): + return self._load_neighbour_overlay + + @listenable_property + def should_widen_focused_item(self): + return self.focused_item.is_loadable and not self.focused_item.is_device + + @property + def context_display_type(self): + return 'custom_button' + def disconnect(self): super(BrowserComponent, self).disconnect() self._lists = [] self._commit_model_changes = None + return @expanded.setter def expanded(self, expanded): @@ -363,15 +429,23 @@ def _on_hotswap_target_changed(self): self._update_root_items() self._update_context() self._update_list_offset() + self._update_load_neighbour_overlay_visibility() + else: + self._load_neighbour_overlay.set_enabled(False) self._current_hotswap_target = self._browser.hotswap_target + @listens('focused_item') + def _on_focused_item_changed(self): + self.notify_should_widen_focused_item() + @property def browse_for_audio_clip(self): main_modes = self._main_modes_ref() if main_modes is None: return False - has_midi_support = self.song.view.selected_track.has_midi_input - return not has_midi_support and 'clip' in main_modes.active_modes + else: + has_midi_support = self.song.view.selected_track.has_midi_input + return not has_midi_support and 'clip' in main_modes.active_modes def _switched_to_empty_pad(self): hotswap_target = self._browser.hotswap_target @@ -379,7 +453,7 @@ def _switched_to_empty_pad(self): was_browsing_pad = isinstance(self._current_hotswap_target, Live.DrumPad.DrumPad) return is_browsing_drumpad and was_browsing_pad and len(hotswap_target.chains) == 0 - def _focus_list_with_index(self, index, crop = True): + def _focus_list_with_index(self, index, crop=True): """ Focus the list with the given index. Raises CannotFocusListError if the operation fails. @@ -387,9 +461,9 @@ def _focus_list_with_index(self, index, crop = True): """ if self._focused_list_index != index: if self._finish_preview_list_task(): - raise index >= len(self._lists) and CannotFocusListError() - if not 0 <= index < len(self._lists): - raise AssertionError + if index >= len(self._lists): + raise CannotFocusListError() + assert 0 <= index < len(self._lists) self._on_focused_selection_changed.subject = None if self._focused_list_index > index and crop: for l in self._lists[self._focused_list_index:]: @@ -405,10 +479,11 @@ def _focus_list_with_index(self, index, crop = True): self._crop_browser_lists(self._focused_list_index + 2) if self._focused_list_index == len(self._lists) - 1: self._replace_preview_list() - self._reset_load_next() + self._load_neighbour_overlay.set_enabled(False) self._update_navigation_buttons() return True - return False + else: + return False @listens('selected_index') def _on_focused_selection_changed(self): @@ -418,34 +493,61 @@ def _on_focused_selection_changed(self): self._replace_preview_list() self._update_navigation_buttons() self._prehear_selected_item() - self._reset_load_next() + self._load_neighbour_overlay.set_enabled(False) self.notify_focused_item() def _get_actual_item(self, item): contained_item = getattr(item, 'contained_item', None) if contained_item is not None: return contained_item - return item + else: + return item + + def _previous_can_be_loaded(self): + return self.focused_list.selected_index > 0 and self.focused_list.items[self.focused_list.selected_index - 1].is_loadable + + def _next_can_be_loaded(self): + items = self.focused_list.items + return self.focused_list.selected_index < len(items) - 1 and items[self.focused_list.selected_index + 1].is_loadable + + @listens('load_next') + def _on_load_next(self): + self.focused_list.selected_index += 1 + self._load_selected_item() + + @listens('load_previous') + def _on_load_previous(self): + self.focused_list.selected_index -= 1 + self._load_selected_item() + + def _update_load_neighbour_overlay_visibility(self): + self._load_neighbour_overlay.set_enabled(liveobj_valid(self._browser.hotswap_target) and (self._next_can_be_loaded() or self._previous_can_be_loaded()) and not self.focused_list.selected_item.is_device) def _load_selected_item(self): focused_list = self.focused_list - if self._load_next: - focused_list.selected_index += 1 - self._load_next = focused_list.selected_index < len(focused_list.items) - 1 and liveobj_valid(self._browser.hotswap_target) - self._update_load_text() + self._update_load_neighbour_overlay_visibility() + self._update_navigation_buttons() item = self._get_actual_item(focused_list.selected_item) - notification_ref = self.show_notification(self._make_notification_text(item)) - self._commit_model_changes() self._load_item(item) self.notify_loaded() - notification = notification_ref() - if notification is not None: - notification.reschedule_after_slow_operation() + + def _show_load_notification(self, item): + notification_text = self._make_notification_text(item) + text_length = len(notification_text) + notification_time = self.MIN_TIME + if text_length > self.MIN_TIME_TEXT_LENGTH: + if text_length > self.MAX_TIME_TEXT_LENGTH: + notification_time = self.MAX_TIME + else: + notification_time = self.MIN_TIME + (self.MAX_TIME - self.MIN_TIME) * float(text_length - self.MIN_TIME_TEXT_LENGTH) / (self.MAX_TIME_TEXT_LENGTH - self.MIN_TIME_TEXT_LENGTH) + self.show_notification(notification_text, notification_time=notification_time) + self._commit_model_changes() def _make_notification_text(self, browser_item): return 'Loading %s' % browser_item.name def _load_item(self, item): + self._show_load_notification(item) if liveobj_valid(self._browser.hotswap_target): if isinstance(item, PluginPresetBrowserItem): self._browser.hotswap_target.selected_preset_index = item.preset_index @@ -456,10 +558,6 @@ def _load_item(self, item): with self._insert_right_of_selected(): self._browser.load_item(item) - def _reset_load_next(self): - self._load_next = False - self._update_load_text() - @contextmanager def _insert_right_of_selected(self): DeviceInsertMode = Live.Track.DeviceInsertMode @@ -482,23 +580,24 @@ def _stop_prehear(self): if self.prehear_button.is_toggled and not self._updating_root_items: self._browser.stop_preview() - def _update_load_text(self): - self.load_text = 'Load Next' if self._load_next else 'Load' - def _update_navigation_buttons(self): focused_list = self.focused_list self.up_button.enabled = focused_list.selected_index > 0 self.down_button.enabled = focused_list.selected_index < len(focused_list.items) - 1 selected_item_loadable = self.focused_list.selected_item.is_loadable - assume_can_enter = self._preview_list_task.is_running and not selected_item_loadable can_exit = self._focused_list_index > 0 + assume_can_enter = self._preview_list_task.is_running and not selected_item_loadable can_enter = self._focused_list_index < len(self._lists) - 1 or assume_can_enter self.back_button.enabled = can_exit self.open_button.enabled = can_enter self.load_button.enabled = selected_item_loadable - context_button_color = translate_color_index(self.context_color_index) if self.context_color_index > -1 else 'Browser.Navigation' + self._load_neighbour_overlay.can_load_previous = self._previous_can_be_loaded() + self._load_neighbour_overlay.can_load_next = self._next_can_be_loaded() + context_button_color = IndexedColor.from_live_index(self.context_color_index, DISPLAY_BUTTON_SHADE_LEVEL) if self.context_color_index > -1 else 'Browser.Navigation' self.load_button.color = context_button_color self.close_button.color = context_button_color + self._load_neighbour_overlay.load_next_button.color = context_button_color + self._load_neighbour_overlay.load_previous_button.color = context_button_color if not self._expanded: self.left_button.enabled = self.back_button.enabled self.right_button.enabled = can_enter or self._can_auto_expand() @@ -527,6 +626,7 @@ def _update_context(self): self.context_text = selected_track.name selected_track_color_index = selected_track.color_index self.context_color_index = selected_track_color_index if selected_track_color_index is not None else -1 + return def _enter_selected_item(self): item_entered = False @@ -589,7 +689,7 @@ def _replace_preview_list(self): enable_wrapping = getattr(selected_item, 'enable_wrapping', True) and self.focused_list.items_wrapped self._append_browser_list(children_iterator=children_iterator, limit=self.num_preview_items, enable_wrapping=enable_wrapping) - def _append_browser_list(self, children_iterator, limit = -1, enable_wrapping = True): + def _append_browser_list(self, children_iterator, limit=-1, enable_wrapping=True): l = BrowserList(item_iterator=children_iterator, item_wrapper=self._wrap_item if enable_wrapping else nop, limit=limit) l.items_wrapped = enable_wrapping self._lists.append(l) @@ -618,6 +718,7 @@ def _content_cache_is_valid(self): def _invalidate_content_cache(self): self._content_hotswap_target = None self._content_filter_type = None + return def _update_content_cache(self): self._content_filter_type = self._browser.filter_type @@ -635,8 +736,9 @@ def _update_root_items(self): self._select_hotswap_target() self._on_focused_selection_changed.subject = self.focused_list self._on_focused_selection_changed() + return - def _select_hotswap_target(self, list_index = 0): + def _select_hotswap_target(self, list_index=0): if list_index < len(self._lists): l = self._lists[list_index] l.access_all = True @@ -661,6 +763,7 @@ def update(self): self._update_root_items() self._update_context() self._update_list_offset() + self._update_load_neighbour_overlay_visibility() self._update_navigation_buttons() self.expanded = False self._update_list_offset() @@ -681,7 +784,7 @@ def _wrap_device_item(self, item): having two actions on an item (open and load). """ wrapped_loadable = WrappedLoadableBrowserItem(name=item.name, is_loadable=True, contained_item=item) - return FolderBrowserItem(name=item.name, contained_item=item, wrapped_loadable=wrapped_loadable) + return FolderBrowserItem(name=item.name, is_loadable=True, is_device=True, contained_item=item, wrapped_loadable=wrapped_loadable, icon='browser_arrowcontent.svg') def _is_hotswap_target_plugin(self, item): return isinstance(self._browser.hotswap_target, Live.PluginDevice.PluginDevice) and isinstance(item, Live.Browser.BrowserItem) and self._browser.relation_to_hotswap_target(item) == Live.Browser.Relation.equal @@ -693,7 +796,7 @@ def _wrap_hotswapped_plugin_item(self, item): class TrackBrowserItem(BrowserItem): filter_type = Live.Browser.FilterType.hotswap_off - def create_track(self, song, index): + def create_track(self, song, selected_track_index): raise NotImplementedError @@ -703,8 +806,8 @@ class MidiTrackBrowserItem(TrackBrowserItem): def __init__(self, *a, **k): super(MidiTrackBrowserItem, self).__init__(name='MIDI track', *a, **k) - def create_track(self, song, index): - song.create_midi_track(index) + def create_track(self, song, selected_track_index): + song.create_midi_track(get_position_for_new_track(song, selected_track_index)) class AudioTrackBrowserItem(TrackBrowserItem): @@ -713,8 +816,8 @@ class AudioTrackBrowserItem(TrackBrowserItem): def __init__(self, *a, **k): super(AudioTrackBrowserItem, self).__init__(name='Audio track', *a, **k) - def create_track(self, song, index): - song.create_audio_track(index) + def create_track(self, song, selected_track_index): + song.create_audio_track(get_position_for_new_track(song, selected_track_index)) class ReturnTrackBrowserItem(TrackBrowserItem): @@ -723,7 +826,7 @@ class ReturnTrackBrowserItem(TrackBrowserItem): def __init__(self, *a, **k): super(ReturnTrackBrowserItem, self).__init__(name='Return track', *a, **k) - def create_track(self, song, index): + def create_track(self, song, selected_track_index): song.create_return_track() @@ -741,7 +844,10 @@ class NewTrackBrowserComponent(BrowserComponent): def __init__(self, *a, **k): self._content = [] - self._track_type_items = [MidiTrackBrowserItem(children=self._content), AudioTrackBrowserItem(children=self._content), ReturnTrackBrowserItem(children=self._content)] + self._track_type_items = [ + MidiTrackBrowserItem(children=self._content), + AudioTrackBrowserItem(children=self._content), + ReturnTrackBrowserItem(children=self._content)] super(NewTrackBrowserComponent, self).__init__(*a, **k) if self.is_enabled(): self._update_filter_type() @@ -758,9 +864,14 @@ def disconnect(self): def browse_for_audio_clip(self): return False + @property + def context_display_type(self): + return 'cancel_button' + def _update_root_content(self): real_root_items = super(NewTrackBrowserComponent, self)._make_root_browser_items() - self._content[:] = [DefaultTrackBrowserItem()] + real_root_items + self._content[:] = [ + DefaultTrackBrowserItem()] + real_root_items def _update_root_items(self): self._set_filter_type(self._track_type_items[0].filter_type) @@ -776,21 +887,31 @@ def _set_filter_type(self, filter_type): self._update_root_content() def _update_context(self): - self.context_text = 'Close' + pass def _load_item(self, item): - self._selected_track_item().create_track(self.song, self._selected_track_index()) - if not isinstance(item, DefaultTrackBrowserItem): - super(NewTrackBrowserComponent, self)._load_item(item) + try: + self._selected_track_item().create_track(self.song, self._selected_track_index()) + if isinstance(item, DefaultTrackBrowserItem): + self._show_load_notification(item) + else: + super(NewTrackBrowserComponent, self)._load_item(item) + except Live.Base.LimitationError: + self.show_notification(MessageBoxText.TRACK_LIMIT_REACHED) + except RuntimeError: + self.show_notification(MessageBoxText.MAX_RETURN_TRACKS_REACHED) def _make_notification_text(self, browser_item): - return '%s loaded in track %i' % (browser_item.name, self._selected_track_index() + 1) + if isinstance(browser_item, DefaultTrackBrowserItem): + return 'Default track created' + new_track_position = self._selected_track_index() + 1 + return '%s loaded in track %i' % (browser_item.name, new_track_position) def _selected_track_index(self): song = self.song selected_track = self._selection.selected_track if selected_track in song.tracks: - return list(song.tracks).index(selected_track) + 1 + return list(song.tracks).index(selected_track) return -1 def _selected_track_item(self): @@ -840,20 +961,27 @@ def make_root_browser_items(browser, filter_type): legacy_libraries = wrap_items(list(browser.legacy_libraries), 'browser_8folder.svg') current_project = wrap_item(browser.current_project, 'browser_currentproject.svg') if filter_type == Live.Browser.FilterType.samples: - categories = [packs] + legacy_libraries + [current_project] + categories = [ + packs] + legacy_libraries + [current_project] else: - common_items = [wrap_item(browser.max_for_live, 'browser_max.svg'), wrap_item(browser.plugins, 'browser_plugins.svg'), packs] + legacy_libraries + [current_project] + common_items = [ + wrap_item(browser.max_for_live, 'browser_max.svg'), wrap_item(browser.plugins, 'browser_plugins.svg'), packs] + legacy_libraries + [current_project] if filter_type == Live.Browser.FilterType.audio_effect_hotswap: - categories = [audio_effects] + common_items + categories = [ + audio_effects] + common_items elif filter_type == Live.Browser.FilterType.midi_effect_hotswap: - categories = [midi_effects] + common_items + categories = [ + midi_effects] + common_items elif filter_type == Live.Browser.FilterType.instrument_hotswap: - categories = [sounds, drums, instruments] + common_items + categories = [ + sounds, drums, instruments] + common_items else: - categories = [sounds, + categories = [ + sounds, drums, instruments, audio_effects, midi_effects] + common_items user_files = UserFilesBrowserItem(browser, name='User Files', icon='browser_userfiles.svg') - return [user_files] + categories \ No newline at end of file + return [ + user_files] + categories \ No newline at end of file diff --git a/Push2/browser_item.py b/Push2/browser_item.py index f9ec5278..bc6ed9a9 100644 --- a/Push2/browser_item.py +++ b/Push2/browser_item.py @@ -1,18 +1,21 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/Push2/browser_item.py +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/Push2/browser_item.py +# Compiled at: 2016-05-20 03:43:52 from __future__ import absolute_import, print_function from ableton.v2.base import Proxy class BrowserItem(object): - def __init__(self, name = '', icon = '', children = None, is_loadable = False, is_selected = False, contained_item = None, enable_wrapping = True, *a, **k): + def __init__(self, name='', icon='', children=None, is_loadable=False, is_selected=False, is_device=False, contained_item=None, enable_wrapping=True, *a, **k): super(BrowserItem, self).__init__(*a, **k) self._name = name self._icon = icon self._children = [] if children is None else children self._is_loadable = is_loadable self._is_selected = is_selected + self._is_device = is_device self._contained_item = contained_item self._enable_wrapping = enable_wrapping + return @property def name(self): @@ -44,7 +47,7 @@ def contained_item(self): @property def is_device(self): - return False + return self._is_device @property def enable_wrapping(self): @@ -54,12 +57,13 @@ def enable_wrapping(self): def uri(self): if self._contained_item is not None: return self._contained_item.uri - return self._name + else: + return self._name class ProxyBrowserItem(Proxy): - def __init__(self, enable_wrapping = True, icon = '', *a, **k): + def __init__(self, enable_wrapping=True, icon='', *a, **k): super(ProxyBrowserItem, self).__init__(*a, **k) self._enable_wrapping = enable_wrapping self._icon = icon diff --git a/Push2/browser_list.py b/Push2/browser_list.py index b55da452..bd23c2a1 100644 --- a/Push2/browser_list.py +++ b/Push2/browser_list.py @@ -1,15 +1,16 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/Push2/browser_list.py +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/Push2/browser_list.py +# Compiled at: 2016-05-20 03:43:52 from __future__ import absolute_import, print_function import Live from itertools import islice -from ableton.v2.base import Subject, listenable_property, clamp, nop +from ableton.v2.base import EventObject, listenable_property, clamp, nop from .model.uniqueid import UniqueIdMixin -class BrowserList(Subject, UniqueIdMixin): +class BrowserList(EventObject, UniqueIdMixin): LAZY_ACCESS_COUNT = 1000 LAZY_ACCESS_THRESHOLD = LAZY_ACCESS_COUNT - 100 - def __init__(self, item_iterator = None, item_wrapper = nop, limit = -1, *a, **k): + def __init__(self, item_iterator=None, item_wrapper=nop, limit=-1, *a, **k): super(BrowserList, self).__init__(*a, **k) self._selected_index = -1 self._item_iterator = item_iterator @@ -18,7 +19,7 @@ def __init__(self, item_iterator = None, item_wrapper = nop, limit = -1, *a, **k self._access_all = False self._items = [] self._update_items() - raise self.LAZY_ACCESS_COUNT > self.LAZY_ACCESS_THRESHOLD or AssertionError + assert self.LAZY_ACCESS_COUNT > self.LAZY_ACCESS_THRESHOLD def _get_limit(self): return self._limit @@ -70,7 +71,8 @@ def _update_items(self): def selected_item(self): if self.selected_index == -1: return None - return self.items[self.selected_index] + else: + return self.items[self.selected_index] @listenable_property def selected_index(self): @@ -78,15 +80,15 @@ def selected_index(self): @selected_index.setter def selected_index(self, value): - if not (value != self._selected_index and (value == -1 or self._limit == -1)): - raise AssertionError - num_children = len(self._items) - if value < -1 or value >= num_children: - raise IndexError('Index %i must be in [-1..%i]' % (value, num_children - 1)) - self._selected_index = value - self.notify_selected_index() - if self._selected_index >= self.LAZY_ACCESS_THRESHOLD and not self._access_all: - self.access_all = True + if value != self._selected_index: + if not value == -1: + assert self._limit == -1 + num_children = len(self._items) + if value < -1 or value >= num_children: + raise IndexError('Index %i must be in [-1..%i]' % (value, num_children - 1)) + self._selected_index = value + self.notify_selected_index() + self.access_all = self._selected_index >= self.LAZY_ACCESS_THRESHOLD and not self._access_all and True def select_index_with_offset(self, offset): self.selected_index = clamp(self._selected_index + offset, 0, len(self._items) - 1) \ No newline at end of file diff --git a/Push2/browser_modes.py b/Push2/browser_modes.py index 79403e66..c128a177 100644 --- a/Push2/browser_modes.py +++ b/Push2/browser_modes.py @@ -1,4 +1,5 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/Push2/browser_modes.py +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/Push2/browser_modes.py +# Compiled at: 2016-05-20 03:43:52 from __future__ import absolute_import, print_function import Live from ableton.v2.base import depends, liveobj_valid @@ -37,10 +38,11 @@ def enter_mode(self): class BrowseModeBase(Mode): - def __init__(self, component_mode = None, *a, **k): - raise component_mode is not None or AssertionError + def __init__(self, component_mode=None, *a, **k): + assert component_mode is not None super(BrowseModeBase, self).__init__() self._component_mode = component_mode + return def enter_mode(self): self._component_mode.enter_mode() @@ -51,25 +53,34 @@ def leave_mode(self): class HotswapBrowseMode(BrowseModeBase): - def __init__(self, application, *a, **k): + def __init__(self, application, drum_group_component, *a, **k): super(HotswapBrowseMode, self).__init__(*a, **k) self._hotswap_mode = BrowserHotswapMode(application=application) self._in_hotswap_mode = False + self._drum_group_component = drum_group_component def leave_mode(self): super(HotswapBrowseMode, self).leave_mode() if self._in_hotswap_mode: self._hotswap_mode.leave_mode() + self._drum_group_component.hotswap_indication_mode = None + return def _enter_hotswap_mode(self): self._hotswap_mode.enter_mode() self._in_hotswap_mode = True + hotswap_target = self._browser.hotswap_target + if liveobj_valid(hotswap_target): + if isinstance(hotswap_target, Live.DrumPad.DrumPad): + self._drum_group_component.hotswap_indication_mode = 'current_pad' + elif isinstance(hotswap_target, Live.RackDevice.RackDevice) and hotswap_target.can_have_drum_pads and hotswap_target == self._drum_group_component.drum_group_device: + self._drum_group_component.hotswap_indication_mode = 'all_pads' class AddDeviceMode(HotswapBrowseMode): @depends(selection=None) - def __init__(self, song, browser, selection = None, *a, **k): + def __init__(self, song, browser, selection=None, *a, **k): super(AddDeviceMode, self).__init__(*a, **k) self._song = song self._browser = browser @@ -83,6 +94,7 @@ def enter_mode(self): self._browser.hotswap_target = None self._browser.filter_type = get_filter_type_for_track(self._song) super(AddDeviceMode, self).enter_mode() + return class AddTrackMode(BrowseModeBase): @@ -94,6 +106,7 @@ def __init__(self, browser, *a, **k): def enter_mode(self): self._browser.hotswap_target = None super(AddTrackMode, self).enter_mode() + return class BrowseMode(HotswapBrowseMode): diff --git a/Push2/chain_selection_component.py b/Push2/chain_selection_component.py index ddf77344..52646bd3 100644 --- a/Push2/chain_selection_component.py +++ b/Push2/chain_selection_component.py @@ -1,13 +1,17 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/Push2/chain_selection_component.py +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/Push2/chain_selection_component.py +# Compiled at: 2016-09-29 19:13:24 from __future__ import absolute_import, print_function -from ableton.v2.base import SlotManager, listens, liveobj_valid +from itertools import count +from ableton.v2.base import listens, listens_group, liveobj_valid +from .colors import DISPLAY_BUTTON_SHADE_LEVEL, IndexedColor from .item_lister_component import ItemListerComponent, ItemProvider -class ChainProvider(SlotManager, ItemProvider): +class ChainProvider(ItemProvider): def __init__(self, *a, **k): super(ChainProvider, self).__init__(*a, **k) self._rack = None + return def set_rack(self, rack): if rack != self._rack: @@ -17,16 +21,25 @@ def set_rack(self, rack): self.__on_selected_chain_changed.subject = rack_view self.notify_items() self.notify_selected_item() + return @property def items(self): chains = self._rack.chains if liveobj_valid(self._rack) else [] return [ (chain, 0) for chain in chains ] + @property + def chains(self): + if liveobj_valid(self._rack): + return self._rack.chains + return [] + @property def selected_item(self): if liveobj_valid(self._rack): return self._rack.view.selected_chain + else: + return None def select_chain(self, chain): self._rack.view.selected_chain = chain @@ -46,10 +59,28 @@ def __init__(self, *a, **k): self._chain_parent = ChainProvider() super(ChainSelectionComponent, self).__init__(item_provider=self._chain_parent, *a, **k) self.register_disconnectable(self._chain_parent) + self.__on_items_changed.subject = self + self.__on_items_changed() def _on_select_button_pressed(self, button): self._chain_parent.select_chain(self.items[button.index].item) + def _color_for_button(self, button_index, is_selected): + if is_selected: + return self.color_class_name + '.ItemSelected' + else: + chain_color = self._chain_parent.chains[button_index].color_index + return IndexedColor.from_live_index(chain_color, DISPLAY_BUTTON_SHADE_LEVEL) + def set_parent(self, parent): - raise parent is None or parent.can_have_chains or AssertionError - self._chain_parent.set_rack(parent) \ No newline at end of file + assert parent is None or parent.can_have_chains + self._chain_parent.set_rack(parent) + return + + @listens('items') + def __on_items_changed(self): + self.__on_chain_color_index_changed.replace_subjects(self._chain_parent.chains, identifiers=count()) + + @listens_group('color_index') + def __on_chain_color_index_changed(self, chain_index): + self.select_buttons[chain_index].color = self._color_for_button(chain_index, self._items_equal(self.items[chain_index], self._item_provider.selected_item)) \ No newline at end of file diff --git a/Push2/clip_control.py b/Push2/clip_control.py index d95cb99d..74b85214 100644 --- a/Push2/clip_control.py +++ b/Push2/clip_control.py @@ -1,15 +1,15 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/Push2/clip_control.py +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/Push2/clip_control.py +# Compiled at: 2016-09-29 19:13:24 from __future__ import absolute_import, print_function from ableton.v2.base import listens, liveobj_valid, listenable_property from ableton.v2.control_surface import CompoundComponent from ableton.v2.control_surface.control import EncoderControl, ToggleButtonControl -from pushbase.clip_control_component import convert_length_to_bars_beats_sixteenths, convert_time_to_bars_beats_sixteenths, LoopSettingsControllerComponent as LoopSettingsControllerComponentBase, AudioClipSettingsControllerComponent as AudioClipSettingsControllerComponentBase, ONE_YEAR_AT_120BPM_IN_BEATS, WARP_MODE_NAMES +from pushbase.clip_control_component import convert_beat_length_to_bars_beats_sixteenths, convert_beat_time_to_bars_beats_sixteenths, LoopSettingsControllerComponent as LoopSettingsControllerComponentBase, AudioClipSettingsControllerComponent as AudioClipSettingsControllerComponentBase, ONE_YEAR_AT_120BPM_IN_BEATS, WARP_MODE_NAMES from pushbase.internal_parameter import WrappingParameter from pushbase.mapped_control import MappedControl from .clip_decoration import ClipDecoratorFactory from .decoration import find_decorated_object from .real_time_channel import RealTimeDataComponent -from .simpler_zoom import ZoomHandling PARAMETERS_LOOPED = ('Loop position', 'Loop length', 'Start offset') PARAMETERS_NOT_LOOPED = ('Start', 'End') PARAMETERS_AUDIO = ('Warp', 'Transpose', 'Detune', 'Gain') @@ -18,36 +18,47 @@ class LoopSetting(WrappingParameter): min = -ONE_YEAR_AT_120BPM_IN_BEATS max = ONE_YEAR_AT_120BPM_IN_BEATS - def __init__(self, use_length_conversion = False, *a, **k): + def __init__(self, use_length_conversion=False, *a, **k): super(LoopSetting, self).__init__(*a, **k) - self._conversion = convert_length_to_bars_beats_sixteenths if use_length_conversion else convert_time_to_bars_beats_sixteenths - self.recording = False + assert self.canonical_parent is not None + self._conversion = convert_beat_length_to_bars_beats_sixteenths if use_length_conversion else convert_beat_time_to_bars_beats_sixteenths + self._recording = False self.set_property_host(self._parent) + self.__on_clip_changed.subject = self.canonical_parent + self.__on_clip_changed() + return @property - def display_value(self): - if not self.recording: - return unicode(self._conversion(self._get_property_value())) - return unicode('...') + def recording(self): + return self._recording + @recording.setter + def recording(self, value): + self._recording = value + self.notify_value() -class ClipZoomHandling(ZoomHandling): + @listens('clip') + def __on_clip_changed(self): + self.__on_signature_numerator_changed.subject = self.canonical_parent.clip + self.__on_signature_denominator_changed.subject = self.canonical_parent.clip - def _set_zoom_parameter(self): - self._zoom_parameter = getattr(self._parameter_host, '_zoom_parameter', None) + @listens('signature_numerator') + def __on_signature_numerator_changed(self): + self.notify_value() - def set_parameter_host(self, parameter_host): - self._parameter_host = parameter_host - self._set_zoom_parameter() - if self._zoom_parameter: - self._zoom_parameter.set_scaling_functions(self._zoom_to_internal, self._internal_to_zoom) - self._on_zoom_changed.subject = self._zoom_parameter + @listens('signature_denominator') + def __on_signature_denominator_changed(self): + self.notify_value() @property - def max_zoom(self): - clip = self._parameter_host - length = float(clip.length if liveobj_valid(clip) and clip.length > 0 else self.SCREEN_WIDTH) - return float(length / self.SCREEN_WIDTH) + def display_value(self): + if not liveobj_valid(self.canonical_parent.clip): + return unicode('-') + if self.recording: + return unicode('...') + return unicode(self._conversion(( + self.canonical_parent.clip.signature_numerator, + self.canonical_parent.clip.signature_denominator), self._get_property_value())) class LoopSettingsControllerComponent(LoopSettingsControllerComponentBase): @@ -56,22 +67,20 @@ class LoopSettingsControllerComponent(LoopSettingsControllerComponentBase): zoom_touch_encoder = EncoderControl() loop_button = ToggleButtonControl(toggled_color='Clip.Option', untoggled_color='Clip.OptionDisabled') - def __init__(self, zoom_handler = None, *a, **k): + def __init__(self, *a, **k): super(LoopSettingsControllerComponent, self).__init__(*a, **k) - self._looping_settings = [LoopSetting(name=PARAMETERS_LOOPED[0], parent=self._loop_model, source_property='position'), LoopSetting(name=PARAMETERS_LOOPED[1], parent=self._loop_model, use_length_conversion=True, source_property='loop_length'), LoopSetting(name=PARAMETERS_LOOPED[2], parent=self._loop_model, source_property='start_marker')] - self._non_looping_settings = [LoopSetting(name=PARAMETERS_NOT_LOOPED[0], parent=self._loop_model, source_property='loop_start'), LoopSetting(name=PARAMETERS_NOT_LOOPED[1], parent=self._loop_model, source_property='loop_end')] + self._looping_settings = [ + LoopSetting(name=PARAMETERS_LOOPED[0], parent=self._loop_model, source_property='position'), + LoopSetting(name=PARAMETERS_LOOPED[1], parent=self._loop_model, use_length_conversion=True, source_property='loop_length'), + LoopSetting(name=PARAMETERS_LOOPED[2], parent=self._loop_model, source_property='start_marker')] + self._non_looping_settings = [ + LoopSetting(name=PARAMETERS_NOT_LOOPED[0], parent=self._loop_model, source_property='loop_start'), + LoopSetting(name=PARAMETERS_NOT_LOOPED[1], parent=self._loop_model, source_property='loop_end')] for setting in self._looping_settings + self._non_looping_settings: self.register_disconnectable(setting) - self._zoom_handler = self.register_disconnectable(zoom_handler or ClipZoomHandling()) - self._processed_zoom_requests = 0 self.__on_looping_changed.subject = self._loop_model self.__on_looping_changed() - self.__on_loop_position_value_changed.subject = self._looping_settings[0] - self.__on_loop_length_value_changed.subject = self._looping_settings[1] - self.__on_start_offset_value_changed.subject = self._looping_settings[2] - self.__on_start_value_changed.subject = self._non_looping_settings[0] - self.__on_end_value_changed.subject = self._non_looping_settings[1] @loop_button.toggled def loop_button(self, toggled, button): @@ -96,21 +105,28 @@ def loop_parameters(self): def zoom(self): if liveobj_valid(self.clip): return getattr(self.clip, 'zoom', None) - - @listenable_property - def processed_zoom_requests(self): - return self._processed_zoom_requests + else: + return None @listenable_property def waveform_navigation(self): if liveobj_valid(self.clip): return getattr(self.clip, 'waveform_navigation', None) + else: + return None @listens('is_recording') def __on_is_recording_changed(self): - recording = False - if liveobj_valid(self._loop_model.clip): - recording = self._loop_model.clip.is_recording + self._update_recording_state() + + @listens('is_overdubbing') + def __on_is_overdubbing_changed(self): + self._update_recording_state() + + def _update_recording_state(self): + clip = self._loop_model.clip + if liveobj_valid(clip): + recording = clip.is_recording and not clip.is_overdubbing self._looping_settings[1].recording = recording self._non_looping_settings[1].recording = recording @@ -128,69 +144,53 @@ def _on_clip_changed(self): self.waveform_navigation.reset_focus_and_animation() self._update_and_notify() self.__on_is_recording_changed.subject = self._loop_model.clip - self.__on_is_recording_changed() - self._zoom_handler.set_parameter_host(self._loop_model.clip) + self.__on_is_overdubbing_changed.subject = self._loop_model.clip + self._update_recording_state() self._connect_encoder() self.notify_waveform_navigation() - - @listens('value') - def __on_loop_position_value_changed(self): - if self.waveform_navigation is not None and self._loop_model.looping: - self.waveform_navigation.change_object(self.waveform_navigation.loop_start_focus) - - @listens('value') - def __on_loop_length_value_changed(self): - if self.waveform_navigation is not None and self._loop_model.looping: - self.waveform_navigation.change_object(self.waveform_navigation.loop_end_focus) - - @listens('value') - def __on_start_offset_value_changed(self): - if self.waveform_navigation is not None and self._loop_model.looping: - self.waveform_navigation.change_object(self.waveform_navigation.start_marker_focus) - - @listens('value') - def __on_start_value_changed(self): - if self.waveform_navigation is not None and not self._loop_model.looping: - self.waveform_navigation.change_object(self.waveform_navigation.start_marker_focus) - - @listens('value') - def __on_end_value_changed(self): - if self.waveform_navigation is not None and not self._loop_model.looping: - self.waveform_navigation.change_object(self.waveform_navigation.loop_end_focus) + return def _on_clip_start_marker_touched(self): if self.waveform_navigation is not None: self.waveform_navigation.touch_object(self.waveform_navigation.start_marker_focus) + return def _on_clip_position_touched(self): if self.waveform_navigation is not None: self.waveform_navigation.touch_object(self.waveform_navigation.loop_start_focus) + return def _on_clip_end_touched(self): if self.waveform_navigation is not None: self.waveform_navigation.touch_object(self.waveform_navigation.loop_end_focus) + return def _on_clip_start_marker_released(self): if self.waveform_navigation is not None: self.waveform_navigation.release_object(self.waveform_navigation.start_marker_focus) + return def _on_clip_position_released(self): if self.waveform_navigation is not None: self.waveform_navigation.release_object(self.waveform_navigation.loop_start_focus) + return def _on_clip_end_released(self): if self.waveform_navigation is not None: self.waveform_navigation.release_object(self.waveform_navigation.loop_end_focus) + return @zoom_touch_encoder.touched def zoom_touch_encoder(self, encoder): if self.waveform_navigation is not None: self.waveform_navigation.touch_object(self.waveform_navigation.zoom_focus) + return @zoom_touch_encoder.released def zoom_touch_encoder(self, encoder): if self.waveform_navigation is not None: self.waveform_navigation.release_object(self.waveform_navigation.zoom_focus) + return def _update_and_notify(self): self._update_loop_button() @@ -206,11 +206,6 @@ def set_zoom_encoder(self, encoder): self.zoom_touch_encoder.set_control_element(encoder) self._connect_encoder() - def request_zoom(self, zoom_factor): - self._zoom_handler.request_zoom(zoom_factor) - self._processed_zoom_requests += 1 - self.notify_processed_zoom_requests() - class GainSetting(WrappingParameter): @@ -274,7 +269,8 @@ class AudioClipSettingsControllerComponent(AudioClipSettingsControllerComponentB def __init__(self, *a, **k): super(AudioClipSettingsControllerComponent, self).__init__(*a, **k) - self._audio_clip_parameters = [WarpSetting(name=PARAMETERS_AUDIO[0], parent=self._audio_clip_model, source_property='warp_mode'), + self._audio_clip_parameters = [ + WarpSetting(name=PARAMETERS_AUDIO[0], parent=self._audio_clip_model, source_property='warp_mode'), PitchSetting(name=PARAMETERS_AUDIO[1], parent=self._audio_clip_model, source_property='pitch_coarse', min_value=-49.0, max_value=49.0, unit='st'), PitchSetting(name=PARAMETERS_AUDIO[2], parent=self._audio_clip_model, source_property='pitch_fine', min_value=-51.0, max_value=51.0, unit='ct'), GainSetting(name=PARAMETERS_AUDIO[3], parent=self._audio_clip_model, source_property='gain')] @@ -292,6 +288,7 @@ def disconnect(self): super(AudioClipSettingsControllerComponent, self).disconnect() self._playhead_real_time_data.set_data(None) self._waveform_real_time_data.set_data(None) + return @property def audio_parameters(self): @@ -347,23 +344,37 @@ def __on_file_path_changed(self): class ClipControlComponent(CompoundComponent): - __events__ = ('clip',) + __events__ = ('clip', ) - def __init__(self, loop_controller = None, audio_clip_controller = None, mode_selector = None, decorator_factory = None, *a, **k): - raise loop_controller is not None or AssertionError - raise audio_clip_controller is not None or AssertionError - raise mode_selector is not None or AssertionError + def __init__(self, loop_controller=None, audio_clip_controller=None, mode_selector=None, decorator_factory=None, *a, **k): + assert loop_controller is not None + assert audio_clip_controller is not None + assert mode_selector is not None super(ClipControlComponent, self).__init__(*a, **k) self._loop_controller = self.register_component(loop_controller) self._audio_clip_controller = self.register_component(audio_clip_controller) self._mode_selector = self.register_component(mode_selector) self._decorator_factory = decorator_factory or ClipDecoratorFactory() + self.__on_selected_scene_changed.subject = self.song.view + self.__on_selected_track_changed.subject = self.song.view self.__on_selected_clip_changed.subject = self.song.view - self.__on_selected_clip_changed() + self.__on_has_clip_changed.subject = self.song.view.highlighted_clip_slot + self._update_controller() + return + + @listens('selected_scene') + def __on_selected_scene_changed(self): + self._update_controller() + + @listens('selected_track') + def __on_selected_track_changed(self): + self._update_controller() @listens('detail_clip') def __on_selected_clip_changed(self): - self._update_controller() + clip = self.song.view.detail_clip + if not liveobj_valid(clip) or clip.is_arrangement_clip: + self._update_controller() def on_enabled_changed(self): super(ClipControlComponent, self).on_enabled_changed() @@ -372,13 +383,19 @@ def on_enabled_changed(self): def _decorate_clip(self, clip): return find_decorated_object(clip, self._decorator_factory) or self._decorator_factory.decorate(clip) + @listens('has_clip') + def __on_has_clip_changed(self): + self._update_controller() + def _update_controller(self): if self.is_enabled(): clip = self.song.view.detail_clip self._update_selected_mode(clip) self._loop_controller.clip = self._decorate_clip(clip) if self._mode_selector.selected_mode == 'audio' else clip self._audio_clip_controller.clip = clip if liveobj_valid(clip) and clip.is_audio_clip else None + self.__on_has_clip_changed.subject = self.song.view.highlighted_clip_slot self.notify_clip() + return def _update_selected_mode(self, clip): if liveobj_valid(clip): diff --git a/Push2/clip_decoration.py b/Push2/clip_decoration.py index fe48b863..7b003022 100644 --- a/Push2/clip_decoration.py +++ b/Push2/clip_decoration.py @@ -1,25 +1,146 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/Push2/clip_decoration.py +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/Push2/clip_decoration.py +# Compiled at: 2016-05-20 03:43:52 from __future__ import absolute_import, print_function -from ableton.v2.base import liveobj_valid, SlotManager, Subject +from ableton.v2.base import liveobj_valid, listenable_property, listens, EventObject from pushbase.decoration import DecoratorFactory, LiveObjectDecorator from pushbase.internal_parameter import InternalParameter from .decoration import find_decorated_object from .waveform_navigation import AudioClipWaveformNavigation +class ClipPositions(EventObject): + __events__ = ('is_recording', 'warp_markers', 'before_update_all', 'after_update_all') + start = listenable_property.managed(0.0) + end = listenable_property.managed(0.0) + start_marker = listenable_property.managed(0.0) + end_marker = listenable_property.managed(0.0) + loop_start = listenable_property.managed(0.0) + loop_end = listenable_property.managed(0.0) + loop_length = listenable_property.managed(0.0) + use_beat_time = listenable_property.managed(False) + + def __init__(self, clip=None, *a, **k): + assert clip is not None + assert clip.is_audio_clip + super(ClipPositions, self).__init__(*a, **k) + self._clip = clip + self._looping = self._clip.looping + self.__on_is_recording_changed.subject = clip + self.__on_warping_changed.subject = clip + self.__on_warp_markers_changed.subject = clip + self.__on_looping_changed.subject = clip + self.__on_start_marker_changed.subject = clip + self.__on_end_marker_changed.subject = clip + self.__on_loop_start_changed.subject = clip + self.__on_loop_end_changed.subject = clip + self.update_all() + return + + def _convert_to_desired_unit(self, beat_time_or_seconds): + """ + This converts the given beat time or seconds unit to the desired unit. + - If the input unit is beat time, we are warped and don't need to do + anything. + - If the input time is seconds, we want to have sample time and need to + convert. + """ + if not self._clip.warping: + beat_time_or_seconds = self._clip.seconds_to_sample_time(beat_time_or_seconds) + return beat_time_or_seconds + + @listens('start_marker') + def __on_start_marker_changed(self): + if not self._process_looping_update(): + self.start_marker = self._convert_to_desired_unit(self._clip.start_marker) + + @listens('end_marker') + def __on_end_marker_changed(self): + if not self._process_looping_update(): + self.end_marker = self._convert_to_desired_unit(self._clip.end_marker) + + @listens('loop_start') + def __on_loop_start_changed(self): + if not self._process_looping_update(): + self.loop_start = self._convert_to_desired_unit(self._clip.loop_start) + self._update_loop_length() + + @listens('loop_end') + def __on_loop_end_changed(self): + if not self._process_looping_update(): + self.loop_end = self._convert_to_desired_unit(self._clip.loop_end) + self._update_loop_length() + + @listens('is_recording') + def __on_is_recording_changed(self): + self._update_start_end() + self.notify_is_recording() + + @listens('warp_markers') + def __on_warp_markers_changed(self): + self.update_all() + self.notify_warp_markers() + + @listens('looping') + def __on_looping_changed(self): + self.update_all() + + @listens('warping') + def __on_warping_changed(self): + self.update_all() + + def _process_looping_update(self): + """ + Changing the looping setting is considered a transaction and will update + all parameters. + This method should be called, before updating any position parameter. + Returns True, in case a looping change has been processed. + """ + looping = self._clip.looping + if looping != self._looping: + self._looping = looping + self.update_all() + return True + return False + + def _update_loop_length(self): + self.loop_length = self._convert_to_desired_unit(self._clip.loop_end) - self._convert_to_desired_unit(self._clip.loop_start) + + def _update_start_end(self): + if self._clip.warping: + self.start = self._clip.sample_to_beat_time(0) + self.end = self._clip.sample_to_beat_time(self._clip.sample_length) + else: + self.start = 0 + self.end = self._clip.sample_length + + def update_all(self): + self.notify_before_update_all() + self._update_start_end() + self.__on_start_marker_changed() + self.__on_end_marker_changed() + self.__on_loop_start_changed() + self.__on_loop_end_changed() + self.use_beat_time = self._clip.warping + self.notify_after_update_all() + + class ZoomParameter(AudioClipWaveformNavigation, InternalParameter): pass -class ClipDecoration(Subject, SlotManager, LiveObjectDecorator): - __events__ = ('zoom',) +class ClipDecoration(EventObject, LiveObjectDecorator): + __events__ = ('zoom', ) def __init__(self, *a, **k): super(ClipDecoration, self).__init__(*a, **k) - waveform_length = max(self._live_object.view.sample_length, 1) - self._zoom_parameter = ZoomParameter(name='Zoom', parent=self._live_object, waveform_length=waveform_length, clip=self) + self._positions = self.register_disconnectable(ClipPositions(self)) + self._zoom_parameter = ZoomParameter(name='Zoom', parent=self._live_object, clip=self) self._zoom_parameter.focus_object(self._zoom_parameter.start_marker_focus) self.register_disconnectable(self._zoom_parameter) + @property + def positions(self): + return self._positions + @property def zoom(self): return self._zoom_parameter @@ -39,13 +160,13 @@ def _should_be_decorated(cls, clip): class ClipDecoratedPropertiesCopier(object): - def __init__(self, target_clip = None, destination_clip = None, decorator_factory = None): - self._target_clip = target_clip + def __init__(self, source_clip=None, destination_clip=None, decorator_factory=None): + self._source_clip = source_clip self._destination_clip = destination_clip self._decorator_factory = decorator_factory def post_duplication_action(self): - decorated_clip = find_decorated_object(self._target_clip, self._decorator_factory) + decorated_clip = find_decorated_object(self._source_clip, self._decorator_factory) if decorated_clip: self._copy_zoom_parameter(decorated_clip) @@ -53,4 +174,4 @@ def _copy_zoom_parameter(self, copied_decorated_clip): if not self._destination_clip: return new_clip_decorated = self._decorator_factory.decorate(self._destination_clip) - new_clip_decorated.zoom.linear_value = copied_decorated_clip.zoom.linear_value \ No newline at end of file + new_clip_decorated.zoom.copy_state(copied_decorated_clip.zoom) \ No newline at end of file diff --git a/Push2/color_chooser.py b/Push2/color_chooser.py new file mode 100644 index 00000000..ee2f38ff --- /dev/null +++ b/Push2/color_chooser.py @@ -0,0 +1,76 @@ +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/Push2/color_chooser.py +# Compiled at: 2016-09-29 19:13:24 +from __future__ import absolute_import, print_function +from ableton.v2.base import liveobj_changed, liveobj_valid, nop +from ableton.v2.control_surface import Component +from ableton.v2.control_surface.control import ButtonControl, control_matrix +from pushbase.colors import Pulse +from pushbase.message_box_component import Messenger +from .colors import IndexedColor, Rgb, inverse_translate_color_index, translate_color_index +from .skin_default import SELECTION_PULSE_SPEED +COLOR_CHOOSER_LAYOUT = ( + (13, 12, 14, 15, 17, 16, 18, 19), + (11, None, None, None, None, None, None, 20), + (9, None, None, None, None, None, None, 21), + (8, None, None, None, None, None, None, 26), + (10, None, None, None, None, None, None, 22), + (7, None, None, None, None, None, None, 25), + (5, None, None, None, None, None, None, 24), + (4, 6, 3, None, None, 1, 23, 2)) + +class ColorChooserComponent(Component, Messenger): + matrix = control_matrix(ButtonControl, dimensions=(8, 8)) + + def __init__(self, *a, **k): + super(ColorChooserComponent, self).__init__(is_enabled=False, *a, **k) + self._object = None + self._notification_ref = nop + for button in self.matrix: + row, column = button.coordinate + button.color_index = COLOR_CHOOSER_LAYOUT[row][column] + + return + + @property + def object(self): + return self._object + + @object.setter + def object(self, obj): + if liveobj_changed(self._object, obj): + self._object = obj + if obj is None: + notification = self._notification_ref() + if notification: + notification.hide() + self.set_enabled(False) + else: + self._render_color_palette(translate_color_index(obj.color_index)) + self.set_enabled(True) + self._notification_ref = self.show_notification('Select a color for: %s' % obj.name, notification_time=-1) + return + + @matrix.pressed + def matrix(self, button): + if liveobj_valid(self.object): + if button.color_index is None: + if hasattr(self.object, 'is_auto_colored'): + self.object.is_auto_colored = True + self.show_notification('Color automatically enabled for: %s' % self.object.name) + else: + self.object.color_index = inverse_translate_color_index(button.color_index) + self.object = None + return + + def _render_color_palette(self, selected_color_index): + for button in self.matrix: + color_index = button.color_index + if color_index is not None: + if color_index == selected_color_index: + button.color = Pulse(IndexedColor.from_push_index(color_index, shade_level=2), IndexedColor.from_push_index(color_index), SELECTION_PULSE_SPEED) + else: + button.color = IndexedColor.from_push_index(color_index) + else: + button.color = Rgb.BLACK + + return \ No newline at end of file diff --git a/Push2/colors.py b/Push2/colors.py index 38615389..0ddbee62 100644 --- a/Push2/colors.py +++ b/Push2/colors.py @@ -1,45 +1,56 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/Push2/colors.py +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/Push2/colors.py +# Compiled at: 2016-11-16 18:13:20 from __future__ import absolute_import, print_function -from ableton.v2.base.util import in_range -from pushbase.colors import Blink, FallbackColor, Pulse, PushColor +from ableton.v2.base import depends, in_range, listens, liveobj_valid, nop +from ableton.v2.control_surface.elements.color import DynamicColorBase, DynamicColorFactory +from pushbase.colors import Blink, FallbackColor, Pulse, PushColor, TransparentColor +from .device_util import find_chain_or_track WHITE_MIDI_VALUE = 122 -TRANSLATED_WHITE_INDEX = 5 +TRANSLATED_WHITE_INDEX = 4 WHITE_COLOR_INDEX_FROM_LIVE = 59 UNCOLORED_INDEX = WHITE_COLOR_INDEX_FROM_LIVE HALFLIT_WHITE_MIDI = 14 +DISPLAY_BUTTON_SHADE_LEVEL = 1 def make_pulsing_track_color(track, pulse_to_color): - return Pulse(pulse_to_color, IndexedColor(translate_color_index(track.color_index)), 48) + return Pulse(pulse_to_color, IndexedColor.from_live_index(track.color_index), 48) def make_blinking_track_color(track, blink_to_color): - return Blink(blink_to_color, IndexedColor(translate_color_index(track.color_index)), 24) + return Blink(blink_to_color, IndexedColor.from_live_index(track.color_index), 24) def determine_shaded_color_index(color_index, shade_level): - raise in_range(color_index, 1, 27) or AssertionError - raise shade_level in (1, 2) or AssertionError - return (color_index - 1) * 2 + 64 + shade_level - - -class TransparentColor(object): - """ - Color that does not transmit any MIDI data. - """ - - def draw(self, interface): - pass + if not in_range(color_index, 1, 27): + assert color_index == WHITE_MIDI_VALUE + assert 0 <= shade_level <= 2 + return shade_level == 0 and color_index + else: + if color_index == WHITE_MIDI_VALUE: + return color_index + shade_level + return (color_index - 1) * 2 + 64 + shade_level class IndexedColor(PushColor): needs_rgb_interface = True midi_value = None - def __init__(self, index = None, *a, **k): + def __init__(self, index=None, *a, **k): super(IndexedColor, self).__init__(midi_value=index, *a, **k) + @staticmethod + def from_push_index(index, shade_level=0): + return IndexedColor(determine_shaded_color_index(index, shade_level)) + + @staticmethod + def from_live_index(index, shade_level=0): + return IndexedColor(determine_shaded_color_index(translate_color_index(index), shade_level)) + def translate_color_index(index): + """ + Translates a color index coming from Live into the reduced color palette of Push + """ try: if index > -1: return COLOR_INDEX_TO_PUSH_INDEX[index] @@ -48,6 +59,106 @@ def translate_color_index(index): return TRANSLATED_WHITE_INDEX +def inverse_translate_color_index(translated_index): + """ + Translates a color index coming with the reduced palette of Push [1..26] to the best + matching color of Live [0..59]. + """ + assert 1 <= translated_index <= len(PUSH_INDEX_TO_COLOR_INDEX) + return PUSH_INDEX_TO_COLOR_INDEX[translated_index - 1] - 1 + + +class SelectedDrumPadColor(DynamicColorBase): + """ + Dynamic color that sets the color of the currently selected drum pad. + The drum rack is used from the percussion_instrument_finder. + """ + + @depends(percussion_instrument_finder=nop) + def __init__(self, song=None, percussion_instrument_finder=None, *a, **k): + assert liveobj_valid(song) + super(SelectedDrumPadColor, self).__init__(*a, **k) + self.song = song + if percussion_instrument_finder is not None: + self.__on_selected_track_color_index_changed.subject = self.song.view + self.__on_instrument_changed.subject = percussion_instrument_finder + self.__on_instrument_changed() + return + + @listens('instrument') + def __on_instrument_changed(self): + drum_group = self.__on_instrument_changed.subject.drum_group + if liveobj_valid(drum_group): + self.__on_selected_drum_pad_chains_changed.subject = drum_group.view + self.__on_selected_drum_pad_chains_changed() + + @listens('selected_drum_pad.chains') + def __on_selected_drum_pad_chains_changed(self): + drum_pad = self.__on_selected_drum_pad_chains_changed.subject.selected_drum_pad + if liveobj_valid(drum_pad) and drum_pad.chains: + self.__on_color_index_changed.subject = drum_pad.chains[0] + self.__on_color_index_changed() + else: + self._update_midi_value(self.song.view.selected_track) + + @listens('color_index') + def __on_color_index_changed(self): + chain = self.__on_color_index_changed.subject + self._update_midi_value(chain) + + @listens('selected_track.color_index') + def __on_selected_track_color_index_changed(self): + drum_group = self.__on_selected_drum_pad_chains_changed.subject + drum_pad = drum_group.selected_drum_pad if liveobj_valid(drum_group) else None + if not liveobj_valid(drum_pad) or not drum_pad.chains: + self._update_midi_value(self.song.view.selected_track) + return + + +class SelectedDrumPadColorFactory(DynamicColorFactory): + + def instantiate(self, song): + return SelectedDrumPadColor(song=song, transformation=self._transform) + + +class SelectedDeviceChainColor(DynamicColorBase): + + @depends(device_provider=nop) + def __init__(self, device_provider=None, *a, **k): + super(SelectedDeviceChainColor, self).__init__(*a, **k) + if device_provider is not None: + self.__on_device_changed.subject = device_provider + self.__on_device_changed() + return + + @listens('device') + def __on_device_changed(self): + device = self.__on_device_changed.subject.device + chain = find_chain_or_track(device) + self.__on_chain_color_index_changed.subject = chain + self.__on_chain_color_index_changed() + + @listens('color_index') + def __on_chain_color_index_changed(self): + chain = self.__on_chain_color_index_changed.subject + if liveobj_valid(chain): + self._update_midi_value(chain) + + +class SelectedDeviceChainColorFactory(DynamicColorFactory): + + def instantiate(self, song): + return SelectedDeviceChainColor(transformation=self._transform) + + +def make_color_factory_func(factory_class): + + def make_color_factory(shade_level=0): + return factory_class(transformation=lambda color_index: determine_shaded_color_index(translate_color_index(color_index), shade_level)) + + return make_color_factory + + class Rgb: AMBER = IndexedColor(3) AMBER_SHADE = IndexedColor(69) @@ -82,34 +193,40 @@ class Basic: TRANSPARENT = TransparentColor() -COLOR_INDEX_TO_PUSH_INDEX = (1, 1, 4, 10, 9, 13, 17, 16, 17, 18, 19, 16, 1, 5, 10, 10, 9, 12, 15, 19, 20, 22, 24, 1, 1, 7, 8, 8, 11, 14, 14, 15, 19, 20, 21, 25, 3, 6, 6, 6, 4, 9, 12, 15, 17, 2, 23, 26, 2, 5, 7, 10, 9, 11, 15, 19, 26, 14, 26, 5) -COLOR_TABLE = ((0, 0, 0), - (1, 16720932, 2), - (2, 15874572, 4), - (3, 16750848, 6), - (4, 10914134, 8), - (5, 15595866, 10), - (6, 12688648, 12), - (7, 16776960, 14), - (8, 5685011, 16), - (9, 2917379, 18), - (10, 2386724, 20), - (11, 1703728, 22), - (12, 1414515, 24), - (13, 1534800, 26), - (14, 65535, 28), - (15, 29948, 30), - (16, 2576332, 32), - (17, 17548, 34), - (18, 6572761, 36), - (19, 5062560, 38), - (20, 8847615, 40), - (21, 15095779, 42), - (22, 6684825, 44), - (23, 16711833, 46), - (24, 10570847, 48), - (25, 16731588, 50), - (26, 15436769, 52), +COLOR_INDEX_TO_PUSH_INDEX = (1, 4, 4, 10, 9, 13, 17, 16, 17, 18, 4, 4, 1, 5, 10, 10, + 9, 12, 15, 16, 20, 22, 24, 4, 3, 7, 8, 8, 11, 14, 14, + 15, 19, 20, 21, 25, 3, 6, 6, 6, 4, 9, 12, 15, 17, 2, + 23, 2, 2, 5, 7, 10, 4, 11, 15, 19, 26, 4, 4, 4) +PUSH_INDEX_TO_COLOR_INDEX = (1, 48, 37, 41, 50, 38, 26, 28, 17, 16, 29, 18, 6, 31, + 19, 20, 7, 10, 33, 21, 35, 22, 47, 23, 36, 57) +COLOR_TABLE = ( + (0, 0, 0), + (1, 15865344, 2), + (2, 16728114, 4), + (3, 16541952, 6), + (4, 9331486, 8), + (5, 16440379, 10), + (6, 16762134, 12), + (7, 11992846, 14), + (8, 7995160, 16), + (9, 3457558, 18), + (10, 5212676, 20), + (11, 6487893, 22), + (12, 2719059, 24), + (13, 2843699, 26), + (14, 3255807, 28), + (15, 3564540, 30), + (16, 1717503, 32), + (17, 1391001, 34), + (18, 1838310, 36), + (19, 3749887, 38), + (20, 5710591, 40), + (21, 9907199, 42), + (22, 8724856, 44), + (23, 16715826, 46), + (24, 11022396, 48), + (25, 16722900, 50), + (26, 15427065, 52), (27, 10892321, 54), (28, 10049064, 56), (29, 8873728, 58), @@ -148,66 +265,66 @@ class Basic: (62, 7632234, 92), (63, 10323076, 92), (64, 7694954, 93), - (65, 5049099, 93), - (66, 1704964, 94), - (67, 5050884, 94), - (68, 1705473, 95), - (69, 5058048, 96), - (70, 1707776, 96), - (71, 4207649, 97), - (72, 1709325, 97), - (73, 5064991, 98), - (74, 1710090, 98), - (75, 4207362, 99), - (76, 1709313, 99), - (77, 5065984, 100), - (78, 1710592, 101), - (79, 1851399, 101), - (80, 727555, 102), - (81, 1127169, 102), - (82, 265472, 103), - (83, 1127185, 103), - (84, 265476, 104), - (85, 675082, 104), - (86, 203269, 105), - (87, 471847, 106), - (88, 134410, 106), - (89, 1068345, 107), - (90, 199946, 107), - (91, 19789, 108), - (92, 6682, 108), - (93, 9037, 109), - (94, 3098, 109), - (95, 792896, 110), - (96, 132365, 110), - (97, 9549, 111), - (98, 3098, 112), - (99, 2300493, 112), - (100, 788762, 113), - (101, 2432589, 113), - (102, 789018, 114), - (103, 3539046, 114), - (104, 851994, 115), - (105, 5053772, 115), - (106, 1706521, 116), - (107, 3342413, 117), - (108, 1114138, 117), - (109, 5046318, 118), - (110, 1703951, 118), - (111, 5055533, 119), - (112, 1707023, 119), - (113, 5052219, 120), - (114, 1706004, 120), - (115, 5057865, 121), - (116, 1707800, 122), + (65, 6293504, 93), + (66, 2032128, 94), + (67, 6691092, 94), + (68, 2164742, 95), + (69, 6564352, 96), + (70, 2100480, 96), + (71, 4665615, 97), + (72, 1839878, 97), + (73, 6576151, 98), + (74, 2104327, 98), + (75, 6704648, 99), + (76, 2169090, 99), + (77, 4744709, 100), + (78, 1515777, 101), + (79, 3171849, 101), + (80, 991491, 102), + (81, 1330440, 102), + (82, 399618, 103), + (83, 2045697, 103), + (84, 659712, 104), + (85, 2582050, 104), + (86, 794891, 105), + (87, 1326633, 106), + (88, 530704, 106), + (89, 1389081, 107), + (90, 529418, 107), + (91, 1262950, 108), + (92, 398881, 108), + (93, 1386340, 109), + (94, 461856, 109), + (95, 660582, 110), + (96, 198177, 110), + (97, 662604, 111), + (98, 264990, 112), + (99, 722012, 112), + (100, 196893, 113), + (101, 1447526, 113), + (102, 460577, 114), + (103, 2231654, 114), + (104, 721953, 115), + (105, 3936614, 115), + (106, 1246497, 116), + (107, 3476784, 117), + (108, 1115151, 117), + (109, 6686228, 118), + (110, 2163206, 118), + (111, 4395800, 119), + (112, 1377799, 119), + (113, 6689108, 120), + (114, 2163995, 120), + (115, 6170723, 121), + (116, 1969440, 122), (117, 0, 122), (118, 5855577, 123), (119, 1710618, 123), (120, 16777215, 124), (121, 5855577, 124), - (122, 16777215, 125), - (123, 5855577, 125), - (124, 1710618, 126), + (122, 13421772, 125), + (123, 4210752, 125), + (124, 1315860, 126), (125, 255, 126), (126, 65280, 127), (127, 16711680, 127)) \ No newline at end of file diff --git a/Push2/convert.py b/Push2/convert.py index 481c0143..2724d3ea 100644 --- a/Push2/convert.py +++ b/Push2/convert.py @@ -1,9 +1,10 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/Push2/convert.py +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/Push2/convert.py +# Compiled at: 2016-11-16 18:13:20 from __future__ import absolute_import, print_function from functools import partial from itertools import izip import Live -from ableton.v2.base import find_if, listenable_property, listens, listens_group, liveobj_valid, SlotManager, Subject, task +from ableton.v2.base import EventObject, find_if, listenable_property, listens, liveobj_valid, task from ableton.v2.control_surface import Component from ableton.v2.control_surface.control import ButtonControl, control_list from pushbase.device_chain_utils import find_instrument_devices @@ -14,67 +15,100 @@ from .mixable_utilities import find_drum_rack_instrument, find_simpler, is_audio_track, is_midi_track from .track_selection import SelectedMixerTrackProvider -def possible_conversions(track, decorator_factory = None): - conversions = [] +def possible_conversions(track, decorator_factory=None): + category = NullConvertCategory() if liveobj_valid(track): if is_midi_track(track) and len(track.devices) > 0: drum_rack = find_drum_rack_instrument(track) if liveobj_valid(drum_rack): drum_pad = drum_rack.view.selected_drum_pad if liveobj_valid(drum_pad) and len(drum_pad.chains) == 1 and find_instrument_devices(drum_pad.chains[0]): - conversions.append(DrumPadToMidiTrack(drum_pad=drum_pad, track=track)) + category = MidiTrackWithDrumRack(actions=[ + DrumPadToMidiTrack()], drum_pad=drum_pad, track=track) else: simpler = find_simpler(track) if simpler != None and simpler.playback_mode == Live.SimplerDevice.PlaybackMode.slicing: - conversions.append(SlicesToDrumRack(device=simpler, track=track)) + category = MidiTrackWithSimpler(actions=[ + SlicesToDrumRack()], device=simpler, track=track) else: - conversions.append(MoveDeviceChain(device=simpler, track=track, decorator_factory=decorator_factory)) + category = MidiTrackWithoutSimpler(actions=[ + MoveDeviceChain()], device=simpler, track=track, decorator_factory=decorator_factory) elif is_audio_track(track): - highlighted_clip_slot = track.canonical_parent.view.highlighted_clip_slot - clip = find_if(lambda slot: slot.has_clip and highlighted_clip_slot == slot, track.clip_slots) - if liveobj_valid(clip) and not clip.is_recording: - conversions.append(CreateTrackWithSimpler(clip_slot=highlighted_clip_slot, track=track)) - return conversions + detail_clip = track.canonical_parent.view.detail_clip + if liveobj_valid(detail_clip) and detail_clip.is_arrangement_clip: + if not detail_clip.is_recording: + category = AudioTrackWithArrangementClip(actions=[ + CreateTrackWithSimpler(), + CreateTrackWithClipInDrumRackPad()], song_view=track.canonical_parent.view, track=track) + else: + highlighted_clip_slot = track.canonical_parent.view.highlighted_clip_slot + clip = find_if(lambda slot: slot.has_clip and highlighted_clip_slot == slot, track.clip_slots) + if liveobj_valid(clip) and not clip.is_recording: + category = AudioTrackWithSessionClip(actions=[ + CreateTrackWithSimpler(), + CreateTrackWithClipInDrumRackPad()], clip_slot=highlighted_clip_slot, track=track) + return category -class ConvertAction(Subject, SlotManager): - __events__ = ('action_invalidated',) +class ConvertAction(object): needs_deferred_invocation = False + + @staticmethod + def conversion(song, *a): + raise NotImplementedError + + +class ConvertCategory(EventObject): + __events__ = ('action_invalidated', ) color_source = None name_source = None - def __init__(self, color_source = None, name_source = None, *a, **k): - super(ConvertAction, self).__init__(*a, **k) + def __init__(self, actions=[], color_source=None, name_source=None, *a, **k): + super(ConvertCategory, self).__init__(*a, **k) + self.actions = actions self.color_source = color_source self.name_source = name_source - def convert(self, song): + def convert(self, song, action_index): raise NotImplementedError -class TrackBasedConvertAction(ConvertAction): +class NullConvertCategory(ConvertCategory): - def __init__(self, track = None, *a, **k): - raise liveobj_valid(track) or AssertionError - super(TrackBasedConvertAction, self).__init__(color_source=track, name_source=track, *a, **k) + def convert(self, song, action_index): + assert False, 'Cannot call convert on NullConvertCategory' + + +class TrackBasedConvertCategory(ConvertCategory): + + def __init__(self, track=None, *a, **k): + assert liveobj_valid(track) + super(TrackBasedConvertCategory, self).__init__(color_source=track, name_source=track, *a, **k) self._track = track -class MoveDeviceChain(TrackBasedConvertAction): +class MoveDeviceChain(ConvertAction): label = 'Drum Pad' + + @staticmethod + def conversion(song, track_index): + return Live.Conversions.move_devices_on_track_to_new_drum_rack_pad(song, track_index) + + +class MidiTrackWithoutSimpler(TrackBasedConvertCategory): internal_name = 'midi_track_to_drum_pad' - def __init__(self, device = None, decorator_factory = None, *a, **k): - super(MoveDeviceChain, self).__init__(*a, **k) + def __init__(self, device=None, decorator_factory=None, *a, **k): + super(MidiTrackWithoutSimpler, self).__init__(*a, **k) self._decorator_factory = decorator_factory if hasattr(device, 'playback_mode'): self.__on_playback_mode_changed.subject = device - def convert(self, song): + def convert(self, song, action): self._track.stop_all_clips() track_index = list(song.tracks).index(self._track) copiers = self._create_copiers() - drum_pad = Live.Conversions.move_devices_on_track_to_new_drum_rack_pad(song, track_index) + drum_pad = action.conversion(song, track_index) if liveobj_valid(drum_pad) and copiers: self._apply_simpler_properties(drum_pad, song, copiers) @@ -94,45 +128,89 @@ def create_copier_if_decorated(simpler): decorated = find_decorated_object(simpler, self._decorator_factory) if decorated: return SimplerDecoratedPropertiesCopier(decorated, self._decorator_factory) + else: + return None return map(create_copier_if_decorated, find_simplers(self._track)) class CreateTrackWithSimpler(ConvertAction): label = 'Simpler' + + @staticmethod + def conversion(song, clip): + Live.Conversions.create_midi_track_with_simpler(song, clip) + + +class CreateTrackWithClipInDrumRackPad(ConvertAction): + label = 'Drum Pad' + + @staticmethod + def conversion(song, clip): + Live.Conversions.create_drum_rack_from_audio_clip(song, clip) + + +class AudioTrackWithSessionClip(ConvertCategory): internal_name = 'audio_clip_to_simpler' - def __init__(self, clip_slot = None, track = None, *a, **k): - raise liveobj_valid(clip_slot) or AssertionError - raise liveobj_valid(track) or AssertionError - super(CreateTrackWithSimpler, self).__init__(name_source=clip_slot.clip, color_source=clip_slot.clip, *a, **k) + def __init__(self, clip_slot=None, track=None, *a, **k): + assert liveobj_valid(clip_slot) + assert liveobj_valid(track) + super(AudioTrackWithSessionClip, self).__init__(name_source=clip_slot.clip, color_source=clip_slot.clip, *a, **k) self._clip_slot = clip_slot self._track = track self.__on_has_clip_changed.subject = self._clip_slot - def convert(self, song): + def convert(self, song, action): self._track.stop_all_clips() - Live.Conversions.create_midi_track_with_simpler(song, self._clip_slot.clip) + action.conversion(song, self._clip_slot.clip) @listens('has_clip') def __on_has_clip_changed(self): self.notify_action_invalidated() -class SlicesToDrumRack(TrackBasedConvertAction): - needs_deferred_invocation = True +class AudioTrackWithArrangementClip(ConvertCategory): + internal_name = 'audio_arrangement_clip_to_simpler' + + def __init__(self, song_view=None, track=None, *a, **k): + assert liveobj_valid(song_view) + assert liveobj_valid(track) + super(AudioTrackWithArrangementClip, self).__init__(name_source=song_view.detail_clip, color_source=song_view.detail_clip, *a, **k) + self._clip = song_view.detail_clip + self._track = track + self.__on_detail_clip_changed.subject = song_view + + def convert(self, song, action): + self._track.stop_all_clips() + action.conversion(song, self._clip) + + @listens('detail_clip') + def __on_detail_clip_changed(self): + self.notify_action_invalidated() + + +class SlicesToDrumRack(ConvertAction): label = 'Drum Rack' + needs_deferred_invocation = True + + @staticmethod + def conversion(song, device): + Live.Conversions.sliced_simpler_to_drum_rack(song, device) + + +class MidiTrackWithSimpler(TrackBasedConvertCategory): internal_name = 'sliced_simpler_to_drum_rack' - def __init__(self, device = None, *a, **k): - raise isinstance(device, Live.SimplerDevice.SimplerDevice) or AssertionError - super(SlicesToDrumRack, self).__init__(*a, **k) + def __init__(self, device=None, *a, **k): + assert isinstance(device, Live.SimplerDevice.SimplerDevice) + super(MidiTrackWithSimpler, self).__init__(*a, **k) self._device = device self.__on_playback_mode_changed.subject = self._device self.__on_sample_changed.subject = self._device - def convert(self, song): - Live.Conversions.sliced_simpler_to_drum_rack(song, self._device) + def convert(self, song, action): + action.conversion(song, self._device) @listens('playback_mode') def __on_playback_mode_changed(self): @@ -145,11 +223,18 @@ def __on_sample_changed(self): class DrumPadToMidiTrack(ConvertAction): label = 'MIDI track' + + @staticmethod + def conversion(song, drum_pad): + Live.Conversions.create_midi_track_from_drum_pad(song, drum_pad) + + +class MidiTrackWithDrumRack(ConvertCategory): internal_name = 'drum_pad_to_midi_track' - def __init__(self, drum_pad = None, track = None, *a, **k): - raise liveobj_valid(drum_pad) or AssertionError - super(DrumPadToMidiTrack, self).__init__(name_source=drum_pad, color_source=track, *a, **k) + def __init__(self, drum_pad=None, track=None, *a, **k): + assert liveobj_valid(drum_pad) + super(MidiTrackWithDrumRack, self).__init__(name_source=drum_pad, color_source=track, *a, **k) self.__on_devices_changed.subject = drum_pad.chains[0] self.__on_chains_changed.subject = drum_pad self._drum_pad = drum_pad @@ -162,8 +247,8 @@ def __on_devices_changed(self): def __on_chains_changed(self): self.notify_action_invalidated() - def convert(self, song): - Live.Conversions.create_midi_track_from_drum_pad(song, self._drum_pad) + def convert(self, song, action): + action.conversion(song, self._drum_pad) class ConvertComponent(Component): @@ -173,48 +258,46 @@ class ConvertComponent(Component): source_color_index = listenable_property.managed(UNCOLORED_INDEX) source_name = listenable_property.managed(unicode('')) - def __init__(self, tracks_provider = None, conversions_provider = possible_conversions, decorator_factory = None, *a, **k): - raise tracks_provider is not None or AssertionError - raise callable(conversions_provider) or AssertionError + def __init__(self, tracks_provider=None, conversions_provider=possible_conversions, decorator_factory=None, *a, **k): + assert tracks_provider is not None + assert callable(conversions_provider) super(ConvertComponent, self).__init__(*a, **k) self._tracks_provider = tracks_provider self._conversions_provider = conversions_provider self._decorator_factory = decorator_factory - self._available_conversions = [] + self._category = NullConvertCategory() self._update_possible_conversions() + return @listenable_property def available_conversions(self): - return map(lambda x: x.label, self._available_conversions) + return map(lambda x: x.label, self._category.actions) def on_enabled_changed(self): super(ConvertComponent, self).on_enabled_changed() self._update_possible_conversions() def _update_possible_conversions(self): - for conversion in self._available_conversions: - self.disconnect_disconnectable(conversion) - + self.disconnect_disconnectable(self._category) track = self._tracks_provider.selected_item - self._available_conversions = map(self.register_disconnectable, self._conversions_provider(track, self._decorator_factory)) - self.__on_action_invalidated.replace_subjects(self._available_conversions) - color_sources = map(lambda c: c.color_source, self._available_conversions) - name_sources = map(lambda c: c.name_source, self._available_conversions) - self.__on_action_source_color_index_changed.replace_subjects(color_sources) - self.__on_action_source_name_changed.replace_subjects(name_sources) - if self._available_conversions: - first_action = self._available_conversions[0] - self.__on_action_source_color_index_changed(first_action.color_source) - self.__on_action_source_name_changed(first_action.name_source) - self.action_buttons.control_count = len(self._available_conversions) + self._category = self.register_disconnectable(self._conversions_provider(track, self._decorator_factory)) + self.__on_action_invalidated.subject = self._category + self.__on_action_source_color_index_changed.subject = self._category.color_source + self.__on_action_source_name_changed.subject = self._category.name_source + self.__on_action_source_color_index_changed() + self.__on_action_source_name_changed() + self.action_buttons.control_count = len(self._category.actions) self.notify_available_conversions() - @listens_group('color_index') - def __on_action_source_color_index_changed(self, color_source): + @listens('color_index') + def __on_action_source_color_index_changed(self): + color_source = self.__on_action_source_color_index_changed.subject self.source_color_index = color_source.color_index if color_source and color_source.color_index is not None else UNCOLORED_INDEX + return - @listens_group('name') - def __on_action_source_name_changed(self, name_source): + @listens('name') + def __on_action_source_name_changed(self): + name_source = self.__on_action_source_name_changed.subject self.source_name = name_source.name if name_source else unicode() @action_buttons.released @@ -224,8 +307,8 @@ def action_buttons(self, button): def _do_conversion(self, action_index): self._update_possible_conversions() - if action_index < len(self._available_conversions): - action = self._available_conversions[action_index] + if action_index < len(self._category.actions): + action = self._category.actions[action_index] if action.needs_deferred_invocation: self._tasks.add(task.sequence(task.delay(1), task.run(lambda : self._do_conversion_deferred(action)))) return False @@ -237,24 +320,24 @@ def _do_conversion_deferred(self, action): self.notify_cancel() def _invoke_conversion(self, action): - action.convert(self.song) - self.notify_success(action.internal_name) + self._category.convert(self.song, action) + self.notify_success(self._category.internal_name) @cancel_button.released def cancel_button(self, button): self.notify_cancel() - @listens_group('action_invalidated') - def __on_action_invalidated(self, action): + @listens('action_invalidated') + def __on_action_invalidated(self): self.notify_cancel() class ConvertEnabler(Component): convert_toggle_button = ButtonControl(color='DefaultButton.On') - def __init__(self, enter_dialog_mode = None, exit_dialog_mode = None, *a, **k): - raise enter_dialog_mode is not None or AssertionError - raise exit_dialog_mode is not None or AssertionError + def __init__(self, enter_dialog_mode=None, exit_dialog_mode=None, *a, **k): + assert enter_dialog_mode is not None + assert exit_dialog_mode is not None super(ConvertEnabler, self).__init__(*a, **k) self._enter_dialog_mode = partial(enter_dialog_mode, 'convert') self._exit_dialog_mode = partial(exit_dialog_mode, 'convert') @@ -264,8 +347,10 @@ def __init__(self, enter_dialog_mode = None, exit_dialog_mode = None, *a, **k): song = self.song self.__on_devices_changed.subject = song.view self.__on_selected_scene_changed.subject = song.view + self.__on_detail_clip_updated.subject = song.view self._update_clip_slot_listener() self._update_drum_pad_listeners() + return @listens('selected_mixer_track') def __on_selected_item_changed(self, _): @@ -277,17 +362,18 @@ def convert_toggle_button(self, button): self._enter_dialog_mode() def _can_enable_mode(self): - conversions = possible_conversions(self._selected_item.selected_mixer_track) - has_conversions = bool(conversions) - for conversion in conversions: - conversion.disconnect() - - return has_conversions + category = possible_conversions(self._selected_item.selected_mixer_track) + category.disconnect() + return len(category.actions) > 0 def _disable_and_check_enabled_state(self): self._exit_dialog_mode() self.convert_toggle_button.enabled = self._can_enable_mode() + @listens('detail_clip') + def __on_detail_clip_updated(self): + self._disable_and_check_enabled_state() + @listens('selected_track.devices') def __on_devices_changed(self): self._disable_and_check_enabled_state() @@ -298,6 +384,7 @@ def _update_drum_pad_listeners(self): drum_rack_view_or_none = drum_rack.view if liveobj_valid(drum_rack) else None self.__on_selected_drum_pad_changed.subject = drum_rack_view_or_none self.__on_drum_pad_chains_changed.subject = drum_rack_view_or_none + return @listens('selected_drum_pad') def __on_selected_drum_pad_changed(self): @@ -309,6 +396,7 @@ def __on_selected_drum_pad_changed(self): if liveobj_valid(selected_drum_pad): first_chain_or_none = selected_drum_pad.chains[0] if len(selected_drum_pad.chains) > 0 else None self.__on_drum_pad_chain_devices_changed.subject = first_chain_or_none + return @listens('selected_drum_pad.chains') def __on_drum_pad_chains_changed(self): diff --git a/Push2/custom_bank_definitions.py b/Push2/custom_bank_definitions.py index f9175a56..1d9fbade 100644 --- a/Push2/custom_bank_definitions.py +++ b/Push2/custom_bank_definitions.py @@ -1,484 +1,757 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/Push2/custom_bank_definitions.py +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/Push2/custom_bank_definitions.py +# Compiled at: 2016-09-29 19:13:24 from __future__ import absolute_import, print_function from ableton.v2.base.collection import IndexedDict from pushbase.parameter_slot_description import use from pushbase.banking_util import PARAMETERS_KEY, MAIN_KEY OPTIONS_KEY = 'Options' SHOW_WAVEFORM_KEY = 'show_waveform' -RACK_BANKS = IndexedDict((('Macros', {PARAMETERS_KEY: ('Macro 1', 'Macro 2', 'Macro 3', 'Macro 4', 'Macro 5', 'Macro 6', 'Macro 7', 'Macro 8')}),)) +RACK_BANKS = IndexedDict(( + ( + 'Macros', + {PARAMETERS_KEY: ('Macro 1', 'Macro 2', 'Macro 3', 'Macro 4', 'Macro 5', 'Macro 6', 'Macro 7', 'Macro 8') + }),)) BANK_DEFINITIONS = {'AudioEffectGroupDevice': RACK_BANKS, - 'MidiEffectGroupDevice': RACK_BANKS, - 'InstrumentGroupDevice': RACK_BANKS, - 'DrumGroupDevice': RACK_BANKS, - 'UltraAnalog': IndexedDict(((MAIN_KEY, {PARAMETERS_KEY: (use('OSC1 Shape').if_parameter('OSC1 On/Off').has_value('1').else_use('OSC2 Shape').if_parameter('OSC2 On/Off').has_value('1'), - use('OSC1 Octave').if_parameter('OSC1 On/Off').has_value('1').else_use('OSC2 Octave').if_parameter('OSC2 On/Off').has_value('1'), - use('OSC2 Shape').if_parameter('OSC1 On/Off').has_value('1').and_parameter('OSC2 On/Off').has_value('1').else_use('OSC1 Semi').if_parameter('OSC1 On/Off').has_value('1').else_use('OSC2 Semi').if_parameter('OSC2 On/Off').has_value('1'), - use('OSC2 Octave').if_parameter('OSC1 On/Off').has_value('1').and_parameter('OSC2 On/Off').has_value('1').else_use('OSC1 Detune').if_parameter('OSC1 On/Off').has_value('1').else_use('OSC2 Detune').if_parameter('OSC2 On/Off').has_value('1'), - use('F1 Type').if_parameter('F1 On/Off').has_value('1').else_use('F2 Type').if_parameter('F2 On/Off').has_value('1'), - use('F1 Freq').if_parameter('F1 On/Off').has_value('1').else_use('F2 Freq').if_parameter('F2 On/Off').has_value('1'), - use('F1 Resonance').if_parameter('F1 On/Off').has_value('1').else_use('F2 Resonance').if_parameter('F2 On/Off').has_value('1'), - 'Volume')}), - ('Osc. 1 Shape', {PARAMETERS_KEY: ('OSC1 On/Off', - use('OSC1 Shape').if_parameter('OSC1 On/Off').has_value('1'), - use('').if_parameter('OSC1 On/Off').has_value('0').else_use('OSC1 PW').if_parameter('OSC1 Shape').has_value('Rect'), - use('').if_parameter('OSC1 On/Off').has_value('0').else_use('O1 PW < LFO').if_parameter('OSC1 Shape').has_value('Rect').else_use('').if_parameter('LFO1 On/Off').has_value('0'), - use('').if_parameter('OSC1 On/Off').has_value('0').else_use('').if_parameter('OSC1 Shape').has_value('Noise').else_use('').if_parameter('OSC1 Shape').has_value('Sine').else_use('OSC1 Mode'), - use('').if_parameter('OSC1 On/Off').has_value('0').else_use('').if_parameter('OSC1 Shape').has_value('Noise').else_use('O1 Sub/Sync'), - use('OSC1 Balance').if_parameter('OSC1 On/Off').has_value('1'), - use('OSC1 Level').if_parameter('OSC1 On/Off').has_value('1'))}), - ('Osc. 1 Pitch', {PARAMETERS_KEY: ('OSC1 On/Off', - use('OSC1 Octave').if_parameter('OSC1 On/Off').has_value('1'), - use('OSC1 Semi').if_parameter('OSC1 On/Off').has_value('1'), - use('OSC1 Detune').if_parameter('OSC1 On/Off').has_value('1'), - use('PEG1 Amount').if_parameter('OSC1 On/Off').has_value('1'), - use('PEG1 Time').if_parameter('OSC1 On/Off').has_value('1'), - use('O1 Keytrack').if_parameter('OSC1 On/Off').has_value('1'), - use('').if_parameter('OSC1 On/Off').has_value('0').else_use('').if_parameter('LFO1 On/Off').has_value('0').else_use('OSC1 < LFO'))}), - ('Osc. 2 Shape', {PARAMETERS_KEY: ('OSC2 On/Off', - use('OSC2 Shape').if_parameter('OSC2 On/Off').has_value('1'), - use('').if_parameter('OSC2 On/Off').has_value('0').else_use('OSC2 PW').if_parameter('OSC2 Shape').has_value('Rect'), - use('').if_parameter('OSC2 On/Off').has_value('0').else_use('O2 PW < LFO').if_parameter('OSC2 Shape').has_value('Rect').else_use('').if_parameter('LFO2 On/Off').has_value('0'), - use('').if_parameter('OSC2 On/Off').has_value('0').else_use('').if_parameter('OSC2 Shape').has_value('Noise').else_use('').if_parameter('OSC2 Shape').has_value('Sine').else_use('OSC2 Mode'), - use('').if_parameter('OSC2 On/Off').has_value('0').else_use('').if_parameter('OSC2 Shape').has_value('Noise').else_use('O2 Sub/Sync'), - use('OSC2 Balance').if_parameter('OSC2 On/Off').has_value('1'), - use('OSC2 Level').if_parameter('OSC2 On/Off').has_value('1'))}), - ('Osc. 2 Pitch', {PARAMETERS_KEY: ('OSC2 On/Off', - use('OSC2 Octave').if_parameter('OSC2 On/Off').has_value('1'), - use('OSC2 Semi').if_parameter('OSC2 On/Off').has_value('1'), - use('OSC2 Detune').if_parameter('OSC2 On/Off').has_value('1'), - use('PEG2 Amount').if_parameter('OSC2 On/Off').has_value('1'), - use('PEG2 Time').if_parameter('OSC2 On/Off').has_value('1'), - use('O2 Keytrack').if_parameter('OSC2 On/Off').has_value('1'), - use('').if_parameter('OSC2 On/Off').has_value('0').else_use('').if_parameter('LFO2 On/Off').has_value('0').else_use('OSC2 < LFO'))}), - ('Filters', {PARAMETERS_KEY: ('F1 On/Off', - use('F1 Type').if_parameter('F1 On/Off').has_value('1'), - use('F1 Freq').if_parameter('F1 On/Off').has_value('1'), - use('F1 Resonance').if_parameter('F1 On/Off').has_value('1'), + 'MidiEffectGroupDevice': RACK_BANKS, + 'InstrumentGroupDevice': RACK_BANKS, + 'DrumGroupDevice': RACK_BANKS, + 'UltraAnalog': IndexedDict(( + ( + MAIN_KEY, + {PARAMETERS_KEY: ( + use('OSC1 Shape').if_parameter('OSC1 On/Off').has_value('on').else_use('OSC2 Shape').if_parameter('OSC2 On/Off').has_value('on'), + use('OSC1 Octave').if_parameter('OSC1 On/Off').has_value('on').else_use('OSC2 Octave').if_parameter('OSC2 On/Off').has_value('on'), + use('OSC2 Shape').if_parameter('OSC1 On/Off').has_value('on').and_parameter('OSC2 On/Off').has_value('on').else_use('OSC1 Semi').if_parameter('OSC1 On/Off').has_value('on').else_use('OSC2 Semi').if_parameter('OSC2 On/Off').has_value('on'), + use('OSC2 Octave').if_parameter('OSC1 On/Off').has_value('on').and_parameter('OSC2 On/Off').has_value('on').else_use('OSC1 Detune').if_parameter('OSC1 On/Off').has_value('on').else_use('OSC2 Detune').if_parameter('OSC2 On/Off').has_value('on'), + use('F1 Type').if_parameter('F1 On/Off').has_value('on').else_use('F2 Type').if_parameter('F2 On/Off').has_value('on'), + use('F1 Freq').if_parameter('F1 On/Off').has_value('on').else_use('F2 Freq').if_parameter('F2 On/Off').has_value('on'), + use('F1 Resonance').if_parameter('F1 On/Off').has_value('on').else_use('F2 Resonance').if_parameter('F2 On/Off').has_value('on'), + 'Volume') + }), + ( + 'Osc. 1 Shape', + {PARAMETERS_KEY: ( + 'OSC1 On/Off', + use('OSC1 Shape').if_parameter('OSC1 On/Off').has_value('on'), + use('').if_parameter('OSC1 On/Off').has_value('off').else_use('OSC1 PW').if_parameter('OSC1 Shape').has_value('Rect'), + use('').if_parameter('OSC1 On/Off').has_value('off').else_use('O1 PW < LFO').if_parameter('OSC1 Shape').has_value('Rect').else_use('').if_parameter('LFO1 On/Off').has_value('off'), + use('').if_parameter('OSC1 On/Off').has_value('off').else_use('').if_parameter('OSC1 Shape').has_value('Noise').else_use('').if_parameter('OSC1 Shape').has_value('Sine').else_use('OSC1 Mode'), + use('').if_parameter('OSC1 On/Off').has_value('off').else_use('').if_parameter('OSC1 Shape').has_value('Noise').else_use('O1 Sub/Sync'), + use('OSC1 Balance').if_parameter('OSC1 On/Off').has_value('on'), + use('OSC1 Level').if_parameter('OSC1 On/Off').has_value('on')) + }), + ( + 'Osc. 1 Pitch', + {PARAMETERS_KEY: ( + 'OSC1 On/Off', + use('OSC1 Octave').if_parameter('OSC1 On/Off').has_value('on'), + use('OSC1 Semi').if_parameter('OSC1 On/Off').has_value('on'), + use('OSC1 Detune').if_parameter('OSC1 On/Off').has_value('on'), + use('PEG1 Amount').if_parameter('OSC1 On/Off').has_value('on'), + use('PEG1 Time').if_parameter('OSC1 On/Off').has_value('on'), + use('O1 Keytrack').if_parameter('OSC1 On/Off').has_value('on'), + use('').if_parameter('OSC1 On/Off').has_value('off').else_use('').if_parameter('LFO1 On/Off').has_value('off').else_use('OSC1 < LFO')) + }), + ( + 'Osc. 2 Shape', + {PARAMETERS_KEY: ( + 'OSC2 On/Off', + use('OSC2 Shape').if_parameter('OSC2 On/Off').has_value('on'), + use('').if_parameter('OSC2 On/Off').has_value('off').else_use('OSC2 PW').if_parameter('OSC2 Shape').has_value('Rect'), + use('').if_parameter('OSC2 On/Off').has_value('off').else_use('O2 PW < LFO').if_parameter('OSC2 Shape').has_value('Rect').else_use('').if_parameter('LFO2 On/Off').has_value('off'), + use('').if_parameter('OSC2 On/Off').has_value('off').else_use('').if_parameter('OSC2 Shape').has_value('Noise').else_use('').if_parameter('OSC2 Shape').has_value('Sine').else_use('OSC2 Mode'), + use('').if_parameter('OSC2 On/Off').has_value('off').else_use('').if_parameter('OSC2 Shape').has_value('Noise').else_use('O2 Sub/Sync'), + use('OSC2 Balance').if_parameter('OSC2 On/Off').has_value('on'), + use('OSC2 Level').if_parameter('OSC2 On/Off').has_value('on')) + }), + ( + 'Osc. 2 Pitch', + {PARAMETERS_KEY: ( + 'OSC2 On/Off', + use('OSC2 Octave').if_parameter('OSC2 On/Off').has_value('on'), + use('OSC2 Semi').if_parameter('OSC2 On/Off').has_value('on'), + use('OSC2 Detune').if_parameter('OSC2 On/Off').has_value('on'), + use('PEG2 Amount').if_parameter('OSC2 On/Off').has_value('on'), + use('PEG2 Time').if_parameter('OSC2 On/Off').has_value('on'), + use('O2 Keytrack').if_parameter('OSC2 On/Off').has_value('on'), + use('').if_parameter('OSC2 On/Off').has_value('off').else_use('').if_parameter('LFO2 On/Off').has_value('off').else_use('OSC2 < LFO')) + }), + ( + 'Filters', + {PARAMETERS_KEY: ( + 'F1 On/Off', + use('F1 Type').if_parameter('F1 On/Off').has_value('on'), + use('F1 Freq').if_parameter('F1 On/Off').has_value('on'), + use('F1 Resonance').if_parameter('F1 On/Off').has_value('on'), 'F2 On/Off', - use('F2 Type').if_parameter('F2 On/Off').has_value('1'), - use('F2 Freq').if_parameter('F2 On/Off').has_value('1'), - use('F2 Resonance').if_parameter('F2 On/Off').has_value('1'))}), - ('Filt. 1 Env.', {PARAMETERS_KEY: ('F1 On/Off', - use('FEG1 < Vel').if_parameter('F1 On/Off').has_value('1'), - use('FEG1 A < Vel').if_parameter('F1 On/Off').has_value('1'), - use('FEG1 Attack').if_parameter('F1 On/Off').has_value('1'), - use('FEG1 Decay').if_parameter('F1 On/Off').has_value('1'), - use('FEG1 Sustain').if_parameter('F1 On/Off').has_value('1'), - use('FEG1 S Time').if_parameter('F1 On/Off').has_value('1'), - use('FEG1 Rel').if_parameter('F1 On/Off').has_value('1'))}), - ('Filt. 2 Env.', {PARAMETERS_KEY: ('F2 On/Off', - use('FEG2 < Vel').if_parameter('F1 On/Off').has_value('1'), - use('FEG2 A < Vel').if_parameter('F1 On/Off').has_value('1'), - use('FEG2 Attack').if_parameter('F1 On/Off').has_value('1'), - use('FEG2 Decay').if_parameter('F1 On/Off').has_value('1'), - use('FEG2 Sustain').if_parameter('F1 On/Off').has_value('1'), - use('FEG2 S Time').if_parameter('F1 On/Off').has_value('1'), - use('FEG2 Rel').if_parameter('F1 On/Off').has_value('1'))}), - ('Filt. Modulation', {PARAMETERS_KEY: ('F1 On/Off', - use('F1 Freq < LFO').if_parameter('F1 On/Off').has_value('1'), - use('F1 Freq < Env').if_parameter('F1 On/Off').has_value('1'), - use('F1 Res < LFO').if_parameter('F1 On/Off').has_value('1'), + use('F2 Type').if_parameter('F2 On/Off').has_value('on'), + use('F2 Freq').if_parameter('F2 On/Off').has_value('on'), + use('F2 Resonance').if_parameter('F2 On/Off').has_value('on')) + }), + ( + 'Filt. 1 Env.', + {PARAMETERS_KEY: ( + 'F1 On/Off', + use('FEG1 < Vel').if_parameter('F1 On/Off').has_value('on'), + use('FEG1 A < Vel').if_parameter('F1 On/Off').has_value('on'), + use('FEG1 Attack').if_parameter('F1 On/Off').has_value('on'), + use('FEG1 Decay').if_parameter('F1 On/Off').has_value('on'), + use('FEG1 Sustain').if_parameter('F1 On/Off').has_value('on'), + use('FEG1 S Time').if_parameter('F1 On/Off').has_value('on'), + use('FEG1 Rel').if_parameter('F1 On/Off').has_value('on')) + }), + ( + 'Filt. 2 Env.', + {PARAMETERS_KEY: ( 'F2 On/Off', - use('F2 Freq < LFO').if_parameter('F2 On/Off').has_value('1'), - use('F2 Freq < Env').if_parameter('F2 On/Off').has_value('1'), - use('F2 Res < LFO').if_parameter('F2 On/Off').has_value('1'))}), - ('Amp', {PARAMETERS_KEY: (use('AMP1 Level').if_parameter('AMP1 On/Off').has_value('1'), - use('AMP1 Pan').if_parameter('AMP1 On/Off').has_value('1'), - use('AMP1 < LFO').if_parameter('AMP1 On/Off').has_value('1'), - use('').if_parameter('LFO1 On/Off').has_value('0').else_use('LFO1 Speed').if_parameter('LFO1 Sync').has_value('Hertz').else_use('LFO1 SncRate'), - use('AMP2 Level').if_parameter('AMP2 On/Off').has_value('1'), - use('AMP2 Pan').if_parameter('AMP2 On/Off').has_value('1'), - use('AMP2 < LFO').if_parameter('AMP2 On/Off').has_value('1'), - use('').if_parameter('LFO2 On/Off').has_value('0').else_use('LFO2 Speed').if_parameter('LFO2 Sync').has_value('Hertz').else_use('LFO2 SncRate'))}), - ('Amp 1 Envelope', {PARAMETERS_KEY: ('AMP1 On/Off', - use('AEG1 < Vel').if_parameter('AMP1 On/Off').has_value('1'), - use('AEG1 A < Vel').if_parameter('AMP1 On/Off').has_value('1'), - use('AEG1 Attack').if_parameter('AMP1 On/Off').has_value('1'), - use('AEG1 Decay').if_parameter('AMP1 On/Off').has_value('1'), - use('AEG1 Sustain').if_parameter('AMP1 On/Off').has_value('1'), - use('AEG1 S Time').if_parameter('AMP1 On/Off').has_value('1'), - use('AEG1 Rel').if_parameter('AMP1 On/Off').has_value('1'))}), - ('Amp 2 Envelope', {PARAMETERS_KEY: ('AMP2 On/Off', - use('AEG2 < Vel').if_parameter('AMP2 On/Off').has_value('1'), - use('AEG2 A < Vel').if_parameter('AMP2 On/Off').has_value('1'), - use('AEG2 Attack').if_parameter('AMP2 On/Off').has_value('1'), - use('AEG2 Decay').if_parameter('AMP2 On/Off').has_value('1'), - use('AEG2 Sustain').if_parameter('AMP2 On/Off').has_value('1'), - use('AEG2 S Time').if_parameter('AMP2 On/Off').has_value('1'), - use('AEG2 Rel').if_parameter('AMP2 On/Off').has_value('1'))}), - ('Noise & Unison', {PARAMETERS_KEY: ('Noise On/Off', - use('Noise Level').if_parameter('Noise On/Off').has_value('1'), - use('Noise Color').if_parameter('Noise On/Off').has_value('1'), - use('Noise Balance').if_parameter('Noise On/Off').has_value('1'), + use('FEG2 < Vel').if_parameter('F1 On/Off').has_value('on'), + use('FEG2 A < Vel').if_parameter('F1 On/Off').has_value('on'), + use('FEG2 Attack').if_parameter('F1 On/Off').has_value('on'), + use('FEG2 Decay').if_parameter('F1 On/Off').has_value('on'), + use('FEG2 Sustain').if_parameter('F1 On/Off').has_value('on'), + use('FEG2 S Time').if_parameter('F1 On/Off').has_value('on'), + use('FEG2 Rel').if_parameter('F1 On/Off').has_value('on')) + }), + ( + 'Filt. Modulation', + {PARAMETERS_KEY: ( + 'F1 On/Off', + use('F1 Freq < LFO').if_parameter('F1 On/Off').has_value('on'), + use('F1 Freq < Env').if_parameter('F1 On/Off').has_value('on'), + use('F1 Res < LFO').if_parameter('F1 On/Off').has_value('on'), + 'F2 On/Off', + use('F2 Freq < LFO').if_parameter('F2 On/Off').has_value('on'), + use('F2 Freq < Env').if_parameter('F2 On/Off').has_value('on'), + use('F2 Res < LFO').if_parameter('F2 On/Off').has_value('on')) + }), + ( + 'Amp', + {PARAMETERS_KEY: ( + use('AMP1 Level').if_parameter('AMP1 On/Off').has_value('on'), + use('AMP1 Pan').if_parameter('AMP1 On/Off').has_value('on'), + use('AMP1 < LFO').if_parameter('AMP1 On/Off').has_value('on'), + use('').if_parameter('LFO1 On/Off').has_value('off').else_use('LFO1 Speed').if_parameter('LFO1 Sync').has_value('Hertz').else_use('LFO1 SncRate'), + use('AMP2 Level').if_parameter('AMP2 On/Off').has_value('on'), + use('AMP2 Pan').if_parameter('AMP2 On/Off').has_value('on'), + use('AMP2 < LFO').if_parameter('AMP2 On/Off').has_value('on'), + use('').if_parameter('LFO2 On/Off').has_value('off').else_use('LFO2 Speed').if_parameter('LFO2 Sync').has_value('Hertz').else_use('LFO2 SncRate')) + }), + ( + 'Amp 1 Envelope', + {PARAMETERS_KEY: ( + 'AMP1 On/Off', + use('AEG1 < Vel').if_parameter('AMP1 On/Off').has_value('on'), + use('AEG1 A < Vel').if_parameter('AMP1 On/Off').has_value('on'), + use('AEG1 Attack').if_parameter('AMP1 On/Off').has_value('on'), + use('AEG1 Decay').if_parameter('AMP1 On/Off').has_value('on'), + use('AEG1 Sustain').if_parameter('AMP1 On/Off').has_value('on'), + use('AEG1 S Time').if_parameter('AMP1 On/Off').has_value('on'), + use('AEG1 Rel').if_parameter('AMP1 On/Off').has_value('on')) + }), + ( + 'Amp 2 Envelope', + {PARAMETERS_KEY: ( + 'AMP2 On/Off', + use('AEG2 < Vel').if_parameter('AMP2 On/Off').has_value('on'), + use('AEG2 A < Vel').if_parameter('AMP2 On/Off').has_value('on'), + use('AEG2 Attack').if_parameter('AMP2 On/Off').has_value('on'), + use('AEG2 Decay').if_parameter('AMP2 On/Off').has_value('on'), + use('AEG2 Sustain').if_parameter('AMP2 On/Off').has_value('on'), + use('AEG2 S Time').if_parameter('AMP2 On/Off').has_value('on'), + use('AEG2 Rel').if_parameter('AMP2 On/Off').has_value('on')) + }), + ( + 'Noise & Unison', + {PARAMETERS_KEY: ( + 'Noise On/Off', + use('Noise Level').if_parameter('Noise On/Off').has_value('on'), + use('Noise Color').if_parameter('Noise On/Off').has_value('on'), + use('Noise Balance').if_parameter('Noise On/Off').has_value('on'), 'Unison On/Off', - use('Unison Detune').if_parameter('Unison On/Off').has_value('1'), - use('Unison Delay').if_parameter('Unison On/Off').has_value('1'), - '')}), - ('Performance', {PARAMETERS_KEY: ('Glide On/Off', - use('Glide Time').if_parameter('Glide On/Off').has_value('1'), - use('Glide Mode').if_parameter('Glide On/Off').has_value('1'), - use('Glide Legato').if_parameter('Glide On/Off').has_value('1'), - 'PB Range', - 'Key Stretch', - 'Key Error', - 'Voices')}), - ('LFO 1', {PARAMETERS_KEY: ('LFO1 On/Off', - use('LFO1 Sync').if_parameter('LFO1 On/Off').has_value('1'), - use('').if_parameter('LFO1 On/Off').has_value('0').else_use('LFO1 Speed').if_parameter('LFO1 Sync').has_value('Hertz').else_use('LFO1 SncRate'), - use('LFO1 Shape').if_parameter('LFO1 On/Off').has_value('1'), - use('').if_parameter('LFO1 On/Off').has_value('0').else_use('LFO1 PW').if_parameter('LFO1 Shape').has_value('Rect').else_use('LFO1 PW').if_parameter('LFO1 Shape').has_value('Tri'), - use('LFO1 Phase').if_parameter('LFO1 On/Off').has_value('1'), - use('LFO1 Delay').if_parameter('LFO1 On/Off').has_value('1'), - use('LFO1 Fade In').if_parameter('LFO1 On/Off').has_value('1'))}), - ('LFO 2', {PARAMETERS_KEY: ('LFO2 On/Off', - use('LFO2 Sync').if_parameter('LFO2 On/Off').has_value('1'), - use('').if_parameter('LFO2 On/Off').has_value('0').else_use('LFO2 Speed').if_parameter('LFO2 Sync').has_value('Hertz').else_use('LFO2 SncRate'), - use('LFO2 Shape').if_parameter('LFO2 On/Off').has_value('1'), - use('').if_parameter('LFO2 On/Off').has_value('0').else_use('LFO2 PW').if_parameter('LFO2 Shape').has_value('Rect').else_use('LFO2 PW').if_parameter('LFO2 Shape').has_value('Tri'), - use('LFO2 Phase').if_parameter('LFO2 On/Off').has_value('1'), - use('LFO2 Delay').if_parameter('LFO2 On/Off').has_value('1'), - use('LFO2 Fade In').if_parameter('LFO2 On/Off').has_value('1'))}), - ('Vibrato', {PARAMETERS_KEY: ('Vib On/Off', - use('Vib Amount').if_parameter('Vib On/Off').has_value('1'), - use('Vib Speed').if_parameter('Vib On/Off').has_value('1'), - use('Vib Delay').if_parameter('Vib On/Off').has_value('1'), - use('Vib Fade-In').if_parameter('Vib On/Off').has_value('1'), - use('Vib Error').if_parameter('Vib On/Off').has_value('1'), - use('Vib < ModWh').if_parameter('Vib On/Off').has_value('1'), - '')}))), - 'Collision': IndexedDict(((MAIN_KEY, {PARAMETERS_KEY: (use('Res 1 Type').if_parameter('Res 1 On/Off').has_value('on'), + use('Unison Detune').if_parameter('Unison On/Off').has_value('on'), + use('Unison Delay').if_parameter('Unison On/Off').has_value('on'), + '') + }), + ( + 'Performance', + {PARAMETERS_KEY: ( + 'Glide On/Off', + use('Glide Time').if_parameter('Glide On/Off').has_value('on'), + use('Glide Mode').if_parameter('Glide On/Off').has_value('on'), + use('Glide Legato').if_parameter('Glide On/Off').has_value('on'), + 'PB Range', 'Key Stretch', 'Key Error', 'Voices') + }), + ( + 'LFO 1', + {PARAMETERS_KEY: ( + 'LFO1 On/Off', + use('LFO1 Sync').if_parameter('LFO1 On/Off').has_value('on'), + use('').if_parameter('LFO1 On/Off').has_value('off').else_use('LFO1 Speed').if_parameter('LFO1 Sync').has_value('Hertz').else_use('LFO1 SncRate'), + use('LFO1 Shape').if_parameter('LFO1 On/Off').has_value('on'), + use('').if_parameter('LFO1 On/Off').has_value('off').else_use('LFO1 PW').if_parameter('LFO1 Shape').has_value('Rect').else_use('LFO1 PW').if_parameter('LFO1 Shape').has_value('Tri'), + use('LFO1 Phase').if_parameter('LFO1 On/Off').has_value('on'), + use('LFO1 Delay').if_parameter('LFO1 On/Off').has_value('on'), + use('LFO1 Fade In').if_parameter('LFO1 On/Off').has_value('on')) + }), + ( + 'LFO 2', + {PARAMETERS_KEY: ( + 'LFO2 On/Off', + use('LFO2 Sync').if_parameter('LFO2 On/Off').has_value('on'), + use('').if_parameter('LFO2 On/Off').has_value('off').else_use('LFO2 Speed').if_parameter('LFO2 Sync').has_value('Hertz').else_use('LFO2 SncRate'), + use('LFO2 Shape').if_parameter('LFO2 On/Off').has_value('on'), + use('').if_parameter('LFO2 On/Off').has_value('off').else_use('LFO2 PW').if_parameter('LFO2 Shape').has_value('Rect').else_use('LFO2 PW').if_parameter('LFO2 Shape').has_value('Tri'), + use('LFO2 Phase').if_parameter('LFO2 On/Off').has_value('on'), + use('LFO2 Delay').if_parameter('LFO2 On/Off').has_value('on'), + use('LFO2 Fade In').if_parameter('LFO2 On/Off').has_value('on')) + }), + ( + 'Vibrato', + {PARAMETERS_KEY: ( + 'Vib On/Off', + use('Vib Amount').if_parameter('Vib On/Off').has_value('on'), + use('Vib Speed').if_parameter('Vib On/Off').has_value('on'), + use('Vib Delay').if_parameter('Vib On/Off').has_value('on'), + use('Vib Fade-In').if_parameter('Vib On/Off').has_value('on'), + use('Vib Error').if_parameter('Vib On/Off').has_value('on'), + use('Vib < ModWh').if_parameter('Vib On/Off').has_value('on'), + '') + }))), + 'Collision': IndexedDict(( + ( + MAIN_KEY, + {PARAMETERS_KEY: ( + use('Res 1 Type').if_parameter('Res 1 On/Off').has_value('on'), use('').if_parameter('Res 1 On/Off').has_value('off').else_use('').if_parameter('Res 1 Type').has_value('Tube').else_use('').if_parameter('Res 1 Type').has_value('Pipe').else_use('Res 1 Brightness'), use('').if_parameter('Res 1 On/Off').has_value('off').else_use('').if_parameter('Res 1 Type').has_value('Tube').else_use('Res 1 Opening').if_parameter('Res 1 Type').has_value('Pipe').else_use('Res 1 Inharmonics'), use('Res 1 Decay').if_parameter('Res 1 On/Off').has_value('on'), use('').if_parameter('Res 1 On/Off').has_value('off').else_use('Res 1 Radius').if_parameter('Res 1 Type').has_value('Tube').else_use('Res 1 Radius').if_parameter('Res 1 Type').has_value('Pipe').else_use('Res 1 Material'), use('Mallet Stiffness').if_parameter('Mallet On/Off').has_value('on'), use('Mallet Noise Amount').if_parameter('Mallet On/Off').has_value('on'), - 'Volume')}), - ('Mix', {PARAMETERS_KEY: (use('Res 1 Volume').if_parameter('Res 1 On/Off').has_value('on'), + 'Volume') + }), + ( + 'Mix', + {PARAMETERS_KEY: ( + use('Res 1 Volume').if_parameter('Res 1 On/Off').has_value('on'), use('Panorama').if_parameter('Res 1 On/Off').has_value('on'), use('Res 1 Bleed').if_parameter('Res 1 On/Off').has_value('on'), use('Res 2 Volume').if_parameter('Res 2 On/Off').has_value('on'), use('Res 2 Panorama').if_parameter('Res 2 On/Off').has_value('on'), use('Res 2 Bleed').if_parameter('Res 2 On/Off').has_value('on'), - 'Structure', - 'Volume')}), - ('Mallet', {PARAMETERS_KEY: ('Mallet On/Off', + 'Structure', 'Volume') + }), + ( + 'Mallet', + {PARAMETERS_KEY: ( + 'Mallet On/Off', use('Mallet Volume').if_parameter('Mallet On/Off').has_value('on'), use('Mallet Noise Amount').if_parameter('Mallet On/Off').has_value('on'), use('Mallet Stiffness').if_parameter('Mallet On/Off').has_value('on'), use('Mallet Noise Color').if_parameter('Mallet On/Off').has_value('on'), use('Mallet Modulation').if_parameter('Mallet On/Off').has_value('on'), use('Mallet Volume < Vel').if_parameter('Mallet On/Off').has_value('on'), - use('Mallet Noise Amount < Vel').if_parameter('Mallet On/Off').has_value('on'))}), - ('Noise Envelope', {PARAMETERS_KEY: ('Noise On/Off', + use('Mallet Noise Amount < Vel').if_parameter('Mallet On/Off').has_value('on')) + }), + ( + 'Noise Envelope', + {PARAMETERS_KEY: ( + 'Noise On/Off', use('Noise Volume').if_parameter('Noise On/Off').has_value('on'), use('Noise Volume < Key').if_parameter('Noise On/Off').has_value('on'), use('Noise Volume < Vel').if_parameter('Noise On/Off').has_value('on'), use('Noise Attack').if_parameter('Noise On/Off').has_value('on'), use('Noise Sustain').if_parameter('Noise On/Off').has_value('on'), use('Noise Decay').if_parameter('Noise On/Off').has_value('on'), - use('Noise Release').if_parameter('Noise On/Off').has_value('on'))}), - ('Noise Filter', {PARAMETERS_KEY: ('Noise On/Off', + use('Noise Release').if_parameter('Noise On/Off').has_value('on')) + }), + ( + 'Noise Filter', + {PARAMETERS_KEY: ( + 'Noise On/Off', use('Noise Volume').if_parameter('Noise On/Off').has_value('on'), use('Noise Filter Type').if_parameter('Noise On/Off').has_value('on'), use('Noise Filter Freq').if_parameter('Noise On/Off').has_value('on'), use('Noise Filter Q').if_parameter('Noise On/Off').has_value('on'), use('Noise Freq < Key').if_parameter('Noise On/Off').has_value('on'), use('Noise Freq < Vel').if_parameter('Noise On/Off').has_value('on'), - use('Noise Freq < Env').if_parameter('Noise On/Off').has_value('on'))}), - ('Res. 1 Body', {PARAMETERS_KEY: ('Res 1 On/Off', + use('Noise Freq < Env').if_parameter('Noise On/Off').has_value('on')) + }), + ( + 'Res. 1 Body', + {PARAMETERS_KEY: ( + 'Res 1 On/Off', use('Res 1 Type').if_parameter('Res 1 On/Off').has_value('on'), use('').if_parameter('Res 1 On/Off').has_value('off').else_use('Res 1 Ratio').if_parameter('Res 1 Type').has_value('Plate').else_use('Res 1 Ratio').if_parameter('Res 1 Type').has_value('Membrane'), use('Res 1 Decay').if_parameter('Res 1 On/Off').has_value('on'), use('').if_parameter('Res 1 On/Off').has_value('off').else_use('Res 1 Radius').if_parameter('Res 1 Type').has_value('Tube').else_use('Res 1 Radius').if_parameter('Res 1 Type').has_value('Pipe').else_use('Res 1 Material'), use('').if_parameter('Res 1 On/Off').has_value('off').else_use('').if_parameter('Res 1 Type').has_value('Tube').else_use('').if_parameter('Res 1 Type').has_value('Pipe').else_use('Res 1 Listening L'), use('').if_parameter('Res 1 On/Off').has_value('off').else_use('').if_parameter('Res 1 Type').has_value('Tube').else_use('').if_parameter('Res 1 Type').has_value('Pipe').else_use('Res 1 Listening R'), - use('').if_parameter('Res 1 On/Off').has_value('off').else_use('').if_parameter('Res 1 Type').has_value('Tube').else_use('').if_parameter('Res 1 Type').has_value('Pipe').else_use('Res 1 Hit'))}), - ('Res. 1 Tune', {PARAMETERS_KEY: ('Res 1 On/Off', + use('').if_parameter('Res 1 On/Off').has_value('off').else_use('').if_parameter('Res 1 Type').has_value('Tube').else_use('').if_parameter('Res 1 Type').has_value('Pipe').else_use('Res 1 Hit')) + }), + ( + 'Res. 1 Tune', + {PARAMETERS_KEY: ( + 'Res 1 On/Off', use('').if_parameter('Res 1 On/Off').has_value('off').else_use('').if_parameter('Res 1 Type').has_value('Tube').else_use('').if_parameter('Res 1 Type').has_value('Pipe').else_use('Res 1 Brightness'), use('Res 1 Quality').if_parameter('Res 1 On/Off').has_value('on'), use('').if_parameter('Res 1 On/Off').has_value('off').else_use('').if_parameter('Res 1 Type').has_value('Tube').else_use('Res 1 Opening').if_parameter('Res 1 Type').has_value('Pipe').else_use('Res 1 Inharmonics'), use('Res 1 Tune').if_parameter('Res 1 On/Off').has_value('on'), use('Res 1 Fine Tune').if_parameter('Res 1 On/Off').has_value('on'), use('Res 1 Pitch Env.').if_parameter('Res 1 On/Off').has_value('on'), - use('Res 1 Pitch Env. Time').if_parameter('Res 1 On/Off').has_value('on'))}), - ('Res. 2 Body', {PARAMETERS_KEY: ('Res 2 On/Off', + use('Res 1 Pitch Env. Time').if_parameter('Res 1 On/Off').has_value('on')) + }), + ( + 'Res. 2 Body', + {PARAMETERS_KEY: ( + 'Res 2 On/Off', use('Res 2 Type').if_parameter('Res 2 On/Off').has_value('on'), use('').if_parameter('Res 2 On/Off').has_value('off').else_use('Res 2 Ratio').if_parameter('Res 2 Type').has_value('Plate').else_use('Res 2 Ratio').if_parameter('Res 2 Type').has_value('Membrane'), use('Res 2 Decay').if_parameter('Res 2 On/Off').has_value('on'), use('').if_parameter('Res 2 On/Off').has_value('off').else_use('Res 2 Radius').if_parameter('Res 2 Type').has_value('Tube').else_use('Res 2 Radius').if_parameter('Res 2 Type').has_value('Pipe').else_use('Res 2 Material'), use('').if_parameter('Res 2 On/Off').has_value('off').else_use('').if_parameter('Res 2 Type').has_value('Tube').else_use('').if_parameter('Res 2 Type').has_value('Pipe').else_use('Res 2 Listening L'), use('').if_parameter('Res 2 On/Off').has_value('off').else_use('').if_parameter('Res 2 Type').has_value('Tube').else_use('').if_parameter('Res 2 Type').has_value('Pipe').else_use('Res 2 Listening R'), - use('').if_parameter('Res 2 On/Off').has_value('off').else_use('').if_parameter('Res 2 Type').has_value('Tube').else_use('').if_parameter('Res 2 Type').has_value('Pipe').else_use('Res 2 Hit'))}), - ('Res. 2 Tune', {PARAMETERS_KEY: ('Res 2 On/Off', + use('').if_parameter('Res 2 On/Off').has_value('off').else_use('').if_parameter('Res 2 Type').has_value('Tube').else_use('').if_parameter('Res 2 Type').has_value('Pipe').else_use('Res 2 Hit')) + }), + ( + 'Res. 2 Tune', + {PARAMETERS_KEY: ( + 'Res 2 On/Off', use('').if_parameter('Res 2 On/Off').has_value('off').else_use('').if_parameter('Res 2 Type').has_value('Tube').else_use('').if_parameter('Res 2 Type').has_value('Pipe').else_use('Res 2 Brightness'), use('Res 2 Quality').if_parameter('Res 2 On/Off').has_value('on'), use('').if_parameter('Res 2 On/Off').has_value('off').else_use('').if_parameter('Res 2 Type').has_value('Tube').else_use('Res 2 Opening').if_parameter('Res 2 Type').has_value('Pipe').else_use('Res 2 Inharmonics'), use('Res 2 Tune').if_parameter('Res 2 On/Off').has_value('on'), use('Res 2 Fine Tune').if_parameter('Res 2 On/Off').has_value('on'), use('Res 2 Pitch Env.').if_parameter('Res 2 On/Off').has_value('on'), - use('Res 2 Pitch Env. Time').if_parameter('Res 2 On/Off').has_value('on'))}), - ('LFO 1', {PARAMETERS_KEY: ('LFO 1 On/Off', + use('Res 2 Pitch Env. Time').if_parameter('Res 2 On/Off').has_value('on')) + }), + ( + 'LFO 1', + {PARAMETERS_KEY: ( + 'LFO 1 On/Off', use('LFO 1 Depth').if_parameter('LFO 1 On/Off').has_value('on'), use('LFO 1 Shape').if_parameter('LFO 1 On/Off').has_value('on'), use('LFO 1 Sync').if_parameter('LFO 1 On/Off').has_value('on'), use('').if_parameter('LFO 1 On/Off').has_value('off').else_use('LFO 1 Sync Rate').if_parameter('LFO 1 Sync').has_value('Sync').else_use('LFO 1 Rate'), use('LFO 1 Offset').if_parameter('LFO 1 On/Off').has_value('on'), use('LFO 1 Destination A').if_parameter('LFO 1 On/Off').has_value('on'), - use('LFO 1 Destination A Amount').if_parameter('LFO 1 On/Off').has_value('on'))}), - ('LFO 2', {PARAMETERS_KEY: ('LFO 2 On/Off', + use('LFO 1 Destination A Amount').if_parameter('LFO 1 On/Off').has_value('on')) + }), + ( + 'LFO 2', + {PARAMETERS_KEY: ( + 'LFO 2 On/Off', use('LFO 2 Depth').if_parameter('LFO 2 On/Off').has_value('on'), use('LFO 2 Shape').if_parameter('LFO 2 On/Off').has_value('on'), use('LFO 2 Sync').if_parameter('LFO 2 On/Off').has_value('on'), use('').if_parameter('LFO 2 On/Off').has_value('off').else_use('LFO 2 Sync Rate').if_parameter('LFO 2 Sync').has_value('Sync').else_use('LFO 2 Rate'), use('LFO 2 Offset').if_parameter('LFO 2 On/Off').has_value('on'), use('LFO 2 Destination A').if_parameter('LFO 2 On/Off').has_value('on'), - use('LFO 2 Destination A Amount').if_parameter('LFO 2 On/Off').has_value('on'))}), - ('Mallet Mod.', {PARAMETERS_KEY: ('Mallet On/Off', + use('LFO 2 Destination A Amount').if_parameter('LFO 2 On/Off').has_value('on')) + }), + ( + 'Mallet Mod.', + {PARAMETERS_KEY: ( + 'Mallet On/Off', use('Mallet Volume < Key').if_parameter('Mallet On/Off').has_value('on'), use('Mallet Volume < Vel').if_parameter('Mallet On/Off').has_value('on'), use('Mallet Noise Amount < Key').if_parameter('Mallet On/Off').has_value('on'), use('Mallet Noise Amount < Vel').if_parameter('Mallet On/Off').has_value('on'), use('Mallet Stiffness < Key').if_parameter('Mallet On/Off').has_value('on'), use('Mallet Stiffness < Vel').if_parameter('Mallet On/Off').has_value('on'), - '')}))), - 'LoungeLizard': IndexedDict(((MAIN_KEY, {PARAMETERS_KEY: ('M Stiffness', 'M Force', 'Noise Amount', 'F Tine Vol', 'F Tone Vol', 'F Release', 'P Symmetry', 'Volume')}), - ('Mallet', {PARAMETERS_KEY: ('M Stiffness', 'M Force', 'Noise Pitch', 'Noise Decay', 'Noise Amount', 'M Stiff < Vel', 'M Force < Vel', 'Volume')}), - ('Fork', {PARAMETERS_KEY: ('F Tine Color', 'F Tine Decay', 'F Tine Vol', 'F Tone Vol', 'F Tone Decay', 'F Release', 'F Tine < Key', 'Volume')}), - ('Damper', {PARAMETERS_KEY: ('Damp Tone', 'Damp Balance', 'Damp Amount', '', '', '', '', 'Volume')}), - ('Pickup', {PARAMETERS_KEY: ('P Symmetry', 'P Distance', 'P Amp In', 'P Amp Out', 'Pickup Model', 'P Amp < Key', '', 'Volume')}), - ('Modulation', {PARAMETERS_KEY: ('M Stiff < Vel', 'M Stiff < Key', 'M Force < Vel', 'M Force < Key', 'Noise < Key', 'F Tine < Key', 'P Amp < Key', 'Volume')}), - ('Global', {PARAMETERS_KEY: ('KB Stretch', 'PB Range', '', '', 'Voices', 'Semitone', 'Detune', 'Volume')}))), - 'InstrumentImpulse': IndexedDict(((MAIN_KEY, {PARAMETERS_KEY: ('1 Transpose', '1 Volume', '3 Transpose', '3 Volume', '7 Transpose', '7 Volume', '8 Transpose', '8 Volume')}), - ('Pad 1', {PARAMETERS_KEY: ('1 Start', '1 Envelope Decay', '1 Stretch Factor', '1 Saturator Drive', '1 Envelope Type', '1 Transpose', '1 Volume <- Vel', '1 Volume')}), - ('1 Filt/Mod/Pan', {PARAMETERS_KEY: ('1 Filter Type', '1 Filter Freq', '1 Filter Res', '1 Filter <- Vel', '1 Filter <- Random', '1 Pan', '1 Pan <- Vel', '1 Pan <- Random')}), - ('Pad 2', {PARAMETERS_KEY: ('2 Start', '2 Envelope Decay', '2 Stretch Factor', '2 Saturator Drive', '2 Envelope Type', '2 Transpose', '2 Volume <- Vel', '2 Volume')}), - ('2 Filt/Mod/Pan', {PARAMETERS_KEY: ('2 Filter Type', '2 Filter Freq', '2 Filter Res', '2 Filter <- Vel', '2 Filter <- Random', '2 Pan', '2 Pan <- Vel', '2 Pan <- Random')}), - ('Pad 3', {PARAMETERS_KEY: ('3 Start', '3 Envelope Decay', '3 Stretch Factor', '3 Saturator Drive', '3 Envelope Type', '3 Transpose', '3 Volume <- Vel', '3 Volume')}), - ('3 Filt/Mod/Pan', {PARAMETERS_KEY: ('3 Filter Type', '3 Filter Freq', '3 Filter Res', '3 Filter <- Vel', '3 Filter <- Random', '3 Pan', '3 Pan <- Vel', '3 Pan <- Random')}), - ('Pad 4', {PARAMETERS_KEY: ('4 Start', '4 Envelope Decay', '4 Stretch Factor', '4 Saturator Drive', '4 Envelope Type', '4 Transpose', '4 Volume <- Vel', '4 Volume')}), - ('4 Filt/Mod/Pan', {PARAMETERS_KEY: ('4 Filter Type', '4 Filter Freq', '4 Filter Res', '4 Filter <- Vel', '4 Filter <- Random', '4 Pan', '4 Pan <- Vel', '4 Pan <- Random')}), - ('Pad 5', {PARAMETERS_KEY: ('5 Start', '5 Envelope Decay', '5 Stretch Factor', '5 Saturator Drive', '5 Envelope Type', '5 Transpose', '5 Volume <- Vel', '5 Volume')}), - ('5 Filt/Mod/Pan', {PARAMETERS_KEY: ('5 Filter Type', '5 Filter Freq', '5 Filter Res', '5 Filter <- Vel', '5 Filter <- Random', '5 Pan', '5 Pan <- Vel', '5 Pan <- Random')}), - ('Pad 6', {PARAMETERS_KEY: ('6 Start', '6 Envelope Decay', '6 Stretch Factor', '6 Saturator Drive', '6 Envelope Type', '6 Transpose', '6 Volume <- Vel', '6 Volume')}), - ('6 Filt/Mod/Pan', {PARAMETERS_KEY: ('6 Filter Type', '6 Filter Freq', '6 Filter Res', '6 Filter <- Vel', '6 Filter <- Random', '6 Pan', '6 Pan <- Vel', '6 Pan <- Random')}), - ('Pad 7', {PARAMETERS_KEY: ('7 Start', '7 Envelope Decay', '7 Stretch Factor', '7 Saturator Drive', '7 Envelope Type', '7 Transpose', '7 Volume <- Vel', '7 Volume')}), - ('7 Filt/Mod/Pan', {PARAMETERS_KEY: ('7 Filter Type', '7 Filter Freq', '7 Filter Res', '7 Filter <- Vel', '7 Filter <- Random', '7 Pan', '7 Pan <- Vel', '7 Pan <- Random')}), - ('Pad 8', {PARAMETERS_KEY: ('8 Start', '8 Envelope Decay', '8 Stretch Factor', '8 Saturator Drive', '8 Envelope Type', '8 Transpose', '8 Volume <- Vel', '8 Volume')}), - ('8 Filt/Mod/Pan', {PARAMETERS_KEY: ('8 Filter Type', '8 Filter Freq', '8 Filter Res', '8 Filter <- Vel', '8 Filter <- Random', '8 Pan', '8 Pan <- Vel', '8 Pan <- Random')}), - ('Stretch Modes', {PARAMETERS_KEY: ('1 Stretch Mode', '2 Stretch Mode', '3 Stretch Mode', '4 Stretch Mode', '5 Stretch Mode', '6 Stretch Mode', '7 Stretch Mode', '8 Stretch Mode')}), - ('Global', {PARAMETERS_KEY: ('Global Time', 'Global Transpose', 'Global Volume', '', '', '', '', '')}))), - 'Operator': IndexedDict((('Oscillators', {PARAMETERS_KEY: ('Oscillator', + '') + }))), + 'LoungeLizard': IndexedDict(( + ( + MAIN_KEY, + {PARAMETERS_KEY: ('M Stiffness', 'M Force', 'Noise Amount', 'F Tine Vol', 'F Tone Vol', 'F Release', + 'P Symmetry', 'Volume') + }), + ( + 'Mallet', + {PARAMETERS_KEY: ('M Stiffness', 'M Force', 'Noise Pitch', 'Noise Decay', 'Noise Amount', 'M Stiff < Vel', + 'M Force < Vel', 'Volume') + }), + ( + 'Fork', + {PARAMETERS_KEY: ('F Tine Color', 'F Tine Decay', 'F Tine Vol', 'F Tone Vol', 'F Tone Decay', 'F Release', + 'F Tine < Key', 'Volume') + }), + ( + 'Damper', + {PARAMETERS_KEY: ('Damp Tone', 'Damp Balance', 'Damp Amount', '', '', '', '', 'Volume') + }), + ( + 'Pickup', + {PARAMETERS_KEY: ('P Symmetry', 'P Distance', 'P Amp In', 'P Amp Out', 'Pickup Model', 'P Amp < Key', + '', 'Volume') + }), + ( + 'Modulation', + {PARAMETERS_KEY: ('M Stiff < Vel', 'M Stiff < Key', 'M Force < Vel', 'M Force < Key', 'Noise < Key', + 'F Tine < Key', 'P Amp < Key', 'Volume') + }), + ( + 'Global', + {PARAMETERS_KEY: ('KB Stretch', 'PB Range', '', '', 'Voices', 'Semitone', 'Detune', 'Volume') + }))), + 'InstrumentImpulse': IndexedDict(( + ( + MAIN_KEY, + {PARAMETERS_KEY: ('1 Transpose', '1 Volume', '3 Transpose', '3 Volume', '7 Transpose', '7 Volume', '8 Transpose', + '8 Volume') + }), + ( + 'Pad 1', + {PARAMETERS_KEY: ('1 Start', '1 Envelope Decay', '1 Stretch Factor', '1 Saturator Drive', '1 Envelope Type', + '1 Transpose', '1 Volume <- Vel', '1 Volume') + }), + ( + '1 Filt/Mod/Pan', + {PARAMETERS_KEY: ('1 Filter Type', '1 Filter Freq', '1 Filter Res', '1 Filter <- Vel', '1 Filter <- Random', + '1 Pan', '1 Pan <- Vel', '1 Pan <- Random') + }), + ( + 'Pad 2', + {PARAMETERS_KEY: ('2 Start', '2 Envelope Decay', '2 Stretch Factor', '2 Saturator Drive', '2 Envelope Type', + '2 Transpose', '2 Volume <- Vel', '2 Volume') + }), + ( + '2 Filt/Mod/Pan', + {PARAMETERS_KEY: ('2 Filter Type', '2 Filter Freq', '2 Filter Res', '2 Filter <- Vel', '2 Filter <- Random', + '2 Pan', '2 Pan <- Vel', '2 Pan <- Random') + }), + ( + 'Pad 3', + {PARAMETERS_KEY: ('3 Start', '3 Envelope Decay', '3 Stretch Factor', '3 Saturator Drive', '3 Envelope Type', + '3 Transpose', '3 Volume <- Vel', '3 Volume') + }), + ( + '3 Filt/Mod/Pan', + {PARAMETERS_KEY: ('3 Filter Type', '3 Filter Freq', '3 Filter Res', '3 Filter <- Vel', '3 Filter <- Random', + '3 Pan', '3 Pan <- Vel', '3 Pan <- Random') + }), + ( + 'Pad 4', + {PARAMETERS_KEY: ('4 Start', '4 Envelope Decay', '4 Stretch Factor', '4 Saturator Drive', '4 Envelope Type', + '4 Transpose', '4 Volume <- Vel', '4 Volume') + }), + ( + '4 Filt/Mod/Pan', + {PARAMETERS_KEY: ('4 Filter Type', '4 Filter Freq', '4 Filter Res', '4 Filter <- Vel', '4 Filter <- Random', + '4 Pan', '4 Pan <- Vel', '4 Pan <- Random') + }), + ( + 'Pad 5', + {PARAMETERS_KEY: ('5 Start', '5 Envelope Decay', '5 Stretch Factor', '5 Saturator Drive', '5 Envelope Type', + '5 Transpose', '5 Volume <- Vel', '5 Volume') + }), + ( + '5 Filt/Mod/Pan', + {PARAMETERS_KEY: ('5 Filter Type', '5 Filter Freq', '5 Filter Res', '5 Filter <- Vel', '5 Filter <- Random', + '5 Pan', '5 Pan <- Vel', '5 Pan <- Random') + }), + ( + 'Pad 6', + {PARAMETERS_KEY: ('6 Start', '6 Envelope Decay', '6 Stretch Factor', '6 Saturator Drive', '6 Envelope Type', + '6 Transpose', '6 Volume <- Vel', '6 Volume') + }), + ( + '6 Filt/Mod/Pan', + {PARAMETERS_KEY: ('6 Filter Type', '6 Filter Freq', '6 Filter Res', '6 Filter <- Vel', '6 Filter <- Random', + '6 Pan', '6 Pan <- Vel', '6 Pan <- Random') + }), + ( + 'Pad 7', + {PARAMETERS_KEY: ('7 Start', '7 Envelope Decay', '7 Stretch Factor', '7 Saturator Drive', '7 Envelope Type', + '7 Transpose', '7 Volume <- Vel', '7 Volume') + }), + ( + '7 Filt/Mod/Pan', + {PARAMETERS_KEY: ('7 Filter Type', '7 Filter Freq', '7 Filter Res', '7 Filter <- Vel', '7 Filter <- Random', + '7 Pan', '7 Pan <- Vel', '7 Pan <- Random') + }), + ( + 'Pad 8', + {PARAMETERS_KEY: ('8 Start', '8 Envelope Decay', '8 Stretch Factor', '8 Saturator Drive', '8 Envelope Type', + '8 Transpose', '8 Volume <- Vel', '8 Volume') + }), + ( + '8 Filt/Mod/Pan', + {PARAMETERS_KEY: ('8 Filter Type', '8 Filter Freq', '8 Filter Res', '8 Filter <- Vel', '8 Filter <- Random', + '8 Pan', '8 Pan <- Vel', '8 Pan <- Random') + }), + ( + 'Stretch Modes', + {PARAMETERS_KEY: ('1 Stretch Mode', '2 Stretch Mode', '3 Stretch Mode', '4 Stretch Mode', '5 Stretch Mode', + '6 Stretch Mode', '7 Stretch Mode', '8 Stretch Mode') + }), + ( + 'Global', + {PARAMETERS_KEY: ('Global Time', 'Global Transpose', 'Global Volume', '', '', '', '', '') + }))), + 'Operator': IndexedDict(( + ( + 'Oscillators', + {PARAMETERS_KEY: ( + 'Oscillator', use('Osc-A On').if_parameter('Oscillator').has_value('A').else_use('Osc-B On').if_parameter('Oscillator').has_value('B').else_use('Osc-C On').if_parameter('Oscillator').has_value('C').else_use('Osc-D On').if_parameter('Oscillator').has_value('D'), use('Osc-A Wave').if_parameter('Oscillator').has_value('A').else_use('Osc-B Wave').if_parameter('Oscillator').has_value('B').else_use('Osc-C Wave').if_parameter('Oscillator').has_value('C').else_use('Osc-D Wave').if_parameter('Oscillator').has_value('D'), use('A Fix On ').if_parameter('Oscillator').has_value('A').else_use('B Fix On ').if_parameter('Oscillator').has_value('B').else_use('C Fix On ').if_parameter('Oscillator').has_value('C').else_use('D Fix On ').if_parameter('Oscillator').has_value('D'), use('A Fix Freq').if_parameter('Oscillator').has_value('A').and_parameter('A Fix On ').has_value('on').else_use('A Coarse').if_parameter('Oscillator').has_value('A').else_use('B Fix Freq').if_parameter('Oscillator').has_value('B').and_parameter('B Fix On ').has_value('on').else_use('B Coarse').if_parameter('Oscillator').has_value('B').else_use('C Fix Freq').if_parameter('Oscillator').has_value('C').and_parameter('C Fix On ').has_value('on').else_use('C Coarse').if_parameter('Oscillator').has_value('C').else_use('D Fix Freq').if_parameter('Oscillator').has_value('D').and_parameter('D Fix On ').has_value('on').else_use('D Coarse').if_parameter('Oscillator').has_value('D'), use('A Fix Freq Mul').if_parameter('Oscillator').has_value('A').and_parameter('A Fix On ').has_value('on').else_use('A Fine').if_parameter('Oscillator').has_value('A').else_use('B Fix Freq Mul').if_parameter('Oscillator').has_value('B').and_parameter('B Fix On ').has_value('on').else_use('B Fine').if_parameter('Oscillator').has_value('B').else_use('C Fix Freq Mul').if_parameter('Oscillator').has_value('C').and_parameter('C Fix On ').has_value('on').else_use('C Fine').if_parameter('Oscillator').has_value('C').else_use('D Fix Freq Mul').if_parameter('Oscillator').has_value('D').and_parameter('D Fix On ').has_value('on').else_use('D Fine').if_parameter('Oscillator').has_value('D'), use('Osc-A Level').if_parameter('Oscillator').has_value('A').else_use('Osc-B Level').if_parameter('Oscillator').has_value('B').else_use('Osc-C Level').if_parameter('Oscillator').has_value('C').else_use('Osc-D Level').if_parameter('Oscillator').has_value('D'), - 'Algorithm')}), - ('Osc. Envelopes', {PARAMETERS_KEY: ('Oscillator', + 'Algorithm') + }), + ( + 'Osc. Envelopes', + {PARAMETERS_KEY: ( + 'Oscillator', use('Osc-A On').if_parameter('Oscillator').has_value('A').else_use('Osc-B On').if_parameter('Oscillator').has_value('B').else_use('Osc-C On').if_parameter('Oscillator').has_value('C').else_use('Osc-D On').if_parameter('Oscillator').has_value('D'), use('Ae Init').if_parameter('Oscillator').has_value('A').else_use('Be Init').if_parameter('Oscillator').has_value('B').else_use('Ce Init').if_parameter('Oscillator').has_value('C').else_use('De Init').if_parameter('Oscillator').has_value('D'), use('Ae Attack').if_parameter('Oscillator').has_value('A').else_use('Be Attack').if_parameter('Oscillator').has_value('B').else_use('Ce Attack').if_parameter('Oscillator').has_value('C').else_use('De Attack').if_parameter('Oscillator').has_value('D'), use('Ae Peak').if_parameter('Oscillator').has_value('A').else_use('Be Peak').if_parameter('Oscillator').has_value('B').else_use('Ce Peak').if_parameter('Oscillator').has_value('C').else_use('De Peak').if_parameter('Oscillator').has_value('D'), use('Ae Decay').if_parameter('Oscillator').has_value('A').else_use('Be Decay').if_parameter('Oscillator').has_value('B').else_use('Ce Decay').if_parameter('Oscillator').has_value('C').else_use('De Decay').if_parameter('Oscillator').has_value('D'), use('Ae Sustain').if_parameter('Oscillator').has_value('A').else_use('Be Sustain').if_parameter('Oscillator').has_value('B').else_use('Ce Sustain').if_parameter('Oscillator').has_value('C').else_use('De Sustain').if_parameter('Oscillator').has_value('D'), - use('Ae Release').if_parameter('Oscillator').has_value('A').else_use('Be Release').if_parameter('Oscillator').has_value('B').else_use('Ce Release').if_parameter('Oscillator').has_value('C').else_use('De Release').if_parameter('Oscillator').has_value('D'))}), - ('Filter', {PARAMETERS_KEY: ('Filter On', + use('Ae Release').if_parameter('Oscillator').has_value('A').else_use('Be Release').if_parameter('Oscillator').has_value('B').else_use('Ce Release').if_parameter('Oscillator').has_value('C').else_use('De Release').if_parameter('Oscillator').has_value('D')) + }), + ( + 'Filter', + {PARAMETERS_KEY: ( + 'Filter On', use('Filter Type').if_parameter('Filter Type').is_available(True).else_use('Filter Type (Legacy)'), use('Filter Freq'), use('Filter Res').if_parameter('Filter Res').is_available(True).else_use('Filter Res (Legacy)'), use('Filter Circuit - LP/HP').if_parameter('Filter Type').has_value('Lowpass').else_use('Filter Circuit - LP/HP').if_parameter('Filter Type').has_value('Highpass').else_use('Filter Circuit - BP/NO/Morph'), use('Filter Morph').if_parameter('Filter Type').has_value('Morph').else_use('').if_parameter('Filter Type').has_value('Lowpass').and_parameter('Filter Circuit - LP/HP').has_value('Clean').else_use('').if_parameter('Filter Type').has_value('Highpass').and_parameter('Filter Circuit - LP/HP').has_value('Clean').else_use('').if_parameter('Filter Type').has_value('Bandpass').and_parameter('Filter Circuit - BP/NO/Morph').has_value('Clean').else_use('').if_parameter('Filter Type').has_value('Notch').and_parameter('Filter Circuit - BP/NO/Morph').has_value('Clean').else_use('Filter Drive'), - 'Shaper Type', - 'Shaper Amt'), - OPTIONS_KEY: (use('Filter Slope').if_parameter('Filter On').has_value('on').and_parameter('Filter Slope').is_available(True), - '', - '', - '', - '', - '', - '')}), - ('Filt. Env.', {PARAMETERS_KEY: ('Filter On', 'Fe Init', 'Fe Attack', 'Fe Decay', 'Fe Sustain', 'Fe Release', 'Fe End', 'Fe Amount')}), - ('Filt. Mod.', {PARAMETERS_KEY: ('Filter On', 'Filt < Vel', 'Filt < LFO', 'Filt < Key', '', '', '', '')}), - ('LFO', {PARAMETERS_KEY: ('LFO On', - 'LFO Type', - 'LFO Range', + 'Shaper Type', 'Shaper Mix'), + OPTIONS_KEY: ( + use('Filter Slope').if_parameter('Filter On').has_value('on').and_parameter('Filter Slope').is_available(True), + '', '', '', '', '', '') + }), + ( + 'Filt. Env.', + {PARAMETERS_KEY: ('Filter On', 'Fe Init', 'Fe Attack', 'Fe Decay', 'Fe Sustain', 'Fe Release', 'Fe End', + 'Fe Amount') + }), + ( + 'Filt. Mod.', + {PARAMETERS_KEY: ('Filter On', 'Filt < Vel', 'Filt < LFO', 'Filt < Key', '', '', '', '') + }), + ( + 'LFO', + {PARAMETERS_KEY: ( + 'LFO On', 'LFO Type', 'LFO Range', use('LFO Sync').if_parameter('LFO Range').has_value('Sync').else_use('LFO Rate'), - 'LFO Amt', - 'Filt < LFO', + 'LFO Amt', 'Filt < LFO', use('Oscillator').if_parameter('LFO On').has_value('on'), - use('Osc-A < LFO').if_parameter('Oscillator').has_value('A').else_use('Osc-B < LFO').if_parameter('Oscillator').has_value('B').else_use('Osc-C < LFO').if_parameter('Oscillator').has_value('C').else_use('Osc-D < LFO').if_parameter('Oscillator').has_value('D'))}), - ('LFO Env.', {PARAMETERS_KEY: ('LFO On', 'Le Init', 'Le Attack', 'Le Decay', 'Le Sustain', 'Le Release', 'Le End', 'LFO Amt')}), - ('Pitch', {PARAMETERS_KEY: ('Transpose', - 'Spread', - 'Glide On', - 'Glide Time', - 'Pe R < Vel', - 'LFO < Pe', + use('Osc-A < LFO').if_parameter('Oscillator').has_value('A').else_use('Osc-B < LFO').if_parameter('Oscillator').has_value('B').else_use('Osc-C < LFO').if_parameter('Oscillator').has_value('C').else_use('Osc-D < LFO').if_parameter('Oscillator').has_value('D')) + }), + ( + 'LFO Env.', + {PARAMETERS_KEY: ('LFO On', 'Le Init', 'Le Attack', 'Le Decay', 'Le Sustain', 'Le Release', 'Le End', + 'LFO Amt') + }), + ( + 'Pitch', + {PARAMETERS_KEY: ( + 'Transpose', 'Spread', 'Glide On', 'Glide Time', + 'Pe R < Vel', 'LFO < Pe', use('Oscillator').if_parameter('Pe On').has_value('on'), - use('Osc-A < Pe').if_parameter('Oscillator').has_value('A').else_use('Osc-B < Pe').if_parameter('Oscillator').has_value('B').else_use('Osc-C < Pe').if_parameter('Oscillator').has_value('C').else_use('Osc-D < Pe').if_parameter('Oscillator').has_value('D'))}), - ('Pitch Env.', {PARAMETERS_KEY: ('Pe On', 'Pe Init', 'Pe Attack', 'Pe Decay', 'Pe Sustain', 'Pe Release', 'Pe End', 'Pe Amount')}), - ('Global', {PARAMETERS_KEY: ('Panorama', 'Pan < Rnd', 'Pan < Key', 'Filt < Key', 'Time', 'Time < Key', 'Tone', 'Volume')}), - ('Modulation', {PARAMETERS_KEY: ('Oscillator', + use('Osc-A < Pe').if_parameter('Oscillator').has_value('A').else_use('Osc-B < Pe').if_parameter('Oscillator').has_value('B').else_use('Osc-C < Pe').if_parameter('Oscillator').has_value('C').else_use('Osc-D < Pe').if_parameter('Oscillator').has_value('D')) + }), + ( + 'Pitch Env.', + {PARAMETERS_KEY: ('Pe On', 'Pe Init', 'Pe Attack', 'Pe Decay', 'Pe Sustain', 'Pe Release', 'Pe End', + 'Pe Amount') + }), + ( + 'Global', + {PARAMETERS_KEY: ('Panorama', 'Pan < Rnd', 'Pan < Key', 'Filt < Key', 'Time', 'Time < Key', 'Tone', + 'Volume') + }), + ( + 'Modulation', + {PARAMETERS_KEY: ( + 'Oscillator', use('Osc-A Lev < Vel').if_parameter('Oscillator').has_value('A').else_use('Osc-B Lev < Vel').if_parameter('Oscillator').has_value('B').else_use('Osc-C Lev < Vel').if_parameter('Oscillator').has_value('C').else_use('Osc-D Lev < Vel').if_parameter('Oscillator').has_value('D'), use('A Freq 1) + self.reset_slices_action = DeviceTriggerOption(name='Reset Slices', default_label='Reset Slices', callback=reset_slices, is_active=lambda : sample_available()) + self.split_slice_action = DeviceTriggerOption(name='Split Slice', default_label='Split Slice', callback=lambda : insert_new_slice(self._live_object), is_active=split_slice_available) def get_parameter_by_name(self, name): return find_if(lambda p: p.name == name, self.parameters) @property def options(self): - return (self.crop_option, + return ( + self.crop_option, self.reverse_option, self.one_shot_sustain_mode_option, self.retrigger_option, @@ -191,7 +407,10 @@ def options(self): self.warp_double_option, self.lfo_sync_option, self.loop_option, - self.filter_slope_option) + self.filter_slope_option, + self.clear_slices_action, + self.reset_slices_action, + self.split_slice_action) @listenable_property def waveform_navigation(self): @@ -201,6 +420,11 @@ def waveform_navigation(self): def available_resolutions(self): return (u'1 Bar', u'\xbd', u'\xbc', u'\u215b', u'\ue001', u'\ue002', u'Transients') + @property + def available_slicing_beat_divisions(self): + return (u'\ue001', u'\ue001T', u'\u215b', u'\u215bT', u'\xbc', u'\xbcT', u'\xbd', + u'\xbdT', u'1 Bar', u'2 Bars', u'4 Bars') + @listens('parameters') def __on_parameters_changed(self): self.lfo_sync_option.set_parameter(get_parameter_by_name(self, 'L Sync')) @@ -210,6 +434,9 @@ def _reconnect_sample_listeners(self): super(_SimplerDeviceDecorator, self)._reconnect_sample_listeners() self._reconnect_to_markers() self._update_warp_as_label() + self.positions.post_sample_changed() + self.zoom.post_sample_changed() + self.zoom.focus_region_of_interest('start_end_marker', self.get_parameter_by_name('Start')) def _reconnect_to_markers(self): self.__on_start_marker_changed.subject = self._live_object.sample @@ -242,6 +469,20 @@ def __on_can_warp_half_changed(self): def __on_can_warp_double_changed(self): self.warp_double_option.notify_active() + @listens('selected_slice') + def __on_selected_slice_changed(self): + self.split_slice_action.notify_active() + + def _on_sample_changed(self): + super(_SimplerDeviceDecorator, self)._on_sample_changed() + self.clear_slices_action.notify_active() + self.reset_slices_action.notify_active() + self.split_slice_action.notify_active() + + def _on_slices_changed(self): + super(_SimplerDeviceDecorator, self)._on_slices_changed() + self.clear_slices_action.notify_active() + def get_warp_as_option_label(self): try: bars = int(self._live_object.guess_playback_length() / self._song.signature_numerator) @@ -250,15 +491,16 @@ def get_warp_as_option_label(self): return 'Warp as X Bars' -class _OperatorDeviceDecorator(SlotManager, LiveObjectDecorator): +class _OperatorDeviceDecorator(EventObject, LiveObjectDecorator): - def __init__(self, song = None, osc_types_provider = None, *a, **k): + def __init__(self, song=None, osc_types_provider=None, *a, **k): super(_OperatorDeviceDecorator, self).__init__(*a, **k) self._osc_types_provider = osc_types_provider if osc_types_provider is not None else OscillatorTypesList() self.__on_parameters_changed.subject = self._live_object - self.oscillator = EnumWrappingParameter(name='Oscillator', parent=self, values_property_host=self._osc_types_provider, index_property_host=self._osc_types_provider, values_property='available_values', index_property='index', value_type=OscillatorType) + self.oscillator = EnumWrappingParameter(name='Oscillator', parent=self, values_host=self._osc_types_provider, index_property_host=self._osc_types_provider, values_property='available_values', index_property='index', value_type=OscillatorType) self.filter_slope_option = DeviceSwitchOption(name='Filter Slope', default_label='12dB', second_label='24dB', parameter=get_parameter_by_name(self, 'Filter Slope')) self.register_disconnectables(self.options) + return @property def parameters(self): @@ -266,16 +508,17 @@ def parameters(self): @property def options(self): - return (self.filter_slope_option,) + return ( + self.filter_slope_option,) @listens('parameters') def __on_parameters_changed(self): self.filter_slope_option.set_parameter(get_parameter_by_name(self, 'Filter Slope')) -class _SamplerDeviceDecorator(SlotManager, LiveObjectDecorator): +class _SamplerDeviceDecorator(EventObject, LiveObjectDecorator): - def __init__(self, song = None, *a, **k): + def __init__(self, song=None, *a, **k): super(_SamplerDeviceDecorator, self).__init__(*a, **k) self.__on_parameters_changed.subject = self._live_object self.filter_slope_option = DeviceSwitchOption(name='Filter Slope', default_label='12dB', second_label='24dB', parameter=get_parameter_by_name(self, 'Filter Slope')) @@ -283,16 +526,17 @@ def __init__(self, song = None, *a, **k): @property def options(self): - return (self.filter_slope_option,) + return ( + self.filter_slope_option,) @listens('parameters') def __on_parameters_changed(self): self.filter_slope_option.set_parameter(get_parameter_by_name(self, 'Filter Slope')) -class _AutoFilterDeviceDecorator(SlotManager, LiveObjectDecorator): +class _AutoFilterDeviceDecorator(EventObject, LiveObjectDecorator): - def __init__(self, song = None, *a, **k): + def __init__(self, song=None, *a, **k): super(_AutoFilterDeviceDecorator, self).__init__(*a, **k) self.__on_parameters_changed.subject = self._live_object self.slope_option = DeviceSwitchOption(name='Slope', default_label='12dB', second_label='24dB', parameter=get_parameter_by_name(self, 'Slope')) @@ -300,7 +544,8 @@ def __init__(self, song = None, *a, **k): @property def options(self): - return (self.slope_option,) + return ( + self.slope_option,) @listens('parameters') def __on_parameters_changed(self): @@ -309,10 +554,11 @@ def __on_parameters_changed(self): class _Eq8DeviceDecorator(LiveObjectDecorator): - def __init__(self, song = None, band_types_provider = None, *a, **k): + def __init__(self, song=None, band_types_provider=None, *a, **k): super(_Eq8DeviceDecorator, self).__init__(*a, **k) self._band_types_provider = band_types_provider if band_types_provider is not None else BandTypesList() - self.band = EnumWrappingParameter(name='Band', parent=self, values_property_host=self._band_types_provider, index_property_host=self._band_types_provider, values_property='available_values', index_property='index') + self.band = EnumWrappingParameter(name='Band', parent=self, values_host=self._band_types_provider, index_property_host=self._band_types_provider, values_property='available_values', index_property='index') + return @property def parameters(self): @@ -321,13 +567,14 @@ def parameters(self): class DeviceDecoratorFactory(DecoratorFactory): DECORATOR_CLASSES = {'OriginalSimpler': _SimplerDeviceDecorator, - 'Operator': _OperatorDeviceDecorator, - 'MultiSampler': _SamplerDeviceDecorator, - 'AutoFilter': _AutoFilterDeviceDecorator, - 'Eq8': _Eq8DeviceDecorator} + 'Operator': _OperatorDeviceDecorator, + 'MultiSampler': _SamplerDeviceDecorator, + 'AutoFilter': _AutoFilterDeviceDecorator, + 'Eq8': _Eq8DeviceDecorator + } @classmethod - def generate_decorated_device(cls, device, additional_properties = {}, song = None, *a, **k): + def generate_decorated_device(cls, device, additional_properties={}, song=None, *a, **k): decorated = cls.DECORATOR_CLASSES[device.class_name](live_object=device, additional_properties=additional_properties, song=song, *a, **k) return decorated @@ -336,25 +583,28 @@ def _should_be_decorated(cls, device): return liveobj_valid(device) and device.class_name in cls.DECORATOR_CLASSES @depends(song=None) - def _get_decorated_object(self, device, additional_properties, song = None, *a, **k): + def _get_decorated_object(self, device, additional_properties, song=None, *a, **k): return self.generate_decorated_device(device, additional_properties=additional_properties, song=song, *a, **k) class SimplerDecoratedPropertiesCopier(object): - ADDITIONAL_PROPERTIES = ['playhead_real_time_channel_id', 'waveform_real_time_channel_id'] - - def __init__(self, decorated_object = None, factory = None, *a, **k): - raise liveobj_valid(decorated_object) or AssertionError - raise factory is not None or AssertionError - raise decorated_object in factory.decorated_objects or AssertionError + ADDITIONAL_PROPERTIES = [ + 'playhead_real_time_channel_id', + 'waveform_real_time_channel_id'] + + def __init__(self, decorated_object=None, factory=None, *a, **k): + assert liveobj_valid(decorated_object) + assert factory is not None + assert decorated_object in factory.decorated_objects super(SimplerDecoratedPropertiesCopier, self).__init__(*a, **k) self._decorated_object = decorated_object self._factory = factory self._copied_additional_properties = {} self._nested_properties = {} - self.copy_properties({'zoom': lambda s: s.zoom.linear_value, - self.ADDITIONAL_PROPERTIES[0]: None, - self.ADDITIONAL_PROPERTIES[1]: None}) + self.copy_properties({self.ADDITIONAL_PROPERTIES[0]: None, + self.ADDITIONAL_PROPERTIES[1]: None + }) + return def copy_properties(self, properties): for prop, getter in properties.iteritems(): @@ -369,5 +619,6 @@ def apply_properties(self, new_object, song): return decorated def _apply_nested_properties(self, decorated_object): - if 'zoom' in self._nested_properties: - decorated_object.zoom.linear_value = self._nested_properties['zoom'] \ No newline at end of file + if decorated_object.zoom.waveform_navigation is not None and self._decorated_object.zoom.waveform_navigation is not None: + decorated_object.zoom.waveform_navigation.copy_state(self._decorated_object.zoom.waveform_navigation) + return \ No newline at end of file diff --git a/Push2/device_enabling.py b/Push2/device_enabling.py deleted file mode 100644 index 70a74da2..00000000 --- a/Push2/device_enabling.py +++ /dev/null @@ -1,75 +0,0 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/midi-remote-scripts/Push2/device_enabling.py -from __future__ import absolute_import -import Live -from _Tools.multipledispatch import dispatch -from ableton.v2.base import listens, liveobj_valid -from ableton.v2.control_surface.control import ButtonControl, control_list -from ableton.v2.control_surface.component import Component -from .device_navigation import DeviceChainEnabledStateWatcher, is_active_element - -def set_enabled(device, is_on): - device.parameters[0].value = int(is_on) - - -def is_on(device): - return bool(device.parameters[0].value) - - -class DeviceEnablingComponent(Component): - toggle_buttons = control_list(ButtonControl) - - def __init__(self, device_navigation = None, *a, **k): - raise device_navigation is not None or AssertionError - super(DeviceEnablingComponent, self).__init__(*a, **k) - self._device_navigation = device_navigation - self.__on_items_changed.subject = device_navigation - self._watcher = self.register_disconnectable(DeviceChainEnabledStateWatcher(device_navigation=device_navigation)) - self.__on_enabled_state_changed.subject = self._watcher - self._update_buttons() - - @toggle_buttons.pressed - def toggle_buttons(self, button): - item = self._item_for_button(button) - if item.is_scrolling_indicator: - if button.index == 0: - self._device_navigation.scroll_left() - else: - self._device_navigation.scroll_right() - else: - self._toggle_device(item.item) - - @listens('enabled_state') - def __on_enabled_state_changed(self): - self._update_buttons() - - @listens('items') - def __on_items_changed(self, *a): - self._update_buttons() - - def _item_for_button(self, button): - return self._device_navigation.items[button.index] - - def _color_for_device(self, device_or_pad): - if liveobj_valid(device_or_pad): - return 'DefaultButton.On' if is_active_element(device_or_pad) else 'DefaultButton.Off' - else: - return 'DefaultButton.Disabled' - - @dispatch(Live.DrumPad.DrumPad) - def _toggle_device(self, drum_pad): - if liveobj_valid(drum_pad): - drum_pad.mute = not drum_pad.mute - - @dispatch(object) - def _toggle_device(self, device): - if liveobj_valid(device) and device.parameters[0].is_enabled: - set_enabled(device, not is_on(device)) - - def _update_buttons(self): - self.toggle_buttons.control_count = len(self._device_navigation.items) - for button in self.toggle_buttons: - item = self._item_for_button(button) - if item.is_scrolling_indicator: - button.color = 'DefaultButton.On' - else: - button.color = self._color_for_device(item.item) \ No newline at end of file diff --git a/Push2/device_navigation.py b/Push2/device_navigation.py index d59f9df4..d9c0688a 100644 --- a/Push2/device_navigation.py +++ b/Push2/device_navigation.py @@ -1,16 +1,19 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/Push2/device_navigation.py +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/Push2/device_navigation.py +# Compiled at: 2016-09-29 19:13:24 from __future__ import absolute_import, print_function from contextlib import contextmanager from itertools import ifilter, imap, chain from functools import partial from multipledispatch import dispatch import Live -from ableton.v2.base import find_if, first, index_if, listenable_property, listens, listens_group, liveobj_changed, liveobj_valid, SlotGroup, SlotManager, Subject, task -from ableton.v2.control_surface.components import device_to_appoint +from ableton.v2.base import find_if, first, index_if, listenable_property, listens, listens_group, liveobj_changed, liveobj_valid, EventObject, SlotGroup, task +from ableton.v2.control_surface import device_to_appoint from ableton.v2.control_surface.control import control_list, StepEncoderControl from ableton.v2.control_surface.mode import Component, ModesComponent from pushbase.decoration import DecoratorFactory from pushbase.device_chain_utils import is_first_device_on_pad +from .colors import DISPLAY_BUTTON_SHADE_LEVEL, IndexedColor +from .device_util import is_drum_pad, find_chain_or_track from .item_lister_component import ItemListerComponent, ItemProvider def find_drum_pad(items): @@ -43,9 +46,11 @@ def is_on(device): def nested_device_parent(device): if device.can_have_chains and device.view.is_showing_chain_devices and not device.view.is_collapsed: return device.view.selected_chain + else: + return None -def collect_devices(track_or_chain, nesting_level = 0): +def collect_devices(track_or_chain, nesting_level=0): chain_devices = track_or_chain.devices if liveobj_valid(track_or_chain) else [] devices = [] for device in chain_devices: @@ -63,7 +68,7 @@ def delete_device(device): device_parent.delete_device(device_index) -class FlattenedDeviceChain(SlotManager, ItemProvider): +class FlattenedDeviceChain(ItemProvider): def __init__(self, *a, **k): super(FlattenedDeviceChain, self).__init__(*a, **k) @@ -73,13 +78,14 @@ def __init__(self, *a, **k): def make_slot_group(event): slot_group = SlotGroup(self._update_devices, event) - return self.register_slot_manager(slot_group) + return self.register_disconnectable(slot_group) self._devices_changed = make_slot_group('devices') self._selected_chain_changed = make_slot_group('selected_chain') self._selected_pad_changed = make_slot_group('selected_drum_pad') self._collapsed_state_changed = make_slot_group('is_collapsed') self._chain_devices_visibility_changed = make_slot_group('is_showing_chain_devices') + return @property def items(self): @@ -115,7 +121,8 @@ def get_rack_views(racks): racks = filter(lambda x: getattr(first(x), 'can_have_chains', False), self._devices) rack_views = get_rack_views(racks) - device_parents = chain(imap(lambda x: x.selected_chain, rack_views), [self._device_parent]) + device_parents = chain(imap(lambda x: x.selected_chain, rack_views), [ + self._device_parent]) def is_empty_pad_drum_rack(item): rack = first(item) @@ -129,23 +136,24 @@ def is_empty_pad_drum_rack(item): self._selected_pad_changed.replace_subjects(empty_pad_drum_rack_views) -def is_drum_pad(item): - return liveobj_valid(item) and isinstance(item, Live.DrumPad.DrumPad) - - def drum_rack_for_pad(drum_pad): return drum_pad.canonical_parent -class DeviceChainEnabledStateWatcher(Subject, SlotManager): - __events__ = ('enabled_state',) +class DeviceChainStateWatcher(EventObject): + """ + Listens to the device navigations items and notifies whenever the items state + changes and the color of the buttons might be affected. + """ + __events__ = ('state', ) - def __init__(self, device_navigation = None, *a, **k): - raise device_navigation is not None or AssertionError - super(DeviceChainEnabledStateWatcher, self).__init__(*a, **k) + def __init__(self, device_navigation=None, *a, **k): + assert device_navigation is not None + super(DeviceChainStateWatcher, self).__init__(*a, **k) self._device_navigation = device_navigation self.__on_items_changed.subject = device_navigation self._update_listeners_and_notify() + return @listens('items') def __on_items_changed(self, *a): @@ -153,11 +161,15 @@ def __on_items_changed(self, *a): @listens_group('is_active') def __on_is_active_changed(self, device): - self._notify() + self.notify_state() + + @listens_group('color_index') + def __on_chain_color_index_changed(self, chain): + self.notify_state() @listens('mute') def __on_mute_changed(self): - self._notify() + self.notify_state() def _navigation_items(self): return ifilter(lambda i: not i.is_scrolling_indicator, self._device_navigation.items) @@ -167,12 +179,12 @@ def _devices(self): return map(lambda i: i.item, device_items) def _update_listeners_and_notify(self): + items = list(self._navigation_items()) + chains = set(filter(liveobj_valid, imap(lambda i: find_chain_or_track(i.item), items))) self.__on_is_active_changed.replace_subjects(self._devices()) - self.__on_mute_changed.subject = find_drum_pad(self._navigation_items()) - self._notify() - - def _notify(self): - self.notify_enabled_state() + self.__on_mute_changed.subject = find_drum_pad(items) + self.__on_chain_color_index_changed.replace_subjects(chains) + self.notify_state() class MoveDeviceComponent(Component): @@ -182,6 +194,7 @@ class MoveDeviceComponent(Component): def __init__(self, *a, **k): super(MoveDeviceComponent, self).__init__(*a, **k) self._device = None + return def set_device(self, device): self._device = device @@ -194,6 +207,7 @@ def move_encoders(self, value, encoder): self._move_right() else: self._move_left() + return @contextmanager def _disabled_encoders(self): @@ -233,12 +247,12 @@ def _move_left(self): elif isinstance(parent, Live.Chain.Chain): self._move_out(parent.canonical_parent) - def _move_out(self, rack, move_behind = False): + def _move_out(self, rack, move_behind=False): parent = rack.canonical_parent rack_index = list(parent.devices).index(rack) self.song.move_device(self._device, parent, rack_index + 1 if move_behind else rack_index) - def _move_in(self, rack, move_to_end = False): + def _move_in(self, rack, move_to_end=False): chain = rack.view.selected_chain if chain: self.song.move_device(self._device, chain, len(chain.devices) if move_to_end else 0) @@ -247,19 +261,18 @@ def _move_in(self, rack, move_to_end = False): class DeviceNavigationComponent(ItemListerComponent): __events__ = ('drum_pad_selection', 'mute_solo_stop_cancel_action_performed') - def __init__(self, device_bank_registry = None, banking_info = None, device_component = None, delete_handler = None, chain_selection = None, bank_selection = None, move_device = None, track_list_component = None, *a, **k): - raise device_bank_registry is not None or AssertionError - raise device_component is not None or AssertionError - raise chain_selection is not None or AssertionError - raise bank_selection is not None or AssertionError - raise move_device is not None or AssertionError - raise track_list_component is not None or AssertionError + def __init__(self, device_bank_registry=None, banking_info=None, device_component=None, delete_handler=None, chain_selection=None, bank_selection=None, move_device=None, track_list_component=None, *a, **k): + assert device_bank_registry is not None + assert device_component is not None + assert chain_selection is not None + assert bank_selection is not None + assert move_device is not None + assert track_list_component is not None self._flattened_chain = FlattenedDeviceChain() super(DeviceNavigationComponent, self).__init__(item_provider=self._flattened_chain, *a, **k) self._track_decorator = DecoratorFactory() self._device_component = device_component self.__on_device_changed.subject = device_component - self.__on_device_changed() self._device_bank_registry = device_bank_registry self._delete_handler = delete_handler self._chain_selection = self.register_component(chain_selection) @@ -268,7 +281,9 @@ def __init__(self, device_bank_registry = None, banking_info = None, device_comp self._last_pressed_button_index = -1 self._selected_on_previous_press = None self._modes = self.register_component(ModesComponent()) - self._modes.add_mode('default', [partial(self._chain_selection.set_parent, None), partial(self._bank_selection.set_device, None)]) + self._modes.add_mode('default', [ + partial(self._chain_selection.set_parent, None), + partial(self._bank_selection.set_device, None)]) self._modes.add_mode('chain_selection', [self._chain_selection]) self._modes.add_mode('bank_selection', [self._bank_selection]) self._modes.selected_mode = 'default' @@ -278,9 +293,11 @@ def __init__(self, device_bank_registry = None, banking_info = None, device_comp self._on_selected_track_changed() self._on_selected_track_changed.subject = self.song.view self._track_list = track_list_component - watcher = self.register_disconnectable(DeviceChainEnabledStateWatcher(device_navigation=self)) - self.__on_enabled_state_changed.subject = watcher + watcher = self.register_disconnectable(DeviceChainStateWatcher(device_navigation=self)) + self.__on_device_item_state_changed.subject = watcher + self.__on_device_changed() self._update_button_colors() + return @property def modes(self): @@ -299,6 +316,7 @@ def _on_select_button_pressed(self, button): if not self._delete_handler or not self._delete_handler.is_deleting: self._selected_on_previous_press = device_or_pad if self.selected_object != device_or_pad else None self._select_item(device_or_pad) + return def _on_select_button_released_immediately(self, button): if not self._in_device_enabling_mode(): @@ -309,6 +327,7 @@ def _on_select_button_released_immediately(self, button): elif self.selected_object == device_or_pad and device_or_pad != self._selected_on_previous_press: self._on_reselecting_object(device_or_pad) self._selected_on_previous_press = None + return def _on_select_button_pressed_delayed(self, button): if not self._in_device_enabling_mode(): @@ -330,8 +349,8 @@ def _toggle_device(self, device): if liveobj_valid(device) and device.parameters[0].is_enabled: set_enabled(device, not is_on(device)) - @listens('enabled_state') - def __on_enabled_state_changed(self): + @listens('state') + def __on_device_item_state_changed(self): self._update_button_colors() @listens('items') @@ -356,9 +375,15 @@ def _color_for_button(self, button_index, is_selected): item = self.items[button_index] device_or_pad = item.item is_active = liveobj_valid(device_or_pad) and is_active_element(device_or_pad) + chain = find_chain_or_track(device_or_pad) if not is_active: return 'DefaultButton.Off' - return super(DeviceNavigationComponent, self)._color_for_button(button_index, is_selected) + else: + if is_selected: + return 'ItemNavigation.ItemSelected' + if liveobj_valid(chain): + return IndexedColor.from_live_index(chain.color_index, DISPLAY_BUTTON_SHADE_LEVEL) + return 'ItemNavigation.ItemNotSelected' def _begin_move_device(self, device): if not self._move_device.is_enabled() and device.type != Live.Device.DeviceType.instrument: @@ -373,6 +398,7 @@ def _end_move_device(self): self._move_device.set_enabled(False) self._scroll_overlay.set_enabled(True) self.notify_moving() + return def _show_selected_item(self): selected_item = self.item_provider.selected_item @@ -384,6 +410,7 @@ def _show_selected_item(self): self.item_offset = selected_index - self._num_visible_items + 2 elif selected_index > 0 and selected_index <= self.item_offset: self.item_offset = selected_index - 1 + return def request_drum_pad_selection(self): self._current_track().drum_pad_selected = True @@ -402,7 +429,7 @@ def is_drum_pad_selected(self): @property def is_drum_pad_unfolded(self): selection = self._flattened_chain.selected_item - raise is_drum_pad(selection) or AssertionError + assert is_drum_pad(selection) return drum_rack_for_pad(selection).view.is_showing_chain_devices def _current_track(self): @@ -432,6 +459,7 @@ def _restore_selection(self, selected_track): if to_select == None: to_select = selected_track.view.selected_device self._select_item(to_select) + return def back_to_top(self): pass @@ -456,6 +484,8 @@ def _first_device_on_pad(self, drum_pad): chain = drum_rack_for_pad(drum_pad).view.selected_chain if chain and chain.devices: return first(chain.devices) + else: + return None def _appoint_device(self, device): if self._device_component._device_changed(device): @@ -516,9 +546,18 @@ def __on_bank_selection_closed(self): @listens('device') def __on_device_changed(self): - if not self._should_select_drum_pad() and self._flattened_chain.has_invalid_selection: + if not self._should_select_drum_pad() and not self._is_drum_rack_selected(): + self._modes.selected_mode = 'default' self._update_item_provider(self._device_component.device()) + def _is_drum_rack_selected(self): + selected_item = self._flattened_chain.selected_item + instrument = self._find_top_level_instrument() + return liveobj_valid(selected_item) and isinstance(selected_item, Live.RackDevice.RackDevice) and selected_item.can_have_drum_pads and not liveobj_changed(selected_item, instrument) + + def _find_top_level_instrument(self): + return find_if(lambda device: device.type == Live.Device.DeviceType.instrument, self._current_track().devices) + @listens('selected_device') def _device_selection_in_track_changed(self): new_selection = self.song.view.selected_track.view.selected_device diff --git a/Push2/device_options.py b/Push2/device_options.py index 09ee851a..3a171a1e 100644 --- a/Push2/device_options.py +++ b/Push2/device_options.py @@ -1,12 +1,14 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/Push2/device_options.py +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/Push2/device_options.py +# Compiled at: 2016-06-08 13:13:04 from __future__ import absolute_import, print_function -from ableton.v2.base import liveobj_valid, listenable_property, listens, const, Subject, Slot, SlotManager +from ableton.v2.base import liveobj_valid, listenable_property, listens, const, EventObject, Slot -class DeviceTriggerOption(Subject): - __events__ = ('default_label',) +class DeviceTriggerOption(EventObject): + __events__ = ('default_label', ) - def __init__(self, name = None, default_label = None, callback = None, is_active = None): - raise callback or AssertionError + def __init__(self, name=None, default_label=None, callback=None, is_active=None, *a, **k): + assert callback + super(DeviceTriggerOption, self).__init__(*a, **k) self.trigger = callback self._name = name or 'Option' self._default_label = default_label or self._name @@ -33,9 +35,9 @@ def _set_default_label(self, label): default_label = property(_get_default_label, _set_default_label) -class DeviceSwitchOption(SlotManager, DeviceTriggerOption): +class DeviceSwitchOption(DeviceTriggerOption): - def __init__(self, second_label = None, parameter = None, *a, **k): + def __init__(self, second_label=None, parameter=None, *a, **k): super(DeviceSwitchOption, self).__init__(callback=self.cycle_index, *a, **k) self._second_label = second_label or '' self.set_parameter(parameter) @@ -68,11 +70,11 @@ def cycle_index(self): self._parameter.value = float((self.active_index + 1.0) % 2) -class DeviceOnOffOption(SlotManager, DeviceTriggerOption): +class DeviceOnOffOption(DeviceTriggerOption): ON_LABEL = 'ON' OFF_LABEL = 'OFF' - def __init__(self, name = None, property_host = None, property_name = '', *a, **k): + def __init__(self, name=None, property_host=None, property_name='', *a, **k): super(DeviceOnOffOption, self).__init__(callback=self.cycle_index, name=name, *a, **k) self._property_host = property_host self._property_name = property_name @@ -81,7 +83,7 @@ def notify_index_and_default_label(): self.notify_active_index() self.notify_default_label() - self._property_slot = self.register_slot(Slot(subject=property_host, event=property_name, listener=notify_index_and_default_label)) + self._property_slot = self.register_slot(Slot(subject=property_host, event_name=property_name, listener=notify_index_and_default_label)) def _property_value(self): if liveobj_valid(self._property_host): @@ -103,4 +105,6 @@ def cycle_index(self): @property def default_label(self): - return '%s %s' % (self._default_label, self.ON_LABEL if self._property_value() else self.OFF_LABEL) \ No newline at end of file + return '%s %s' % ( + self._default_label, + self.ON_LABEL if self._property_value() else self.OFF_LABEL) \ No newline at end of file diff --git a/Push2/device_parameter_bank.py b/Push2/device_parameter_bank.py deleted file mode 100644 index 78091faa..00000000 --- a/Push2/device_parameter_bank.py +++ /dev/null @@ -1,148 +0,0 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/midi-remote-scripts/Push2/device_parameter_bank.py -from __future__ import absolute_import -from ableton.v2.base import SlotManager, Subject, find_if, listens, listens_group, listenable_property, liveobj_valid, clamp -from .bank_definitions import PARAMETERS_KEY, MAIN_KEY -from .banking_util import BANK_FORMAT, all_parameters - -class DeviceParameterBank(SlotManager, Subject): - - def __init__(self, size = None, device = None, banking_info = None, *a, **k): - raise size is not None or AssertionError - super(DeviceParameterBank, self).__init__(*a, **k) - self._size = size - self._device = device - self._banking_info = banking_info - self._index = 0 - self._parameters = None - self._on_parameters_changed.subject = device - self._update_parameters() - - def bank_count(self): - return self._banking_info.device_bank_count(self._device, bank_size=self._size) - - def _adjust_index(self, index): - return clamp(index, 0, max(0, self.bank_count() - 1)) - - def _is_index_valid(self, index): - return self.bank_count() > index - - def _get_index(self): - return self._index - - def _set_index(self, index): - if self._index != index and self._is_index_valid(index): - index = self._adjust_index(index) - self._index = index - self._update_parameters() - - index = property(_get_index, _set_index) - - @listens('parameters') - def _on_parameters_changed(self): - self._index = self._adjust_index(self._index) - self._update_parameters() - - @listenable_property - def parameters(self): - return self._parameters - - def _calc_name(self): - return BANK_FORMAT % (self.index + 1) - - @property - def name(self): - return self._calc_name() if liveobj_valid(self._device) else '' - - @property - def device(self): - return self._device - - def _collect_parameters(self): - parameters = all_parameters(self._device) - offset = self._index * self._size - params = parameters[offset:] - params.extend([None] * (self._size - len(params))) - return params - - def _update_parameters(self): - parameters = self._collect_parameters()[:self._size] - if self._parameters != parameters: - self._parameters = parameters - self.notify_parameters() - - -class DescribedDeviceParameterBank(DeviceParameterBank): - - def __init__(self, device = None, banking_info = None, *a, **k): - self._definition = banking_info.device_bank_definition(device) - self._dynamic_slots = [] - super(DescribedDeviceParameterBank, self).__init__(device=device, banking_info=banking_info, *a, **k) - self._update_parameters() - - @listens_group('content') - def _on_slot_content_changed(self, _slot): - self._update_parameters() - - def _current_parameter_slots(self): - return self._definition.value_by_index(self.index).get(PARAMETERS_KEY) or tuple() - - def _content_slots(self): - return self._current_parameter_slots() - - def _setup_dynamic_slots(self): - for slot in self._dynamic_slots: - slot.set_parameter_host(None) - self.unregister_disconnectable(slot) - - self._dynamic_slots = filter(lambda s: hasattr(s, 'notify_content'), self._content_slots()) - for slot in self._dynamic_slots: - self.register_disconnectable(slot) - slot.set_parameter_host(self.device) - - self._on_slot_content_changed.replace_subjects(self._dynamic_slots) - - def _calc_name(self): - return self._definition.key_by_index(self.index) - - def _collect_parameters(self): - parameters = self._device.parameters - bank_slots = self._current_parameter_slots() - return [ find_if(lambda p: p.original_name == str(slot_definition), parameters) for slot_definition in bank_slots ] - - def _update_parameters(self): - self._setup_dynamic_slots() - super(DescribedDeviceParameterBank, self)._update_parameters() - - -class MaxDeviceParameterBank(DeviceParameterBank): - - def __init__(self, *a, **k): - super(MaxDeviceParameterBank, self).__init__(*a, **k) - - def _calc_name(self): - if self.bank_count() == 0: - return MAIN_KEY - mx_index = self.index - int(self._banking_info.has_main_bank(self._device)) - provided_name = self.device.get_bank_name(mx_index) - return provided_name if len(provided_name) > 0 else super(MaxDeviceParameterBank, self)._calc_name() - - def _collect_parameters(self): - if self.bank_count() == 0: - return [None] * self._size - parameters = self._device.parameters - mx_index = self.index - int(self._banking_info.has_main_bank(self._device)) - indices = self.device.get_bank_parameters(mx_index) - return [ (parameters[index] if index >= 0 else None) for index in indices ] - - -def create_device_bank(device, banking_info): - bank = None - if liveobj_valid(device): - if banking_info.has_bank_count(device): - bank_class = MaxDeviceParameterBank - elif banking_info.device_bank_definition(device) is not None: - bank_class = DescribedDeviceParameterBank - else: - bank_class = DeviceParameterBank - bank = bank_class(device=device, size=8, banking_info=banking_info) - return bank \ No newline at end of file diff --git a/Push2/device_parameter_bank_with_options.py b/Push2/device_parameter_bank_with_options.py index b8debab0..a1b854e6 100644 --- a/Push2/device_parameter_bank_with_options.py +++ b/Push2/device_parameter_bank_with_options.py @@ -1,4 +1,5 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/Push2/device_parameter_bank_with_options.py +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/Push2/device_parameter_bank_with_options.py +# Compiled at: 2016-05-20 03:43:52 from __future__ import absolute_import, print_function from ableton.v2.base import listenable_property, liveobj_valid, find_if from pushbase.device_parameter_bank import create_device_bank, DescribedDeviceParameterBank @@ -19,7 +20,7 @@ def wants_waveform_shown(self): def _current_option_slots(self): bank = self._definition.value_by_index(self.index) - return bank.get(OPTIONS_KEY) or ('',) * OPTIONS_PER_BANK + return bank.get(OPTIONS_KEY) or ('', ) * OPTIONS_PER_BANK def _content_slots(self): return self._current_option_slots() + super(DescribedDeviceParameterBankWithOptions, self)._content_slots() @@ -27,7 +28,9 @@ def _content_slots(self): def _collect_options(self): option_slots = self._current_option_slots() options = getattr(self._device, 'options', []) - return [ find_if(lambda o: o.name == str(slot_definition), options) for slot_definition in option_slots ] + return [ find_if(lambda o: o.name == str(slot_definition), options) + for slot_definition in option_slots + ] def _update_parameters(self): super(DescribedDeviceParameterBankWithOptions, self)._update_parameters() diff --git a/Push2/device_parameter_component.py b/Push2/device_parameter_component.py deleted file mode 100644 index 8b2e0ab9..00000000 --- a/Push2/device_parameter_component.py +++ /dev/null @@ -1,20 +0,0 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/midi-remote-scripts/Push2/device_parameter_component.py -from __future__ import absolute_import -from ableton.v2.control_surface.control import ControlList -from pushbase.device_parameter_component import DeviceParameterComponentBase -from .mapped_control import MappedControl - -class DeviceParameterComponent(DeviceParameterComponentBase): - controls = ControlList(MappedControl, 8) - - def set_parameter_controls(self, encoders): - self.controls.set_control_element(encoders) - self._connect_parameters() - - def _connect_parameters(self): - parameters = self._parameter_provider.parameters[:self.controls.control_count] - for control, parameter_info in map(None, self.controls, parameters): - parameter = parameter_info.parameter if parameter_info else None - control.mapped_parameter = parameter - if parameter: - control.update_sensitivities(parameter_info.default_encoder_sensitivity, parameter_info.fine_grain_encoder_sensitivity) \ No newline at end of file diff --git a/Push2/device_util.py b/Push2/device_util.py new file mode 100644 index 00000000..c95128f6 --- /dev/null +++ b/Push2/device_util.py @@ -0,0 +1,36 @@ +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/Push2/device_util.py +# Compiled at: 2016-05-20 03:43:52 +from __future__ import absolute_import, print_function +import Live +from ableton.v2.base import liveobj_valid + +def is_drum_pad(item): + return liveobj_valid(item) and isinstance(item, Live.DrumPad.DrumPad) + + +def find_chain_or_track(item): + """ + Finds a chain for the given item. + - If it's a device, returns the parent chain or track + - If it's a drum pad, returns the first chain if it exists, otherwise also the parent + chain or track + """ + if is_drum_pad(item) and item.chains: + chain = item.chains[0] + else: + chain = item + while liveobj_valid(chain) and not isinstance(chain, (Live.Track.Track, Live.Chain.Chain)): + chain = getattr(chain, 'canonical_parent', None) + + return chain + + +def find_rack(item): + """ + Finds the parent rack of the given item or None, if it doesn't exist + """ + rack = item + while liveobj_valid(rack) and not isinstance(rack, Live.RackDevice.RackDevice): + rack = getattr(rack, 'canonical_parent', None) + + return rack \ No newline at end of file diff --git a/Push2/device_view_component.py b/Push2/device_view_component.py index 1be5b735..4d554ad0 100644 --- a/Push2/device_view_component.py +++ b/Push2/device_view_component.py @@ -1,4 +1,5 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/Push2/device_view_component.py +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/Push2/device_view_component.py +# Compiled at: 2016-05-20 03:43:52 from __future__ import absolute_import, print_function from ableton.v2.base import const, listens, liveobj_valid from ableton.v2.control_surface import Component @@ -6,20 +7,24 @@ class DeviceViewConnector(Component): - def __init__(self, parameter_provider = None, device_type_provider = const('default'), view = None, *a, **k): - raise parameter_provider is not None or AssertionError - raise view is not None or AssertionError + def __init__(self, device_component=None, parameter_provider=None, device_type_provider=const('default'), view=None, *a, **k): + assert device_component is not None + assert parameter_provider is not None + assert view is not None super(DeviceViewConnector, self).__init__(*a, **k) + self._device_component = device_component self._parameter_provider = parameter_provider self._view = view self._parameters = None self._parameter_names = [] self._device_type_provider = device_type_provider + return def update(self): super(DeviceViewConnector, self).update() if self.is_enabled(): self._view.deviceType = self._device_type_provider() + self._view.device = self._device_component.device() parameters = self._value_for_state(map(lambda p: p and p.parameter, self._parameter_provider.parameters), []) if self._parameters_changed(parameters): self._view.parameters = parameters @@ -30,6 +35,7 @@ def on_enabled_changed(self): self._view.visible = self.is_enabled() self._on_parameters_changed.subject = self._value_for_state(self._parameter_provider, None) super(DeviceViewConnector, self).on_enabled_changed() + return @listens('parameters') def _on_parameters_changed(self): @@ -49,42 +55,40 @@ def _value_for_state(self, enabled_value, disabled_value): class SimplerDeviceViewConnector(DeviceViewConnector): - def __init__(self, device_component = None, *a, **k): - super(SimplerDeviceViewConnector, self).__init__(*a, **k) - self._device = device_component - self.__on_processed_zoom_requests_changed.subject = device_component - def update(self): super(SimplerDeviceViewConnector, self).update() - device = self._value_for_state(self._device.device(), None) - raise device == None or device.class_name == 'OriginalSimpler' or AssertionError + device = self._value_for_state(self._device_component.device(), None) + assert device == None or device.class_name == 'OriginalSimpler' self._view.properties = device self._view.wants_waveform_shown = self._parameter_provider.wants_waveform_shown - self._view.simpler = device - self._update_zoom_requests() - - def _update_zoom_requests(self): - self._view.processed_zoom_requests = self._parameter_provider.processed_zoom_requests - - @listens('processed_zoom_requests') - def __on_processed_zoom_requests_changed(self): - self._update_zoom_requests() + return class DeviceViewComponent(ModesComponent): - def __init__(self, device_component = None, view_model = None, *a, **k): - raise device_component is not None or AssertionError - raise view_model is not None or AssertionError + def __init__(self, device_component=None, view_model=None, *a, **k): + assert device_component is not None + assert view_model is not None super(DeviceViewComponent, self).__init__(*a, **k) self._get_device = device_component.device for view in (view_model.deviceParameterView, view_model.simplerDeviceView): view.visible = False - self.add_mode('default', DeviceViewConnector(parameter_provider=device_component, device_type_provider=self._device_type, view=view_model.deviceParameterView, is_enabled=False)) - self.add_mode('OriginalSimpler', SimplerDeviceViewConnector(parameter_provider=device_component, device_component=device_component, device_type_provider=self._device_type, view=view_model.simplerDeviceView, is_enabled=False)) + self.add_mode('default', DeviceViewConnector(device_component=device_component, parameter_provider=device_component, device_type_provider=self._device_type, view=view_model.deviceParameterView, is_enabled=False)) + self.add_mode('OriginalSimpler', SimplerDeviceViewConnector(device_component=device_component, parameter_provider=device_component, device_type_provider=self._device_type, view=view_model.simplerDeviceView, is_enabled=False)) self._on_parameters_changed.subject = device_component self._on_parameters_changed() + return + + def on_enabled_changed(self): + self._last_selected_mode = None + super(DeviceViewComponent, self).on_enabled_changed() + return + + def update(self): + super(DeviceViewComponent, self).update() + if self.is_enabled(): + self.selected_mode = self._mode_to_select() def _device_type(self): device = self._get_device() @@ -97,7 +101,8 @@ def _mode_to_select(self): device_type = device and device.class_name if self.get_mode(device_type) != None: return device_type - return 'default' + else: + return 'default' @listens('parameters') def _on_parameters_changed(self): diff --git a/Push2/drum_group_component.py b/Push2/drum_group_component.py index d9c65e5b..e8e8a2cc 100644 --- a/Push2/drum_group_component.py +++ b/Push2/drum_group_component.py @@ -1,9 +1,12 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/Push2/drum_group_component.py +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/Push2/drum_group_component.py +# Compiled at: 2016-09-29 19:13:24 from __future__ import absolute_import, print_function from itertools import ifilter, izip -from ableton.v2.base import flatten, liveobj_valid +from ableton.v2.base import flatten, listens_group, liveobj_valid +from ableton.v2.control_surface.control import ButtonControl from pushbase.device_chain_utils import find_instrument_devices from pushbase.drum_group_component import DrumGroupComponent as DrumGroupComponentBase, DrumPadCopyHandler as DrumPadCopyHandlerBase +from .colors import IndexedColor from .decoration import find_decorated_object from .device_decoration import SimplerDecoratedPropertiesCopier @@ -21,7 +24,7 @@ def find_all_simplers_on_pad(drum_pad): class DrumPadCopyHandler(DrumPadCopyHandlerBase): - def __init__(self, decorator_factory = None, song = None, *a, **k): + def __init__(self, decorator_factory=None, song=None, *a, **k): super(DrumPadCopyHandler, self).__init__(*a, **k) self._song = song self._decorator_factory = decorator_factory @@ -43,34 +46,156 @@ def _copy_simpler_properties(self, source_simpler, destination_simpler): copier.apply_properties(destination_simpler, song=self._song) +class DrumPadColorAdapter(object): + """ Adapter that takes care of coloring all chains """ + + def __init__(self, drum_pad=None, *a, **k): + assert drum_pad is not None + super(DrumPadColorAdapter, self).__init__(*a, **k) + self._drum_pad = drum_pad + return + + @property + def name(self): + return self._drum_pad.name + + @property + def color_index(self): + if self._drum_pad.chains: + return self._drum_pad.chains[0].color_index + else: + return None + + @color_index.setter + def color_index(self, color_index): + for chain in self._drum_pad.chains: + chain.color_index = color_index + + @property + def is_auto_colored(self): + if self._drum_pad.chains: + return self._drum_pad.chains[0].is_auto_colored + else: + return None + + @is_auto_colored.setter + def is_auto_colored(self, is_auto_colored): + for chain in self._drum_pad.chains: + chain.is_auto_colored = is_auto_colored + + class DrumGroupComponent(DrumGroupComponentBase): - __events__ = ('mute_solo_stop_cancel_action_performed',) + __events__ = ('mute_solo_stop_cancel_action_performed', ) + select_color_button = ButtonControl() - def __init__(self, tracks_provider = None, device_decorator_factory = None, *a, **k): - raise tracks_provider is not None or AssertionError + def __init__(self, tracks_provider=None, device_decorator_factory=None, color_chooser=None, *a, **k): + assert tracks_provider is not None self._decorator_factory = device_decorator_factory super(DrumGroupComponent, self).__init__(*a, **k) self.mute_button.color = 'DefaultButton.Transparent' self.solo_button.color = 'DefaultButton.Transparent' self._tracks_provider = tracks_provider + self._hotswap_indication_mode = None + self._color_chooser = color_chooser + return + + @property + def drum_group_device(self): + return self._drum_group_device + + @select_color_button.value + def select_color_button(self, value, button): + self._set_control_pads_from_script(bool(value)) + + @select_color_button.released + def select_color_button(self, button): + if self._color_chooser is not None: + self._color_chooser.object = None + return def select_drum_pad(self, drum_pad): if len(drum_pad.chains) > 0 and self.song.view.selected_track.is_showing_chains: self._tracks_provider.scroll_into_view(drum_pad.chains[0]) - def _on_matrix_pressed(self, pad): - super(DrumGroupComponent, self)._on_matrix_pressed(pad) + def _on_matrix_pressed(self, button): + if self.select_color_button.is_pressed and self._color_chooser is not None: + pad = self._pad_for_button(button) + if liveobj_valid(pad) and pad.chains and liveobj_valid(pad.chains[0]): + self._color_chooser.object = DrumPadColorAdapter(pad) + else: + self.show_notification('Cannot color an empty drum pad') + else: + super(DrumGroupComponent, self)._on_matrix_pressed(button) self.notify_mute_solo_stop_cancel_action_performed() + return def _on_selected_drum_pad_changed(self): super(DrumGroupComponent, self)._on_selected_drum_pad_changed() chain = self._selected_drum_pad.chains[0] if self._selected_drum_pad and len(self._selected_drum_pad.chains) > 0 else None if self.song.view.selected_track.is_showing_chains and liveobj_valid(chain): self._tracks_provider.set_selected_item_without_updating_view(self._selected_drum_pad.chains[0]) + return + + @property + def hotswap_indication_mode(self): + return self._hotswap_indication_mode + + @hotswap_indication_mode.setter + def hotswap_indication_mode(self, mode): + self._hotswap_indication_mode = mode + self._update_led_feedback() + + def _color_for_pad(self, pad): + if self._is_hotswapping(pad): + color = 'DrumGroup.PadHotswapping' + else: + color = super(DrumGroupComponent, self)._color_for_pad(pad) + color = self._chain_color_for_pad(pad, color) + return color + + def _chain_color_for_pad(self, pad, color): + if color == 'DrumGroup.PadFilled': + assert pad.chains and liveobj_valid(pad.chains[0]), 'Filled pads should have a chain' + color = IndexedColor.from_live_index(pad.chains[0].color_index) + elif color == 'DrumGroup.PadMuted': + assert pad.chains and liveobj_valid(pad.chains[0]), 'Filled pads should have a chain' + color = IndexedColor.from_live_index(pad.chains[0].color_index, shade_level=1) + return color + + def _is_hotswapping(self, pad): + if self._hotswap_indication_mode == 'current_pad': + return pad == self._selected_drum_pad + if self._hotswap_indication_mode == 'all_pads': + return True + return False + + def _update_drum_pad_listeners(self): + super(DrumGroupComponent, self)._update_drum_pad_listeners() + self._update_drum_pad_chain_listeners() + self._update_color_index_listeners() + + @listens_group('chains') + def __on_drum_pad_chains_changed(self, pad): + self._update_color_index_listeners() + self._update_led_feedback() + + @listens_group('color_index') + def __on_chain_color_index_changed(self, pad): + self._update_led_feedback() + + def _update_color_index_listeners(self): + drum_group = self._drum_group_device + chains = [ pad.chains[0] for pad in drum_group.drum_pads if pad.chains ] if liveobj_valid(drum_group) else [] + self.__on_chain_color_index_changed.replace_subjects(chains) + + def _update_drum_pad_chain_listeners(self): + drum_group = self._drum_group_device + pads = drum_group.drum_pads if liveobj_valid(drum_group) else [] + self.__on_drum_pad_chains_changed.replace_subjects(pads) def delete_drum_pad_content(self, drum_pad): self._tracks_provider.synchronize_selection_with_live_view() super(DrumGroupComponent, self).delete_drum_pad_content(drum_pad) - def _make_copy_handler(self, notification_formatter): - return DrumPadCopyHandler(show_notification=self.show_notification, notification_formatter=notification_formatter, decorator_factory=self._decorator_factory, song=self.song) \ No newline at end of file + def _make_copy_handler(self): + return DrumPadCopyHandler(show_notification=self.show_notification, decorator_factory=self._decorator_factory, song=self.song) \ No newline at end of file diff --git a/Push2/drum_pad_parameter_component.py b/Push2/drum_pad_parameter_component.py index 669257f5..fb16f211 100644 --- a/Push2/drum_pad_parameter_component.py +++ b/Push2/drum_pad_parameter_component.py @@ -1,41 +1,55 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/Push2/drum_pad_parameter_component.py +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/Push2/drum_pad_parameter_component.py +# Compiled at: 2016-05-20 03:43:52 from __future__ import absolute_import, print_function -from ableton.v2.base import clamp, listenable_property, listens, liveobj_valid, SlotManager +from ableton.v2.base import clamp, listenable_property, listens, liveobj_valid from ableton.v2.control_surface import CompoundComponent from ableton.v2.control_surface.control import StepEncoderControl -from pushbase.internal_parameter import InternalParameterBase -from pushbase.parameter_provider import generate_info, ParameterProvider +from pushbase.internal_parameter import InternalParameterBase, EnumWrappingParameter +from pushbase.parameter_provider import ParameterInfo, ParameterProvider +from .parameter_mapping_sensitivities import parameter_mapping_sensitivity, fine_grain_parameter_mapping_sensitivity from .device_view_component import DeviceViewConnector NO_CHOKE_GROUP = u'None' MAX_CHOKE_GROUP = 16 NUM_CHOKE_GROUPS = MAX_CHOKE_GROUP + 1 -class ChokeParameter(SlotManager, InternalParameterBase): +def get_first_chain(drum_pad): + if liveobj_valid(drum_pad) and len(drum_pad.chains) > 0: + return drum_pad.chains[0] + else: + return None + + +class ChokeParameter(InternalParameterBase): is_quantized = True - value_items = [NO_CHOKE_GROUP] + map(unicode, range(1, NUM_CHOKE_GROUPS)) + value_items = [ + NO_CHOKE_GROUP] + map(unicode, range(1, NUM_CHOKE_GROUPS)) min = 0 max = MAX_CHOKE_GROUP - def __init__(self, drum_pad = None, *a, **k): - raise liveobj_valid(drum_pad) or AssertionError + def __init__(self, drum_pad=None, *a, **k): super(ChokeParameter, self).__init__(name='Choke', *a, **k) + self.set_drum_pad(drum_pad) + + def set_drum_pad(self, drum_pad): self._pad = drum_pad - self._on_pad_updated.subject = drum_pad + self._on_choke_group_changed.subject = get_first_chain(drum_pad) + self.notify_value() @listens('choke_group') - def _on_pad_updated(self): + def _on_choke_group_changed(self): self.notify_value() @listenable_property def value(self): - if len(self._pad.chains) > 0: - return self._pad.choke_group + first_chain = get_first_chain(self._pad) + if liveobj_valid(first_chain): + return first_chain.choke_group return 0 @value.setter def value(self, value): value = clamp(value, 0, MAX_CHOKE_GROUP) - self._pad.choke_group = value + get_first_chain(self._pad).choke_group = value @property def canonical_parent(self): @@ -46,21 +60,66 @@ def display_value(self): return unicode(self.value) -def parameters_for_pad(pad): - if not pad or len(pad.chains) == 0: +DEFAULT_OUT_NOTE = 60 + +class DrumPadTransposeParameter(EnumWrappingParameter): + + def __init__(self, drum_pad=None, *a, **k): + super(DrumPadTransposeParameter, self).__init__(name='Transpose', values_host=self, values_property='available_transpose_steps', index_property_host=get_first_chain(drum_pad), index_property='out_note', *a, **k) + + @property + def available_transpose_steps(self, steps=range(128)): + return steps + + @property + def value_items(self): return [] - return [generate_info(ChokeParameter(drum_pad=pad))] + + @property + def min(self): + return self.available_transpose_steps[0] + + @property + def max(self): + return self.available_transpose_steps[-1] + + @property + def canonical_parent(self): + return self._parent.drum_pad + + @property + def display_value(self): + difference = self.value - DEFAULT_OUT_NOTE + sign = u'-' if difference < 0 else (u'+' if difference > 0 else u'') + return sign + unicode(abs(difference)) + u' st' + + def set_drum_pad(self, drum_pad): + self.set_property_host(get_first_chain(drum_pad)) + self.notify_value() class DrumPadParameterComponent(CompoundComponent, ParameterProvider): choke_encoder = StepEncoderControl(num_steps=10) + transpose_encoder = StepEncoderControl(num_steps=10) - def __init__(self, view_model = None, *a, **k): - raise view_model is not None or AssertionError + def __init__(self, device_component=None, view_model=None, *a, **k): + assert device_component is not None + assert view_model is not None super(DrumPadParameterComponent, self).__init__(*a, **k) self._drum_pad = None self._parameters = [] - self._view_connector = self.register_component(DeviceViewConnector(parameter_provider=self, view=view_model.deviceParameterView)) + self.choke_param = ChokeParameter() + self.transpose_param = DrumPadTransposeParameter(parent=self) + self.register_disconnectables([self.choke_param, self.transpose_param]) + self._view_connector = self.register_component(DeviceViewConnector(device_component=device_component, parameter_provider=self, view=view_model.deviceParameterView)) + return + + def parameters_for_pad(self): + if not self.has_filled_pad: + return [] + return [ ParameterInfo(parameter=parameter, default_encoder_sensitivity=parameter_mapping_sensitivity(parameter), fine_grain_encoder_sensitivity=fine_grain_parameter_mapping_sensitivity(parameter)) for parameter in [ + self.choke_param, self.transpose_param] + ] def _get_drum_pad(self): return self._drum_pad @@ -68,24 +127,25 @@ def _get_drum_pad(self): def _set_drum_pad(self, pad): if pad != self._drum_pad: self._drum_pad = pad - self._rebuild_parameter_list() + self._update_parameters() self._on_chains_in_pad_changed.subject = self._drum_pad drum_pad = property(_get_drum_pad, _set_drum_pad) @listens('chains') def _on_chains_in_pad_changed(self): - self._rebuild_parameter_list() - - def _rebuild_parameter_list(self): - for info in self._parameters: - self.disconnect_disconnectable(info.parameter) - - self._parameters = parameters_for_pad(self._drum_pad) - for info in self._parameters: - self.register_disconnectable(info.parameter) + self._update_parameters() + def _update_parameters(self): + self.transpose_param.set_drum_pad(self._drum_pad if self.has_filled_pad else None) + self.choke_param.set_drum_pad(self._drum_pad if self.has_filled_pad else None) + self._parameters = self.parameters_for_pad() self._view_connector.update() + return + + @property + def has_filled_pad(self): + return self._drum_pad and len(self._drum_pad.chains) > 0 @property def parameters(self): @@ -94,4 +154,11 @@ def parameters(self): @choke_encoder.value def choke_encoder(self, value, encoder): if len(self._parameters) > 0: - self._parameters[0].parameter.value += value \ No newline at end of file + self._parameters[0].parameter.value += value + + @transpose_encoder.value + def transpose_encoder(self, value, encoder): + if len(self._parameters) > 0: + parameter = self._parameters[1].parameter + if parameter.value + value in self.transpose_param.available_transpose_steps: + parameter.value += value \ No newline at end of file diff --git a/Push2/elements.py b/Push2/elements.py index 79127167..88432951 100644 --- a/Push2/elements.py +++ b/Push2/elements.py @@ -1,4 +1,5 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/Push2/elements.py +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/Push2/elements.py +# Compiled at: 2016-05-20 03:43:52 from __future__ import absolute_import, print_function from ableton.v2.control_surface.elements import SysexElement from pushbase.control_element_factory import create_button, create_note_button @@ -9,8 +10,8 @@ class Elements(ElementsBase): - def __init__(self, model = None, *a, **k): - raise model is not None or AssertionError + def __init__(self, model=None, *a, **k): + assert model is not None self._model = model super(Elements, self).__init__(continuous_mapping_sensitivity=CONTINUOUS_MAPPING_SENSITIVITY, fine_grained_continuous_mapping_sensitivity=FINE_GRAINED_CONTINUOUS_MAPPING_SENSITIVITY, *a, **k) for button in self.select_buttons_raw: @@ -35,6 +36,7 @@ def __init__(self, model = None, *a, **k): self.layout_button = create_button(31, 'Layout') self._create_touch_strip() self.aftertouch_control = SysexElement(send_message_generator=sysex.make_aftertouch_mode_message, default_value='polyphonic') + return def _create_touch_strip(self): touch_strip_mode_element = SysexElement(send_message_generator=sysex.make_touch_strip_mode_message) diff --git a/Push2/firmware.py b/Push2/firmware.py index 9f6eb89b..a423f527 100644 --- a/Push2/firmware.py +++ b/Push2/firmware.py @@ -1,40 +1,94 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/Push2/firmware.py +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/Push2/firmware.py +# Compiled at: 2016-05-20 03:43:52 from __future__ import absolute_import, print_function import fnmatch import logging import os import re -from ableton.v2.base import find_if, first, listenable_property, task +from collections import namedtuple +from itertools import imap +from ableton.v2.base import find_if, listenable_property, task from ableton.v2.control_surface import Component +import Live logger = logging.getLogger(__name__) WELCOME_STATE_TIME = 2.0 FIRMWARE_PATH = os.path.join(os.path.dirname(__file__), 'firmware') class FirmwareVersion(object): - def __init__(self, major = 0, minor = 0, build = 0, *a, **k): + def __init__(self, major=0, minor=0, build=0, release_type='unknown', *a, **k): super(FirmwareVersion, self).__init__(*a, **k) self.major = major self.minor = minor self.build = build + self.release_type = release_type def __cmp__(self, other): - if self.major == other.major and self.minor == other.minor and self.build == other.build: - return 0 - if self.major > other.major or self.major == other.major and self.minor > other.minor or self.major == other.major and self.minor == other.minor and self.build > other.build: - return 1 - return -1 + if other is None: + return -1 + else: + if self.major == other.major and self.minor == other.minor and self.build == other.build: + return 0 + if self.major > other.major or self.major == other.major and self.minor > other.minor or self.major == other.major and self.minor == other.minor and self.build > other.build: + return 1 + return -1 def __repr__(self): - return '' % (self.major, self.minor, self.build) + return '' % ( + self.major, + self.minor, + self.build, + self.release_type) -_firmware_file_prog = re.compile('app_push2_([0-9]+).([0-9]+).([0-9]+).*') +_firmware_version_re = '([0-9]+)\\.([0-9]+)\\.([0-9]+)' -def extract_firmware_version(filename): - match = _firmware_file_prog.match(filename) +def extract_firmware_version(filename, prefix, release_type='unknown'): + match = re.match('%s%s.*' % (prefix, _firmware_version_re), filename) if match: - return FirmwareVersion(int(match.group(1)), int(match.group(2)), int(match.group(3))) + return FirmwareVersion(int(match.group(1)), int(match.group(2)), int(match.group(3)), release_type) + else: + return None + + +FirmwareInfo = namedtuple('FirmwareInfo', ['version', 'filename']) + +class FirmwareCollector(object): + STABLE_PREFIX = 'app_push2_stable_' + DEV_PREFIX = 'app_push2_dev_' + + def __init__(self, firmware_path=FIRMWARE_PATH, *a, **k): + super(FirmwareCollector, self).__init__(*a, **k) + self.firmware_path = firmware_path + self.stable_firmwares = self._collect_firmware_files(self.STABLE_PREFIX, 'stable') + self.dev_firmwares = self._collect_firmware_files(self.DEV_PREFIX, 'dev') + logger.debug('Available stable firmware files %r', self.stable_firmwares) + logger.debug('Available dev firmware files %r', self.dev_firmwares) + + @property + def latest_stable_firmware(self): + if self.stable_firmwares: + return max(self.stable_firmwares, key=lambda f: f.version) + else: + return None + + @property + def latest_dev_firmware(self): + if self.dev_firmwares: + return max(self.dev_firmwares, key=lambda f: f.version) + else: + return None + + def get_release_type(self, version): + if version in imap(lambda f: f.version, self.stable_firmwares): + return 'stable' + if version in imap(lambda f: f.version, self.dev_firmwares): + return 'dev' + return 'unknown' + + def _collect_firmware_files(self, prefix, release_type): + return filter(lambda x: x.version is not None, [ FirmwareInfo(extract_firmware_version(f, prefix, release_type), f) for f in os.listdir(FIRMWARE_PATH) if fnmatch.fnmatch(f, '*.upgrade') + ]) class FirmwareUpdateComponent(Component): @@ -42,39 +96,80 @@ class FirmwareUpdateComponent(Component): def __init__(self, *a, **k): super(FirmwareUpdateComponent, self).__init__(is_enabled=False, *a, **k) - available_firmware_files = self._collect_firmware_files() - logger.debug('Available firmware files %r', available_firmware_files) - self._latest_firmware = max(available_firmware_files, key=first) - - def start(self): - raise self.state == 'welcome' or AssertionError + self._firmware = None + return + + def start(self, firmware): + assert firmware is not None + assert self.state == 'welcome' + logger.info('Start firmware update using %r', firmware.filename) + self._firmware = firmware + self.notify_firmware_file() self.set_enabled(True) def set_state(): self.state = 'start' self._tasks.add(task.sequence(task.wait(WELCOME_STATE_TIME), task.run(set_state))) + return def process_firmware_response(self, data): - if not self.state == 'start': - raise AssertionError - entry = find_if(lambda entry: entry['type'] == 'firmware', data) - self.state = entry and ('success' if entry['success'] else 'failure') + assert self.state == 'start', "'%s' != 'start'" % self.state + entry = find_if(lambda entry: entry['type'] == 'firmware', data) + if entry: + self.state = 'success' if entry['success'] else 'failure' + + @listenable_property + def firmware_file(self): + if self._firmware is not None: + return os.path.join(FIRMWARE_PATH, self._firmware.filename) + else: + return '' + + @property + def data_file(self): + return os.path.join(FIRMWARE_PATH, 'FlashData.bin') + - def has_newer_firmware(self, major, minor, build): - return self.provided_version > FirmwareVersion(major, minor, build) +class FirmwareSwitcher(object): + """ + Class for switching between the stable and the dev firmware + """ - def _collect_firmware_files(self): - return filter(lambda x: x[0] is not None, [ (extract_firmware_version(f), f) for f in os.listdir(FIRMWARE_PATH) if fnmatch.fnmatch(f, '*.upgrade') ]) + def __init__(self, firmware_collector=None, firmware_update=None, installed_firmware_version=None, *a, **k): + assert firmware_collector is not None + assert firmware_update is not None + assert installed_firmware_version is not None + super(FirmwareSwitcher, self).__init__(*a, **k) + self._installed_version = installed_firmware_version + self._update = firmware_update + self._collector = firmware_collector + return @property - def provided_version(self): - return self._latest_firmware[0] + def firmware_to_switch_to(self): + if self.can_switch_firmware: + if self._installed_version.release_type == 'dev': + return self._collector.latest_stable_firmware + else: + return self._collector.latest_dev_firmware + + return None @property - def firmware_file(self): - return os.path.join(FIRMWARE_PATH, self._latest_firmware[1]) + def version_to_switch_to(self): + firmware = self.firmware_to_switch_to + if firmware is not None: + return firmware.version + else: + return @property - def data_file(self): - return os.path.join(FIRMWARE_PATH, 'FlashData.bin') \ No newline at end of file + def can_switch_firmware(self): + application = Live.Application.get_application() + return application.has_option('_Push2DevFirmware') and self._collector.latest_stable_firmware is not None and self._collector.latest_dev_firmware is not None + + def switch_firmware(self): + if not self.can_switch_firmware: + raise RuntimeError('Cannot switch firmware') + self._update.start(self.firmware_to_switch_to) \ No newline at end of file diff --git a/Push2/firmware/app_push2_1.0.44.upgrade b/Push2/firmware/app_push2_1.0.44.upgrade deleted file mode 100644 index 9133252bfbede6ee725b76c2deaffa44463f4cae..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 58112 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]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 z
Nba&^!r%$6M`aSSI`5enU&gxhmT%ImDH$V;=( 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 J|9|)2{{gSkEY1J` diff --git a/Push2/hardware_settings_component.py b/Push2/hardware_settings_component.py index 79110ca8..a2875d77 100644 --- a/Push2/hardware_settings_component.py +++ b/Push2/hardware_settings_component.py @@ -1,4 +1,5 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/Push2/hardware_settings_component.py +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/Push2/hardware_settings_component.py +# Compiled at: 2016-05-20 03:43:52 from __future__ import absolute_import, print_function import Live from ableton.v2.base import clamp, listens, task @@ -10,10 +11,10 @@ class HardwareSettingsComponent(Component): - def __init__(self, led_brightness_element = None, display_brightness_element = None, settings = None, *a, **k): - raise led_brightness_element is not None or AssertionError - raise display_brightness_element is not None or AssertionError - raise settings is not None or AssertionError + def __init__(self, led_brightness_element=None, display_brightness_element=None, settings=None, *a, **k): + assert led_brightness_element is not None + assert display_brightness_element is not None + assert settings is not None super(HardwareSettingsComponent, self).__init__(*a, **k) self._settings = settings self._led_brightness_element = led_brightness_element @@ -24,14 +25,20 @@ def __init__(self, led_brightness_element = None, display_brightness_element = N self._fade_in_delay_task = self._tasks.add(task.sequence(task.wait(LED_FADE_IN_DELAY), task.run(self._led_brightness_timer.restart))).kill() self.__on_led_brightness_changed.subject = settings self.__on_display_brightness_changed.subject = settings + return def disconnect(self): super(HardwareSettingsComponent, self).disconnect() self._led_brightness_timer.stop() self._led_brightness_timer = None + return + + def hardware_initialized(self): + self.fade_in_led_brightness(self._settings.led_brightness) + self._display_brightness_element.send_value(self._settings.display_brightness) def fade_in_led_brightness(self, target_brightness): - raise MIN_BRIGHTNESS_FOR_FADE_IN <= target_brightness <= self._settings.max_led_brightness or AssertionError + assert MIN_BRIGHTNESS_FOR_FADE_IN <= target_brightness <= self._settings.max_led_brightness self._led_brightness = MIN_BRIGHTNESS_FOR_FADE_IN self._target_led_brightness = target_brightness self._led_brightness_element.send_value(MIN_BRIGHTNESS_FOR_FADE_IN) diff --git a/Push2/internal_parameter.py b/Push2/internal_parameter.py deleted file mode 100644 index adc298d2..00000000 --- a/Push2/internal_parameter.py +++ /dev/null @@ -1,276 +0,0 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/midi-remote-scripts/Push2/internal_parameter.py -from __future__ import absolute_import -from Live import DeviceParameter -from ableton.v2.base import listenable_property, liveobj_valid, nop, Slot, SlotManager, Subject, SlotError - -def identity(value, _parent): - return value - - -def to_percentage_display(value): - percentage = 100.0 * value - percentage_str = '100' - if percentage < 100.0: - precision = 2 if percentage < 10.0 else 1 - format_str = '%.' + str(precision) + 'f' - percentage_str = format_str % percentage - return unicode(percentage_str + ' %') - - -class InternalParameterBase(Subject): - is_enabled = True - is_quantized = False - - def __init__(self, name = None, *a, **k): - raise name is not None or AssertionError - super(InternalParameterBase, self).__init__(*a, **k) - self._name = name - - def _has_valid_parent(self): - return liveobj_valid(self._parent) - - @property - def canonical_parent(self): - raise NotImplementedError - - @property - def display_value(self): - raise NotImplementedError - - @property - def min(self): - raise NotImplementedError - - @property - def max(self): - raise NotImplementedError - - @property - def value(self): - raise NotImplementedError - - @property - def name(self): - return self._name - - @property - def original_name(self): - return self._name - - @property - def default_value(self): - return self.min - - @listenable_property - def automation_state(self): - return DeviceParameter.AutomationState.none - - @listenable_property - def state(self): - return DeviceParameter.ParameterState.enabled - - @property - def _live_ptr(self): - return id(self) - - def __str__(self): - return self.display_value - - -class InternalParameter(InternalParameterBase): - """ - Class implementing the DeviceParameter interface. Using instances of this class, - we can mix script-internal values with DeviceParameter instances. - """ - __events__ = ('value',) - - def __init__(self, parent = None, display_value_conversion = None, *a, **k): - super(InternalParameter, self).__init__(*a, **k) - self._value = 0.0 - self._parent = parent - self.set_display_value_conversion(display_value_conversion) - self.set_scaling_functions(None, None) - - def set_display_value_conversion(self, display_value_conversion): - self._display_value_conversion = display_value_conversion or to_percentage_display - self.notify_value() - - def set_scaling_functions(self, to_internal, from_internal): - self._to_internal = to_internal or identity - self._from_internal = from_internal or identity - - @property - def canonical_parent(self): - return self._parent - - def _get_value(self): - return self._from_internal(self.linear_value, self._parent) if self._has_valid_parent() else self.min - - def _set_value(self, new_value): - raise self.min <= new_value <= self.max or AssertionError, 'Invalid value %f' % new_value - self.linear_value = self._to_internal(new_value, self._parent) - - value = property(_get_value, _set_value) - - def _get_linear_value(self): - return self._value - - def _set_linear_value(self, new_value): - if new_value != self._value: - self._value = new_value - self.notify_value() - - linear_value = property(_get_linear_value, _set_linear_value) - - @property - def min(self): - return 0.0 - - @property - def max(self): - return 1.0 - - @property - def display_value(self): - return self._display_value_conversion(self.value) - - -class WrappingParameter(InternalParameter, SlotManager): - - def __init__(self, source_property = None, from_property_value = None, to_property_value = None, display_value_conversion = nop, value_items = [], *a, **k): - raise source_property is not None or AssertionError - super(WrappingParameter, self).__init__(display_value_conversion=display_value_conversion, *a, **k) - raise hasattr(self._parent, source_property) or source_property in dir(self._parent) or AssertionError - self._source_property = source_property - self._value_items = value_items - self.set_scaling_functions(to_property_value, from_property_value) - self._property_slot = self.register_slot(Slot(listener=self.notify_value, event=source_property)) - self.connect() - - def connect(self): - self._property_slot.subject = None - self._property_slot.subject = self._parent - - def _get_property_value(self): - return getattr(self._parent, self._source_property) if self._has_valid_parent() else self.min - - def _get_value(self): - try: - return self._from_internal(self._get_property_value(), self._parent) if self._has_valid_parent() else self.min - except RuntimeError: - return self.min - - def _set_value(self, new_value): - raise self.min <= new_value <= self.max or AssertionError, 'Invalid value %f' % new_value - try: - setattr(self._parent, self._source_property, self._to_internal(new_value, self._parent)) - except RuntimeError: - pass - - linear_value = property(_get_value, _set_value) - value = property(_get_value, _set_value) - - @property - def display_value(self): - try: - value = self._get_property_value() - return unicode(self._display_value_conversion(value)) - except RuntimeError: - return unicode() - - @property - def is_quantized(self): - return len(self._value_items) > 0 - - @property - def value_items(self): - return self._value_items - - -class EnumWrappingParameter(InternalParameterBase, SlotManager): - is_enabled = True - is_quantized = True - - def __init__(self, parent = None, values_property = None, index_property = None, value_type = int, to_index_conversion = None, from_index_conversion = None, *a, **k): - raise parent is not None or AssertionError - raise values_property is not None or AssertionError - raise index_property is not None or AssertionError - super(EnumWrappingParameter, self).__init__(*a, **k) - self._parent = parent - self._values_property = values_property - self._index_property = index_property - self._to_index = to_index_conversion or (lambda x: x) - self._from_index = from_index_conversion or (lambda x: x) - self.value_type = value_type - self._index_property_slot = self.register_slot(self._parent, self.notify_value, index_property) - try: - self.register_slot(self._parent, self.notify_value_items, values_property) - except SlotError: - pass - - def connect(self): - self._index_property_slot.subject = None - self._index_property_slot.subject = self._parent - - @property - def display_value(self): - index = self._get_index() - values = self._get_values() - if index < len(values): - return unicode(values[index]) - else: - return unicode() - - @listenable_property - def value_items(self): - return self._get_values() - - @listenable_property - def value(self): - return self._get_index() - - @value.setter - def value(self, new_value): - self._set_index(new_value) - - def _get_values(self): - return getattr(self._parent, self._values_property) if self._has_valid_parent() else [] - - def _get_index(self): - return self._from_index(int(getattr(self._parent, self._index_property)) if self._has_valid_parent() else 0) - - def _set_index(self, index): - index = self._to_index(index) - setattr(self._parent, self._index_property, self.value_type(index)) - - @property - def canonical_parent(self): - self._parent - - @property - def max(self): - return len(self.value_items) - 1 - - @property - def min(self): - return 0 - - -class RelativeInternalParameter(InternalParameter): - __events__ = ('delta',) - - @property - def default_value(self): - return 0.5 - - def _get_value(self): - return self.default_value - - def _set_value(self, new_value): - delta = new_value - self.value - if delta != 0.0: - self.notify_value() - self.notify_delta(delta) - - value = property(_get_value, _set_value) - linear_value = property(_get_value, _set_value) \ No newline at end of file diff --git a/Push2/item_lister_component.py b/Push2/item_lister_component.py index 55e41b3c..cc0fe1ec 100644 --- a/Push2/item_lister_component.py +++ b/Push2/item_lister_component.py @@ -1,27 +1,24 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/Push2/item_lister_component.py +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/Push2/item_lister_component.py +# Compiled at: 2016-09-29 19:13:24 from __future__ import absolute_import, print_function from itertools import izip -from ableton.v2.base import forward_property, listens, SlotManager, Subject +from ableton.v2.base import forward_property, listenable_property, listens, EventObject from ableton.v2.control_surface import Component, CompoundComponent -from ableton.v2.control_surface.control import control_list, ButtonControl, RadioButtonControl +from ableton.v2.control_surface.control import control_list, ButtonControl -class SimpleItemSlot(SlotManager, Subject): - __events__ = ('name',) +class SimpleItemSlot(EventObject): - def __init__(self, item = None, name = '', nesting_level = -1, icon = '', *a, **k): + def __init__(self, item=None, name='', nesting_level=-1, icon='', *a, **k): super(SimpleItemSlot, self).__init__(*a, **k) self._item = item self._name = name self._nesting_level = nesting_level self._icon = icon - self.__on_name_changed.subject = self._item if hasattr(self._item, 'name_has_listener') else None + self.__on_name_changed.subject = self._item if getattr(self._item, 'name_has_listener', None) else None + self.__on_color_index_changed.subject = self._item if getattr(self._item, 'color_index_has_listener', None) else None + return - @listens('name') - def __on_name_changed(self): - self.notify_name() - self._name = self._item.name - - @property + @listenable_property def name(self): return self._name @@ -37,12 +34,26 @@ def nesting_level(self): def icon(self): return self._icon + @listenable_property + def color_index(self): + return getattr(self._item, 'color_index', -1) + + @listens('name') + def __on_name_changed(self): + self.notify_name() + self._name = self._item.name + + @listens('color_index') + def __on_color_index_changed(self): + self.notify_color_index() + class ItemSlot(SimpleItemSlot): - def __init__(self, item = None, nesting_level = 0, **k): - raise item != None or AssertionError + def __init__(self, item=None, nesting_level=0, **k): + assert item != None super(ItemSlot, self).__init__(item=item, name=item.name, nesting_level=nesting_level, **k) + return def __eq__(self, other): return id(self) == id(other) or self._item == other @@ -56,7 +67,7 @@ def __hash__(self): _live_ptr = forward_property('_item')('_live_ptr') -class ItemProvider(Subject): +class ItemProvider(EventObject): """ General interface to implement for providers used in ItemListerComponent """ __events__ = ('items', 'selected_item') @@ -74,9 +85,9 @@ def selected_item(self): class ItemListerComponentBase(CompoundComponent): - __events__ = ('items',) + __events__ = ('items', ) - def __init__(self, item_provider = ItemProvider(), num_visible_items = 8, *a, **k): + def __init__(self, item_provider=ItemProvider(), num_visible_items=8, *a, **k): super(ItemListerComponentBase, self).__init__(*a, **k) self._item_offset = 0 self._item_provider = item_provider @@ -147,9 +158,11 @@ def create_slot(index, item, nesting_level): new_items = [] if num_slots > 0: - new_items = [ create_slot(index, *item) for index, item in enumerate(items[:num_slots]) if item[0] != None ] + new_items = [ create_slot(index, *item) for index, item in enumerate(items[:num_slots]) if item[0] != None + ] self._items = map(self.register_disconnectable, new_items) self.notify_items() + return @listens('items') def __on_items_changed(self): @@ -157,7 +170,7 @@ def __on_items_changed(self): class ScrollComponent(Component): - __events__ = ('scroll',) + __events__ = ('scroll', ) button = ButtonControl(color='ItemNavigation.ItemNotSelected', repeat=True) @button.pressed @@ -208,7 +221,8 @@ def update(self): class ItemListerComponent(ItemListerComponentBase): - select_buttons = control_list(ButtonControl, unavailable_color='ItemNavigation.NoItem') + color_class_name = 'ItemNavigation' + select_buttons = control_list(ButtonControl, unavailable_color=color_class_name + '.NoItem') def __init__(self, *a, **k): super(ItemListerComponent, self).__init__(*a, **k) @@ -233,16 +247,18 @@ def __on_items_changed(self): def __on_selection_changed(self): self._update_button_colors() + def _items_equal(self, item, selected_item): + return item == selected_item + def _update_button_colors(self): selected_item = self._item_provider.selected_item for button, item in izip(self.select_buttons, self.items): - is_selected = item == selected_item - button.color = self._color_for_button(button.index, is_selected) + button.color = self._color_for_button(button.index, self._items_equal(item, selected_item)) def _color_for_button(self, button_index, is_selected): if is_selected: - return 'ItemNavigation.ItemSelected' - return 'ItemNavigation.ItemNotSelected' + return self.color_class_name + '.ItemSelected' + return self.color_class_name + '.ItemNotSelected' @select_buttons.pressed def select_buttons(self, button): diff --git a/Push2/mapped_control.py b/Push2/mapped_control.py deleted file mode 100644 index e8819a30..00000000 --- a/Push2/mapped_control.py +++ /dev/null @@ -1,63 +0,0 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/midi-remote-scripts/Push2/mapped_control.py -from __future__ import absolute_import -from ableton.v2.base import clamp, listens, liveobj_valid -from ableton.v2.control_surface.control import MappedControl as MappedControlBase -from ableton.v2.control_surface.control.encoder import ValueStepper -from .internal_parameter import InternalParameterBase - -def is_internal_parameter(parameter): - return isinstance(parameter, InternalParameterBase) - - -class MappedControl(MappedControlBase): - - class State(MappedControlBase.State): - default_sensitivity = 1.0 - fine_sensitivity = 0.1 - - def __init__(self, *a, **k): - super(MappedControl.State, self).__init__(*a, **k) - self._quantized_stepper = ValueStepper() - - def update_sensitivities(self, default, fine): - self.default_sensitivity = default - self.fine_sensitivity = fine - if self._control_element: - self._update_control_sensitivity() - - def _update_direct_connection(self): - if self._control_element is None or is_internal_parameter(self.mapped_parameter): - if self._control_element: - self._control_element.release_parameter() - self._control_value.subject = self._control_element - else: - self._control_value.subject = None - self._update_control_element() - self._quantized_stepper.reset() - - def _update_control_element(self): - if liveobj_valid(self.mapped_parameter): - self._control_element.connect_to(self.mapped_parameter) - else: - self._control_element.release_parameter() - self._update_control_sensitivity() - self._quantized_stepper.reset() - - def _update_control_sensitivity(self): - if hasattr(self._control_element, 'set_sensitivities'): - self._control_element.set_sensitivities(self.default_sensitivity, self.fine_sensitivity) - else: - self._control_element.mapping_sensitivity = self.default_sensitivity - - @listens('normalized_value') - def _control_value(self, value): - if self.mapped_parameter.is_quantized: - steps = self._quantized_stepper.advance(value) - if steps != 0: - self.mapped_parameter.value = self._clamp_value_to_parameter_range(self.mapped_parameter.value + steps) - else: - value_offset = value * self._control_element.mapping_sensitivity - self.mapped_parameter.linear_value = self._clamp_value_to_parameter_range(self.mapped_parameter.linear_value + value_offset) - - def _clamp_value_to_parameter_range(self, value): - return clamp(value, self.mapped_parameter.min, self.mapped_parameter.max) \ No newline at end of file diff --git a/Push2/master_track.py b/Push2/master_track.py index b86d7139..3169526b 100644 --- a/Push2/master_track.py +++ b/Push2/master_track.py @@ -1,4 +1,5 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/Push2/master_track.py +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/Push2/master_track.py +# Compiled at: 2016-05-20 03:43:52 from __future__ import absolute_import, print_function from ableton.v2.base import listens from ableton.v2.control_surface import Component @@ -7,13 +8,14 @@ class MasterTrackComponent(Component): toggle_button = ToggleButtonControl() - def __init__(self, tracks_provider = None, *a, **k): - raise tracks_provider is not None or AssertionError + def __init__(self, tracks_provider=None, *a, **k): + assert tracks_provider is not None super(MasterTrackComponent, self).__init__(*a, **k) self._tracks_provider = tracks_provider self.__on_selected_item_changed.subject = self._tracks_provider self._previous_selection = self._tracks_provider.selected_item self._update_button_state() + return @listens('selected_item') def __on_selected_item_changed(self, *a): diff --git a/Push2/mixable_utilities.py b/Push2/mixable_utilities.py index 8e4fa7fb..8dcf7810 100644 --- a/Push2/mixable_utilities.py +++ b/Push2/mixable_utilities.py @@ -1,4 +1,5 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/Push2/mixable_utilities.py +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/Push2/mixable_utilities.py +# Compiled at: 2016-05-20 03:43:52 from __future__ import absolute_import, print_function import Live from pushbase.device_chain_utils import find_instrument_meeting_requirement diff --git a/Push2/mixer_component.py b/Push2/mixer_component.py deleted file mode 100644 index cfbba6f2..00000000 --- a/Push2/mixer_component.py +++ /dev/null @@ -1,207 +0,0 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/midi-remote-scripts/Push2/mixer_component.py -from itertools import izip -from ableton.v2.base import find_if, listenable_property, listens, listens_group, liveobj_valid, Subject, task -from ableton.v2.control_surface import Component -from ableton.v2.control_surface.components import MixerComponent as MixerComponentBase -from ableton.v2.control_surface.components import ChannelStripComponent as ChannelStripComponentBase -from ableton.v2.control_surface.control import ButtonControl -from ableton.v2.control_surface.mode import AddLayerMode, ModesComponent -from .track_selection import get_all_mixer_tracks, mixable_button_color - -class MixerDeviceMuteSoloComponent(Component): - mute_button = ButtonControl() - solo_button = ButtonControl() - __events__ = ('mute_or_solo_pressed',) - - def __init__(self, *a, **k): - super(MixerDeviceMuteSoloComponent, self).__init__(*a, **k) - self.mute_button.enabled = False - self.solo_button.enabled = False - - def set_track(self, track): - self._track = track - enabled_state = True if self._track is not None else False - self.mute_button.enabled = enabled_state - self.solo_button.enabled = enabled_state - - @property - def track(self): - return self._track - - @mute_button.pressed - def mute_button(self, button): - self.toggle_mute() - self.notify_mute_or_solo_pressed() - - def toggle_mute(self): - if self._track: - self._track.mute = not self._track.mute - - @solo_button.pressed - def solo_button(self, button): - self.toggle_solo() - self.notify_mute_or_solo_pressed() - - def toggle_solo(self): - if self._track and self._track != self.song.master_track: - song = self.song - tracks = get_all_mixer_tracks(song) - other_solos = any([ track.solo for track in tracks ]) - if other_solos and song.exclusive_solo and not self._track.solo: - for track in tracks: - track.solo = False - - self._track.solo = not self._track.solo - - -class MixerButtonStateManager(Subject): - is_pressed = listenable_property.managed(False) - - -class MixerComponent(MixerComponentBase, ModesComponent): - solo_track_button = ButtonControl() - mute_track_button = ButtonControl() - MIXER_BUTTON_STATE_DELAY = 0.1 - - def __init__(self, solo_layer = None, mute_layer = None, *a, **k): - super(MixerComponent, self).__init__(*a, **k) - self._allow_released_immediately_action = True - self.mixer_button_state = self.register_disconnectable(MixerButtonStateManager()) - self._mixer_button_state_task = self._tasks.add(task.sequence(task.wait(self.MIXER_BUTTON_STATE_DELAY), task.run(self._update_mixer_button_state))).kill() - self.add_mode('default', None) - self.add_mode('solo', AddLayerMode(self, solo_layer)) - self.add_mode('mute', AddLayerMode(self, mute_layer)) - self.selected_mode = 'default' - self._on_items_changed.subject = self._provider - self._on_selected_item_changed.subject = self._provider - self._on_selected_item_changed() - self._update_channel_strip_button_colors() - self.__on_channel_strip_mute_or_solo_changed.replace_subjects(self._channel_strips) - - @listens_group('mute_or_solo_pressed') - def __on_channel_strip_mute_or_solo_changed(self, _): - self._allow_released_immediately_action = False - - def _create_strip(self): - return MixerDeviceMuteSoloComponent() - - def _create_master_strip(self): - return ChannelStripComponentBase() - - @listens('items') - def _on_items_changed(self): - mixer_tracks = self._provider.items - for track, channel_strip in izip(mixer_tracks, self._channel_strips): - channel_strip.set_track(track) - - self._on_solo_changed.replace_subjects(mixer_tracks) - self._on_mute_changed.replace_subjects(mixer_tracks) - self._update_button_state_colors() - self._update_channel_strip_button_colors() - - def set_mute_buttons(self, buttons): - for strip, button in map(None, self._channel_strips, buttons or []): - strip.mute_button.set_control_element(button) - - def set_solo_buttons(self, buttons): - for strip, button in map(None, self._channel_strips, buttons or []): - strip.solo_button.set_control_element(button) - - def _reassign_tracks(self): - self._on_items_changed() - - def _update_selected_strip(self): - """ - We are not interested in setting the selected channel strip, which occurs in the - base mixer component. - """ - pass - - def _update_send_index(self): - """ - We are not interested in the base mixer's send index, which is updated when the - return tracks change. This also reassigns the send_controls, which we are not - interested in doing. - """ - pass - - def _mute_or_solo_is_pressed(self): - return self.solo_track_button.is_pressed or self.mute_track_button.is_pressed - - def _update_mixer_button_state(self): - self.mixer_button_state.is_pressed = self._mute_or_solo_is_pressed() - - @listens('selected_item') - def _on_selected_item_changed(self): - self._update_button_state_colors() - self._update_channel_strip_button_colors() - - @listens_group('solo') - def _on_solo_changed(self, mixable): - self._update_button_state_colors() - self._update_channel_strip_button_colors() - - @listens_group('mute') - def _on_mute_changed(self, mixable): - self._update_button_state_colors() - self._update_channel_strip_button_colors() - - @solo_track_button.released_immediately - def solo_track_button(self, button): - if self._allow_released_immediately_action: - self._toggle_channel_strip_property(lambda channel_strip: channel_strip.toggle_solo()) - - @solo_track_button.pressed - def solo_track_button(self, button): - self._allow_released_immediately_action = True - self.push_mode('solo') - self._mixer_button_state_task.restart() - - @solo_track_button.released - def solo_track_button(self, button): - self.pop_mode('solo') - self.mixer_button_state.is_pressed = self._mute_or_solo_is_pressed() - self._update_button_state_colors() - - @mute_track_button.released_immediately - def mute_track_button(self, button): - if self._allow_released_immediately_action: - self._toggle_channel_strip_property(lambda channel_strip: channel_strip.toggle_mute()) - - @mute_track_button.pressed - def mute_track_button(self, button): - self._allow_released_immediately_action = True - self.push_mode('mute') - self._mixer_button_state_task.restart() - - @mute_track_button.released - def mute_track_button(self, button): - self.pop_mode('mute') - self.mixer_button_state.is_pressed = self._mute_or_solo_is_pressed() - self._update_button_state_colors() - - def _toggle_channel_strip_property(self, toggle_function): - channel_strip = self._get_selected_track_channel_strip() - if channel_strip: - toggle_function(channel_strip) - - def _get_selected_track_channel_strip(self): - selected_track = self._provider.selected_item - return find_if(lambda strip: strip.track == selected_track, self._channel_strips) - - def _update_channel_strip_button_colors(self): - song = self.song - for strip in self._channel_strips: - color = mixable_button_color(strip.track, song, self._provider.selected_item) - strip.mute_button.color = color - strip.solo_button.color = color - - def _update_button_state_colors(self): - song = self.song - selected_track = self._provider.selected_item - if selected_track != song.master_track: - self.mute_track_button.color = self._get_track_state_mode_state('mute', selected_track.mute if liveobj_valid(selected_track) else False, 'Mixer.MuteOff') - self.solo_track_button.color = self._get_track_state_mode_state('solo', selected_track.solo if liveobj_valid(selected_track) else False, 'Mixer.SoloOn') - - def _get_track_state_mode_state(self, mode, track_state_parameter, on_color): - return on_color if track_state_parameter or self.selected_mode == mode else 'DefaultButton.On' \ No newline at end of file diff --git a/Push2/mixer_control_component.py b/Push2/mixer_control_component.py index 2b41d737..82e14fbc 100644 --- a/Push2/mixer_control_component.py +++ b/Push2/mixer_control_component.py @@ -1,30 +1,25 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/Push2/mixer_control_component.py +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/Push2/mixer_control_component.py +# Compiled at: 2016-09-29 19:13:24 from __future__ import absolute_import, print_function from contextlib import contextmanager from functools import partial from itertools import izip, izip_longest from math import ceil -from ableton.v2.base import clamp, depends, listens, liveobj_valid, NamedTuple +import Live +from ableton.v2.base import clamp, depends, listens, listens_group, liveobj_valid, NamedTuple from ableton.v2.control_surface.control import control_list, ButtonControl from ableton.v2.control_surface.mode import ModesComponent from pushbase.mapped_control import MappedControl from .real_time_channel import RealTimeDataComponent from .item_lister_component import SimpleItemSlot MIXER_SECTIONS = ('Volumes', 'Pans') -SEND_SECTIONS = ['A Sends', - 'B Sends', - 'C Sends', - 'D Sends', - 'E Sends', - 'F Sends', - 'G Sends', - 'H Sends', - 'I Sends', - 'J Sends', - 'K Sends', - 'L Sends'] +SEND_SECTIONS = [ + 'A Sends', 'B Sends', 'C Sends', 'D Sends', + 'E Sends', 'F Sends', 'G Sends', 'H Sends', + 'I Sends', 'J Sends', 'K Sends', 'L Sends'] SEND_LIST_LENGTH = 5 -SEND_MODE_NAMES = ['send_slot_one', +SEND_MODE_NAMES = [ + 'send_slot_one', 'send_slot_two', 'send_slot_three', 'send_slot_four', @@ -35,19 +30,43 @@ class MixerSectionDescription(NamedTuple): parameter_name = None +def find_parent_track(live_obj): + if isinstance(live_obj, Live.Track.Track): + return live_obj + else: + return find_parent_track(live_obj.canonical_parent) + + +def assign_parameters(controls, parameters): + for control, parameter in izip_longest(controls, parameters): + if control: + if not liveobj_valid(parameter) or isinstance(parameter.canonical_parent, Live.MixerDevice.MixerDevice): + control.mapped_parameter = parameter + else: + track = find_parent_track(parameter) + control.mapped_parameter = parameter if liveobj_valid(track) and not track.is_frozen else None + + return + + class MixerControlComponent(ModesComponent): __events__ = ('items', 'selected_item') controls = control_list(MappedControl) cycle_sends_button = ButtonControl(color='DefaultButton.Off') + @staticmethod + def get_tracks(items): + return filter(lambda item: item is not None and isinstance(item.proxied_object, Live.Track.Track), items) + @depends(tracks_provider=None, real_time_mapper=None, register_real_time_data=None) - def __init__(self, view_model = None, tracks_provider = None, real_time_mapper = None, register_real_time_data = None, *a, **k): - raise liveobj_valid(real_time_mapper) or AssertionError - raise view_model is not None or AssertionError - raise tracks_provider is not None or AssertionError + def __init__(self, view_model=None, tracks_provider=None, real_time_mapper=None, register_real_time_data=None, *a, **k): + assert liveobj_valid(real_time_mapper) + assert view_model is not None + assert tracks_provider is not None super(MixerControlComponent, self).__init__(*a, **k) self._send_offset = 0 - self.real_time_meter_handlers = [ RealTimeDataComponent(channel_type='meter', real_time_mapper=real_time_mapper, register_real_time_data=register_real_time_data, is_enabled=False) for _ in xrange(8) ] + self.real_time_meter_handlers = [ RealTimeDataComponent(channel_type='meter', real_time_mapper=real_time_mapper, register_real_time_data=register_real_time_data, is_enabled=False) for _ in xrange(8) + ] self._track_provider = tracks_provider self._on_return_tracks_changed.subject = self.song self._on_mode_changed.subject = self @@ -63,18 +82,21 @@ def __init__(self, view_model = None, tracks_provider = None, real_time_mapper = self._update_mixer_sections() self._on_items_changed.subject = self._track_provider self._on_selected_item_changed.subject = self._track_provider + self.__on_track_frozen_state_changed.replace_subjects(self.get_tracks(self._track_provider.items)) + return def _setup_modes(self, view_model): self._add_mode('volume', view_model.volumeControlListView, lambda mixer: mixer.volume, additional_mode_contents=self.real_time_meter_handlers) self._add_mode('panning', view_model.panControlListView, lambda mixer: mixer.panning) def add_send_mode(index): - self._add_mode(SEND_MODE_NAMES[index], view_model.sendControlListView, lambda mixer: (mixer.sends[self._send_offset + index] if len(mixer.sends) > self._send_offset + index else None)) + self._add_mode(SEND_MODE_NAMES[index], view_model.sendControlListView, lambda mixer: if len(mixer.sends) > self._send_offset + index: +mixer.sends[self._send_offset + index]None) for i in xrange(SEND_LIST_LENGTH): add_send_mode(i) - def _add_mode(self, mode, view, parameter_getter, additional_mode_contents = []): + def _add_mode(self, mode, view, parameter_getter, additional_mode_contents=[]): description = MixerSectionDescription(view=view, parameter_getter=parameter_getter) self.add_mode(mode, additional_mode_contents + [partial(self._set_mode, description)]) mode_button = self.get_mode_button(mode) @@ -126,6 +148,10 @@ def _on_return_tracks_changed(self): def _on_items_changed(self): self._update_controls(self._parameter_getter, self._selected_view) + @listens_group('is_frozen') + def __on_track_frozen_state_changed(self, identifier): + self._update_controls(self._parameter_getter, self._selected_view) + @listens('selected_item') def _on_selected_item_changed(self): if self.number_sends <= SEND_LIST_LENGTH: @@ -150,7 +176,8 @@ def _update_mixer_sections(self): mixer_section_names = list(MIXER_SECTIONS) + SEND_SECTIONS[position:position + pos_range] self._mixer_sections = [ SimpleItemSlot(name=name) for name in mixer_section_names ] if self.number_sends > SEND_LIST_LENGTH: - self._mixer_sections.extend([SimpleItemSlot()] * (8 - len(self._mixer_sections))) + self._mixer_sections.extend([ + SimpleItemSlot()] * (8 - len(self._mixer_sections))) self._mixer_sections[7] = SimpleItemSlot(icon='page_right.svg') self.notify_items() if self.selected_mode in SEND_MODE_NAMES: @@ -167,21 +194,24 @@ def selected_item(self): return self._selected_item def _update_controls(self, parameter_getter, control_view): - parameters = self._get_parameter_for_tracks(parameter_getter) - control_view.parameters = parameters - self._update_realtime_ids() - for control, parameter in izip_longest(self.controls, parameters): - control.mapped_parameter = parameter + if self.is_enabled(): + parameters = self._get_parameter_for_tracks(parameter_getter) + control_view.parameters = parameters + self._update_realtime_ids() + assign_parameters(self.controls, parameters) def _update_realtime_ids(self): mixables = self._track_provider.items for handler, mixable in izip(self.real_time_meter_handlers, mixables): handler.set_data(mixable.mixer_device if liveobj_valid(mixable) else None) + return + def _get_parameter_for_tracks(self, parameter_getter): tracks = self._track_provider.items self.controls.control_count = len(tracks) - return map(lambda t: (parameter_getter(t.mixer_device) if t else None), tracks) + return map(lambda t: if t: +parameter_getter(t.mixer_device)None, tracks) def mode_can_be_used(self, mode): return mode not in SEND_MODE_NAMES or SEND_MODE_NAMES.index(mode) + self._send_offset < self.number_sends diff --git a/Push2/mode_collector.py b/Push2/mode_collector.py index 20a4badf..32aec266 100644 --- a/Push2/mode_collector.py +++ b/Push2/mode_collector.py @@ -1,10 +1,11 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/Push2/mode_collector.py +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/Push2/mode_collector.py +# Compiled at: 2016-05-20 03:43:52 from __future__ import absolute_import, print_function -from ableton.v2.base import listenable_property, listens, Subject, SlotManager +from ableton.v2.base import listenable_property, listens, EventObject -class ModeCollector(SlotManager, Subject): +class ModeCollector(EventObject): - def __init__(self, main_modes = None, mix_modes = None, global_mix_modes = None, device_modes = None, *a, **k): + def __init__(self, main_modes=None, mix_modes=None, global_mix_modes=None, device_modes=None, *a, **k): super(ModeCollector, self).__init__(*a, **k) self._main_modes = main_modes self._mix_modes = mix_modes diff --git a/Push2/model/__init__.py b/Push2/model/__init__.py index 709f0290..ca1fc8f9 100644 --- a/Push2/model/__init__.py +++ b/Push2/model/__init__.py @@ -1,13 +1,47 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/Push2/model/__init__.py +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/Push2/model/__init__.py +# Compiled at: 2016-09-29 19:13:24 from __future__ import absolute_import, print_function from .declaration import Binding, custom_property, id_property, listmodel, listof, view_property, ViewModel, ModelVisitor -from .repr import BrowserItemAdapter, BrowserListWrapper, ClipAdapter, DeviceAdapter, DeviceParameterAdapter, EditModeOptionAdapter, ItemListAdapter, ItemSlotAdapter, LiveDialogAdapter, OptionsListAdapter, SimplerDeviceAdapter, TrackAdapter, TrackControlAdapter, TrackListAdapter, VisibleAdapter -__all__ = (ModelVisitor,) +from .repr import BrowserItemAdapter, BrowserListWrapper, ClipAdapter, DeviceAdapter, DeviceParameterAdapter, EditModeOptionAdapter, ItemListAdapter, ItemSlotAdapter, LiveDialogAdapter, OptionsListAdapter, RoutingAdapter, SimplerDeviceAdapter, TrackAdapter, TrackControlAdapter, TrackListAdapter, TrackMixAdapter, VisibleAdapter +__all__ = ( + ModelVisitor,) + +class RealTimeChannel(Binding): + channel_id = view_property(unicode, '') + object_id = view_property(unicode, '') + class VisibleModel(ViewModel): visible = view_property(bool, False) +class ClipPositions(Binding): + start = view_property(float, -1) + end = view_property(float, -1) + start_marker = view_property(float, -1) + end_marker = view_property(float, -1) + loop_start = view_property(float, -1) + loop_end = view_property(float, -1) + + +class ClipModel(Binding): + ADAPTER = ClipAdapter + id = id_property() + name = view_property(unicode, '') + color_index = view_property(int, -1) + is_recording = view_property(bool, False) + warping = view_property(bool, False) + positions = view_property(ClipPositions) + loop_start = view_property(float, 0.0) + loop_end = view_property(float, 0.0) + signature_numerator = view_property(int, 4) + signature_denominator = view_property(int, 4) + + +class ClipControlModel(Binding): + clip = view_property(ClipModel) + + class Track(Binding): ADAPTER = TrackAdapter name = view_property(unicode, '') @@ -18,10 +52,15 @@ class Track(Binding): nestingLevel = view_property(int, 0) activated = view_property(bool, True) isFrozen = view_property(bool, True) + parent_track_frozen = view_property(bool, False) parentColorIndex = view_property(int, -1) arm = view_property(bool, False) isMaster = view_property(bool, False) isAudio = view_property(bool, False) + isReturn = view_property(bool, False) + hasPlayingClip = view_property(bool, False) + playingClip = view_property(ClipModel) + outputRouting = view_property(unicode, '') id = id_property() @@ -31,16 +70,20 @@ class TrackListModel(Binding): tracks = view_property(listof(Track)) selectedTrack = view_property(Track) absolute_selected_track_index = view_property(int, -1) + playhead_real_time_channels = view_property(listof(RealTimeChannel)) class Device(Binding): ADAPTER = DeviceAdapter name = view_property(unicode, '') + navigation_name = view_property(unicode, '') nestingLevel = view_property(int, 0) is_active = view_property(bool, False) id = id_property() class_name = view_property(unicode, '') icon = view_property(unicode, '') + chain_color_index = view_property(int, -1) + rack_color_index = view_property(int, -1) class DeviceListModel(Binding): @@ -75,15 +118,25 @@ class EditModeOption(Binding): class EditModeOptionsModel(Binding): ADAPTER = VisibleAdapter visible = view_property(bool, False) - device = view_property(unicode, '') + device = view_property(Device) options = view_property(listof(EditModeOption)) +class TransportState(Binding): + count_in_duration = view_property(int, 0) + count_in_real_time_channel_id = view_property(unicode, '') + is_counting_in = view_property(bool, False) + signature_numerator = view_property(int, 4) + signature_denominator = view_property(int, 4) + is_playing = view_property(bool, False) + + class Chain(Binding): ADAPTER = ItemSlotAdapter name = view_property(unicode, '') id = id_property() icon = view_property(unicode, '') + color_index = view_property(int, -1) class ChainListModel(Binding): @@ -134,34 +187,39 @@ class Controls(ViewModel): class Slice(Binding): id = id_property() - time = view_property(int, -1) - - -class Sample(Binding): - start_marker = view_property(int, 0) - end_marker = view_property(int, 0) - length = view_property(int, 0) - - -class SimplerView(Binding): - sample_start = view_property(int, 0) - sample_end = view_property(int, 0) - sample_loop_start = view_property(int, 0) - sample_loop_end = view_property(int, 0) - sample_loop_fade = view_property(int, 0) - sample_env_fade_in = view_property(int, 0) - sample_env_fade_out = view_property(int, 0) + time = view_property(float, -1) + + +class SimplerPositions(Binding): + start = view_property(float, 0) + end = view_property(float, 0) + start_marker = view_property(float, 0) + end_marker = view_property(float, 0) + active_start = view_property(float, 0) + active_end = view_property(float, 0) + loop_start = view_property(float, 0) + loop_end = view_property(float, 0) + loop_fade_in_samples = view_property(float, 0) + env_fade_in = view_property(float, 0) + env_fade_out = view_property(float, 0) + slices = view_property(listmodel(Slice)) + selected_slice = view_property(Slice) class WaveformNavigationFocusMarker(Binding): name = view_property(unicode, u'') - position = view_property(int, -1) + position = view_property(float, -1) + + +class WaveformRegion(Binding): + start = view_property(float, 0.0) + end = view_property(float, 0.0) class WaveformNavigation(Binding): animate_visible_region = view_property(bool, False) - visible_start = view_property(float, 0.0, depends=animate_visible_region) - visible_end = view_property(float, 0.0, depends=animate_visible_region) + visible_region = view_property(WaveformRegion, depends=animate_visible_region) + visible_region_in_samples = view_property(WaveformRegion, depends=animate_visible_region) show_focus = view_property(bool, False) focus_marker = view_property(WaveformNavigationFocusMarker) @@ -172,7 +230,6 @@ class SimplerProperties(Binding): sample_length = view_property(DeviceParameter) loop_length = view_property(DeviceParameter) loop_on = view_property(DeviceParameter) - zoom = view_property(DeviceParameter) gain = view_property(float, 0.0) start_marker = view_property(int, 0) end_marker = view_property(int, 0) @@ -182,40 +239,87 @@ class SimplerProperties(Binding): selected_slice = view_property(Slice) playhead_real_time_channel_id = view_property(unicode, '') waveform_real_time_channel_id = view_property(unicode, '') - sample = view_property(Sample) - view = view_property(SimplerView) + warping = view_property(bool, False) + positions = view_property(SimplerPositions) waveform_navigation = view_property(WaveformNavigation) class DeviceParameterListModel(ViewModel): visible = view_property(bool, False) deviceType = view_property(unicode, '') + device = view_property(Device) parameters = view_property(listof(DeviceParameter)) class SimplerDeviceViewModel(ViewModel): visible = view_property(bool, False) deviceType = view_property(unicode, '') - simpler = view_property(Device) + device = view_property(Device) parameters = view_property(listof(DeviceParameter)) properties = view_property(SimplerProperties) wants_waveform_shown = view_property(bool, False) - processed_zoom_requests = view_property(int, 0) -class RealTimeChannel(Binding): - channel_id = view_property(unicode, '') - object_id = view_property(unicode, '') - - -class TrackControlModel(Binding): - ADAPTER = TrackControlAdapter +class TrackMixModel(Binding): + ADAPTER = TrackMixAdapter visible = view_property(bool, False) parameters = view_property(listof(DeviceParameter)) scrollOffset = view_property(int, 0) real_time_meter_channel = view_property(RealTimeChannel) +class RoutingType(Binding): + id = id_property() + name = view_property(unicode, u'') + + +class RoutingChannel(Binding): + id = id_property() + name = view_property(unicode, u'') + layout = view_property(unicode, u'') + realtime_channel = view_property(RealTimeChannel) + + +class RoutingTypeList(Binding): + id = id_property() + targets = view_property(listof(RoutingType)) + selected_target = view_property(RoutingType) + selected_index = view_property(int, -1, depends=targets) + selected_track = view_property(Track) + + +class RoutingChannelList(Binding): + id = id_property() + targets = view_property(listof(RoutingChannel)) + selected_target = view_property(RoutingChannel) + selected_index = view_property(int, -1, depends=targets) + + +class RoutingChannelPositionList(Binding): + id = id_property() + targets = view_property(listof(unicode)) + selected_index = view_property(int, -1, depends=targets) + + +class RoutingControlModel(Binding): + ADAPTER = RoutingAdapter + monitoring_state_index = view_property(int, 0) + can_monitor = view_property(bool, False) + can_route = view_property(bool, False) + is_choosing_output = view_property(bool, False) + routingTypeList = view_property(listof(RoutingTypeList)) + routingChannelList = view_property(listof(RoutingChannelList)) + routingChannelPositionList = view_property(listof(RoutingChannelPositionList)) + + +class TrackControlModel(Binding): + ADAPTER = TrackControlAdapter + track_control_mode = view_property(unicode, '') + routing_mode_available = view_property(bool, False) + track_mix = view_property(TrackMixModel) + routing = view_property(RoutingControlModel) + + class BrowserListView(Binding): id = id_property() selected_index = view_property(int, -1) @@ -227,6 +331,14 @@ class BrowserItem(Binding): name = view_property(unicode, '') icon = view_property(unicode, '') is_loadable = view_property(bool, False) + is_device = view_property(bool, False) + + +class BrowserLoadNeighbourOverlay(Binding): + ADAPTER = VisibleAdapter + visible = view_property(bool, False) + can_load_next = view_property(bool, False) + can_load_previous = view_property(bool, False) class BrowserModel(Binding): @@ -241,10 +353,12 @@ class BrowserModel(Binding): can_enter = view_property(bool, False) can_exit = view_property(bool, False) expanded = view_property(bool, False) - load_text = view_property(unicode, u'') prehear_enabled = view_property(bool, False) context_text = view_property(unicode, u'') context_color_index = view_property(int, -1) + context_display_type = view_property(unicode, u'') + load_neighbour_overlay = view_property(BrowserLoadNeighbourOverlay) + should_widen_focused_item = view_property(bool, False) class BrowserList(Binding): @@ -276,6 +390,7 @@ class ConvertModel(Binding): class NoteLayout(Binding): is_in_key = view_property(bool, False) is_fixed = view_property(bool, False) + is_horizontal = view_property(bool, False) class ScalesModel(Binding): @@ -283,6 +398,8 @@ class ScalesModel(Binding): visible = view_property(bool, False) scale_names = view_property(listof(unicode), '') selected_scale_index = view_property(int, -1) + layout_names = view_property(listof(unicode), '') + selected_layout_index = view_property(int, 0) root_note_names = view_property(listof(unicode), '') selected_root_note_index = view_property(int, -1) note_layout = view_property(NoteLayout) @@ -310,6 +427,7 @@ class StepAutomationSettingsModel(Binding): visible = view_property(bool, False) deviceType = view_property(unicode, '') parameters = view_property(listof(DeviceParameter)) + device = view_property(Device) can_automate_parameters = view_property(bool, False) @@ -332,6 +450,7 @@ class FixedLengthSettingsModel(Binding): option_names = view_property(listof(unicode)) selected_index = view_property(int, -1) enabled = view_property(bool, False) + legato_launch = view_property(bool, False) class FixedLengthSelectorModel(Binding): @@ -342,8 +461,6 @@ class FixedLengthSelectorModel(Binding): class LoopSettingsModel(Binding): looping = view_property(bool, False) loop_parameters = view_property(listof(DeviceParameter)) - zoom = view_property(DeviceParameter) - processed_zoom_requests = view_property(int, 0) waveform_navigation = view_property(WaveformNavigation) @@ -355,27 +472,6 @@ class AudioClipSettingsModel(Binding): playhead_real_time_channel_id = view_property(unicode, '') -class ClipViewModel(Binding): - sample_length = view_property(int, -1) - sample_start_marker = view_property(int, -1) - sample_end_marker = view_property(int, -1) - sample_loop_start = view_property(int, -1) - sample_loop_end = view_property(int, -1) - - -class ClipModel(Binding): - ADAPTER = ClipAdapter - id = id_property() - name = view_property(unicode, '') - color_index = view_property(int, -1) - is_recording = view_property(bool, False) - view = view_property(ClipViewModel) - - -class ClipControlModel(Binding): - clip = view_property(ClipModel) - - class ModeState(Binding): main_mode = view_property(unicode, '') mix_mode = view_property(unicode, '') @@ -383,10 +479,6 @@ class ModeState(Binding): device_mode = view_property(unicode, '') -class MixerRealTimeMeterModel(Binding): - real_time_meter_channel_ids = view_property(listof(unicode), '') - - class MixerViewModel(ViewModel): volumeControlListView = view_property(DeviceParameterListModel) panControlListView = view_property(DeviceParameterListModel) @@ -435,17 +527,12 @@ class ProfilingSettingsModel(Binding): show_realtime_ipc_stats = view_property(bool, False) -class ExperimentalSettingsModel(Binding): - new_waveform_navigation = view_property(bool, False) - - class SettingsModel(Binding): general = view_property(GeneralSettingsModel) pad_settings = view_property(PadSettingsModel) hardware = view_property(HardwareSettingsModel) display_debug = view_property(DisplayDebugSettingsModel) profiling = view_property(ProfilingSettingsModel) - experimental = view_property(ExperimentalSettingsModel) class VelocityCurveModel(Binding): @@ -459,6 +546,7 @@ class SetupModel(Binding): selected_mode = view_property(unicode, '') modes = view_property(listof(unicode)) velocity_curve = view_property(VelocityCurveModel) + make_it_go_boom = view_property(bool, False) class ValueModel(Binding): @@ -474,10 +562,15 @@ class ImportantGlobals(ViewModel): tempo = view_property(ValueModel) -class FirmwareInfo(ViewModel): +class FirmwareVersion(Binding): major = view_property(int, 0) minor = view_property(int, 0) build = view_property(int, 0) + release_type = view_property(unicode, u'') + + +class HardwareInfo(ViewModel): + firmwareVersion = view_property(FirmwareVersion) serialNumber = view_property(int, 0) @@ -489,6 +582,11 @@ class FirmwareUpdateModel(Binding): state = view_property(unicode, u'') +class FirmwareSwitcher(Binding): + can_switch_firmware = view_property(bool, False) + version_to_switch_to = view_property(FirmwareVersion) + + class LiveDialogViewModel(Binding): ADAPTER = LiveDialogAdapter visible = view_property(bool, False) @@ -501,6 +599,7 @@ class RootModel(ViewModel): realTimeClient = view_property(RealTimeClient) modeState = view_property(ModeState) controls = view_property(Controls) + transportState = view_property(TransportState) liveDialogView = view_property(LiveDialogViewModel) mixerSelectView = view_property(MixerSelectionListModel) trackMixerSelectView = view_property(TrackMixerSelectionListModel) @@ -527,5 +626,6 @@ class RootModel(ViewModel): clipView = view_property(ClipControlModel) setupView = view_property(SetupModel) importantGlobals = view_property(ImportantGlobals) - firmwareInfo = view_property(FirmwareInfo) - firmwareUpdate = view_property(FirmwareUpdateModel) \ No newline at end of file + hardwareInfo = view_property(HardwareInfo) + firmwareUpdate = view_property(FirmwareUpdateModel) + firmwareSwitcher = view_property(FirmwareSwitcher) \ No newline at end of file diff --git a/Push2/model/declaration.py b/Push2/model/declaration.py index f5bf18d8..32c6d8f6 100644 --- a/Push2/model/declaration.py +++ b/Push2/model/declaration.py @@ -1,4 +1,5 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/Push2/model/declaration.py +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/Push2/model/declaration.py +# Compiled at: 2016-05-20 03:43:52 from __future__ import absolute_import, print_function from itertools import count @@ -49,12 +50,7 @@ def is_reference_property_decl(decl): def is_value_property_type(decl): - return decl.property_type in (int, - long, - float, - unicode, - str, - bool) + return decl.property_type in (int, long, float, unicode, str, bool) def is_custom_property(decl): @@ -69,16 +65,18 @@ class view_property(property_declaration): sentinel = object() GLOBAL_ORDER = count() - def __init__(self, property_type, default_value = sentinel, depends = (), *a, **k): + def __init__(self, property_type, default_value=sentinel, depends=(), *a, **k): super(view_property, self).__init__(*a, **k) self.property_type = property_type self.default_value = default_value self.order = self.GLOBAL_ORDER.next() - raise default_value is not self.sentinel or is_view_model_property_decl(self) or is_list_property_decl(self) or is_binding_property_decl(self) or is_reference_property_decl(self) or AssertionError + assert default_value is not self.sentinel or is_view_model_property_decl(self) or is_list_property_decl(self) or is_binding_property_decl(self) or is_reference_property_decl(self) depends = depends def __repr__(self): - return '<%s %r>' % (self.__class__.__name__, self.property_type) + return '<%s %r>' % ( + self.__class__.__name__, + self.property_type) def visit(self, name, visitor): if name == 'id': @@ -88,10 +86,11 @@ def visit(self, name, visitor): class custom_property(view_property): - def __init__(self, property_type, wrapper_class = None, *a, **k): - raise wrapper_class is not None or AssertionError + def __init__(self, property_type, wrapper_class=None, *a, **k): + assert wrapper_class is not None super(custom_property, self).__init__(property_type=property_type, *a, **k) self.wrapper_class = wrapper_class + return def visit(self, name, visitor): if name == 'id': @@ -125,7 +124,9 @@ def __init__(self, property_type, *a, **k): self.property_type = property_type def __repr__(self): - return '<%s %r>' % (self.__class__.__name__, self.property_type) + return '<%s %r>' % ( + self.__class__.__name__, + self.property_type) class listmodel(listof): diff --git a/Push2/model/generation.py b/Push2/model/generation.py index 7a49d7be..4582ca41 100644 --- a/Push2/model/generation.py +++ b/Push2/model/generation.py @@ -1,11 +1,12 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/Push2/model/generation.py +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/Push2/model/generation.py +# Compiled at: 2016-06-13 08:15:55 from __future__ import absolute_import, print_function from hashlib import md5 from contextlib import contextmanager from collections import namedtuple from operator import attrgetter from functools import partial -from ableton.v2.base import Disconnectable, SlotManager, Slot, has_event +from ableton.v2.base import Disconnectable, EventObject, Slot, has_event from .repr import ModelAdapter from .declaration import ViewModelsCantContainRefs, ViewModelCantContainListModels, UndeclaredReferenceClass, ModelVisitor @@ -22,11 +23,12 @@ def subject_valid(self, adapter): class ModelUpdateNotifier(object): - def __init__(self, step = None, parent = None, delegate = None): - raise parent is not None or step is None or AssertionError((parent, step)) + def __init__(self, step=None, parent=None, delegate=None): + assert parent is not None or step is None, (parent, step) self._step = step self._delegate = delegate self.path = [] if self._step is None else parent.path + [self._step] + return def step(self, step): return ModelUpdateNotifier(step=step, parent=self, delegate=self._delegate) @@ -42,7 +44,7 @@ def structural_change(self): class WrapperBase(Disconnectable): - def __init__(self, notifier = ModelUpdateNotifier(), *a, **k): + def __init__(self, notifier=ModelUpdateNotifier(), *a, **k): super(WrapperBase, self).__init__(*a, **k) self._notifier = notifier @@ -78,17 +80,17 @@ def notify(self): self._notifier.structural_change() -class BoundListWrapper(SlotManager, SimpleWrapper): +class BoundListWrapper(EventObject, SimpleWrapper): - def __init__(self, parent_object, name = None, wrapper = None, notifier = ModelUpdateNotifier(), verify_unique_ids = False, *a, **k): - raise wrapper is not None or AssertionError - raise name is not None or AssertionError + def __init__(self, parent_object, name=None, wrapper=None, notifier=ModelUpdateNotifier(), *a, **k): + assert wrapper is not None + assert name is not None super(BoundListWrapper, self).__init__([], notifier=notifier, *a, **k) self.wrapper = wrapper self.attrgetter = partial(getattr, parent_object, name) - self._verify_unique_ids = verify_unique_ids self._update_list() self._connect(parent_object, name) + return def notify(self): self._notifier.structural_change() @@ -97,13 +99,11 @@ def _update_list(self): for value in self._value: self.disconnect_disconnectable(value) - self._value = [ self.wrapper(v, notifier=self._notifier.step(i)) for i, v in enumerate(self.attrgetter()) ] + self._value = [ self.wrapper(v, notifier=self._notifier.step(i)) for i, v in enumerate(self.attrgetter()) + ] for value in self._value: self.register_disconnectable(value) - if self._verify_unique_ids: - raise len(self._value) == len(set((item.values['id'].get() for item in self._value))) or AssertionError('BoundListWrapper requires unique ids for items') - def to_json(self): return [ v.to_json() for v in self._value ] @@ -116,10 +116,11 @@ def _connect(self, parent_object, name): class BoundAttributeWrapper(WrapperBase): - def __init__(self, bound_object, attr_getter = None, *a, **k): + def __init__(self, bound_object, attr_getter=None, *a, **k): super(BoundAttributeWrapper, self).__init__(*a, **k) - raise attr_getter is not None or AssertionError + assert attr_getter is not None self.attrgetter = partial(attr_getter, bound_object) + return def get(self): return self.attrgetter() @@ -131,21 +132,22 @@ def notify(self): self._notifier.attribute_changed(self.get()) -class BoundObjectWrapper(SlotManager, SimpleWrapper): +class BoundObjectWrapper(EventObject, SimpleWrapper): - def __init__(self, bound_object, wrappers = None, adapter = None, *a, **k): - if not adapter is not None: - raise AssertionError - raise wrappers is not None or AssertionError - bound_object = adapter(bound_object) if bound_object != None else None - super(BoundObjectWrapper, self).__init__(bound_object, *a, **k) - self.wrappers = wrappers - self.values = {} - bound_object is not None and self.register_disconnectable(bound_object) + def __init__(self, bound_object, wrappers=None, adapter=None, *a, **k): + assert adapter is not None + assert wrappers is not None + bound_object = adapter(bound_object) if bound_object != None else None + super(BoundObjectWrapper, self).__init__(bound_object, *a, **k) + self.wrappers = wrappers + self.values = {} + if bound_object is not None: + self.register_disconnectable(bound_object) for name in wrappers.keys(): self._update_wrapper(name) self.connect() + return def notify(self): self._notifier.structural_change() @@ -157,6 +159,8 @@ def to_json(self): res[name] = wrapper.to_json() return res + else: + return def _update_wrapper(self, name): if name in self.values: @@ -186,6 +190,7 @@ def __init__(self, parent, name, bound_object_wrapper, *a, **k): self._value = NullValueWrapper(None, notifier=self._notifier.step(name)) else: self._value = bound_object_wrapper(value, *a, **k) + return def get(self): return self._value.get() @@ -202,12 +207,14 @@ def notify(self): class NotifyingList(WrapperBase): - def __init__(self, value, wrapper = None, *a, **k): + def __init__(self, value, wrapper=None, *a, **k): super(NotifyingList, self).__init__(*a, **k) - raise wrapper is not None or AssertionError - raise value is not None or AssertionError + assert wrapper is not None + assert value is not None self.wrapper = wrapper - self.data = [ self.wrapper(item, notifier=self._notifier.step(i)) for i, item in enumerate(value) ] + self.data = [ self.wrapper(item, notifier=self._notifier.step(i)) for i, item in enumerate(value) + ] + return def notify(self): self._notifier.structural_change() @@ -286,19 +293,16 @@ def get(self): return self -def make_bound_child_wrapper(name = None, wrapper = None): +def make_bound_child_wrapper(name=None, wrapper=None): - def apply_wrapper(bound_object, name = None, wrapper = None, notifier = None): + def apply_wrapper(bound_object, name=None, wrapper=None, notifier=None): return wrapper(getattr(bound_object, name), notifier=notifier) return partial(apply_wrapper, name=name, wrapper=wrapper) -ClassInfo = namedtuple('ClassInfo', ['class_', - 'd', - 'default_data', - 'wrappers', - 'children']) +ClassInfo = namedtuple('ClassInfo', [ + 'class_', 'd', 'default_data', 'wrappers', 'children']) @contextmanager def pushpop(collection, item): @@ -347,11 +351,11 @@ def visit_binding_property(self, name, decl): def visit_value_list_property(self, name, decl, value_type): super(BindingModelVisitor, self).visit_value_list_property(name, decl, value_type) - self.current_class_info.wrappers[name] = partial(BoundListWrapper, name=name, wrapper=SimpleWrapper, verify_unique_ids=False) + self.current_class_info.wrappers[name] = partial(BoundListWrapper, name=name, wrapper=SimpleWrapper) def visit_complex_list_property(self, name, decl, value_type): super(BindingModelVisitor, self).visit_complex_list_property(name, decl, value_type) - self.current_class_info.wrappers[name] = partial(BoundListWrapper, name=name, wrapper=self._decl2class[value_type], verify_unique_ids=False) + self.current_class_info.wrappers[name] = partial(BoundListWrapper, name=name, wrapper=self._decl2class[value_type]) def visit_custom_property(self, name, decl): super(BindingModelVisitor, self).visit_custom_property(name, decl) @@ -359,7 +363,7 @@ def visit_custom_property(self, name, decl): def visit_list_model_property(self, name, decl, value_type): super(BindingModelVisitor, self).visit_list_model_property(name, decl, value_type) - self.current_class_info.wrappers[name] = partial(BoundListWrapper, name=name, wrapper=self._decl2class[value_type], verify_unique_ids=True) + self.current_class_info.wrappers[name] = partial(BoundListWrapper, name=name, wrapper=self._decl2class[value_type]) def visit_reference_property(self, name, decl): super(BindingModelVisitor, self).visit_reference_property(name, decl) @@ -376,7 +380,7 @@ def _resolve_reference(class_name, wrappers, name, decl2class): @staticmethod def _resolve_reference_list(class_name, wrappers, name, decl2class): generated_class = decl2class[class_name] - wrappers[name] = partial(BoundListWrapper, name=name, wrapper=generated_class, verify_unique_ids=False) + wrappers[name] = partial(BoundListWrapper, name=name, wrapper=generated_class) class ViewModelVisitor(ModelVisitor): @@ -444,6 +448,7 @@ def visit_binding_property(self, name, decl): ci.d[name] = _generate_model_mixin_property(name) ci.default_data[name] = None ci.wrappers[name] = self._decl2class[decl.property_type] + return def visit_complex_list_property(self, name, decl, value_type): super(ViewModelVisitor, self).visit_complex_list_property(name, decl, value_type) @@ -466,6 +471,7 @@ def __init__(self, class_): self._class2proplist = {} self._property_prints = [] self.visit_class(class_) + return @property def property_prints(self): @@ -501,6 +507,14 @@ def visit_list_model_property(self, name, decl, property_type): super(ModelFingerprintVisitor, self).visit_list_model_property(name, decl, property_type) self.property_prints.append('%s:listmodel(%s)' % (name, property_type.__name__)) + def visit_complex_list_property(self, name, decl, value_type): + super(ModelFingerprintVisitor, self).visit_complex_list_property(name, decl, value_type) + self.property_prints.append('%s:listof(%s)' % (name, value_type.__name__)) + + def visit_binding_property(self, name, decl): + super(ModelFingerprintVisitor, self).visit_binding_property(name, decl) + self.property_prints.append('%s:%s' % (name, decl.property_type.__name__)) + def generate_model_fingerprint(cls): return md5(ModelFingerprintVisitor(cls).fingerprint).hexdigest() diff --git a/Push2/model/repr.py b/Push2/model/repr.py index d2a25c48..24e96d45 100644 --- a/Push2/model/repr.py +++ b/Push2/model/repr.py @@ -1,9 +1,11 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/Push2/model/repr.py +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/Push2/model/repr.py +# Compiled at: 2016-09-29 19:13:24 from __future__ import absolute_import, print_function import re from functools import partial -from ableton.v2.base import Slot, SlotError, SlotManager, Subject, find_if, listenable_property, listens, liveobj_valid -DEVICE_TYPES_WITH_PRESET_NAME = ['InstrumentGroupDevice', +from ableton.v2.base import EventObject, Slot, EventError, find_if, listenable_property, listens, liveobj_valid +DEVICE_TYPES_WITH_PRESET_NAME = [ + 'InstrumentGroupDevice', 'DrumGroupDevice', 'AudioEffectGroupDevice', 'MidiEffectGroupDevice', @@ -66,10 +68,18 @@ def strip_formatted_string(str): return re.sub('\\s\\s+', ' ', str).strip() -class ModelAdapter(Subject, SlotManager): +def convert_color_index(color_index): + from ..colors import UNCOLORED_INDEX + if color_index is None: + return UNCOLORED_INDEX + else: + return color_index + - def __init__(self, adaptee = None, *a, **k): - raise liveobj_valid(adaptee) or AssertionError +class ModelAdapter(EventObject): + + def __init__(self, adaptee=None, *a, **k): + assert liveobj_valid(adaptee) super(ModelAdapter, self).__init__(*a, **k) self._adaptee = adaptee @@ -77,7 +87,7 @@ def is_valid(self): return liveobj_valid(self._adaptee) def __getattr__(self, name): - if name in self.__dict__: + if name in self.__dict__ or name in self.__class__.__dict__: return object.__getattribute__(self, name) return getattr(self._adaptee, name) @@ -87,7 +97,7 @@ def _live_ptr(self): return self._adaptee._live_ptr return id(self._adaptee) - def _alias_observable_property(self, prop_name, alias_name, getter = None): + def _alias_observable_property(self, prop_name, alias_name, getter=None): default_getter = lambda self_: getattr(self_._adaptee, prop_name) aliased_prop = property(getter or default_getter) setattr(self.__class__, alias_name, aliased_prop) @@ -108,9 +118,17 @@ def name(self): name = 'MIDI clip' if self._adaptee.is_midi_clip else 'Audio clip' return name + @property + def positions(self): + return getattr(self._adaptee, 'positions', None) + + @property + def warping(self): + return self._adaptee.is_audio_clip and self._adaptee.warping + class DeviceParameterAdapter(ModelAdapter): - __events__ = ('hasAutomation',) + __events__ = ('hasAutomation', ) def __init__(self, *a, **k): super(DeviceParameterAdapter, self).__init__(*a, **k) @@ -121,7 +139,7 @@ def __init__(self, *a, **k): self.register_slot(self._adaptee, self.notify_isActive, 'state') try: self.register_slot(self._adaptee, self.notify_valueItems, 'value_items') - except SlotError: + except EventError: pass @listenable_property @@ -214,20 +232,13 @@ def __on_sample_changed(self): @listenable_property def slices(self): - def unique_id(id_, existing = set()): - while id_ in existing: - id_ += 0.01 - - existing.add(id_) - return id_ - class SlicePoint(object): def __init__(self, __id__, time): self.__id__ = __id__ self.time = time - return [ SlicePoint(unique_id(time), time) for time in self._get_slice_times() ] + return [ SlicePoint(time, time) for time in self._get_slice_times() ] @listenable_property def selected_slice(self): @@ -257,10 +268,16 @@ def gain(self): return self._adaptee.sample.gain return 0.0 + @listenable_property + def warping(self): + if liveobj_valid(self._adaptee) and liveobj_valid(self._adaptee.sample): + return self._adaptee.sample.warping + return False + class VisibleAdapter(ModelAdapter): - def __init__(self, adaptee = None, *a, **k): + def __init__(self, adaptee=None, *a, **k): super(VisibleAdapter, self).__init__(adaptee=adaptee, *a, **k) self.__on_enabled_changed.subject = adaptee @@ -273,16 +290,24 @@ def __on_enabled_changed(self, enabled): self.notify_visible() +class TrackMixAdapter(VisibleAdapter): + __events__ = ('scrollOffset', ) + + def __init__(self, *a, **k): + super(TrackMixAdapter, self).__init__(*a, **k) + self._alias_observable_property('scroll_offset', 'scrollOffset') + + class TrackControlAdapter(VisibleAdapter): - __events__ = ('scrollOffset',) + __events__ = ('track_control_mode', ) def __init__(self, *a, **k): super(TrackControlAdapter, self).__init__(*a, **k) - self._alias_observable_property('scroll_offset', 'scrollOffset') + self._alias_observable_property('selected_mode', 'track_control_mode') class OptionsListAdapter(VisibleAdapter): - __events__ = ('selectedItem',) + __events__ = ('selectedItem', ) def __init__(self, *a, **k): super(OptionsListAdapter, self).__init__(*a, **k) @@ -290,7 +315,7 @@ def __init__(self, *a, **k): class ItemListAdapter(VisibleAdapter): - __events__ = ('selectedItem',) + __events__ = ('selectedItem', ) def __init__(self, *a, **k): super(ItemListAdapter, self).__init__(*a, **k) @@ -313,10 +338,10 @@ def icon(self): class DeviceAdapter(ModelAdapter): - __events__ = ('is_active',) + __events__ = ('is_active', ) def __init__(self, *a, **k): - from ..device_navigation import is_drum_pad + from ..device_util import is_drum_pad, find_chain_or_track, find_rack super(DeviceAdapter, self).__init__(*a, **k) item = self._unwrapped_item() if hasattr(item, 'is_active'): @@ -324,17 +349,24 @@ def __init__(self, *a, **k): elif is_drum_pad(item): self.__on_is_active_changed.subject = item.canonical_parent self.__on_mute_changed.subject = item + if hasattr(item, 'name'): + self.__on_name_changed.subject = item + self._chain = find_chain_or_track(item) + self._rack_chain = find_chain_or_track(find_rack(item)) + self.__on_chain_color_index_changed.subject = self._chain + self.__on_rack_color_index_changed.subject = self._rack_chain def _unwrapped_item(self): return getattr(self._adaptee, 'item', self._adaptee) - @property - def name(self): + @listenable_property + def navigation_name(self): item = self._unwrapped_item() + name = getattr(item, 'name', '') if hasattr(item, 'class_name') and item.class_name in DEVICE_TYPES_WITH_PRESET_NAME: - return item.name + return name else: - return getattr(item, 'class_display_name', item.name) + return getattr(item, 'class_display_name', name) @property def class_name(self): @@ -368,13 +400,37 @@ def __on_is_active_changed(self): def __on_mute_changed(self): self.notify_is_active() + @listens('name') + def __on_name_changed(self): + self.notify_navigation_name() + @property def icon(self): return getattr(self._adaptee, 'icon', '') + @listenable_property + def chain_color_index(self): + if liveobj_valid(self._chain): + return convert_color_index(self._chain.color_index) + return -1 + + @listens('color_index') + def __on_chain_color_index_changed(self): + self.notify_chain_color_index() + + @listenable_property + def rack_color_index(self): + if liveobj_valid(self._rack_chain): + return convert_color_index(self._rack_chain.color_index) + return -1 + + @listens('color_index') + def __on_rack_color_index_changed(self): + self.notify_rack_color_index() + class TrackAdapter(ModelAdapter): - __events__ = ('activated',) + __events__ = ('activated', ) def __init__(self, *a, **k): super(TrackAdapter, self).__init__(*a, **k) @@ -382,9 +438,18 @@ def __init__(self, *a, **k): self.__on_mute_changed.subject = self._adaptee self.__on_solo_changed.subject = self._adaptee self.__on_muted_via_solo_changed.subject = self._adaptee + self.has_playing_clip = False + self._update_has_playing_clip() + if hasattr(self._adaptee, 'playing_slot_index'): + self.__on_playing_slot_index_changed.subject = self._adaptee + try: + self.__on_is_frozen_changed.subject = self._adaptee.parent_track + except AttributeError: + pass + try: self.register_slot(self._adaptee, self.notify_colorIndex, 'color_index') - except SlotError: + except EventError: pass try: @@ -394,12 +459,17 @@ def __init__(self, *a, **k): try: self.register_slot(self._adaptee, self.notify_isFrozen, 'is_frozen') - except SlotError: + except EventError: pass try: self.register_slot(self._adaptee, self.notify_arm, 'arm') - except SlotError: + except EventError: + pass + + try: + self.register_slot(self._adaptee, self.notify_outputRouting, 'output_routing_type') + except EventError: pass @property @@ -445,6 +515,17 @@ def arm(self): except AttributeError: return False + @listenable_property + def parent_track_frozen(self): + try: + return self._adaptee.parent_track.is_frozen + except AttributeError: + return False + + @listens('is_frozen') + def __on_is_frozen_changed(self): + self.notify_parent_track_frozen() + @listens('mute') def __on_mute_changed(self): self.notify_activated() @@ -457,24 +538,45 @@ def __on_solo_changed(self): def __on_muted_via_solo_changed(self): self.notify_activated() - @staticmethod - def _convert_color_index(color_index): - from ..colors import UNCOLORED_INDEX - if color_index is None: - return UNCOLORED_INDEX - return color_index + @listens('playing_slot_index') + def __on_playing_slot_index_changed(self): + self._update_has_playing_clip() + self.notify_playingClip() + + def _update_has_playing_clip(self): + has_playing_clip = self._adaptee.playing_slot_index >= 0 if hasattr(self._adaptee, 'playing_slot_index') else False + if has_playing_clip != self.has_playing_clip: + self.has_playing_clip = has_playing_clip + self.notify_hasPlayingClip() + + def _playing_clip_slot(self): + if hasattr(self._adaptee, 'playing_slot_index'): + try: + if self._adaptee.playing_slot_index >= 0: + return self._adaptee.clip_slots[self._adaptee.playing_slot_index] + except RuntimeError: + pass + + return None + + def _playing_clip(self): + playing_clip_slot = self._playing_clip_slot() + if playing_clip_slot is not None: + return playing_clip_slot.clip + else: + return @listenable_property def colorIndex(self): try: - return self._convert_color_index(self._adaptee.color_index) + return convert_color_index(self._adaptee.color_index) except AttributeError: return self.parentColorIndex @listenable_property def parentColorIndex(self): try: - return self._convert_color_index(self._adaptee.parent_track.color_index) + return convert_color_index(self._adaptee.parent_track.color_index) except AttributeError: return -1 @@ -492,9 +594,32 @@ def isAudio(self): except AttributeError: return False + @property + def isReturn(self): + try: + return self._adaptee in list(self._adaptee.canonical_parent.return_tracks) + except AttributeError: + return False + + @listenable_property + def outputRouting(self): + routing_type = getattr(self._adaptee, 'output_routing_type', None) + if routing_type is not None: + return routing_type.display_name + else: + return '' + + @listenable_property + def hasPlayingClip(self): + return self.has_playing_clip + + @listenable_property + def playingClip(self): + return self._playing_clip() + class TrackListAdapter(VisibleAdapter): - __events__ = ('selectedTrack',) + __events__ = ('selectedTrack', ) def __init__(self, *a, **k): super(TrackListAdapter, self).__init__(*a, **k) @@ -508,14 +633,14 @@ def icon(self): return getattr(self._adaptee, 'icon', '') -class BrowserListWrapper(SlotManager): +class BrowserListWrapper(EventObject): """ Custom object wrapper that takes care of binding a browser list and serializing it. This is necessary to greatly improve performance and avoid unnecessary wrapping of each browser item. """ - def __init__(self, browser_list, notifier = None, *a, **k): + def __init__(self, browser_list, notifier=None, *a, **k): super(BrowserListWrapper, self).__init__(*a, **k) self._browser_list = browser_list self._notifier = notifier @@ -526,9 +651,11 @@ def __init__(self, browser_list, notifier = None, *a, **k): @staticmethod def _serialize_browser_item(item): return {'id': item.uri, - 'name': item.name, - 'is_loadable': item.is_loadable, - 'icon': getattr(item, 'icon', '')} + 'name': item.name, + 'is_loadable': item.is_loadable, + 'is_device': item.is_device, + 'icon': getattr(item, 'icon', '') + } def to_json(self): return map(self._serialize_browser_item, self._browser_list.items) @@ -539,6 +666,7 @@ def notify(self): def disconnect(self): super(BrowserListWrapper, self).disconnect() self._browser_list = None + return class LiveDialogAdapter(VisibleAdapter): @@ -548,4 +676,18 @@ def text(self): text = self._adaptee.text if text is not None: return strip_formatted_string(text) - return '' \ No newline at end of file + else: + return '' + + +class RoutingAdapter(VisibleAdapter): + __events__ = ('routingTypeList', 'routingChannelList', 'routingChannelPositionList') + + def __init__(self, *a, **k): + super(RoutingAdapter, self).__init__(*a, **k) + self._alias_observable_property('routing_type_list', 'routingTypeList', lambda self_: [ + self_._adaptee.routing_type_list]) + self._alias_observable_property('routing_channel_list', 'routingChannelList', lambda self_: [ + self_._adaptee.routing_channel_list]) + self._alias_observable_property('routing_channel_position_list', 'routingChannelPositionList', lambda self_: [ + self_._adaptee.routing_channel_position_list]) \ No newline at end of file diff --git a/Push2/model/uniqueid.py b/Push2/model/uniqueid.py index 83b8608b..bdfb3a94 100644 --- a/Push2/model/uniqueid.py +++ b/Push2/model/uniqueid.py @@ -1,4 +1,5 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/Push2/model/uniqueid.py +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/Push2/model/uniqueid.py +# Compiled at: 2016-05-20 03:43:52 from __future__ import absolute_import, print_function from itertools import count diff --git a/Push2/mute_solo_stop.py b/Push2/mute_solo_stop.py index 426e6e0a..22af873e 100644 --- a/Push2/mute_solo_stop.py +++ b/Push2/mute_solo_stop.py @@ -1,7 +1,8 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/Push2/mute_solo_stop.py +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/Push2/mute_solo_stop.py +# Compiled at: 2016-06-08 13:13:04 from __future__ import absolute_import, print_function from functools import partial -from ableton.v2.base import listenable_property, listens, listens_group, liveobj_valid, MultiSlot, SlotManager, Subject +from ableton.v2.base import listenable_property, listens, listens_group, liveobj_valid, EventObject, MultiSlot from ableton.v2.control_surface import Component, CompoundComponent, Layer from ableton.v2.control_surface.control import ButtonControl from pushbase.message_box_component import Messenger @@ -14,17 +15,18 @@ def stop_clip_in_selected_track(song): selected_track.stop_all_clips() -class TrackStateColorIndicator(Subject, SlotManager): +class TrackStateColorIndicator(EventObject): color = listenable_property.managed('DefaultButton.On') - def __init__(self, item_provider = None, track_property = None, property_active_color = None, song = None, *a, **k): + def __init__(self, item_provider=None, track_property=None, property_active_color=None, song=None, *a, **k): super(TrackStateColorIndicator, self).__init__(*a, **k) self._provider = item_provider self._active_color = property_active_color self._property = track_property self._song = song self.__on_items_changed.subject = item_provider - self.register_slot(MultiSlot(listener=self.__on_property_changed, event=('selected_item', track_property), subject=item_provider)) + self.register_slot(MultiSlot(listener=self.__on_property_changed, event_name_list=( + 'selected_item', track_property), subject=item_provider)) self._update_color() @listens('items') @@ -43,23 +45,24 @@ def _update_color(self): class GlobalMixerActionComponent(Component): action_button = ButtonControl(delay_time=GLOBAL_ACTION_LOCK_MODE_DELAY) - def __init__(self, track_list_component = None, mode = None, immediate_action = None, default_color_indicator = None, mode_locked_color = None, mode_active_color = None, *a, **k): - if not track_list_component is not None: - raise AssertionError - raise mode is not None or AssertionError - raise mode in track_list_component.modes or AssertionError - super(GlobalMixerActionComponent, self).__init__(*a, **k) - self._mode = mode - self._immediate_action = immediate_action - self._mode_locked = False - self._default_color_indicator = None - self._locked_color = mode_locked_color - self._active_color = mode_active_color if mode_active_color is not None else 'DefaultButton.On' - self._allow_released_immediately_action = True - self._track_list_component = track_list_component - self._default_color_indicator = default_color_indicator is not None and self.register_disconnectable(default_color_indicator) + def __init__(self, track_list_component=None, mode=None, immediate_action=None, default_color_indicator=None, mode_locked_color=None, mode_active_color=None, *a, **k): + assert track_list_component is not None + assert mode is not None + assert mode in track_list_component.modes + super(GlobalMixerActionComponent, self).__init__(*a, **k) + self._mode = mode + self._immediate_action = immediate_action + self._mode_locked = False + self._default_color_indicator = None + self._locked_color = mode_locked_color + self._active_color = mode_active_color if mode_active_color is not None else 'DefaultButton.On' + self._allow_released_immediately_action = True + self._track_list_component = track_list_component + if default_color_indicator is not None: + self._default_color_indicator = self.register_disconnectable(default_color_indicator) self.__on_default_color_changed.subject = default_color_indicator self._update_default_color() + return @listenable_property def mode_locked(self): @@ -123,11 +126,12 @@ def _update_default_color(self): class MuteSoloStopClipComponent(CompoundComponent, Messenger): MESSAGE_FOR_MODE = {'mute': 'Mute: %s', - 'solo': 'Solo: %s', - 'stop': 'Stop Clips: %s'} + 'solo': 'Solo: %s', + 'stop': 'Stop Clips: %s' + } stop_all_clips_button = ButtonControl() - def __init__(self, item_provider = None, solo_track_button = None, mute_track_button = None, stop_clips_button = None, track_list_component = None, cancellation_action_performers = [], *a, **k): + def __init__(self, item_provider=None, solo_track_button=None, mute_track_button=None, stop_clips_button=None, track_list_component=None, cancellation_action_performers=[], *a, **k): super(MuteSoloStopClipComponent, self).__init__(*a, **k) self._currently_locked_button_handler = None self._track_list = track_list_component @@ -137,10 +141,15 @@ def __init__(self, item_provider = None, solo_track_button = None, mute_track_bu self._mute_button_handler.layer = Layer(action_button=mute_track_button) self._stop_button_handler = self.register_component(GlobalMixerActionComponent(track_list_component=track_list_component, mode='stop', immediate_action=partial(stop_clip_in_selected_track, self.song), mode_locked_color='StopClips.LockedStopMode')) self._stop_button_handler.layer = Layer(action_button=stop_clips_button) - self.__on_mute_solo_stop_cancel_action_performed.replace_subjects([track_list_component] + cancellation_action_performers) - button_handlers = (self._mute_button_handler, self._solo_button_handler, self._stop_button_handler) + self.__on_mute_solo_stop_cancel_action_performed.replace_subjects([ + track_list_component] + cancellation_action_performers) + button_handlers = ( + self._mute_button_handler, + self._solo_button_handler, + self._stop_button_handler) self.__on_mode_locked_changed.replace_subjects(button_handlers, button_handlers) self.__on_selected_item_changed.subject = item_provider + return @stop_all_clips_button.pressed def stop_all_clips_button(self, button): @@ -160,6 +169,7 @@ def __on_mode_locked_changed(self, is_locked, button_handler): self._currently_locked_button_handler = None self._track_list.locked_mode = self._currently_locked_button_handler.mode if is_locked else None self._show_mode_lock_change_notification(is_locked, button_handler.mode) + return @listens('selected_item') def __on_selected_item_changed(self): diff --git a/Push2/note_editor.py b/Push2/note_editor.py index 3680b3b6..1e3d308d 100644 --- a/Push2/note_editor.py +++ b/Push2/note_editor.py @@ -1,10 +1,11 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/Push2/note_editor.py +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/Push2/note_editor.py +# Compiled at: 2016-11-16 18:13:20 from __future__ import absolute_import, print_function from pushbase.note_editor_component import NoteEditorComponent class Push2NoteEditorComponent(NoteEditorComponent): - __events__ = ('mute_solo_stop_cancel_action_performed',) + __events__ = ('mute_solo_stop_cancel_action_performed', ) - def _on_pad_pressed(self, value, x, y, is_momentary): - super(Push2NoteEditorComponent, self)._on_pad_pressed(value, x, y, is_momentary) + def _on_pad_pressed(self, coordinate): + super(Push2NoteEditorComponent, self)._on_pad_pressed(coordinate) self.notify_mute_solo_stop_cancel_action_performed() \ No newline at end of file diff --git a/Push2/note_settings.py b/Push2/note_settings.py index 8c3d7e4d..78c17951 100644 --- a/Push2/note_settings.py +++ b/Push2/note_settings.py @@ -1,8 +1,11 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/Push2/note_settings.py +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/Push2/note_settings.py +# Compiled at: 2016-05-20 03:43:52 from __future__ import absolute_import, print_function -from ableton.v2.base import listenable_property, listens, liveobj_valid +from ableton.v2.base import listenable_property, listens from ableton.v2.control_surface.control import StepEncoderControl +from ableton.v2.control_surface.elements.color import SelectedClipColor from pushbase.note_settings_component import NoteSettingBase, NoteSettingsComponentBase, step_offset_percentage +from .colors import SelectedDrumPadColor class NoteSetting(NoteSettingBase): @@ -72,7 +75,10 @@ class NoteSettingsComponent(NoteSettingsComponentBase): def __init__(self, *a, **k): super(NoteSettingsComponent, self).__init__(*a, **k) - self.__on_color_index_changed.subject = self.song.view + self._selected_drum_pad_color = self.register_disconnectable(SelectedDrumPadColor(song=self.song)) + self._selected_clip_color = self.register_disconnectable(SelectedClipColor(song_view=self.song.view)) + self._color = self._selected_clip_color + self.__on_midi_value_changed.subject = self._color def _create_settings(self, grid_resolution): args = dict(grid_resolution=grid_resolution) @@ -85,6 +91,19 @@ def _create_settings(self, grid_resolution): self._add_setting(self._fine) self._add_setting(self._velocity) + def set_color_mode(self, color_mode): + self._color = self.get_color_for_mode(color_mode) + self.__on_midi_value_changed.subject = self._color + self.notify_color_index() + + def get_color_for_mode(self, color_mode): + if color_mode == 'drum_pad': + return self._selected_drum_pad_color + else: + if color_mode == 'clip': + return self._selected_clip_color + return None + @property def nudge(self): return self._nudge @@ -103,11 +122,12 @@ def velocity(self): @listenable_property def color_index(self): - clip = self.song.view.detail_clip - if liveobj_valid(clip): - return clip.color_index - return -1 - - @listens('detail_clip.color_index') - def __on_color_index_changed(self, *a): + color_index = self._color.midi_value + if color_index is not None: + return color_index + else: + return -1 + + @listens('midi_value') + def __on_midi_value_changed(self, *a): self.notify_color_index() \ No newline at end of file diff --git a/Push2/notification_component.py b/Push2/notification_component.py index 55125251..7316367e 100644 --- a/Push2/notification_component.py +++ b/Push2/notification_component.py @@ -1,25 +1,25 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/Push2/notification_component.py +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/Push2/notification_component.py +# Compiled at: 2016-05-20 03:43:52 from __future__ import absolute_import, print_function from weakref import ref -import time -from ableton.v2.base import nop, task, listenable_property +import Live +from ableton.v2.base import nop, listenable_property from ableton.v2.control_surface import ControlElement, Component -from pushbase.message_box_component import Notification +from pushbase.message_box_component import Notification, strip_restriction_markup_and_format from .model.repr import strip_formatted_string class NotificationComponent(Component): - def __init__(self, default_notification_time = 2.5, *a, **k): + def __init__(self, default_notification_time=2.5, *a, **k): super(NotificationComponent, self).__init__(*a, **k) self._visible = False self._message = '' - self._shown_at = None - self._duration = None self.show_notification = self._show_notification - self._notification_timeout_task = None + self._notification_timer = None self._default_notification_time = default_notification_time self._dummy_control_element = ControlElement() self._dummy_control_element.reset = nop + return def disconnect(self): self.hide_notification() @@ -34,37 +34,31 @@ def visible(self): def message(self): return self._message - def _create_notification_timeout_task(self, duration): - self._notification_timeout_task = self._tasks.add(task.sequence(task.wait(duration), task.run(self.hide_notification))) - - def _show_notification(self, text, blink_text = None, notification_time = None): + def _show_notification(self, text, blink_text=None, notification_time=None): + text = strip_restriction_markup_and_format(text) self._message = strip_formatted_string(text) - self._duration = notification_time if notification_time is not None else self._default_notification_time - self._create_notification_timeout_task(self._duration) + if notification_time is None: + notification_time = self._default_notification_time + if self._notification_timer: + self._notification_timer.stop() + if notification_time != -1: + self._notification_timer = Live.Base.Timer(callback=self.hide_notification, interval=int(1000 * notification_time), repeat=False) + self._notification_timer.start() if not self._visible: self._visible = True - self._shown_at = time.clock() self.notify_visible() - self.notify_message() + self.notify_message() self._current_notification = Notification(self) - self._current_notification.reschedule_after_slow_operation = self._reschedule_after_slow_operation return ref(self._current_notification) - def _reschedule_after_slow_operation(self): - time_remaining = self._duration - (time.clock() - self._shown_at) - if time_remaining > 0: - if self._notification_timeout_task: - self._notification_timeout_task.kill() - self._create_notification_timeout_task(time_remaining) - else: - self.hide_notification() - def hide_notification(self): - if self._notification_timeout_task: - self._notification_timeout_task.kill() + if self._notification_timer: + self._notification_timer.stop() + self._notification_timer = None if self._visible: self._visible = False self.notify_visible() + return def use_single_line(self, *a): """ diff --git a/Push2/observable_property_alias.py b/Push2/observable_property_alias.py index 2e3d81d9..47d729b8 100644 --- a/Push2/observable_property_alias.py +++ b/Push2/observable_property_alias.py @@ -1,10 +1,11 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/Push2/observable_property_alias.py +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/Push2/observable_property_alias.py +# Compiled at: 2016-05-20 03:43:52 from __future__ import absolute_import, print_function -from ableton.v2.base import SlotManager, Slot +from ableton.v2.base import EventObject, Slot -class ObservablePropertyAlias(SlotManager): +class ObservablePropertyAlias(EventObject): - def __init__(self, alias_host, property_host = None, property_name = '', alias_name = None, getter = None, *a, **k): + def __init__(self, alias_host, property_host=None, property_name='', alias_name=None, getter=None, *a, **k): super(ObservablePropertyAlias, self).__init__(*a, **k) self._alias_host = alias_host self._alias_name = alias_name or property_name @@ -12,6 +13,7 @@ def __init__(self, alias_host, property_host = None, property_name = '', alias_n self._property_name = property_name self._property_slot = None self._setup_alias(getter) + return def _get_property_host(self): return self._property_host diff --git a/Push2/pad_sensitivity.py b/Push2/pad_sensitivity.py new file mode 100644 index 00000000..6a25f783 --- /dev/null +++ b/Push2/pad_sensitivity.py @@ -0,0 +1,33 @@ +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/Push2/pad_sensitivity.py +# Compiled at: 2016-09-29 19:13:24 +from __future__ import absolute_import, print_function +playing_profile = 0 +default_profile = 1 +loop_selector_profile = 2 + +def index_to_pad_coordinate(index): + """ + Maps a linear range to appropriate x and y coordinates of the pad matrix. + The coordinates are 1-based, since the pad sensitivity sysex commands expect this + when setting individual pads. + """ + x, y = divmod(index, 8) + return ( + 8 - x, y + 1) + + +def pad_parameter_sender(global_control, pad_control): + """ + Sends the sensitivity parameters for a given pad, or all pads + (pad == None) over the given SysexElement. + """ + + def do_send(sensitivity_value, pad=None): + if pad is None: + global_control.send_value(0, 0, sensitivity_value) + else: + scene, track = index_to_pad_coordinate(pad) + pad_control.send_value(scene, track, sensitivity_value) + return + + return do_send \ No newline at end of file diff --git a/Push2/pad_velocity_curve.py b/Push2/pad_velocity_curve.py index 2bf0a514..a62e82d0 100644 --- a/Push2/pad_velocity_curve.py +++ b/Push2/pad_velocity_curve.py @@ -1,4 +1,5 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/Push2/pad_velocity_curve.py +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/Push2/pad_velocity_curve.py +# Compiled at: 2016-05-20 03:43:52 from __future__ import absolute_import, print_function import math from ableton.v2.base import SerializableListenableProperties, chunks, clamp, listenable_property, task @@ -7,83 +8,16 @@ LAST_INDEX_FOR_DISPLAY = 58 class LookupTable: - MAXW = [1700.0, - 1660.0, - 1590.0, - 1510.0, - 1420.0, - 1300.0, - 1170.0, - 1030.0, - 860.0, - 640.0, - 400.0] - CPMIN = [1650.0, - 1580.0, - 1500.0, - 1410.0, - 1320.0, - 1220.0, - 1110.0, - 1000.0, - 900.0, - 800.0, - 700.0] - CPMAX = [2050.0, - 1950.0, - 1850.0, - 1750.0, - 1650.0, - 1570.0, - 1490.0, - 1400.0, - 1320.0, - 1240.0, - 1180.0] - GAMMA = [0.7, - 0.64, - 0.58, - 0.54, - 0.5, - 0.46, - 0.43, - 0.4, - 0.36, - 0.32, - 0.25] - MINV = [1.0, - 1, - 1.0, - 1.0, - 1.0, - 1.0, - 3.0, - 6.0, - 12.0, - 24.0, - 36.0] - MAXV = [96.0, - 102.0, - 116.0, - 121.0, - 124.0, - 127.0, - 127.0, - 127.0, - 127.0, - 127.0, - 127.0] - ALPHA = [90.0, - 70.0, - 54.0, - 40.0, - 28.0, - 20.0, - 10.0, - -5.0, - -25.0, - -55.0, - -90.0] + MAXW = [ + 1700.0, 1660.0, 1590.0, 1510.0, 1420.0, 1300.0, 1170.0, 1030.0, 860.0, 640.0, 400.0] + CPMIN = [1650.0, 1580.0, 1500.0, 1410.0, 1320.0, 1220.0, 1110.0, 1000.0, 900.0, 800.0, 700.0] + CPMAX = [2050.0, 1950.0, 1850.0, 1750.0, 1650.0, 1570.0, 1490.0, 1400.0, 1320.0, 1240.0, 1180.0] + GAMMA = [ + 0.7, 0.64, 0.58, 0.54, 0.5, 0.46, 0.43, 0.4, 0.36, 0.32, 0.25] + MINV = [1.0, 1, 1.0, 1.0, 1.0, 1.0, 3.0, 6.0, 12.0, 24.0, 36.0] + MAXV = [96.0, 102.0, 116.0, 121.0, 124.0, 127.0, 127.0, 127.0, 127.0, 127.0, 127.0] + ALPHA = [ + 90.0, 70.0, 54.0, 40.0, 28.0, 20.0, 10.0, -5.0, -25.0, -55.0, -90.0] def gamma_func(x, gamma): @@ -98,10 +32,8 @@ def calculate_points(alpha): p1y = 0.5 + r * math.sin(a1) p2x = 0.5 + r * math.cos(a2) p2y = 0.5 + r * math.sin(a2) - return (p1x, - p1y, - p2x, - p2y) + return ( + p1x, p1y, p2x, p2y) def bezier(x, t, p1x, p1y, p2x, p2y): @@ -156,10 +88,8 @@ def generate_thresholds(sensitivity, gain, dynamics): cpmax = LookupTable.CPMAX[sensitivity] threshold0 = 33 threshold1 = 31 - return (threshold0, - threshold1, - int(cpmin), - int(cpmax)) + return ( + threshold0, threshold1, int(cpmin), int(cpmax)) class PadVelocityCurveSettings(SerializableListenableProperties): @@ -178,11 +108,11 @@ class PadVelocityCurveSender(Component): SEND_RATE = 0.5 curve_points = listenable_property.managed([]) - def __init__(self, curve_sysex_element = None, threshold_sysex_element = None, settings = None, chunk_size = None, *a, **k): - raise curve_sysex_element is not None or AssertionError - raise threshold_sysex_element is not None or AssertionError - raise settings is not None or AssertionError - raise chunk_size is not None or AssertionError + def __init__(self, curve_sysex_element=None, threshold_sysex_element=None, settings=None, chunk_size=None, *a, **k): + assert curve_sysex_element is not None + assert threshold_sysex_element is not None + assert settings is not None + assert chunk_size is not None super(PadVelocityCurveSender, self).__init__(*a, **k) self._curve_sysex_element = curve_sysex_element self._threshold_sysex_element = threshold_sysex_element @@ -194,6 +124,7 @@ def __init__(self, curve_sysex_element = None, threshold_sysex_element = None, s self.register_slot(settings, self._on_setting_changed, 'gain') self.register_slot(settings, self._on_setting_changed, 'dynamics') self._update_curve_model() + return def send(self): self._send_velocity_curve() diff --git a/Push2/parameter_mapping_sensitivities.py b/Push2/parameter_mapping_sensitivities.py index c95b2dc9..031e4ecc 100644 --- a/Push2/parameter_mapping_sensitivities.py +++ b/Push2/parameter_mapping_sensitivities.py @@ -1,106 +1,242 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/Push2/parameter_mapping_sensitivities.py +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/Push2/parameter_mapping_sensitivities.py +# Compiled at: 2016-05-20 03:43:52 from __future__ import absolute_import, print_function +from pushbase.parameter_provider import is_parameter_quantized DEFAULT_SENSITIVITY_KEY = 'normal_sensitivity' FINE_GRAINED_SENSITIVITY_KEY = 'fine_grained_sensitivity' CONTINUOUS_MAPPING_SENSITIVITY = 1.0 FINE_GRAINED_CONTINUOUS_MAPPING_SENSITIVITY = 0.01 QUANTIZED_MAPPING_SENSITIVITY = 1.0 / 15.0 -PARAMETER_SENSITIVITIES = {'Analog': {'OSC1 Octave': {DEFAULT_SENSITIVITY_KEY: 0.1}, - 'OSC2 Octave': {DEFAULT_SENSITIVITY_KEY: 0.1}, - 'OSC1 Semi': {DEFAULT_SENSITIVITY_KEY: 0.5}, - 'OSC1 Detune': {DEFAULT_SENSITIVITY_KEY: 0.5}, - 'OSC2 Semi': {DEFAULT_SENSITIVITY_KEY: 0.5}, - 'OSC2 Detune': {DEFAULT_SENSITIVITY_KEY: 0.5}}, - 'LoungeLizard': {'Noise Pitch': {DEFAULT_SENSITIVITY_KEY: 0.5}, - 'Damp Balance': {DEFAULT_SENSITIVITY_KEY: 0.5}, - 'P Amp < Key': {DEFAULT_SENSITIVITY_KEY: 0.5}, - 'Semitone': {DEFAULT_SENSITIVITY_KEY: 0.5}}, - 'Collision': {'Res 1 Decay': {DEFAULT_SENSITIVITY_KEY: 0.5}}, - 'Impulse': {'1 Transpose': {DEFAULT_SENSITIVITY_KEY: 0.5}, - '2 Transpose': {DEFAULT_SENSITIVITY_KEY: 0.5}, - '3 Transpose': {DEFAULT_SENSITIVITY_KEY: 0.5}, - '4 Transpose': {DEFAULT_SENSITIVITY_KEY: 0.5}, - '5 Transpose': {DEFAULT_SENSITIVITY_KEY: 0.5}, - '6 Transpose': {DEFAULT_SENSITIVITY_KEY: 0.5}, - '7 Transpose': {DEFAULT_SENSITIVITY_KEY: 0.5}, - '8 Transpose': {DEFAULT_SENSITIVITY_KEY: 0.5}}, - 'OriginalSimpler': {'Zoom': {DEFAULT_SENSITIVITY_KEY: 1}, - 'Mode': {DEFAULT_SENSITIVITY_KEY: 0.5}, - 'Playback': {DEFAULT_SENSITIVITY_KEY: 0.5}, - 'Start': {DEFAULT_SENSITIVITY_KEY: 0.2}, - 'End': {DEFAULT_SENSITIVITY_KEY: 0.2}, - 'Sensitivity': {DEFAULT_SENSITIVITY_KEY: 0.5}, - 'S Start': {DEFAULT_SENSITIVITY_KEY: 0.2}, - 'S Length': {DEFAULT_SENSITIVITY_KEY: 0.2}, - 'S Loop Length': {DEFAULT_SENSITIVITY_KEY: 0.2}, - 'Transpose': {DEFAULT_SENSITIVITY_KEY: 0.1}, - 'Detune': {DEFAULT_SENSITIVITY_KEY: 0.1}, - 'Gain': {DEFAULT_SENSITIVITY_KEY: 0.1}, - 'Env. Type': {DEFAULT_SENSITIVITY_KEY: 0.1}, - 'Filter Freq': {DEFAULT_SENSITIVITY_KEY: 0.5}, - 'Filt < Vel': {DEFAULT_SENSITIVITY_KEY: 0.5}, - 'Filt < Key': {DEFAULT_SENSITIVITY_KEY: 0.5}, - 'Filt < LFO': {DEFAULT_SENSITIVITY_KEY: 0.5}, - 'FE < ENV': {DEFAULT_SENSITIVITY_KEY: 0.5}, - 'LR < Key': {DEFAULT_SENSITIVITY_KEY: 0.5}, - 'Vol < LFO': {DEFAULT_SENSITIVITY_KEY: 0.5}, - 'Pan < RND': {DEFAULT_SENSITIVITY_KEY: 0.5}, - 'Pan < LFO': {DEFAULT_SENSITIVITY_KEY: 0.5}, - 'L Sync Rate': {DEFAULT_SENSITIVITY_KEY: 0.5}}, - 'Operator': {'Oscillator': {DEFAULT_SENSITIVITY_KEY: 0.5}, - 'A Coarse': {DEFAULT_SENSITIVITY_KEY: 0.1}, - 'B Coarse': {DEFAULT_SENSITIVITY_KEY: 0.1}, - 'C Coarse': {DEFAULT_SENSITIVITY_KEY: 0.1}, - 'D Coarse': {DEFAULT_SENSITIVITY_KEY: 0.1}, - 'LFO Sync': {DEFAULT_SENSITIVITY_KEY: 0.1}}, - 'MidiArpeggiator': {'Style': {DEFAULT_SENSITIVITY_KEY: 0.1}, - 'Synced Rate': {DEFAULT_SENSITIVITY_KEY: 0.1}, - 'Offset': {DEFAULT_SENSITIVITY_KEY: 0.1}, - 'Transp. Steps': {DEFAULT_SENSITIVITY_KEY: 0.1}, - 'Transp. Dist.': {DEFAULT_SENSITIVITY_KEY: 0.5}, - 'Repeats': {DEFAULT_SENSITIVITY_KEY: 0.1}, - 'Ret. Interval': {DEFAULT_SENSITIVITY_KEY: 0.5}, - 'Groove': {DEFAULT_SENSITIVITY_KEY: 0.1}, - 'Retrigger Mode': {DEFAULT_SENSITIVITY_KEY: 0.1}}, - 'MidiNoteLength': {'Synced Length': {DEFAULT_SENSITIVITY_KEY: 0.1}}, - 'MidiScale': {'Base': {DEFAULT_SENSITIVITY_KEY: 0.5}, - 'Transpose': {DEFAULT_SENSITIVITY_KEY: 0.5}}, - 'Amp': {'Bass': {DEFAULT_SENSITIVITY_KEY: 0.5}, - 'Middle': {DEFAULT_SENSITIVITY_KEY: 0.5}, - 'Treble': {DEFAULT_SENSITIVITY_KEY: 0.5}, - 'Presence': {DEFAULT_SENSITIVITY_KEY: 0.5}, - 'Gain': {DEFAULT_SENSITIVITY_KEY: 0.5}, - 'Volume': {DEFAULT_SENSITIVITY_KEY: 0.5}, - 'Dry/Wet': {DEFAULT_SENSITIVITY_KEY: 0.5}}, - 'AutoFilter': {'Frequency': {DEFAULT_SENSITIVITY_KEY: 1}, - 'Env. Modulation': {DEFAULT_SENSITIVITY_KEY: 0.5}, - 'LFO Sync Rate': {DEFAULT_SENSITIVITY_KEY: 0.1}, - 'LFO Phase': {DEFAULT_SENSITIVITY_KEY: 0.5}, - 'LFO Offset': {DEFAULT_SENSITIVITY_KEY: 0.5}}, - 'AutoPan': {'Sync Rate': {DEFAULT_SENSITIVITY_KEY: 0.1}}, - 'BeatRepeat': {'Grid': {DEFAULT_SENSITIVITY_KEY: 0.1}, - 'Interval': {DEFAULT_SENSITIVITY_KEY: 0.1}, - 'Offset': {DEFAULT_SENSITIVITY_KEY: 0.1}, - 'Gate': {DEFAULT_SENSITIVITY_KEY: 0.1}, - 'Pitch': {DEFAULT_SENSITIVITY_KEY: 0.1}, - 'Variation': {DEFAULT_SENSITIVITY_KEY: 0.1}, - 'Mix Type': {DEFAULT_SENSITIVITY_KEY: 0.1}, - 'Grid': {DEFAULT_SENSITIVITY_KEY: 0.1}, - 'Variation Type': {DEFAULT_SENSITIVITY_KEY: 0.1}}, - 'Corpus': {'LFO Sync Rate': {DEFAULT_SENSITIVITY_KEY: 0.1}}, - 'Eq8': {'Band': {DEFAULT_SENSITIVITY_KEY: 0.5}, - '1 Frequency A': {DEFAULT_SENSITIVITY_KEY: 0.4}, - '2 Frequency A': {DEFAULT_SENSITIVITY_KEY: 0.4}, - '3 Frequency A': {DEFAULT_SENSITIVITY_KEY: 0.4}, - '4 Frequency A': {DEFAULT_SENSITIVITY_KEY: 0.4}, - '5 Frequency A': {DEFAULT_SENSITIVITY_KEY: 0.4}, - '6 Frequency A': {DEFAULT_SENSITIVITY_KEY: 0.4}, - '7 Frequency A': {DEFAULT_SENSITIVITY_KEY: 0.4}, - '8 Frequency A': {DEFAULT_SENSITIVITY_KEY: 0.4}}, - 'Flanger': {'Sync Rate': {DEFAULT_SENSITIVITY_KEY: 0.1}}, - 'GrainDelay': {'Pitch': {DEFAULT_SENSITIVITY_KEY: 0.5}}, - 'Phaser': {'LFO Sync Rate': {DEFAULT_SENSITIVITY_KEY: 0.1}}, - 'Resonator': {'II Pitch': {DEFAULT_SENSITIVITY_KEY: 0.5}, - 'III Pitch': {DEFAULT_SENSITIVITY_KEY: 0.5}, - 'IV Pitch': {DEFAULT_SENSITIVITY_KEY: 0.5}, - 'V Pitch': {DEFAULT_SENSITIVITY_KEY: 0.5}}} \ No newline at end of file + +def parameter_mapping_sensitivity(parameter): + is_quantized = is_parameter_quantized(parameter, parameter and parameter.canonical_parent) + if is_quantized: + return QUANTIZED_MAPPING_SENSITIVITY + return CONTINUOUS_MAPPING_SENSITIVITY + + +def fine_grain_parameter_mapping_sensitivity(parameter): + is_quantized = is_parameter_quantized(parameter, parameter and parameter.canonical_parent) + if is_quantized: + return QUANTIZED_MAPPING_SENSITIVITY + return FINE_GRAINED_CONTINUOUS_MAPPING_SENSITIVITY + + +PARAMETER_SENSITIVITIES = {'Analog': {'OSC1 Octave': {DEFAULT_SENSITIVITY_KEY: 0.1 + }, + 'OSC2 Octave': {DEFAULT_SENSITIVITY_KEY: 0.1 + }, + 'OSC1 Semi': {DEFAULT_SENSITIVITY_KEY: 0.5 + }, + 'OSC1 Detune': {DEFAULT_SENSITIVITY_KEY: 0.5 + }, + 'OSC2 Semi': {DEFAULT_SENSITIVITY_KEY: 0.5 + }, + 'OSC2 Detune': {DEFAULT_SENSITIVITY_KEY: 0.5 + } + }, + 'LoungeLizard': {'Noise Pitch': {DEFAULT_SENSITIVITY_KEY: 0.5 + }, + 'Damp Balance': {DEFAULT_SENSITIVITY_KEY: 0.5 + }, + 'P Amp < Key': {DEFAULT_SENSITIVITY_KEY: 0.5 + }, + 'Semitone': {DEFAULT_SENSITIVITY_KEY: 0.5 + } + }, + 'Collision': {'Res 1 Decay': {DEFAULT_SENSITIVITY_KEY: 0.5 + } + }, + 'Impulse': {'1 Transpose': {DEFAULT_SENSITIVITY_KEY: 0.5 + }, + '2 Transpose': {DEFAULT_SENSITIVITY_KEY: 0.5 + }, + '3 Transpose': {DEFAULT_SENSITIVITY_KEY: 0.5 + }, + '4 Transpose': {DEFAULT_SENSITIVITY_KEY: 0.5 + }, + '5 Transpose': {DEFAULT_SENSITIVITY_KEY: 0.5 + }, + '6 Transpose': {DEFAULT_SENSITIVITY_KEY: 0.5 + }, + '7 Transpose': {DEFAULT_SENSITIVITY_KEY: 0.5 + }, + '8 Transpose': {DEFAULT_SENSITIVITY_KEY: 0.5 + } + }, + 'OriginalSimpler': {'Zoom': {DEFAULT_SENSITIVITY_KEY: 1 + }, + 'Mode': {DEFAULT_SENSITIVITY_KEY: 0.5 + }, + 'Playback': {DEFAULT_SENSITIVITY_KEY: 0.5 + }, + 'Start': {DEFAULT_SENSITIVITY_KEY: 0.2 + }, + 'End': {DEFAULT_SENSITIVITY_KEY: 0.2 + }, + 'Sensitivity': {DEFAULT_SENSITIVITY_KEY: 0.5 + }, + 'S Start': {DEFAULT_SENSITIVITY_KEY: 0.2 + }, + 'S Length': {DEFAULT_SENSITIVITY_KEY: 0.2 + }, + 'S Loop Length': {DEFAULT_SENSITIVITY_KEY: 0.2 + }, + 'Transpose': {DEFAULT_SENSITIVITY_KEY: 0.1 + }, + 'Detune': {DEFAULT_SENSITIVITY_KEY: 0.1 + }, + 'Gain': {DEFAULT_SENSITIVITY_KEY: 0.1 + }, + 'Env. Type': {DEFAULT_SENSITIVITY_KEY: 0.1 + }, + 'Filter Freq': {DEFAULT_SENSITIVITY_KEY: 0.5 + }, + 'Filt < Vel': {DEFAULT_SENSITIVITY_KEY: 0.5 + }, + 'Filt < Key': {DEFAULT_SENSITIVITY_KEY: 0.5 + }, + 'Filt < LFO': {DEFAULT_SENSITIVITY_KEY: 0.5 + }, + 'FE < ENV': {DEFAULT_SENSITIVITY_KEY: 0.5 + }, + 'LR < Key': {DEFAULT_SENSITIVITY_KEY: 0.5 + }, + 'Vol < LFO': {DEFAULT_SENSITIVITY_KEY: 0.5 + }, + 'Pan < RND': {DEFAULT_SENSITIVITY_KEY: 0.5 + }, + 'Pan < LFO': {DEFAULT_SENSITIVITY_KEY: 0.5 + }, + 'L Sync Rate': {DEFAULT_SENSITIVITY_KEY: 0.5 + } + }, + 'Operator': {'Oscillator': {DEFAULT_SENSITIVITY_KEY: 0.5 + }, + 'A Coarse': {DEFAULT_SENSITIVITY_KEY: 0.1 + }, + 'B Coarse': {DEFAULT_SENSITIVITY_KEY: 0.1 + }, + 'C Coarse': {DEFAULT_SENSITIVITY_KEY: 0.1 + }, + 'D Coarse': {DEFAULT_SENSITIVITY_KEY: 0.1 + }, + 'LFO Sync': {DEFAULT_SENSITIVITY_KEY: 0.1 + } + }, + 'MidiArpeggiator': {'Style': {DEFAULT_SENSITIVITY_KEY: 0.1 + }, + 'Synced Rate': {DEFAULT_SENSITIVITY_KEY: 0.1 + }, + 'Offset': {DEFAULT_SENSITIVITY_KEY: 0.1 + }, + 'Transp. Steps': {DEFAULT_SENSITIVITY_KEY: 0.1 + }, + 'Transp. Dist.': {DEFAULT_SENSITIVITY_KEY: 0.5 + }, + 'Repeats': {DEFAULT_SENSITIVITY_KEY: 0.1 + }, + 'Ret. Interval': {DEFAULT_SENSITIVITY_KEY: 0.5 + }, + 'Groove': {DEFAULT_SENSITIVITY_KEY: 0.1 + }, + 'Retrigger Mode': {DEFAULT_SENSITIVITY_KEY: 0.1 + } + }, + 'MidiNoteLength': {'Synced Length': {DEFAULT_SENSITIVITY_KEY: 0.1 + } + }, + 'MidiScale': {'Base': {DEFAULT_SENSITIVITY_KEY: 0.5 + }, + 'Transpose': {DEFAULT_SENSITIVITY_KEY: 0.5 + } + }, + 'Amp': {'Bass': {DEFAULT_SENSITIVITY_KEY: 0.5 + }, + 'Middle': {DEFAULT_SENSITIVITY_KEY: 0.5 + }, + 'Treble': {DEFAULT_SENSITIVITY_KEY: 0.5 + }, + 'Presence': {DEFAULT_SENSITIVITY_KEY: 0.5 + }, + 'Gain': {DEFAULT_SENSITIVITY_KEY: 0.5 + }, + 'Volume': {DEFAULT_SENSITIVITY_KEY: 0.5 + }, + 'Dry/Wet': {DEFAULT_SENSITIVITY_KEY: 0.5 + } + }, + 'AutoFilter': {'Frequency': {DEFAULT_SENSITIVITY_KEY: 1 + }, + 'Env. Modulation': {DEFAULT_SENSITIVITY_KEY: 0.5 + }, + 'LFO Sync Rate': {DEFAULT_SENSITIVITY_KEY: 0.1 + }, + 'LFO Phase': {DEFAULT_SENSITIVITY_KEY: 0.5 + }, + 'LFO Offset': {DEFAULT_SENSITIVITY_KEY: 0.5 + } + }, + 'AutoPan': {'Sync Rate': {DEFAULT_SENSITIVITY_KEY: 0.1 + } + }, + 'BeatRepeat': {'Grid': {DEFAULT_SENSITIVITY_KEY: 0.1 + }, + 'Interval': {DEFAULT_SENSITIVITY_KEY: 0.1 + }, + 'Offset': {DEFAULT_SENSITIVITY_KEY: 0.1 + }, + 'Gate': {DEFAULT_SENSITIVITY_KEY: 0.1 + }, + 'Pitch': {DEFAULT_SENSITIVITY_KEY: 0.1 + }, + 'Variation': {DEFAULT_SENSITIVITY_KEY: 0.1 + }, + 'Mix Type': {DEFAULT_SENSITIVITY_KEY: 0.1 + }, + 'Grid': {DEFAULT_SENSITIVITY_KEY: 0.1 + }, + 'Variation Type': {DEFAULT_SENSITIVITY_KEY: 0.1 + } + }, + 'Corpus': {'LFO Sync Rate': {DEFAULT_SENSITIVITY_KEY: 0.1 + } + }, + 'Eq8': {'Band': {DEFAULT_SENSITIVITY_KEY: 0.5 + }, + '1 Frequency A': {DEFAULT_SENSITIVITY_KEY: 0.4 + }, + '2 Frequency A': {DEFAULT_SENSITIVITY_KEY: 0.4 + }, + '3 Frequency A': {DEFAULT_SENSITIVITY_KEY: 0.4 + }, + '4 Frequency A': {DEFAULT_SENSITIVITY_KEY: 0.4 + }, + '5 Frequency A': {DEFAULT_SENSITIVITY_KEY: 0.4 + }, + '6 Frequency A': {DEFAULT_SENSITIVITY_KEY: 0.4 + }, + '7 Frequency A': {DEFAULT_SENSITIVITY_KEY: 0.4 + }, + '8 Frequency A': {DEFAULT_SENSITIVITY_KEY: 0.4 + } + }, + 'Flanger': {'Sync Rate': {DEFAULT_SENSITIVITY_KEY: 0.1 + } + }, + 'GrainDelay': {'Pitch': {DEFAULT_SENSITIVITY_KEY: 0.5 + } + }, + 'Phaser': {'LFO Sync Rate': {DEFAULT_SENSITIVITY_KEY: 0.1 + } + }, + 'Resonator': {'II Pitch': {DEFAULT_SENSITIVITY_KEY: 0.5 + }, + 'III Pitch': {DEFAULT_SENSITIVITY_KEY: 0.5 + }, + 'IV Pitch': {DEFAULT_SENSITIVITY_KEY: 0.5 + }, + 'V Pitch': {DEFAULT_SENSITIVITY_KEY: 0.5 + } + } + } \ No newline at end of file diff --git a/Push2/parameter_slot_description.py b/Push2/parameter_slot_description.py deleted file mode 100644 index 2d8447fc..00000000 --- a/Push2/parameter_slot_description.py +++ /dev/null @@ -1,109 +0,0 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/midi-remote-scripts/Push2/parameter_slot_description.py -from __future__ import absolute_import -from ableton.v2.base import find_if, listens_group, liveobj_valid, Subject, SlotManager -RESULTING_NAME_KEY = 'ResultingName' -CONDITION_NAME_KEY = 'ConditionName' -CONDITIONS_LIST_NAME_KEY = 'ConditionsListName' -PREDICATE_KEY = 'Predicate' -OPERAND_NAME_KEY = 'Operand' -AND = 'and' -OR = 'or' - -def find_parameter(name, host): - parameters = host.parameters if host != None else [] - return find_if(lambda p: p.original_name == name, parameters) - - -class ParameterSlotDescription(Subject, SlotManager): - """ - Description class that allows chosing a parameter (name) based on - the values of other parameters. To retrieve the chosen parameter name - turn the slot into a string. - - Examples: - - slot = use('A').if_parameter('B').has_value('1.0') - - slot = use('A').if_parameter('B').has_value('1.0').else_use('C') - - slot = use('A').if_parameter('B').has_value('1.0') .and_parameter('C').has_value('0.5').else_use('D') - - parameter_name = str(slot) - """ - __events__ = ('content',) - - def __init__(self, *a, **k): - super(ParameterSlotDescription, self).__init__(*a, **k) - self._parameter_host = None - self._default_parameter_name = '' - self._conditions = [] - self._cached_content = None - - def _calc_content(self): - content = self._default_parameter_name - for condition in self._conditions: - result = True - for subcond in condition[CONDITIONS_LIST_NAME_KEY]: - result = eval('%s %s %s' % (result, subcond[OPERAND_NAME_KEY], subcond[PREDICATE_KEY](find_parameter(subcond[CONDITION_NAME_KEY], self._parameter_host)))) - if not result: - continue - - if result: - content = condition[RESULTING_NAME_KEY] - break - - return content - - @listens_group('value') - def __on_condition_value_changed(self, _parameter): - new_content = self._calc_content() - if new_content != self._cached_content: - self._cached_content = new_content - self.notify_content() - - def set_parameter_host(self, host): - self._parameter_host = host - self._cached_content = self._calc_content() - params_names = set() - for c in self._conditions: - params_names.update([ cond[CONDITION_NAME_KEY] for cond in c[CONDITIONS_LIST_NAME_KEY] ]) - - self.__on_condition_value_changed.replace_subjects([ find_parameter(name, self._parameter_host) for name in params_names ]) - - def if_parameter(self, parameter_name): - self._conditions.append({RESULTING_NAME_KEY: self._default_parameter_name, - CONDITIONS_LIST_NAME_KEY: [{CONDITION_NAME_KEY: parameter_name, - OPERAND_NAME_KEY: AND}]}) - self._default_parameter_name = '' - return self - - def chain_condition(self, operand, parameter_name): - raise len(self._conditions) > 0 and len(self._conditions[-1][CONDITIONS_LIST_NAME_KEY]) > 0 and not self._default_parameter_name or AssertionError - self._conditions[-1][CONDITIONS_LIST_NAME_KEY].append({CONDITION_NAME_KEY: parameter_name, - OPERAND_NAME_KEY: operand}) - return self - - def and_parameter(self, parameter_name): - return self.chain_condition(AND, parameter_name) - - def or_parameter(self, parameter_name): - return self.chain_condition(OR, parameter_name) - - def _add_condition_predicate(self, predicate): - raise len(self._conditions) > 0 and PREDICATE_KEY not in self._conditions[-1][CONDITIONS_LIST_NAME_KEY][-1] or AssertionError - self._conditions[-1][CONDITIONS_LIST_NAME_KEY][-1][PREDICATE_KEY] = predicate - - def has_value(self, value): - self._add_condition_predicate(lambda p: str(p) == value) - return self - - def is_available(self, value): - self._add_condition_predicate(lambda p: liveobj_valid(p) == value) - return self - - def else_use(self, parameter_name): - self._default_parameter_name = parameter_name - return self - - def __str__(self): - return self._cached_content - - -def use(parameter_name): - return ParameterSlotDescription().else_use(parameter_name) \ No newline at end of file diff --git a/Push2/push2.py b/Push2/push2.py index 098fa59b..3369d133 100644 --- a/Push2/push2.py +++ b/Push2/push2.py @@ -1,4 +1,5 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/Push2/push2.py +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/Push2/push2.py +# Compiled at: 2016-11-16 18:13:20 from __future__ import absolute_import, print_function from contextlib import contextmanager from functools import partial @@ -7,25 +8,29 @@ import weakref import Live import MidiRemoteScript -from ableton.v2.base import const, inject, listens, listens_group, task, Subject, NamedTuple +from ableton.v2.base import const, inject, listens, listens_group, task, EventObject, NamedTuple from ableton.v2.control_surface import BackgroundLayer, Component, IdentifiableControlSurface, Layer, get_element from ableton.v2.control_surface.control import ButtonControl +from ableton.v2.control_surface.defaults import TIMER_DELAY from ableton.v2.control_surface.elements import ButtonMatrixElement, ComboElement, SysexElement from ableton.v2.control_surface.mode import EnablingModesComponent, ModesComponent, LayerMode, LazyComponentMode, ReenterBehaviour, SetAttributeMode from pushbase.actions import select_clip_and_get_name_from_slot, select_scene_and_get_name from pushbase.device_parameter_component import DeviceParameterComponentBase as DeviceParameterComponent +from pushbase.pad_sensitivity import PadUpdateComponent from pushbase.quantization_component import QUANTIZATION_NAMES_UNICODE, QuantizationComponent, QuantizationSettingsComponent from pushbase.selection import PushSelection -from pushbase.percussion_instrument_finder_component import find_drum_group_device +from pushbase.percussion_instrument_finder import find_drum_group_device from pushbase import consts from pushbase.push_base import PushBase, NUM_TRACKS, NUM_SCENES from pushbase.track_frozen_mode import TrackFrozenModesComponent +from pushbase.messenger_mode_component import MessengerModesComponent from . import sysex from .actions import CaptureAndInsertSceneComponent from .automation import AutomationComponent from .elements import Elements from .browser_component import BrowserComponent, NewTrackBrowserComponent from .browser_modes import AddDeviceMode, AddTrackMode, BrowseMode, BrowserComponentMode, BrowserModeBehaviour +from .color_chooser import ColorChooserComponent from .device_decoration import DeviceDecoratorFactory from .skin_default import make_default_skin from .mute_solo_stop import MuteSoloStopClipComponent @@ -40,36 +45,44 @@ from .colors import COLOR_TABLE from .convert import ConvertComponent, ConvertEnabler from .bank_selection_component import BankSelectionComponent -from .firmware import FirmwareUpdateComponent, FirmwareVersion +from .firmware import FirmwareCollector, FirmwareUpdateComponent, FirmwareSwitcher, FirmwareVersion from .hardware_settings_component import HardwareSettingsComponent from .master_track import MasterTrackComponent from .mixer_control_component import MixerControlComponent from .note_editor import Push2NoteEditorComponent from .note_settings import NoteSettingsComponent from .notification_component import NotificationComponent +from .pad_sensitivity import default_profile, loop_selector_profile, pad_parameter_sender, playing_profile from .pad_velocity_curve import PadVelocityCurveSender +from .routing import RoutingControlComponent, TrackOrRoutingControlChooserComponent from .scales_component import ScalesComponent, ScalesEnabler -from .session_component import SessionComponent +from .selected_track_parameter_provider import SelectedTrackParameterProvider +from .session_component import DecoratingCopyHandler, SessionComponent from .session_recording import SessionRecordingComponent from .session_ring_selection_linking import SessionRingSelectionLinking from .settings import create_settings +from .sliced_simpler import Push2SlicedSimplerComponent from .track_mixer_control_component import TrackMixerControlComponent from .mode_collector import ModeCollector from .real_time_channel import update_real_time_attachments from .setup_component import SetupComponent, Settings from .track_list import TrackListComponent from .track_selection import SessionRingTrackProvider, ViewControlComponent +from .transport_state import TransportState from .user_component import UserButtonBehavior, UserComponent from .custom_bank_definitions import BANK_DEFINITIONS logger = logging.getLogger(__name__) -VELOCITY_RANGE_THRESHOLDS = [120, 60, 0] +VELOCITY_RANGE_THRESHOLDS = [ + 120, 60, 0] class QmlError(Exception): pass -def make_dialog_layer(priority = consts.DIALOG_PRIORITY, *a, **k): - return (BackgroundLayer('global_param_controls', 'select_buttons', 'track_state_buttons', priority=priority), Layer(priority=priority, *a, **k)) +def make_dialog_layer(priority=consts.DIALOG_PRIORITY, *a, **k): + return ( + BackgroundLayer('global_param_controls', 'select_buttons', 'track_state_buttons', priority=priority), + Layer(priority=priority, *a, **k)) def tracks_to_use_from_song(song): @@ -77,15 +90,18 @@ def tracks_to_use_from_song(song): def wrap_button(select_buttons, modifier): - return [ ComboElement(button, modifier=modifier) for button in get_element(select_buttons) ] + return [ ComboElement(button, modifier=modifier) for button in get_element(select_buttons) + ] -def make_freeze_aware(component, layer, default_mode_extras = [], frozen_mode_extras = []): - return TrackFrozenModesComponent(default_mode=[component, LayerMode(component, layer)] + default_mode_extras, frozen_mode=[component, LayerMode(component, Layer())] + frozen_mode_extras, is_enabled=False) +def make_freeze_aware(component, layer, default_mode_extras=[], frozen_mode_extras=[]): + return TrackFrozenModesComponent(default_mode=[ + component, LayerMode(component, layer)] + default_mode_extras, frozen_mode=[ + component, LayerMode(component, Layer())] + frozen_mode_extras, is_enabled=False) -class RealTimeClientModel(Subject): - __events__ = ('clientId',) +class RealTimeClientModel(EventObject): + __events__ = ('clientId', ) def __init__(self): self._client_id = '' @@ -101,27 +117,31 @@ def _set_client_id(self, client_id): class Push2(IdentifiableControlSurface, PushBase): - session_component_type = SessionComponent drum_group_note_editor_skin = 'DrumGroupNoteEditor' - input_target_name_for_auto_arm = 'Push2 Input' + slicing_note_editor_skin = 'SlicingNoteEditor' + drum_group_velocity_levels_skin = 'DrumGroupVelocityLevels' + slicing_velocity_levels_skin = 'VelocityLevels' note_editor_velocity_range_thresholds = VELOCITY_RANGE_THRESHOLDS device_component_class = DeviceComponent device_provider_class = Push2DeviceProvider + selected_track_parameter_provider_class = SelectedTrackParameterProvider bank_definitions = BANK_DEFINITIONS note_editor_class = Push2NoteEditorComponent + sliced_simpler_class = Push2SlicedSimplerComponent RESEND_MODEL_DATA_TIMEOUT = 5.0 DEFUNCT_EXTERNAL_PROCESS_RELAUNCH_TIMEOUT = 2.0 - def __init__(self, c_instance = None, model = None, bank_definitions = None, *a, **k): - if not model is not None: - raise AssertionError - self._model = model - self._real_time_mapper = c_instance.real_time_mapper - self._clip_decorator_factory = ClipDecoratorFactory() - self._real_time_data_list = [] - self.bank_definitions = bank_definitions is not None and bank_definitions + def __init__(self, c_instance=None, model=None, bank_definitions=None, *a, **k): + assert model is not None + self._model = model + self._real_time_mapper = c_instance.real_time_mapper + self._clip_decorator_factory = ClipDecoratorFactory() + self._real_time_data_list = [] + if bank_definitions is not None: + self.bank_definitions = bank_definitions super(Push2, self).__init__(c_instance=c_instance, product_id_bytes=sysex.IDENTITY_RESPONSE_PRODUCT_ID_BYTES, *a, **k) self._board_revision = 0 + self._firmware_collector = FirmwareCollector() self._firmware_version = FirmwareVersion(0, 0, 0) self._real_time_client = RealTimeClientModel() self._connected = False @@ -133,49 +153,64 @@ def __init__(self, c_instance = None, model = None, bank_definitions = None, *a, self._model.realTimeClient = self._real_time_client self._real_time_client.clientId = self._real_time_mapper.client_id logger.info('Push 2 script loaded') + return def initialize(self): if not self._initialized: self._initialized = True self._init_hardware_settings() self._init_pad_curve() - self._hardware_settings.fade_in_led_brightness(self._setup_settings.hardware.led_brightness) + self._hardware_settings.hardware_initialized() self._pad_curve_sender.send() self._send_color_palette() super(Push2, self).initialize() + self._init_transport_state() self.__on_selected_track_frozen_changed.subject = self.song.view self.__on_selected_track_frozen_changed() self._switch_to_live_mode() self.update() - if self._firmware_update.provided_version > self._firmware_version and self._board_revision > 0 and self._identified: - self._firmware_update.start() + latest_stable_firmware = self._firmware_collector.latest_stable_firmware + if latest_stable_firmware is not None and latest_stable_firmware.version > self._firmware_version and self._board_revision > 0 and self._identified: + self._firmware_update.start(latest_stable_firmware) + return def _try_initialize(self): if self._connected and self._identified: self.initialize() def on_process_state_changed(self, state): + logger.debug('Process state changed %r' % state) StateEnum = MidiRemoteScript.Push2ProcessState self._connected = state == StateEnum.connected if state == StateEnum.died: + if self._initialized: + self._setup_component.make_it_go_boom = False self._c_instance.launch_external_process() elif state == StateEnum.connected: with self.component_guard(): self._try_initialize() self._model.commit_changes(send_all=True) - elif state in (StateEnum.defunct_process_terminated, StateEnum.defunct_process_killed): + elif state in ( + StateEnum.defunct_process_terminated, + StateEnum.defunct_process_killed): self._tasks.add(task.sequence(task.wait(self.DEFUNCT_EXTERNAL_PROCESS_RELAUNCH_TIMEOUT), task.run(self._c_instance.launch_external_process))) def on_user_data_arrived(self, message): if self._initialized: + logger.debug('User data arrived %r' % message) data = json.loads(message) self._process_qml_errors(data) self._firmware_update.process_firmware_response(data) def _process_qml_errors(self, data): - qmlerrors = [ entry['description'] for entry in data if entry['type'] == 'qmlerror' ] + qmlerrors = [ entry for entry in data if entry['type'] == 'qmlerror' ] if qmlerrors: - raise QmlError('\n'.join(qmlerrors)) + first_error = qmlerrors[0] + line = first_error['line'] + url = first_error['url'] + description = first_error['description'].replace('"', '\\"') + code = '\n' * (line - 1) + 'raise QmlError("%s")' % description + exec compile(code, url, 'exec') def disconnect(self): super(Push2, self).disconnect() @@ -195,15 +230,15 @@ def _create_skin(self): return self.register_disconnectable(make_default_skin()) def _create_injector(self): - return inject(double_press_context=const(self._double_press_context), expect_dialog=const(self.expect_dialog), show_notification=const(self.show_notification), commit_model_changes=const(self._model.commit_changes), register_real_time_data=const(self.register_real_time_data), selection=lambda : PushSelection(application=self.application(), device_component=self._device_component, navigation_component=self._device_navigation)) + return inject(double_press_context=const(self._double_press_context), expect_dialog=const(self.expect_dialog), show_notification=const(self.show_notification), commit_model_changes=const(self._model.commit_changes), register_real_time_data=const(self.register_real_time_data), percussion_instrument_finder=const(self._percussion_instrument_finder), selection=lambda : PushSelection(application=self.application, device_component=self._device_component, navigation_component=self._device_navigation)) def _create_components(self): self._init_dialog_modes() super(Push2, self)._create_components() self._init_browser() self._init_session_ring_selection_linking() - self._init_setup_component() self._init_firmware_update() + self._init_setup_component() self._init_convert_enabler() self._init_mute_solo_stop() @@ -244,6 +279,10 @@ def _init_note_settings_component(self): self._note_settings_component = NoteSettingsComponent(grid_resolution=self._grid_resolution, is_enabled=False, layer=Layer(full_velocity_button='accent_button', priority=consts.MOMENTARY_DIALOG_PRIORITY)) self._model.noteSettingsView = self._note_settings_component + def _select_note_mode(self): + super(Push2, self)._select_note_mode() + self._note_settings_component.set_color_mode('drum_pad' if self._note_modes.selected_mode == 'drums' else 'clip') + def _init_note_editor_settings_component(self): super(Push2, self)._init_note_editor_settings_component() self._model.stepSettingsView = self._note_editor_settings_component.step_settings @@ -259,6 +298,7 @@ def _init_convert_enabler(self): @listens('cancel') def __on_convert_closed(self): self._dialog_modes.selected_mode = None + return @listens('success') def __on_convert_suceeded(self, action_name): @@ -267,9 +307,12 @@ def __on_convert_suceeded(self, action_name): def _init_main_modes(self): super(Push2, self)._init_main_modes() - self._main_modes.add_mode('user', [self._user_mode_ui_blocker, SetAttributeMode(obj=self._user, attribute='mode', value=sysex.USER_MODE)], behaviour=UserButtonBehavior(user_component=self._user)) + self._main_modes.add_mode('user', [ + self._user_mode_ui_blocker, + SetAttributeMode(obj=self._user, attribute='mode', value=sysex.USER_MODE)], behaviour=UserButtonBehavior(user_component=self._user)) self._model.modeState = self.register_disconnectable(ModeCollector(main_modes=self._main_modes, mix_modes=self._mix_modes, global_mix_modes=self._mixer_control, device_modes=self._device_navigation.modes)) - self.__on_main_mode_button_value.replace_subjects([self.elements.vol_mix_mode_button, + self.__on_main_mode_button_value.replace_subjects([ + self.elements.vol_mix_mode_button, self.elements.pan_send_mix_mode_button, self.elements.single_track_mix_mode_button, self.elements.clip_mode_button, @@ -286,12 +329,15 @@ def __on_main_mode_button_value(self, value, sender): def _exit_modal_modes(self): self._dialog_modes.selected_mode = None self._setup_enabler.selected_mode = 'disabled' + return def _create_capture_and_insert_scene_component(self): return CaptureAndInsertSceneComponent(name='Capture_And_Insert_Scene', decorator_factory=self._clip_decorator_factory, is_root=True) def _init_mute_solo_stop(self): - self._mute_solo_stop = MuteSoloStopClipComponent(is_root=True, item_provider=self._session_ring, track_list_component=self._track_list_component, cancellation_action_performers=[self._device_navigation, self._drum_component] + self._note_editor_settings_component.editors, solo_track_button='global_solo_button', mute_track_button='global_mute_button', stop_clips_button='global_track_stop_button') + self._mute_solo_stop = MuteSoloStopClipComponent(is_root=True, item_provider=self._session_ring, track_list_component=self._track_list_component, cancellation_action_performers=[ + self._device_navigation, + self._drum_component] + self._note_editor_settings_component.editors, solo_track_button='global_solo_button', mute_track_button='global_mute_button', stop_clips_button='global_track_stop_button') self._mute_solo_stop.layer = Layer(stop_all_clips_button=self._with_shift('global_track_stop_button')) self._master_selector = MasterTrackComponent(tracks_provider=self._session_ring, is_enabled=False, layer=Layer(toggle_button='master_select_button')) self._master_selector.set_enabled(True) @@ -299,8 +345,16 @@ def _init_mute_solo_stop(self): def _create_instrument_layer(self): return super(Push2, self)._create_instrument_layer() + Layer(prev_loop_page_button='page_left_button', next_loop_page_button='page_right_button') - def _create_step_sequencer_layer(self): - return super(Push2, self)._create_step_sequencer_layer() + Layer(prev_loop_page_button='page_left_button', next_loop_page_button='page_right_button') + def _create_drum_step_sequencer_layer(self): + return super(Push2, self)._create_drum_step_sequencer_layer() + Layer(prev_loop_page_button='page_left_button', next_loop_page_button='page_right_button') + + def _create_slice_step_sequencer_layer(self): + return super(Push2, self)._create_slice_step_sequencer_layer() + Layer(prev_loop_page_button='page_left_button', next_loop_page_button='page_right_button') + + def _create_color_chooser(self): + color_chooser = ColorChooserComponent() + color_chooser.layer = Layer(matrix='matrix', priority=consts.MOMENTARY_DIALOG_PRIORITY) + return color_chooser def _create_session(self): session = super(Push2, self)._create_session() @@ -308,6 +362,7 @@ def _create_session(self): scene = session.scene(scene_ix) for track_ix in xrange(8): clip_slot = scene.clip_slot(track_ix) + clip_slot.layer += Layer(select_color_button='shift_button') clip_slot.set_decorator_factory(self._clip_decorator_factory) return session @@ -322,26 +377,41 @@ def on_select_scene(self, scene): self.show_notification('Scene Selected: ' + select_scene_and_get_name(scene, self.song)) def _create_session_mode(self): - session_modes = ModesComponent(is_enabled=False) - session_modes.add_mode('session', self._session_mode) - session_modes.add_mode('overview', self._session_overview_mode) + session_modes = MessengerModesComponent(is_enabled=False) + session_modes.add_mode('session', [ + self._session_mode], message=consts.MessageBoxText.LAYOUT_SESSION_VIEW) + session_modes.add_mode('overview', [ + self._session_overview_mode], message=consts.MessageBoxText.LAYOUT_SESSION_OVERVIEW) session_modes.layer = Layer(cycle_mode_button='layout_button') session_modes.selected_mode = 'session' - return [session_modes, self._session_navigation] + return [ + session_modes, + self._session_navigation] def _create_session_overview_layer(self): return Layer(button_matrix='matrix') + def _instantiate_session(self): + return SessionComponent(session_ring=self._session_ring, is_enabled=False, auto_name=True, clip_slot_copy_handler=DecoratingCopyHandler(decorator_factory=self._clip_decorator_factory), fixed_length_recording=self._create_fixed_length_recording(), color_chooser=self._create_color_chooser(), layer=self._create_session_layer()) + def _create_drum_component(self): - return DrumGroupComponent(name='Drum_Group', is_enabled=False, notification_formatter=self._drum_pad_notification_formatter(), tracks_provider=self._session_ring, device_decorator_factory=self._device_decorator_factory, quantizer=self._quantize) + return DrumGroupComponent(name='Drum_Group', is_enabled=False, tracks_provider=self._session_ring, device_decorator_factory=self._device_decorator_factory, quantizer=self._quantize, color_chooser=self._create_color_chooser()) + + def _init_drum_component(self): + super(Push2, self)._init_drum_component() + self._drum_component.layer += Layer(select_color_button='shift_button') def _create_device_mode(self): - self._drum_pad_parameter_component = DrumPadParameterComponent(view_model=self._model, is_enabled=False, layer=Layer(choke_encoder='parameter_controls_raw[0]')) + self._drum_pad_parameter_component = DrumPadParameterComponent(device_component=self._device_component, view_model=self._model, is_enabled=False, layer=Layer(choke_encoder='parameter_controls_raw[0]', transpose_encoder='parameter_controls_raw[1]')) self._device_or_pad_parameter_chooser = ModesComponent() - self._device_or_pad_parameter_chooser.add_mode('device', [make_freeze_aware(self._device_parameter_component, self._device_parameter_component.layer), self._device_view]) - self._device_or_pad_parameter_chooser.add_mode('drum_pad', [make_freeze_aware(self._drum_pad_parameter_component, self._drum_pad_parameter_component.layer)]) + self._device_or_pad_parameter_chooser.add_mode('device', [ + make_freeze_aware(self._device_parameter_component, self._device_parameter_component.layer), + self._device_view]) + self._device_or_pad_parameter_chooser.add_mode('drum_pad', [ + make_freeze_aware(self._drum_pad_parameter_component, self._drum_pad_parameter_component.layer)]) self._device_or_pad_parameter_chooser.selected_mode = 'device' - return [partial(self._view_control.show_view, 'Detail/DeviceChain'), + return [ + partial(self._view_control.show_view, 'Detail/DeviceChain'), self._device_or_pad_parameter_chooser, self._setup_freeze_aware_device_navigation(), self._device_note_editor_mode, @@ -352,7 +422,12 @@ def _setup_freeze_aware_device_navigation(self): def create_layer_setter(layer_name, layer): return SetAttributeMode(obj=self._device_navigation, attribute=layer_name, value=layer) - return make_freeze_aware(self._device_navigation, self._device_navigation.layer, default_mode_extras=[create_layer_setter('scroll_right_layer', Layer(button=self.elements.track_state_buttons_raw[-1])), create_layer_setter('scroll_left_layer', Layer(button=self.elements.track_state_buttons_raw[0]))], frozen_mode_extras=[lambda : setattr(self._device_navigation.modes, 'selected_mode', 'default'), create_layer_setter('scroll_right_layer', Layer()), create_layer_setter('scroll_left_layer', Layer())]) + return make_freeze_aware(self._device_navigation, self._device_navigation.layer, default_mode_extras=[ + create_layer_setter('scroll_right_layer', Layer(button=self.elements.track_state_buttons_raw[-1])), + create_layer_setter('scroll_left_layer', Layer(button=self.elements.track_state_buttons_raw[0]))], frozen_mode_extras=[ + lambda : setattr(self._device_navigation.modes, 'selected_mode', 'default'), + create_layer_setter('scroll_right_layer', Layer()), + create_layer_setter('scroll_left_layer', Layer())]) @listens('drum_pad_selection') def __on_drum_pad_selection_changed(self): @@ -371,17 +446,23 @@ def _init_browser(self): def _init_browse_mode(self): application = Live.Application.get_application() browser = application.browser - self._main_modes.add_mode('browse', [BrowseMode(application=application, song=self.song, browser=browser, component_mode=self._browser_component_mode)], behaviour=BrowserModeBehaviour()) - self._main_modes.add_mode('add_device', [AddDeviceMode(application=application, song=self.song, browser=browser, component_mode=self._browser_component_mode)], behaviour=BrowserModeBehaviour()) - self._main_modes.add_mode('add_track', [AddTrackMode(browser=browser, component_mode=self._new_track_browser_component_mode)], behaviour=BrowserModeBehaviour()) + self._main_modes.add_mode('browse', [ + BrowseMode(application=application, song=self.song, browser=browser, drum_group_component=self._drum_component, component_mode=self._browser_component_mode)], behaviour=BrowserModeBehaviour()) + self._main_modes.add_mode('add_device', [ + AddDeviceMode(application=application, song=self.song, browser=browser, drum_group_component=self._drum_component, component_mode=self._browser_component_mode)], behaviour=BrowserModeBehaviour()) + self._main_modes.add_mode('add_track', [ + AddTrackMode(browser=browser, component_mode=self._new_track_browser_component_mode)], behaviour=BrowserModeBehaviour()) def _create_browser_layer(self): - return (BackgroundLayer('select_buttons', 'track_state_buttons', priority=consts.DIALOG_PRIORITY), Layer(up_button='nav_up_button', down_button='nav_down_button', right_button='nav_right_button', left_button='nav_left_button', back_button='track_state_buttons_raw[-2]', open_button='track_state_buttons_raw[-1]', load_button='select_buttons_raw[-1]', scroll_encoders=self.elements.global_param_controls.submatrix[:-1, :], scroll_focused_encoder='parameter_controls_raw[-1]', close_button='track_state_buttons_raw[0]', prehear_button='track_state_buttons_raw[1]', priority=consts.DIALOG_PRIORITY)) + return ( + BackgroundLayer('select_buttons', 'track_state_buttons', priority=consts.DIALOG_PRIORITY), + Layer(up_button='nav_up_button', down_button='nav_down_button', right_button='nav_right_button', left_button='nav_left_button', back_button='track_state_buttons_raw[-2]', open_button='track_state_buttons_raw[-1]', load_button='select_buttons_raw[-1]', scroll_encoders=self.elements.global_param_controls.submatrix[:-1, :], scroll_focused_encoder='parameter_controls_raw[-1]', close_button='track_state_buttons_raw[0]', prehear_button='track_state_buttons_raw[1]', priority=consts.DIALOG_PRIORITY)) def _create_browser(self): browser = BrowserComponent(name='Browser', is_enabled=False, preferences=self.preferences, main_modes_ref=weakref.ref(self._main_modes), layer=self._create_browser_layer()) self._on_browser_loaded.add_subject(browser) self._on_browser_closed.add_subject(browser) + browser.load_neighbour_overlay.layer = Layer(load_previous_button='track_state_buttons_raw[7]', load_next_button='select_buttons_raw[7]', priority=consts.DIALOG_PRIORITY) return browser def _create_new_track_browser(self): @@ -405,6 +486,7 @@ def _on_browser_loaded(self, sender): if not self._device_navigation.is_drum_pad_unfolded: self._device_navigation.unfold_current_drum_pad() self._device_navigation.sync_selection_to_selected_device() + return @listens_group('close') def _on_browser_closed(self, sender): @@ -465,16 +547,14 @@ def _create_device_navigation(self): def _init_device(self): super(Push2, self)._init_device() - self._device_component.layer = Layer(parameter_touch_buttons=ButtonMatrixElement(rows=[self.elements.global_param_touch_buttons_raw])) + self._device_component.layer = Layer(parameter_touch_buttons=ButtonMatrixElement(rows=[ + self.elements.global_param_touch_buttons_raw])) self._device_view = DeviceViewComponent(name='DeviceView', device_component=self._device_component, view_model=self._model, is_enabled=False) self._model.devicelistView = self._device_navigation self._model.chainListView = self._chain_selection self._model.parameterBankListView = self._bank_selection self._model.editModeOptionsView = self._bank_selection.options - def _drum_pad_notification_formatter(self): - return None - def _create_view_control_component(self): return ViewControlComponent(name='View_Control', tracks_provider=self._session_ring) @@ -482,13 +562,13 @@ def _create_session_recording(self): return SessionRecordingComponent(fixed_length_setting=self._fixed_length_setting, clip_creator=self._clip_creator, view_controller=self._view_control, name='Session_Recording', is_root=True) def _init_session_ring(self): - self._session_ring = SessionRingTrackProvider(name='Session_Ring', num_tracks=NUM_TRACKS, num_scenes=NUM_SCENES, tracks_to_use=partial(tracks_to_use_from_song, self.song), is_enabled=True, is_root=True) + self._session_ring = SessionRingTrackProvider(name='Session_Ring', num_tracks=NUM_TRACKS, num_scenes=NUM_SCENES, is_enabled=True, is_root=True) def _init_session_ring_selection_linking(self): self._sessionring_link = self.register_disconnectable(SessionRingSelectionLinking(session_ring=self._session_ring, selection_changed_notifier=self._view_control)) def _init_track_list(self): - self._track_list_component = TrackListComponent(tracks_provider=self._session_ring, trigger_recording_on_release_callback=self._session_recording.set_trigger_recording_on_release, is_enabled=False, is_root=True, layer=Layer(track_action_buttons='select_buttons', lock_override_button='select_button', delete_button='delete_button', duplicate_button='duplicate_button', arm_button='record_button')) + self._track_list_component = TrackListComponent(tracks_provider=self._session_ring, trigger_recording_on_release_callback=self._session_recording.set_trigger_recording_on_release, color_chooser=self._create_color_chooser(), is_enabled=False, is_root=True, layer=Layer(track_action_buttons='select_buttons', lock_override_button='select_button', delete_button='delete_button', duplicate_button='duplicate_button', arm_button='record_button', select_color_button='shift_button')) self._track_list_component.set_enabled(True) self._model.tracklistView = self._track_list_component @@ -496,10 +576,12 @@ def _create_main_mixer_modes(self): self._mixer_control = MixerControlComponent(name='Global_Mix_Component', view_model=self._model.mixerView, tracks_provider=self._session_ring, is_enabled=False, layer=Layer(controls='fine_grain_param_controls', volume_button='track_state_buttons_raw[0]', panning_button='track_state_buttons_raw[1]', send_slot_one_button='track_state_buttons_raw[2]', send_slot_two_button='track_state_buttons_raw[3]', send_slot_three_button='track_state_buttons_raw[4]', send_slot_four_button='track_state_buttons_raw[5]', send_slot_five_button='track_state_buttons_raw[6]', cycle_sends_button='track_state_buttons_raw[7]')) self._model.mixerView.realtimeMeterData = self._mixer_control.real_time_meter_handlers track_mixer_control = TrackMixerControlComponent(name='Track_Mix_Component', is_enabled=False, tracks_provider=self._session_ring, layer=Layer(controls='fine_grain_param_controls', scroll_left_button='track_state_buttons_raw[6]', scroll_right_button='track_state_buttons_raw[7]')) - self._model.mixerView.trackControlView = track_mixer_control + routing_control = RoutingControlComponent(is_enabled=False, layer=Layer(monitor_state_encoder='parameter_controls_raw[0]', input_output_choice_encoder='parameter_controls_raw[1]', routing_type_encoder='parameter_controls_raw[2]', routing_channel_encoders=self.elements.global_param_controls.submatrix[3:7, :], routing_channel_position_encoder='parameter_controls_raw[7]')) + track_mix_or_routing_chooser = TrackOrRoutingControlChooserComponent(tracks_provider=self._session_ring, track_mixer_component=track_mixer_control, routing_control_component=routing_control, is_enabled=False, layer=Layer(mix_button='track_state_buttons_raw[0]', routing_button='track_state_buttons_raw[1]')) + self._model.mixerView.trackControlView = track_mix_or_routing_chooser self._mix_modes = ModesComponent(is_enabled=False) self._mix_modes.add_mode('global', self._mixer_control) - self._mix_modes.add_mode('track', track_mixer_control) + self._mix_modes.add_mode('track', track_mix_or_routing_chooser) self._mix_modes.selected_mode = 'global' self._model.mixerSelectView = self._mixer_control self._model.trackMixerSelectView = track_mixer_control @@ -515,12 +597,16 @@ def on_reenter(behaviour_self): if not self._is_on_master(): self._mix_modes.cycle_mode() - self._main_modes.add_mode('mix', [self._mix_modes, SetAttributeMode(obj=self._note_editor_settings_component, attribute='parameter_provider', value=self._track_parameter_provider)], behaviour=MixModeBehaviour()) + self._main_modes.add_mode('mix', [ + self._mix_modes, + SetAttributeMode(obj=self._note_editor_settings_component, attribute='parameter_provider', value=self._track_parameter_provider)], behaviour=MixModeBehaviour()) def _init_dialog_modes(self): self._dialog_modes = ModesComponent(is_root=True) self._dialog_modes.add_mode('convert', LazyComponentMode(self._create_convert)) - self.__dialog_mode_button_value.replace_subjects([self.elements.scale_presets_button, self.elements.convert_button]) + self.__dialog_mode_button_value.replace_subjects([ + self.elements.scale_presets_button, + self.elements.convert_button]) @listens_group('value') def __dialog_mode_button_value(self, value, sender): @@ -529,15 +615,18 @@ def __dialog_mode_button_value(self, value, sender): def _enter_dialog_mode(self, mode_name): self._dialog_modes.selected_mode = None if self._dialog_modes.selected_mode == mode_name else mode_name + return def _exit_dialog_mode(self, mode_name): if self._dialog_modes.selected_mode == mode_name: self._dialog_modes.selected_mode = None + return def _create_scales(self): - root_note_buttons = ButtonMatrixElement(rows=[self.elements.track_state_buttons_raw[1:-1], self.elements.select_buttons_raw[1:-1]]) - scales = ScalesComponent(note_layout=self._note_layout, is_enabled=False, layer=make_dialog_layer(root_note_buttons=root_note_buttons, in_key_toggle_button='select_buttons_raw[0]', fixed_toggle_button='select_buttons_raw[-1]', scale_encoders=self.elements.global_param_controls.submatrix[1:-1, :], close_button='track_state_buttons_raw[0]', up_button='nav_up_button', down_button='nav_down_button', right_button='nav_right_button', left_button='nav_left_button')) - self.__on_scales_closed.subject = scales + root_note_buttons = ButtonMatrixElement(rows=[ + self.elements.track_state_buttons_raw[1:-1], + self.elements.select_buttons_raw[1:-1]]) + scales = ScalesComponent(note_layout=self._note_layout, is_enabled=False, layer=make_dialog_layer(root_note_buttons=root_note_buttons, in_key_toggle_button='select_buttons_raw[0]', fixed_toggle_button='select_buttons_raw[-1]', scale_encoders=self.elements.global_param_controls.submatrix[1:-1, :], layout_encoder='parameter_controls_raw[0]', direction_encoder='parameter_controls_raw[-1]', up_button='nav_up_button', down_button='nav_down_button', right_button='nav_right_button', left_button='nav_left_button')) self._model.scalesView = scales return scales @@ -548,27 +637,26 @@ def _init_scales(self): def _create_scales_enabler(self): return ScalesEnabler(enter_dialog_mode=self._enter_dialog_mode, exit_dialog_mode=self._exit_dialog_mode, is_enabled=False, is_root=True, layer=Layer(toggle_button='scale_presets_button')) - @listens('close') - def __on_scales_closed(self): - self._dialog_modes.selected_mode = None - def _create_clip_mode(self): - base_loop_layer = Layer(shift_button='shift_button', loop_button='track_state_buttons_raw[1]', zoom_encoder='fine_grain_param_controls_raw[0]', encoders=self.elements.global_param_controls.submatrix[1:4, :]) + base_loop_layer = Layer(shift_button='shift_button', loop_button='track_state_buttons_raw[1]') self._loop_controller = LoopSettingsControllerComponent(is_enabled=False) self._model.loopSettingsView = self._loop_controller audio_clip_layer = Layer(warp_mode_encoder='parameter_controls_raw[5]', transpose_encoder='parameter_controls_raw[6]', detune_encoder=self._with_shift('parameter_controls_raw[6]'), gain_encoder='parameter_controls_raw[7]', shift_button='shift_button') audio_clip_controller = AudioClipSettingsControllerComponent(is_enabled=False) self._model.audioClipSettingsView = audio_clip_controller clip_control_mode_selector = ModesComponent(is_enabled=False) - clip_control_mode_selector.add_mode('midi', [make_freeze_aware(self._loop_controller, base_loop_layer + Layer(encoders=self.elements.global_param_controls.submatrix[:3, :]))]) - clip_control_mode_selector.add_mode('audio', [make_freeze_aware(self._loop_controller, base_loop_layer + Layer(encoders=self.elements.global_param_controls.submatrix[1:4, :])), make_freeze_aware(audio_clip_controller, audio_clip_layer)]) + clip_control_mode_selector.add_mode('midi', [ + make_freeze_aware(self._loop_controller, base_loop_layer + Layer(encoders=self.elements.global_param_controls.submatrix[:3, :]))]) + clip_control_mode_selector.add_mode('audio', [ + make_freeze_aware(self._loop_controller, base_loop_layer + Layer(encoders=self.elements.global_param_controls.submatrix[1:4, :], zoom_encoder='fine_grain_param_controls_raw[0]')), + make_freeze_aware(audio_clip_controller, audio_clip_layer)]) clip_control_mode_selector.add_mode('no_clip', []) clip_control_mode_selector.selected_mode = 'no_clip' clip_control = ClipControlComponent(loop_controller=self._loop_controller, audio_clip_controller=audio_clip_controller, mode_selector=clip_control_mode_selector, decorator_factory=self._clip_decorator_factory, is_enabled=False) self._model.clipView = clip_control - return [clip_control_mode_selector, - make_freeze_aware(self._loop_controller, base_loop_layer), - make_freeze_aware(audio_clip_controller, audio_clip_layer), + return [ + partial(self._view_control.show_view, 'Detail/Clip'), + clip_control_mode_selector, clip_control] def _init_quantize_actions(self): @@ -578,7 +666,7 @@ def _init_quantize_actions(self): def _init_fixed_length(self): super(Push2, self)._init_fixed_length() - self._fixed_length_settings_component.layer = make_dialog_layer(length_option_buttons='select_buttons', fixed_length_toggle_button='track_state_buttons_raw[0]', priority=consts.MOMENTARY_DIALOG_PRIORITY) + self._fixed_length_settings_component.layer = make_dialog_layer(length_option_buttons='select_buttons', fixed_length_toggle_button='track_state_buttons_raw[0]', legato_launch_toggle_button='track_state_buttons_raw[7]', priority=consts.MOMENTARY_DIALOG_PRIORITY) self._model.fixedLengthSelectorView = self._fixed_length_settings_component self._model.fixedLengthSettings = self._fixed_length_setting @@ -593,7 +681,7 @@ def _create_main_modes_layer(self): return Layer(mix_button='mix_button', clip_button='clip_mode_button', device_button='device_mode_button', browse_button='browse_mode_button', add_device_button='create_device_button', add_track_button='create_track_button') + Layer(user_button='user_button', priority=consts.USER_BUTTON_PRIORITY) def _should_send_palette(self): - return self._firmware_version <= FirmwareVersion(0, 5, 28) + return self._firmware_version < FirmwareVersion(1, 0, 59) def _send_color_palette(self): if self._should_send_palette(): @@ -621,54 +709,54 @@ def _init_hardware_settings(self): self._setup_settings = self.register_disconnectable(Settings(preferences=self.preferences)) self._hardware_settings = HardwareSettingsComponent(led_brightness_element=SysexElement(sysex.make_led_brightness_message), display_brightness_element=SysexElement(sysex.make_display_brightness_message), settings=self._setup_settings.hardware) + def _init_transport_state(self): + self._model.transportState = TransportState(song=self.song) + def _init_setup_component(self): self._setup_settings.general.workflow = 'scene' if self._settings['workflow'].value else 'clip' self.__on_workflow_setting_changed.subject = self._setup_settings.general - self.__on_new_waveform_navigation_setting_changed.subject = self._setup_settings.experimental - self.__on_new_waveform_navigation_setting_changed(self._setup_settings.experimental.new_waveform_navigation) - setup = SetupComponent(name='Setup', settings=self._setup_settings, pad_curve_sender=self._pad_curve_sender, in_developer_mode=self._c_instance.in_developer_mode, is_enabled=False, layer=make_dialog_layer(category_radio_buttons='select_buttons', priority=consts.SETUP_DIALOG_PRIORITY)) + setup = SetupComponent(name='Setup', settings=self._setup_settings, pad_curve_sender=self._pad_curve_sender, firmware_switcher=self._firmware_switcher, is_enabled=False, layer=make_dialog_layer(category_radio_buttons='select_buttons', priority=consts.SETUP_DIALOG_PRIORITY, make_it_go_boom_button='track_state_buttons_raw[7]')) setup.general.layer = Layer(workflow_encoder='parameter_controls_raw[0]', display_brightness_encoder='parameter_controls_raw[1]', led_brightness_encoder='parameter_controls_raw[2]', priority=consts.SETUP_DIALOG_PRIORITY) + setup.info.layer = Layer(install_firmware_button='track_state_buttons_raw[6]', priority=consts.SETUP_DIALOG_PRIORITY) setup.pad_settings.layer = Layer(sensitivity_encoder='parameter_controls_raw[4]', gain_encoder='parameter_controls_raw[5]', dynamics_encoder='parameter_controls_raw[6]', priority=consts.SETUP_DIALOG_PRIORITY) setup.display_debug.layer = Layer(show_row_spaces_button='track_state_buttons_raw[0]', show_row_margins_button='track_state_buttons_raw[1]', show_row_middle_button='track_state_buttons_raw[2]', show_button_spaces_button='track_state_buttons_raw[3]', show_unlit_button_button='track_state_buttons_raw[4]', show_lit_button_button='track_state_buttons_raw[5]', priority=consts.SETUP_DIALOG_PRIORITY) setup.profiling.layer = Layer(show_qml_stats_button='track_state_buttons_raw[0]', show_usb_stats_button='track_state_buttons_raw[1]', show_realtime_ipc_stats_button='track_state_buttons_raw[2]', priority=consts.SETUP_DIALOG_PRIORITY) - setup.experimental.layer = Layer(new_waveform_navigation_button='track_state_buttons_raw[0]', priority=consts.SETUP_DIALOG_PRIORITY) self._model.setupView = setup self._setup_enabler = EnablingModesComponent(component=setup, enabled_color='DefaultButton.On', disabled_color='DefaultButton.On') self._setup_enabler.layer = Layer(cycle_mode_button='setup_button') + self._setup_component = setup def _init_firmware_update(self): self._firmware_update = FirmwareUpdateComponent(layer=self._create_message_box_background_layer()) self._model.firmwareUpdate = self._firmware_update + self._firmware_switcher = FirmwareSwitcher(firmware_collector=self._firmware_collector, firmware_update=self._firmware_update, installed_firmware_version=self._firmware_version) + self._model.firmwareSwitcher = self._firmware_switcher @listens('workflow') def __on_workflow_setting_changed(self, value): self._settings['workflow'].value = value == 'scene' - @listens('new_waveform_navigation') - def __on_new_waveform_navigation_setting_changed(self, value): - self._device_component.use_waveform_navigation = value - def _create_note_mode(self): class NoteLayoutSwitcher(Component): cycle_button = ButtonControl() - def __init__(self, switch_note_mode_layout = None, *a, **k): - raise switch_note_mode_layout is not None or AssertionError + def __init__(self, switch_note_mode_layout=None, *a, **k): + assert switch_note_mode_layout is not None super(NoteLayoutSwitcher, self).__init__(*a, **k) self._switch_note_mode_layout = switch_note_mode_layout + return @cycle_button.pressed def cycle_button(self, button): self._switch_note_mode_layout() note_layout_switcher = NoteLayoutSwitcher(switch_note_mode_layout=self._switch_note_mode_layout, is_enabled=False, layer=Layer(cycle_button='layout_button')) - return super(Push2, self)._create_note_mode() + [note_layout_switcher] - - def _create_note_mode_behaviour(self): - return self._auto_arm.auto_arm_restore_behaviour() + return super(Push2, self)._create_note_mode() + [ + note_layout_switcher] def _create_controls(self): + self._create_pad_sensitivity_update() class Deleter(object): @@ -679,10 +767,19 @@ def is_deleting(_): def delete_clip_envelope(_, param): return self._delete_default_component.delete_clip_envelope(param) - self.elements = Elements(deleter=Deleter(), undo_handler=self.song, playhead=self._c_instance.playhead, model=self._model) + self.elements = Elements(deleter=Deleter(), undo_handler=self.song, pad_sensitivity_update=self._pad_sensitivity_update, playhead=self._c_instance.playhead, velocity_levels=self._c_instance.velocity_levels, model=self._model) self.__on_param_encoder_touched.replace_subjects(self.elements.global_param_touch_buttons_raw) self._update_encoder_model() + def _create_pad_sensitivity_update(self): + all_pad_sysex_control = SysexElement(sysex.make_pad_setting_message) + pad_sysex_control = SysexElement(sysex.make_pad_setting_message) + sensitivity_sender = pad_parameter_sender(all_pad_sysex_control, pad_sysex_control) + self._pad_sensitivity_update = PadUpdateComponent(all_pads=range(64), parameter_sender=sensitivity_sender, default_profile=default_profile, update_delay=TIMER_DELAY, is_root=True) + self._pad_sensitivity_update.set_profile('drums', playing_profile) + self._pad_sensitivity_update.set_profile('instrument', playing_profile) + self._pad_sensitivity_update.set_profile('loop', loop_selector_profile) + def _update_full_velocity(self, accent_is_active): super(Push2, self)._update_full_velocity(accent_is_active) self._slice_step_sequencer.full_velocity = accent_is_active @@ -696,7 +793,8 @@ def __on_param_encoder_touched(self, value, encoder): self._update_encoder_model() def _update_encoder_model(self): - self._model.controls.encoders = [ NamedTuple(__id__='encoder_%i' % i, touched=e.is_pressed()) for i, e in enumerate(self.elements.global_param_touch_buttons_raw) ] + self._model.controls.encoders = [ NamedTuple(__id__='encoder_%i' % i, touched=e.is_pressed()) for i, e in enumerate(self.elements.global_param_touch_buttons_raw) + ] def _with_firmware_version(self, major_version, minor_version, control_element): """ @@ -718,15 +816,14 @@ def port_settings_changed(self): def on_identified(self, response_bytes): try: major, minor, build, sn, board_revision = sysex.extract_identity_response_info(response_bytes) - self._model.firmwareInfo.major = major - self._model.firmwareInfo.minor = minor - self._model.firmwareInfo.build = build - self._model.firmwareInfo.serialNumber = sn + self._firmware_version = FirmwareVersion(major, minor, build) + self._firmware_version.release_type = self._firmware_collector.get_release_type(self._firmware_version) + self._model.hardwareInfo.firmwareVersion = self._firmware_version + self._model.hardwareInfo.serialNumber = sn logger.info('Push 2 identified') logger.info('Firmware %i.%i Build %i' % (major, minor, build)) logger.info('Serial number %i' % sn) logger.info('Board Revision %i' % board_revision) - self._firmware_version = FirmwareVersion(major, minor, build) self._board_revision = board_revision self._identified = True self._try_initialize() @@ -735,11 +832,4 @@ def on_identified(self, response_bytes): def update(self): if self._initialized: - super(Push2, self).update() - - def request_zoom(self, zoom_factor): - mode = self._main_modes.selected_mode - if mode == 'device': - self._device_component.request_zoom(zoom_factor) - elif mode == 'clip': - self._loop_controller.request_zoom(zoom_factor) \ No newline at end of file + super(Push2, self).update() \ No newline at end of file diff --git a/Push2/push2_model.py b/Push2/push2_model.py index 5f4e36d7..682507b4 100644 --- a/Push2/push2_model.py +++ b/Push2/push2_model.py @@ -1,4 +1,5 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/Push2/push2_model.py +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/Push2/push2_model.py +# Compiled at: 2016-05-20 03:43:52 from __future__ import absolute_import, print_function from pprint import pformat import logging @@ -9,25 +10,27 @@ class Sender(object): - def __init__(self, message_sink = None, process_connected = None, *a, **k): + def __init__(self, message_sink=None, process_connected=None, *a, **k): super(Sender, self).__init__(*a, **k) - if not message_sink is not None: - raise AssertionError - self._message_sink = message_sink - process_connected = process_connected is None and (lambda : True) + assert message_sink is not None + self._message_sink = message_sink + if process_connected is None: + process_connected = lambda : True self._process_connected = process_connected self._attribute_paths = [] self._structural_change = False self.notifier = ModelUpdateNotifier(delegate=self) + return def structural_change(self, path): self._attribute_paths.append((path, None)) self._structural_change = True + return def attribute_changed(self, path, value): self._attribute_paths.append((path, value)) - def send(self, root_model, send_all = False): + def send(self, root_model, send_all=False): def send_data(data): if data['command'] == 'full-model-update': @@ -52,17 +55,19 @@ def send_data(data): class Root(generate_mrs_model(RootModel)): - def __init__(self, sender = None, *a, **k): + def __init__(self, sender=None, *a, **k): self._sender = sender if sender is not None: k['notifier'] = sender.notifier super(Root, self).__init__(*a, **k) + return - def commit_changes(self, send_all = False): + def commit_changes(self, send_all=False): if self._sender is not None: self._sender.send(self, send_all) + return - def to_json(self, root_keys = None): + def to_json(self, root_keys=None): if root_keys is None: return super(Root, self).to_json() else: @@ -70,4 +75,5 @@ def to_json(self, root_keys = None): for key in root_keys: res[key] = self.data[key].to_json() - return res \ No newline at end of file + return res + return \ No newline at end of file diff --git a/Push2/real_time_channel.py b/Push2/real_time_channel.py index 00863496..88548059 100644 --- a/Push2/real_time_channel.py +++ b/Push2/real_time_channel.py @@ -1,4 +1,5 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/Push2/real_time_channel.py +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/Push2/real_time_channel.py +# Compiled at: 2016-09-29 19:13:24 from __future__ import absolute_import, print_function from ableton.v2.control_surface import Component from ableton.v2.base import depends, listenable_property, liveobj_changed, liveobj_valid @@ -19,9 +20,9 @@ def update_real_time_attachments(real_time_data_components): class RealTimeDataComponent(Component): @depends(real_time_mapper=None, register_real_time_data=None) - def __init__(self, real_time_mapper = None, register_real_time_data = None, channel_type = None, *a, **k): - raise channel_type is not None or AssertionError - raise liveobj_valid(real_time_mapper) or AssertionError + def __init__(self, real_time_mapper=None, register_real_time_data=None, channel_type=None, *a, **k): + assert channel_type is not None + assert liveobj_valid(real_time_mapper) super(RealTimeDataComponent, self).__init__(*a, **k) self._channel_type = channel_type self._real_time_channel_id = '' @@ -30,6 +31,12 @@ def __init__(self, real_time_mapper = None, register_real_time_data = None, chan self._data = None self._valid = True register_real_time_data(self) + return + + def disconnect(self): + super(RealTimeDataComponent, self).disconnect() + self._data = None + return @listenable_property def channel_id(self): @@ -39,6 +46,10 @@ def channel_id(self): def object_id(self): return self._object_id + @property + def attached_object(self): + return self._data + def on_enabled_changed(self): super(RealTimeDataComponent, self).on_enabled_changed() self.invalidate() @@ -68,4 +79,5 @@ def attach(self): self._real_time_channel_id, self._object_id = self._real_time_mapper.attach_object(data, self._channel_type) self.notify_channel_id() self.notify_object_id() - self._valid = True \ No newline at end of file + self._valid = True + return \ No newline at end of file diff --git a/Push2/routing.py b/Push2/routing.py new file mode 100644 index 00000000..3740a717 --- /dev/null +++ b/Push2/routing.py @@ -0,0 +1,896 @@ +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/Push2/routing.py +# Compiled at: 2016-11-16 18:13:20 +from __future__ import absolute_import, print_function +from functools import partial +from itertools import imap, izip +import Live +from ableton.v2.base import EventObject, MultiSlot, const, depends, find_if, listenable_property, listens, listens_group, liveobj_valid, nop, task +from ableton.v2.base.util import index_if +from ableton.v2.control_surface import CompoundComponent +from ableton.v2.control_surface.mode import ModesComponent, SetAttributeMode +from ableton.v2.control_surface.control import ListIndexEncoderControl, ListValueEncoderControl, control_list +from pushbase.song_utils import is_return_track +from .mixable_utilities import is_chain +from .real_time_channel import RealTimeDataComponent +MASTER_OUTPUT_TARGET_ID = u'Master' +NO_INPUT_TARGET_ID = u'No Input' +AUDIO_CHANNEL_POSITION_POSTFIXES = [ + 'Pre FX', 'Post FX', 'Post Mixer'] +MIDI_CHANNEL_POSITION_POSTFIXES = AUDIO_CHANNEL_POSITION_POSTFIXES[:2] + +class RoutingMeterRealTimeChannelAssigner(CompoundComponent): + list_index_to_pool_index_mapping = listenable_property.managed({}) + + def __init__(self, real_time_mapper=None, register_real_time_data=nop, sliding_window_size=None, *a, **k): + assert real_time_mapper is not None + super(RoutingMeterRealTimeChannelAssigner, self).__init__(*a, **k) + if sliding_window_size is None: + sliding_window_size = real_time_mapper.METER_POOLSIZE + assert sliding_window_size > 0 + self._half_window_size = sliding_window_size // 2 + self._routing_channels = [] + self._selected_index = -1 + self.real_time_channels = self.register_components(*[ RealTimeDataComponent(channel_type='meter', real_time_mapper=real_time_mapper, register_real_time_data=register_real_time_data) for _ in xrange(sliding_window_size) + ]) + return + + def disconnect(self): + super(RoutingMeterRealTimeChannelAssigner, self).disconnect() + self._routing_channels = [] + + @property + def selected_index(self): + return self._selected_index + + @selected_index.setter + def selected_index(self, index): + self._selected_index = index + self._update_attachments() + + @property + def routing_channels(self): + return self._routing_channels + + @routing_channels.setter + def routing_channels(self, channels): + self._routing_channels = channels + for rt in self.real_time_channels: + rt.set_data(None) + + self.list_index_to_pool_index_mapping = {} + self._update_attachments() + return + + def _update_attachments(self): + visible_routing_channels = set(self._visible_routing_channels()) + attached_routing_channels = set(self._attached_routing_channels()) + to_be_detached = attached_routing_channels - visible_routing_channels + to_be_attached = visible_routing_channels - attached_routing_channels + for routing_channel in to_be_detached: + rt_assignment = find_if(lambda rt: rt.attached_object == routing_channel, self.real_time_channels) + rt_assignment.set_data(None) + + for routing_channel in to_be_attached: + free_channel = find_if(lambda rt: rt.attached_object is None, self.real_time_channels) + free_channel.set_data(routing_channel) + + self._update_list_index_to_pool_index_mapping() + return + + def _visible_routing_channels(self): + window_start = max(0, self._selected_index - self._half_window_size) + window_end = self._selected_index + self._half_window_size + 1 + return self._routing_channels[window_start:window_end] + + def _attached_routing_channels(self): + return filter(liveobj_valid, imap(lambda real_time_assignment: real_time_assignment.attached_object, self.real_time_channels)) + + def _update_list_index_to_pool_index_mapping(self): + new_mapping = {} + for index, rt_assignment in enumerate(self.real_time_channels): + if liveobj_valid(rt_assignment.attached_object): + list_index = self._routing_channels.index(rt_assignment.attached_object) + new_mapping[list_index] = index + + self.list_index_to_pool_index_mapping = new_mapping + + +class TrackOrRoutingControlChooserComponent(ModesComponent): + + def __init__(self, tracks_provider=None, track_mixer_component=None, routing_control_component=None, *a, **k): + super(TrackOrRoutingControlChooserComponent, self).__init__(*a, **k) + self._tracks_provider = tracks_provider + self._track_mixer = track_mixer_component + self._routing_control = routing_control_component + self.add_mode('mix', track_mixer_component) + self.add_mode('routing', routing_control_component) + self.selected_mode = 'mix' + for mode in ['mix', 'routing']: + button = self.get_mode_button(mode) + button.mode_selected_color = 'MixOrRoutingChooser.ModeActive' + button.mode_unselected_color = 'MixOrRoutingChooser.ModeInactive' + + self._routing_previously_available = False + self._update_buttons(False) + self.__on_selected_item_changed.subject = self._tracks_provider + self.__on_selected_item_changed() + + @property + def track_mix(self): + return self._track_mixer + + @property + def routing(self): + return self._routing_control + + @listenable_property + def routing_mode_available(self): + return self._can_enable_routing_mode() + + def update(self): + super(TrackOrRoutingControlChooserComponent, self).update() + if self.is_enabled(): + self._update_routing_mode_availability() + + @listens('selected_item') + def __on_selected_item_changed(self): + if self.is_enabled(): + self._update_routing_mode_availability() + + def _update_routing_mode_availability(self): + is_available = self._can_enable_routing_mode() + if is_available != self._routing_previously_available: + self._update_buttons(enable_buttons=is_available) + if is_available and 'routing' in self.active_modes: + self.pop_mode('mix') + else: + self.push_mode('mix') + self.notify_routing_mode_available() + self._routing_previously_available = is_available + + def _can_enable_routing_mode(self): + return not is_chain(self._tracks_provider.selected_item) + + def _update_buttons(self, enable_buttons): + for mode in ['mix', 'routing']: + self.get_mode_button(mode).enabled = enable_buttons + + +def reorder_routing_targets(targets, desired_first_target_display_name): + targets = list(targets) + index_of_desired_first_target = None + index_of_desired_first_target = index_if(lambda target: target.display_name == desired_first_target_display_name, targets) + if index_of_desired_first_target >= 0 and index_of_desired_first_target < len(targets): + return [ + targets[index_of_desired_first_target]] + targets[:index_of_desired_first_target] + targets[index_of_desired_first_target + 1:] + else: + return targets + return + + +class Router(EventObject): + current_target_index = listenable_property.managed(-1) + + def __init__(self, routing_level=None, routing_direction=None, song=None, *a, **k): + assert song is not None + assert routing_level is not None + assert routing_direction is not None + super(Router, self).__init__(*a, **k) + self._song = song + self._current_target_property = '%s_routing_%s' % ( + routing_direction, routing_level) + self.register_slot(MultiSlot(subject=song.view, event_name_list=( + 'selected_track', self._current_target_property), listener=self.__on_current_routing_changed)) + self.register_slot(MultiSlot(subject=song.view, event_name_list=( + 'selected_track', + 'available_%s_routing_%ss' % ( + routing_direction, routing_level)), listener=self.__on_routings_changed)) + self.current_target_index = self._current_target_index() + return + + @listenable_property + def routing_targets(self): + return self._get_targets() + + def _current_target_index(self): + try: + return self._get_targets().index(self._get_current_target()) + except ValueError: + return -1 + + @property + def current_target(self): + return self._get_current_target() + + @current_target.setter + def current_target(self, new_target): + self._set_current_target(new_target) + + def __on_current_routing_changed(self): + self.current_target_index = self._current_target_index() + + def __on_routings_changed(self): + self.notify_routing_targets() + + def _track(self): + return self._song.view.selected_track + + def _get_targets(self): + raise NotImplementedError + + def _get_current_target(self): + return getattr(self._track(), self._current_target_property) + + def _set_current_target(self, new_target_id): + setattr(self._track(), self._current_target_property, new_target_id) + + @listenable_property + def has_input_channel_position(self): + return False + + +class InputTypeRouter(Router): + + def __init__(self, *a, **k): + super(InputTypeRouter, self).__init__(routing_direction='input', routing_level='type', *a, **k) + + def _get_targets(self): + return reorder_routing_targets(self._track().available_input_routing_types, NO_INPUT_TARGET_ID) + + +class OutputTypeRouter(Router): + + def __init__(self, *a, **k): + super(OutputTypeRouter, self).__init__(routing_direction='output', routing_level='type', *a, **k) + + def _get_targets(self): + return reorder_routing_targets(self._track().available_output_routing_types, MASTER_OUTPUT_TARGET_ID) + + +class InputChannelRouter(Router): + + def __init__(self, *a, **k): + super(InputChannelRouter, self).__init__(routing_direction='input', routing_level='channel', *a, **k) + + def _get_targets(self): + return list(self._track().available_input_routing_channels) + + +def _target_has_postfix(target_and_postfix): + target, postfix = target_and_postfix + return target.display_name.endswith(postfix) + + +def can_combine_targets(targets, postfixes): + if len(targets) == len(postfixes): + if all(imap(_target_has_postfix, izip(targets, postfixes))): + first_name = targets[0].display_name + common_prefix = first_name[:first_name.rfind(postfixes[0])] + return all(imap(lambda t: t.display_name.startswith(common_prefix), targets)) + return False + + +def targets_can_be_grouped(targets, postfixes): + if len(targets) > 0: + num_postfixes = len(postfixes) + while can_combine_targets(targets[:num_postfixes], postfixes): + targets = targets[num_postfixes:] + + return len(targets) == 0 + return False + + +class InputChannelAndPositionRouter(EventObject): + """ + Adapts an InputChannelRouter (and InputTypeRouter). + + For non-track input types, the input channel interface is passed through + unaltered, so this looks exactly like the wrapped InputChannelRouter. + + For track types, the list of input channels is compressed by combining + groups of three (pre-fx, post-fx and post mixer - called "positions") + items into a single item in the routing_targets list. The position + is then selected with input_channel_position_index. + """ + has_input_channel_position = listenable_property.managed(False) + + def __init__(self, input_channel_router=None, input_type_router=None, *a, **k): + super(InputChannelAndPositionRouter, self).__init__(*a, **k) + self._input_type_router = input_type_router + self._input_channel_router = input_channel_router + self._input_channel_postfixes = [] + self._update_channel_grouping() + if self.has_input_channel_position: + self._last_input_channel_position_index = self.input_channel_position_index + else: + self._last_input_channel_position_index = None + self.__on_routing_targets_changed.subject = input_channel_router + self.__on_current_target_index_changed.subject = input_channel_router + self.__on_input_type_changed.subject = input_type_router + return + + @listens('current_target_index') + def __on_input_type_changed(self, _): + self._update_channel_grouping() + self.notify_routing_targets() + self.notify_input_channel_position_index() + + @listens('routing_targets') + def __on_routing_targets_changed(self): + self._update_channel_grouping() + self.notify_routing_targets() + + @listens('current_target_index') + def __on_current_target_index_changed(self, _): + if self.has_input_channel_position and self._last_input_channel_position_index != self.input_channel_position_index: + self.notify_input_channel_position_index() + self._last_input_channel_position_index = self.input_channel_position_index + self.notify_current_target_index() + + @listenable_property + def routing_targets(self): + """ + Input channels of wrapped InputChannelRouter if has_input_channel_position + is false. + Input channels of from wrapped InputChannelRouter that are in the "position" + input_channel_position if has_input_channel_position is true + """ + complete_list = self._input_channel_router.routing_targets + if self.has_input_channel_position: + slice_size = len(self.live_position_postfixes) + index_in_complete_list = self._input_channel_router.current_target_index + return complete_list[index_in_complete_list % slice_size::slice_size] + else: + return complete_list + + @listenable_property + def current_target_index(self): + """ + Index in routing_targets of the current_target + """ + index_in_complete_list = self._input_channel_router.current_target_index + if self.has_input_channel_position: + slice_size = len(self.live_position_postfixes) + return index_in_complete_list // slice_size + else: + return index_in_complete_list + + @listenable_property + def current_target(self): + """ + Currently selected target + """ + return self._input_channel_router.current_target + + @current_target.setter + def current_target(self, new_target): + self._input_channel_router.current_target = new_target + + @listenable_property + def input_channel_positions(self): + """ + List of strings naming the input channel positions. + Only use if has_input_channel_position is true. + """ + return self.live_position_postfixes + + @property + def live_position_postfixes(self): + """ + List of postfixes found in the names of Live's routing channels with position. + Only use if has_input_channel_position is true. + """ + assert self.has_input_channel_position + return self._input_channel_postfixes + + @listenable_property + def input_channel_position_index(self): + """ + Index into input_channel_positions of current channel position. + Only use if has_input_channel_position is true. + """ + assert self.has_input_channel_position + slice_size = len(self.live_position_postfixes) + return self._input_channel_router.current_target_index % slice_size + + @input_channel_position_index.setter + def input_channel_position_index(self, new_index): + assert self.has_input_channel_position + complete_list = self._input_channel_router.routing_targets + index_in_complete_list = self._input_channel_router.current_target_index + slice_size = len(self.live_position_postfixes) + self._input_channel_router.current_target = complete_list[index_in_complete_list // slice_size * slice_size + new_index] + + @property + def input_type_name(self): + """ + Name of the input type. + Only use if has_input_channel_position is true. + """ + assert self.has_input_channel_position + return self._input_type_router.current_target.attached_object.name + + def _update_channel_grouping(self): + attached_object = self._input_type_router.current_target.attached_object + original_channels = self._input_channel_router.routing_targets + if can_combine_targets(original_channels[:len(AUDIO_CHANNEL_POSITION_POSTFIXES)], AUDIO_CHANNEL_POSITION_POSTFIXES): + postfixes = AUDIO_CHANNEL_POSITION_POSTFIXES + else: + postfixes = MIDI_CHANNEL_POSITION_POSTFIXES + has_positions = liveobj_valid(attached_object) and targets_can_be_grouped(original_channels, postfixes) + self._input_channel_postfixes = postfixes if has_positions else [] + self.has_input_channel_position = has_positions + self.notify_input_channel_positions() + + +class OutputChannelRouter(Router): + + def __init__(self, *a, **k): + super(OutputChannelRouter, self).__init__(routing_direction='output', routing_level='channel', *a, **k) + + def _get_targets(self): + return list(self._track().available_output_routing_channels) + + +class RoutingTarget(EventObject): + + def __init__(self, live_target, name=None, *a, **k): + super(RoutingTarget, self).__init__(*a, **k) + self._live_target = live_target + self._name = name if name is not None else live_target.display_name + return + + @property + def name(self): + return self._name + + def __eq__(self, other): + return other is not None and self._live_target == getattr(other, '_live_target', None) + + def __ne__(self, other): + return not self.__eq__(other) + + def __hash__(self): + return hash(self._live_target) + + @property + def __id__(self): + return hash(self) + + def __repr__(self): + return '<%s name=%s>' % (self.__class__.__name__, self._name) + + +class RoutingChannel(RoutingTarget): + realtime_channel = listenable_property.managed(None) + + def __init__(self, realtime_channel=None, *a, **k): + super(RoutingChannel, self).__init__(*a, **k) + self.realtime_channel = realtime_channel + self._layout_names = {Live.Track.RoutingChannelLayout.mono: 'mono', + Live.Track.RoutingChannelLayout.midi: 'midi', + Live.Track.RoutingChannelLayout.stereo: 'stereo' + } + + @property + def layout(self): + return self._layout_names[self._live_target.layout] + + +class RoutingTargetList(EventObject): + APPLY_SELECTION_DELAY = 0.2 + + def __init__(self, router=None, parent_task_group=None, *a, **k): + assert router is not None + super(RoutingTargetList, self).__init__(*a, **k) + self._router = router + self._targets = [] + self._selected_target = None + self._apply_selection_task = parent_task_group.add(task.sequence(task.wait(self.APPLY_SELECTION_DELAY), task.run(self._apply_selected_target))) + self.__on_current_target_index_changed.subject = router + self.__on_routing_targets_changed.subject = router + self._update_targets() + self._update_selected_target() + return + + def disconnect(self): + super(RoutingTargetList, self).disconnect() + self._targets = [] + self._selected_target = None + return + + @listenable_property + def targets(self): + return self._targets + + @listenable_property + def selected_target(self): + return self._selected_target + + @selected_target.setter + def selected_target(self, value): + if self._selected_target != value: + self._set_selected_target(value) + self._apply_selection_task.restart() + + @listenable_property + def selected_index(self): + if self._selected_target is not None: + return self._targets.index(self._selected_target) + else: + return -1 + + @listens('routing_targets') + def __on_routing_targets_changed(self): + self._update_targets() + + @listens('current_target_index') + def __on_current_target_index_changed(self, *a): + self._update_selected_target() + + def _set_selected_target(self, target): + if self._selected_target != target: + self._selected_target = target + self.notify_selected_target() + self.notify_selected_index() + + def _update_selected_target(self): + self._apply_selection_task.kill() + index = self._router.current_target_index + if 0 <= index < len(self._targets): + new_target = self._targets[index] + else: + new_target = None + self._set_selected_target(new_target) + return + + def _update_targets(self): + targets = self._make_targets() + if targets != self._targets: + self._targets = self.register_disconnectables(targets) + self.notify_targets() + self._update_selected_target() + + def _apply_selected_target(self): + if self._selected_target is not None: + index = self._targets.index(self._selected_target) + self._router.current_target = self._router.routing_targets[index] + return + + def _make_targets(self): + raise NotImplementedError + + +class RoutingTypeList(RoutingTargetList): + + def __init__(self, *a, **k): + super(RoutingTypeList, self).__init__(*a, **k) + self.__on_routing_targets_changed.subject = self._router + self.__on_current_target_index_changed.subject = self._router + + @listenable_property + def selected_track(self): + if self.selected_index < 0: + return None + else: + attached_object = self._router.routing_targets[self.selected_index].attached_object + if isinstance(attached_object, Live.Track.Track): + return attached_object + return None + + @listens('routing_targets') + def __on_routing_targets_changed(self): + self.notify_selected_track() + + @listens('current_target_index') + def __on_current_target_index_changed(self, *a): + self.notify_selected_track() + + def _make_targets(self): + return map(RoutingTarget, self._router.routing_targets) + + +class RoutingChannelList(RoutingTargetList): + + def __init__(self, rt_channel_assigner=None, router=None, *a, **k): + assert rt_channel_assigner is not None + self._rt_channel_assigner = rt_channel_assigner + self._rt_channel_assigner.routing_channels = router.routing_targets + self._rt_channel_assigner.selected_index = router.current_target_index + super(RoutingChannelList, self).__init__(router=router, *a, **k) + self.__on_selected_index_changed.subject = self + self.__on_list_index_to_pool_index_mapping_changed.subject = self._rt_channel_assigner + self.__on_routing_targets_changed.subject = router + return + + def _make_targets(self): + targets = self._router.routing_targets + name_transform = nop + if self._router.has_input_channel_position: + live_position_names = self._router.live_position_postfixes + position_name = live_position_names[self._router.input_channel_position_index] + strip_length = len(position_name) + 3 + + def stripped_name(target_name): + if len(target_name) > strip_length: + return target_name[:-strip_length] + return self._router.input_type_name + + name_transform = stripped_name + return [ RoutingChannel(live_target=target, name=name_transform(target.display_name), realtime_channel=self._get_realtime_channel(list_index)) for list_index, target in enumerate(targets) + ] + + def _get_realtime_channel(self, list_index): + mapping = self._rt_channel_assigner.list_index_to_pool_index_mapping + if list_index in mapping: + pool_index = mapping[list_index] + return self._rt_channel_assigner.real_time_channels[pool_index] + else: + return None + + @listens('routing_targets') + def __on_routing_targets_changed(self): + self._rt_channel_assigner.routing_channels = self._router.routing_targets + + @listens('selected_index') + def __on_selected_index_changed(self, *a): + self._rt_channel_assigner.selected_index = self.selected_index + + @listens('list_index_to_pool_index_mapping') + def __on_list_index_to_pool_index_mapping_changed(self, *a): + self._reassign_realtime_channels() + + def _reassign_realtime_channels(self): + for list_index, routing_channel in enumerate(self._targets): + routing_channel.realtime_channel = self._get_realtime_channel(list_index) + + +class RoutingChannelPositionList(EventObject): + + def __init__(self, input_channel_router=None, *a, **k): + super(RoutingChannelPositionList, self).__init__(*a, **k) + self._input_channel_router = input_channel_router + self._targets = [] + self.__on_input_channel_position_index_changed.subject = input_channel_router + self.__on_input_channel_positions_changed.subject = input_channel_router + self.__on_has_input_channel_position_changed.subject = input_channel_router + self._update_targets() + + @listenable_property + def targets(self): + return self._targets + + @listenable_property + def selected_index(self): + if not self._input_channel_router.has_input_channel_position: + return -1 + return self._input_channel_router.input_channel_position_index + + @listens('has_input_channel_position') + def __on_has_input_channel_position_changed(self, _): + self._update_targets() + self.notify_selected_index() + + @listens('input_channel_positions') + def __on_input_channel_positions_changed(self): + self._update_targets() + + @listens('input_channel_position_index') + def __on_input_channel_position_index_changed(self): + self.notify_selected_index() + + def _update_targets(self): + original_targets = self._targets + if not self._input_channel_router.has_input_channel_position: + self._targets = [] + else: + self._targets = self._input_channel_router.input_channel_positions + if self._targets != original_targets: + self.notify_targets() + + +def can_set_input_routing(track, song): + return not is_return_track(song, track) and not track.is_frozen and not track.is_foldable + + +class RoutingControlComponent(ModesComponent): + monitor_state_encoder = ListValueEncoderControl(num_steps=10) + input_output_choice_encoder = ListValueEncoderControl(num_steps=10) + routing_type_encoder = ListValueEncoderControl(num_steps=10) + routing_channel_encoders = control_list(ListValueEncoderControl, control_count=4, num_steps=10) + routing_channel_position_encoder = ListIndexEncoderControl(num_steps=10) + can_route = listenable_property.managed(False) + + @depends(real_time_mapper=None, register_real_time_data=const(nop)) + def __init__(self, real_time_mapper=None, register_real_time_data=None, *a, **k): + super(RoutingControlComponent, self).__init__(*a, **k) + self.__on_current_monitoring_state_changed.subject = self.song.view + self._real_time_channel_assigner = RoutingMeterRealTimeChannelAssigner(real_time_mapper=real_time_mapper, register_real_time_data=register_real_time_data, is_enabled=False) + input_type_router = self.register_disconnectable(InputTypeRouter(song=self.song)) + output_type_router = self.register_disconnectable(OutputTypeRouter(song=self.song)) + input_channel_router = self.register_disconnectable(InputChannelRouter(song=self.song)) + output_channel_router = self.register_disconnectable(OutputChannelRouter(song=self.song)) + input_channel_and_position_router = self.register_disconnectable(InputChannelAndPositionRouter(input_channel_router, input_type_router)) + self._active_type_router = input_type_router + self._active_channel_router = input_channel_and_position_router + self._can_route = can_set_input_routing + self._update_can_route() + self._routing_type_list, self._routing_channel_list = self.register_disconnectables([ + RoutingTypeList(parent_task_group=self._tasks, router=self._active_type_router), + RoutingChannelList(parent_task_group=self._tasks, rt_channel_assigner=self._real_time_channel_assigner, router=self._active_channel_router)]) + self.__on_input_channel_position_index_changed.subject = input_channel_and_position_router + self._routing_channel_position_list = None + self._update_routing_channel_position_list() + self.add_mode('input', [ + SetAttributeMode(self, '_can_route', can_set_input_routing), + partial(self._set_active_routers, input_type_router, input_channel_and_position_router), + self._real_time_channel_assigner]) + self.add_mode('output', [ + SetAttributeMode(self, '_can_route', lambda *a: True), + partial(self._set_active_routers, output_type_router, output_channel_router), + self._real_time_channel_assigner]) + self.selected_mode = 'input' + self.__on_selected_track_changed.subject = self.song.view + self.__on_selected_track_changed() + self._connect_monitoring_state_encoder() + self.input_output_choice_encoder.connect_static_list(self, 'selected_mode', list_values=[ + 'input', 'output']) + self.__on_selected_mode_changed.subject = self + self.__on_tracks_changed.subject = self.song + self.__on_return_tracks_changed.subject = self.song + self._update_track_listeners() + return + + @listenable_property + def can_monitor(self): + track = self.song.view.selected_track + return hasattr(track, 'current_monitoring_state') and not track.is_frozen and track.can_be_armed + + @listenable_property + def monitoring_state_index(self): + if self.can_monitor: + return self.song.view.selected_track.current_monitoring_state + else: + return None + + @listenable_property + def is_choosing_output(self): + return self.selected_mode == 'output' + + @listenable_property + def routing_type_list(self): + return self._routing_type_list + + @listenable_property + def routing_channel_list(self): + return self._routing_channel_list + + @listenable_property + def routing_channel_position_list(self): + return self._routing_channel_position_list + + @listens('tracks') + def __on_tracks_changed(self): + self._update_track_listeners() + + @listens('return_tracks') + def __on_return_tracks_changed(self): + self._update_track_listeners() + + @listens('selected_mode') + def __on_selected_mode_changed(self, _): + self.notify_is_choosing_output() + + @listens('selected_track.current_monitoring_state') + def __on_current_monitoring_state_changed(self): + self.notify_monitoring_state_index() + + @listens('selected_track') + def __on_selected_track_changed(self): + self._update_monitoring_state() + self._update_can_route() + self._update_routing_type_list() + self._update_routing_channel_list() + self._update_routing_channel_position_list() + self._reconnect_selected_track_slots() + + @listens_group('output_routing_type') + def __on_any_output_routing_type_changed(self, *_a): + self._update_monitoring_state() + + @listens('is_frozen') + def __on_is_frozen_changed(self): + self._update_can_monitor() + self._update_can_route() + + @listens('input_channel_position_index') + def __on_input_channel_position_index_changed(self): + self._update_routing_channel_list() + + @listens('has_input_channel_position') + def __on_has_input_channel_position_changed(self, *a): + self._update_routing_channel_position_list() + self._connect_input_channel_position_encoder() + + @listens('input_routing_type') + def __on_input_routing_type_changed(self): + self._update_can_monitor() + + def _update_can_route(self): + track = self.song.view.selected_track + self.can_route = self._can_route(track, self.song) and track != self.song.master_track + self._enable_encoders(self.can_route) + + def _enable_encoders(self, enabled): + self.routing_type_encoder.enabled = enabled + self.routing_channel_position_encoder.enabled = enabled + for encoder in self.routing_channel_encoders: + encoder.enabled = enabled + + def _set_active_routers(self, type_router, channel_router): + self._active_type_router = type_router + self._active_channel_router = channel_router + self._update_can_route() + self._update_routing_type_list() + self._update_routing_channel_list() + self._update_routing_channel_position_list() + self._connect_input_channel_position_encoder() + self.__on_has_input_channel_position_changed.subject = channel_router + + def _connect_input_channel_position_encoder(self): + if self._active_channel_router.has_input_channel_position: + self.routing_channel_position_encoder.connect_list_property(self._active_channel_router, current_index_property_name='input_channel_position_index', max_index=len(self._active_channel_router.input_channel_positions) - 1) + self.routing_channel_position_encoder.enabled = self.can_route + else: + self.routing_channel_position_encoder.enabled = False + self.routing_channel_position_encoder.disconnect_property() + + def _update_routing_type_list(self): + self.unregister_disconnectable(self._routing_type_list) + self._routing_type_list.disconnect() + self._routing_type_list = self.register_disconnectable(RoutingTypeList(parent_task_group=self._tasks, router=self._active_type_router)) + self.notify_routing_type_list() + self.routing_type_encoder.connect_list_property(self._routing_type_list, current_value_property_name='selected_target', list_property_name='targets') + + def _update_routing_channel_list(self): + self.unregister_disconnectable(self._routing_channel_list) + self._routing_channel_list.disconnect() + self._routing_channel_list = self.register_disconnectable(RoutingChannelList(parent_task_group=self._tasks, rt_channel_assigner=self._real_time_channel_assigner, router=self._active_channel_router)) + self.notify_routing_channel_list() + for encoder in self.routing_channel_encoders: + encoder.connect_list_property(self._routing_channel_list, current_value_property_name='selected_target', list_property_name='targets') + + def _update_routing_channel_position_list(self): + if self._routing_channel_position_list is not None: + self.unregister_disconnectable(self._routing_channel_position_list) + self._routing_channel_position_list.disconnect() + if self._active_channel_router.has_input_channel_position: + self._routing_channel_position_list = self.register_disconnectable(RoutingChannelPositionList(self._active_channel_router)) + else: + self._routing_channel_position_list = None + self.notify_routing_channel_position_list() + return + + def _connect_monitoring_state_encoder(self): + self.monitor_state_encoder.connect_static_list(self.song.view.selected_track, 'current_monitoring_state', list_values=[ + Live.Track.Track.monitoring_states.IN, + Live.Track.Track.monitoring_states.AUTO, + Live.Track.Track.monitoring_states.OFF]) + + def _update_monitoring_state(self): + self._connect_monitoring_state_encoder() + self._update_can_monitor() + + def _update_can_monitor(self): + if self.monitor_state_encoder.enabled != self.can_monitor: + self.monitor_state_encoder.enabled = self.can_monitor + self.notify_can_monitor() + + def _update_track_listeners(self): + self.__on_any_output_routing_type_changed.replace_subjects(list(self.song.tracks) + list(self.song.return_tracks)) + self.__on_any_output_routing_type_changed() + + def _reconnect_selected_track_slots(self): + selected_track = self.song.view.selected_track + self.__on_is_frozen_changed.subject = selected_track + self.__on_input_routing_type_changed.subject = selected_track \ No newline at end of file diff --git a/Push2/scales_component.py b/Push2/scales_component.py index 7cd28f20..792fcb72 100644 --- a/Push2/scales_component.py +++ b/Push2/scales_component.py @@ -1,14 +1,16 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/Push2/scales_component.py +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/Push2/scales_component.py +# Compiled at: 2016-09-29 19:13:24 from __future__ import absolute_import, print_function +from collections import namedtuple from math import ceil from functools import partial -from ableton.v2.base import clamp, listens, listenable_property +from ableton.v2.base import clamp, index_if, listens, listenable_property from ableton.v2.control_surface import Component from ableton.v2.control_surface.control import ButtonControl, RadioButtonControl, StepEncoderControl, ToggleButtonControl, control_list from pushbase.melodic_pattern import ROOT_NOTES, SCALES, NOTE_NAMES +Layout = namedtuple('Layout', ('name', 'interval')) class ScalesComponent(Component): - __events__ = ('close',) navigation_colors = dict(color='Scales.Navigation', disabled_color='Scales.NavigationDisabled') up_button = ButtonControl(repeat=True, **navigation_colors) down_button = ButtonControl(repeat=True, **navigation_colors) @@ -18,25 +20,34 @@ class ScalesComponent(Component): in_key_toggle_button = ToggleButtonControl(toggled_color='Scales.OptionOn', untoggled_color='Scales.OptionOn') fixed_toggle_button = ToggleButtonControl(toggled_color='Scales.OptionOn', untoggled_color='Scales.OptionOff') scale_encoders = control_list(StepEncoderControl) - close_button = ButtonControl(color='Scales.Close') + layout_encoder = StepEncoderControl() + direction_encoder = StepEncoderControl() horizontal_navigation = listenable_property.managed(False) NUM_DISPLAY_ROWS = 4 NUM_DISPLAY_COLUMNS = int(ceil(float(len(SCALES)) / NUM_DISPLAY_ROWS)) - def __init__(self, note_layout = None, *a, **k): - raise note_layout is not None or AssertionError + def __init__(self, note_layout=None, *a, **k): + assert note_layout is not None super(ScalesComponent, self).__init__(*a, **k) self._note_layout = note_layout self._scale_list = list(SCALES) self._scale_name_list = map(lambda m: m.name, self._scale_list) self._selected_scale_index = -1 self._selected_root_note_index = -1 + self._layouts = ( + Layout('4ths', 3), + Layout('3rds', 2), + Layout('Sequential', None)) + self._selected_layout_index = 0 self.in_key_toggle_button.connect_property(note_layout, 'is_in_key') self.fixed_toggle_button.connect_property(note_layout, 'is_fixed') self.__on_root_note_changed.subject = self._note_layout self.__on_scale_changed.subject = self._note_layout + self.__on_interval_changed.subject = self._note_layout self.__on_root_note_changed(note_layout.root_note) self.__on_scale_changed(note_layout.scale) + self.__on_interval_changed(self._note_layout.interval) + return def _set_selected_scale_index(self, index): index = clamp(index, 0, len(self._scale_list) - 1) @@ -104,9 +115,30 @@ def __on_scale_changed(self, scale): self.right_button.enabled = index < len(self._scale_list) - 1 self.notify_selected_scale_index() - @close_button.pressed - def close_button(self, button): - self.notify_close() + @layout_encoder.value + def layout_encoder(self, value, encoder): + index = clamp(self._selected_layout_index + value, 0, len(self._layouts) - 1) + self.selected_layout_index = index + + @property + def layout_names(self): + return [ layout.name for layout in self._layouts ] + + @listenable_property + def selected_layout_index(self): + return self._selected_layout_index + + @selected_layout_index.setter + def selected_layout_index(self, index): + if index != self._selected_layout_index: + self._selected_layout_index = index + interval = self._layouts[index].interval + self._note_layout.interval = interval + self.notify_selected_layout_index() + + @direction_encoder.value + def direction_encoder(self, value, encoder): + self._note_layout.is_horizontal = value < 0 @property def note_layout(self): @@ -115,16 +147,22 @@ def note_layout(self): def _update_horizontal_navigation(self): self.horizontal_navigation = self.right_button.is_pressed or self.left_button.is_pressed + @listens('interval') + def __on_interval_changed(self, interval): + index = index_if(lambda layout: layout.interval == interval, self._layouts) + self.selected_layout_index = index + class ScalesEnabler(Component): toggle_button = ButtonControl(color='DefaultButton.On') - def __init__(self, enter_dialog_mode = None, exit_dialog_mode = None, *a, **k): - raise enter_dialog_mode is not None or AssertionError - raise exit_dialog_mode is not None or AssertionError + def __init__(self, enter_dialog_mode=None, exit_dialog_mode=None, *a, **k): + assert enter_dialog_mode is not None + assert exit_dialog_mode is not None super(ScalesEnabler, self).__init__(*a, **k) self._enable_dialog_mode = partial(enter_dialog_mode, 'scales') self._exit_dialog_mode = partial(exit_dialog_mode, 'scales') + return @toggle_button.pressed def toggle_button(self, button): diff --git a/Push2/selected_track_parameter_provider.py b/Push2/selected_track_parameter_provider.py new file mode 100644 index 00000000..88ff7937 --- /dev/null +++ b/Push2/selected_track_parameter_provider.py @@ -0,0 +1,12 @@ +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/Push2/selected_track_parameter_provider.py +# Compiled at: 2016-05-20 03:43:52 +from __future__ import absolute_import, print_function +from pushbase.parameter_provider import ParameterInfo +from pushbase.selected_track_parameter_provider import SelectedTrackParameterProvider as SelectedTrackParameterProviderBase +from .parameter_mapping_sensitivities import parameter_mapping_sensitivity, fine_grain_parameter_mapping_sensitivity + +class SelectedTrackParameterProvider(SelectedTrackParameterProviderBase): + + def _create_parameter_info(self, parameter, name): + assert name is not None + return ParameterInfo(name=name, parameter=parameter, default_encoder_sensitivity=parameter_mapping_sensitivity(parameter), fine_grain_encoder_sensitivity=fine_grain_parameter_mapping_sensitivity(parameter)) \ No newline at end of file diff --git a/Push2/session_component.py b/Push2/session_component.py index e093af4a..93fb0414 100644 --- a/Push2/session_component.py +++ b/Push2/session_component.py @@ -1,30 +1,80 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/Push2/session_component.py +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/Push2/session_component.py +# Compiled at: 2016-09-29 19:13:24 from __future__ import absolute_import, print_function +import Live from itertools import imap -from pushbase.special_session_component import SpecialClipSlotComponent, SpecialSceneComponent, SpecialSessionComponent +from ableton.v2.base import Proxy, liveobj_valid +from ableton.v2.control_surface.control import ButtonControl +from pushbase.actions import get_clip_name +from pushbase.colors import Blink, Pulse +from pushbase.special_session_component import ClipSlotCopyHandler, SpecialClipSlotComponent, SpecialSceneComponent, SpecialSessionComponent from .clip_decoration import ClipDecoratedPropertiesCopier -from .colors import translate_color_index, WHITE_COLOR_INDEX_FROM_LIVE, WHITE_MIDI_VALUE +from .colors import IndexedColor, translate_color_index +from .skin_default import CLIP_PLAYING_COLOR, RECORDING_COLOR +PLAYING_CLIP_PULSE_SPEED = 48 +TRIGGERED_CLIP_BLINK_SPEED = 24 + +class DecoratingCopyHandler(ClipSlotCopyHandler): + + def __init__(self, decorator_factory=None, *a, **k): + assert decorator_factory is not None + super(DecoratingCopyHandler, self).__init__(*a, **k) + self._decorator_factory = decorator_factory + return + + def _on_duplicated(self, source_clip_slot, target_clip_slot): + super(DecoratingCopyHandler, self)._on_duplicated(source_clip_slot, target_clip_slot) + ClipDecoratedPropertiesCopier(source_clip=source_clip_slot.clip, destination_clip=target_clip_slot.clip, decorator_factory=self._decorator_factory).post_duplication_action() + + +class ClipProxy(Proxy): + + @property + def name(self): + return get_clip_name(self.proxied_object) + class ClipSlotComponent(SpecialClipSlotComponent): _decorator_factory = None + select_color_button = ButtonControl() - def _color_value(self, slot_or_clip): - if slot_or_clip.color_index == WHITE_COLOR_INDEX_FROM_LIVE: - return WHITE_MIDI_VALUE - return translate_color_index(slot_or_clip.color_index) + def __init__(self, color_chooser=None, *a, **k): + super(ClipSlotComponent, self).__init__(*a, **k) + self._color_chooser = color_chooser - def _do_duplicate_clip(self): + @select_color_button.released + def select_color_button(self, button): + self._color_chooser.object = None + return - def get_destination_clip(destination_slot_ix): - track = self._clip_slot.canonical_parent - return track.clip_slots[destination_slot_ix].clip + def _on_launch_button_pressed(self): + if self.select_color_button.is_pressed and self._color_chooser is not None: + clip = self._clip_slot.clip if self.has_clip() else None + if liveobj_valid(clip): + self._color_chooser.object = ClipProxy(clip) + else: + super(ClipSlotComponent, self)._on_launch_button_pressed() + return - if not (self._clip_slot and self._clip_slot.has_clip): - return - destination_slot_ix = super(ClipSlotComponent, self)._do_duplicate_clip() - if destination_slot_ix is not None and self._decorator_factory: - ClipDecoratedPropertiesCopier(target_clip=self._clip_slot.clip, destination_clip=get_destination_clip(destination_slot_ix), decorator_factory=self._decorator_factory).post_duplication_action() - return destination_slot_ix + def _feedback_value(self, track, slot_or_clip): + clip_color = self._color_value(slot_or_clip) + if slot_or_clip.is_triggered and not slot_or_clip.will_record_on_start: + if isinstance(slot_or_clip, Live.Clip.Clip): + return Blink(color1=CLIP_PLAYING_COLOR, color2=IndexedColor(clip_color), speed=TRIGGERED_CLIP_BLINK_SPEED) + return 'Session.EmptySlotTriggeredPlay' + else: + if slot_or_clip.is_playing: + animate_to_color = RECORDING_COLOR if slot_or_clip.is_recording else IndexedColor(clip_color) + return Pulse(color1=IndexedColor.from_push_index(clip_color, 2), color2=animate_to_color, speed=PLAYING_CLIP_PULSE_SPEED) + return super(ClipSlotComponent, self)._feedback_value(track, slot_or_clip) + + def _color_value(self, slot_or_clip): + return translate_color_index(slot_or_clip.color_index) + + def _on_clip_duplicated(self, source_clip, destination_clip): + super(ClipSlotComponent, self)._on_clip_duplicated(source_clip, destination_clip) + if self._decorator_factory: + ClipDecoratedPropertiesCopier(source_clip=source_clip, destination_clip=destination_clip, decorator_factory=self._decorator_factory).post_duplication_action() def set_decorator_factory(self, factory): self._decorator_factory = factory @@ -33,6 +83,10 @@ def set_decorator_factory(self, factory): class SceneComponent(SpecialSceneComponent): clip_slot_component_type = ClipSlotComponent + def __init__(self, color_chooser=None, *a, **k): + self._color_chooser = color_chooser + super(SceneComponent, self).__init__(*a, **k) + def build_clip_slot_list(self): scene_index = list(self.song.scenes).index(self._scene) @@ -41,9 +95,20 @@ def slot_for_track(mixable): return None else: return mixable.clip_slots[scene_index] + return None return imap(slot_for_track, self._session_ring.controlled_tracks()) + def _create_clip_slot(self): + return self.clip_slot_component_type(color_chooser=self._color_chooser) + class SessionComponent(SpecialSessionComponent): - scene_component_type = SceneComponent \ No newline at end of file + scene_component_type = SceneComponent + + def __init__(self, color_chooser=None, *a, **k): + self._color_chooser = color_chooser + super(SessionComponent, self).__init__(*a, **k) + + def _create_scene(self): + return self.scene_component_type(session_ring=self._session_ring, color_chooser=self._color_chooser) \ No newline at end of file diff --git a/Push2/session_recording.py b/Push2/session_recording.py index 016d29c1..27f7bf52 100644 --- a/Push2/session_recording.py +++ b/Push2/session_recording.py @@ -1,20 +1,29 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/Push2/session_recording.py +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/Push2/session_recording.py +# Compiled at: 2016-09-29 19:13:24 from __future__ import absolute_import, print_function +from ableton.v2.base import nop from pushbase.session_recording_component import FixedLengthSessionRecordingComponent class SessionRecordingComponent(FixedLengthSessionRecordingComponent): def __init__(self, *a, **k): super(SessionRecordingComponent, self).__init__(*a, **k) - self.set_trigger_recording_on_release(not self._record_button.is_pressed) + self._on_record_button_pressed = nop + self._on_arrangement_record_button_pressed = nop + self.set_trigger_recording_on_release(not any(( + self._record_button.is_pressed, + self.arrangement_record_button.is_pressed))) def set_trigger_recording_on_release(self, trigger_recording): self._should_trigger_recording = trigger_recording - def _on_record_button_pressed(self): - pass - def _on_record_button_released(self): + self._trigger_recording_action(self._trigger_recording) + + def _on_arrangement_record_button_released(self): + self._trigger_recording_action(self._toggle_arrangement_recording) + + def _trigger_recording_action(self, recording_action): if self._should_trigger_recording: - self._trigger_recording() + recording_action() self._should_trigger_recording = True \ No newline at end of file diff --git a/Push2/session_ring_selection_linking.py b/Push2/session_ring_selection_linking.py index c163c0d7..3ed3ee9a 100644 --- a/Push2/session_ring_selection_linking.py +++ b/Push2/session_ring_selection_linking.py @@ -1,20 +1,22 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/Push2/session_ring_selection_linking.py +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/Push2/session_ring_selection_linking.py +# Compiled at: 2016-06-08 13:13:04 from __future__ import absolute_import, print_function -from ableton.v2.base.slot import SlotManager, listens +from ableton.v2.base.event import EventObject, listens from ableton.v2.base.dependency import depends from ableton.v2.base.util import index_if -class SessionRingSelectionLinking(SlotManager): +class SessionRingSelectionLinking(EventObject): @depends(song=None) - def __init__(self, session_ring = None, selection_changed_notifier = None, song = None, *a, **k): + def __init__(self, session_ring=None, selection_changed_notifier=None, song=None, *a, **k): super(SessionRingSelectionLinking, self).__init__(*a, **k) - raise session_ring is not None or AssertionError - raise selection_changed_notifier is not None or AssertionError - raise song is not None or AssertionError + assert session_ring is not None + assert selection_changed_notifier is not None + assert song is not None self._session_ring = session_ring self._song = song self._on_selection_changed.subject = selection_changed_notifier + return @listens('selection_changed') def _on_selection_changed(self): diff --git a/Push2/settings.py b/Push2/settings.py index 260a5f2c..99f812a4 100644 --- a/Push2/settings.py +++ b/Push2/settings.py @@ -1,7 +1,10 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/Push2/settings.py +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/Push2/settings.py +# Compiled at: 2016-05-20 03:43:52 from __future__ import absolute_import, print_function from pushbase.setting import OnOffSetting -def create_settings(preferences = None): +def create_settings(preferences=None): preferences = preferences if preferences is not None else {} - return {'workflow': OnOffSetting(name='Workflow', value_labels=['Scene', 'Clip'], default_value=True, preferences=preferences)} \ No newline at end of file + return {'workflow': OnOffSetting(name='Workflow', value_labels=[ + 'Scene', 'Clip'], default_value=True, preferences=preferences) + } \ No newline at end of file diff --git a/Push2/setup_component.py b/Push2/setup_component.py index cfa83886..6b5c4efa 100644 --- a/Push2/setup_component.py +++ b/Push2/setup_component.py @@ -1,15 +1,17 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/Push2/setup_component.py +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/Push2/setup_component.py +# Compiled at: 2016-09-29 19:13:24 from __future__ import absolute_import, print_function -from ableton.v2.base import CompoundDisconnectable, SerializableListenableProperties, Subject, clamp, listenable_property +from ableton.v2.base import CompoundDisconnectable, SerializableListenableProperties, EventObject, clamp, listenable_property from ableton.v2.control_surface import Component -from ableton.v2.control_surface.control import RadioButtonControl, StepEncoderControl, ToggleButtonControl, control_list +from ableton.v2.control_surface.control import RadioButtonControl, StepEncoderControl, ToggleButtonControl, ButtonControl, control_list from ableton.v2.control_surface.mode import ModesComponent from .pad_velocity_curve import PadVelocityCurveSettings PAD_SETTING_STEP_SIZE = 20 +MAX_DISPLAY_BRIGHTNESS = 255 MIN_USER_FACING_LED_BRIGHTNESS = 13 MIN_USER_FACING_DISPLAY_BRIGHTNESS = 2 -class GeneralSettings(Subject): +class GeneralSettings(EventObject): workflow = listenable_property.managed('scene') @@ -18,7 +20,7 @@ class HardwareSettings(SerializableListenableProperties): max_led_brightness = 127 led_brightness = listenable_property.managed(max_led_brightness) min_display_brightness = MIN_USER_FACING_DISPLAY_BRIGHTNESS - max_display_brightness = 255 + max_display_brightness = MAX_DISPLAY_BRIGHTNESS display_brightness = listenable_property.managed(max_display_brightness) @@ -37,21 +39,17 @@ class ProfilingSettings(SerializableListenableProperties): show_realtime_ipc_stats = listenable_property.managed(False) -class ExperimentalSettings(SerializableListenableProperties): - new_waveform_navigation = listenable_property.managed(False) - - class Settings(CompoundDisconnectable): - def __init__(self, preferences = None, *a, **k): - raise preferences is not None or AssertionError + def __init__(self, preferences=None, *a, **k): + assert preferences is not None super(Settings, self).__init__(*a, **k) self._general = self.register_disconnectable(GeneralSettings()) self._pad_settings = self.register_disconnectable(preferences.setdefault('settings_pad_velocity_curve', PadVelocityCurveSettings())) self._hardware = self.register_disconnectable(preferences.setdefault('settings_hardware', HardwareSettings())) self._display_debug = self.register_disconnectable(preferences.setdefault('settings_display_debug', DisplayDebugSettings())) self._profiling = self.register_disconnectable(preferences.setdefault('settings_profiling', ProfilingSettings())) - self._experimental = self.register_disconnectable(preferences.setdefault('experimental', ExperimentalSettings())) + return @property def general(self): @@ -73,23 +71,21 @@ def display_debug(self): def profiling(self): return self._profiling - @property - def experimental(self): - return self._experimental - class GeneralSettingsComponent(Component): workflow_encoder = StepEncoderControl() led_brightness_encoder = StepEncoderControl(num_steps=60) display_brightness_encoder = StepEncoderControl(num_steps=120) - def __init__(self, settings = None, hardware_settings = None, *a, **k): - raise settings is not None or AssertionError - raise hardware_settings is not None or AssertionError + def __init__(self, settings=None, hardware_settings=None, *a, **k): + assert settings is not None + assert hardware_settings is not None super(GeneralSettingsComponent, self).__init__(*a, **k) self._settings = settings self._hardware_settings = hardware_settings - self.workflow_encoder.connect_property(settings, 'workflow', lambda v: ('clip' if v > 0 else 'scene')) + self.workflow_encoder.connect_property(settings, 'workflow', lambda v: if v > 0: +'clip''scene') + return @led_brightness_encoder.value def led_brightness_encoder(self, value, encoder): @@ -105,10 +101,11 @@ class PadSettingsComponent(Component): gain_encoder = StepEncoderControl(num_steps=PAD_SETTING_STEP_SIZE) dynamics_encoder = StepEncoderControl(num_steps=PAD_SETTING_STEP_SIZE) - def __init__(self, pad_settings = None, hardware_settings = None, *a, **k): - raise pad_settings is not None or AssertionError + def __init__(self, pad_settings=None, hardware_settings=None, *a, **k): + assert pad_settings is not None super(PadSettingsComponent, self).__init__(*a, **k) self._pad_settings = pad_settings + return @sensitivity_encoder.value def sensitivity_encoder(self, value, encoder): @@ -131,8 +128,8 @@ class DisplayDebugSettingsComponent(Component): show_unlit_button_button = ToggleButtonControl() show_lit_button_button = ToggleButtonControl() - def __init__(self, settings = None, *a, **k): - raise settings is not None or AssertionError + def __init__(self, settings=None, *a, **k): + assert settings is not None super(DisplayDebugSettingsComponent, self).__init__(*a, **k) self.show_row_spaces_button.connect_property(settings, 'show_row_spaces') self.show_row_margins_button.connect_property(settings, 'show_row_margins') @@ -140,6 +137,7 @@ def __init__(self, settings = None, *a, **k): self.show_button_spaces_button.connect_property(settings, 'show_button_spaces') self.show_unlit_button_button.connect_property(settings, 'show_unlit_button') self.show_lit_button_button.connect_property(settings, 'show_lit_button') + return class ProfilingSettingsComponent(Component): @@ -147,50 +145,69 @@ class ProfilingSettingsComponent(Component): show_usb_stats_button = ToggleButtonControl() show_realtime_ipc_stats_button = ToggleButtonControl() - def __init__(self, settings = None, *a, **k): - raise settings is not None or AssertionError + def __init__(self, settings=None, *a, **k): + assert settings is not None super(ProfilingSettingsComponent, self).__init__(*a, **k) self.show_qml_stats_button.connect_property(settings, 'show_qml_stats') self.show_usb_stats_button.connect_property(settings, 'show_usb_stats') self.show_realtime_ipc_stats_button.connect_property(settings, 'show_realtime_ipc_stats') + return + +class InfoComponent(Component): + install_firmware_button = ButtonControl() -class ExperimentalSettingsComponent(Component): - new_waveform_navigation_button = ToggleButtonControl() + def __init__(self, firmware_switcher=None, *a, **k): + assert firmware_switcher is not None + super(InfoComponent, self).__init__(*a, **k) + self._firmware_switcher = firmware_switcher + self.install_firmware_button.enabled = self._firmware_switcher.can_switch_firmware + return - def __init__(self, settings = None, *a, **k): - raise settings is not None or AssertionError - super(ExperimentalSettingsComponent, self).__init__(*a, **k) - self.new_waveform_navigation_button.connect_property(settings, 'new_waveform_navigation') + @install_firmware_button.pressed + def install_firmware_button(self, button): + self._firmware_switcher.switch_firmware() class SetupComponent(ModesComponent): category_radio_buttons = control_list(RadioButtonControl, checked_color='Option.Selected', unchecked_color='Option.Unselected') + make_it_go_boom_button = ButtonControl() + make_it_go_boom = listenable_property.managed(False) - def __init__(self, settings = None, pad_curve_sender = None, in_developer_mode = False, *a, **k): - if not settings is not None: - raise AssertionError - super(SetupComponent, self).__init__(*a, **k) - self._settings = settings - self._pad_curve_sender = pad_curve_sender - self._general = self.register_component(GeneralSettingsComponent(settings=settings.general, hardware_settings=settings.hardware, is_enabled=False)) - self._pad_settings = self.register_component(PadSettingsComponent(pad_settings=settings.pad_settings, is_enabled=False)) - self._display_debug = self.register_component(DisplayDebugSettingsComponent(settings=settings.display_debug, is_enabled=False)) - self._profiling = self.register_component(ProfilingSettingsComponent(settings=settings.profiling, is_enabled=False)) - self._experimental = self.register_component(ExperimentalSettingsComponent(settings=settings.experimental, is_enabled=False)) - self.add_mode('Settings', [self._general, self._pad_settings]) - self.add_mode('Info', []) - in_developer_mode and self.add_mode('Display Debug', [self._display_debug]) + def __init__(self, settings=None, pad_curve_sender=None, firmware_switcher=None, *a, **k): + assert settings is not None + super(SetupComponent, self).__init__(*a, **k) + self._settings = settings + self._pad_curve_sender = pad_curve_sender + has_option = self.application.has_option + self.make_it_go_boom_button.enabled = not has_option('_Push2DeveloperMode') and has_option('_MakePush2GoBoom') + self._general = self.register_component(GeneralSettingsComponent(settings=settings.general, hardware_settings=settings.hardware, is_enabled=False)) + self._info = self.register_component(InfoComponent(firmware_switcher=firmware_switcher, is_enabled=False)) + self._pad_settings = self.register_component(PadSettingsComponent(pad_settings=settings.pad_settings, is_enabled=False)) + self._display_debug = self.register_component(DisplayDebugSettingsComponent(settings=settings.display_debug, is_enabled=False)) + self._profiling = self.register_component(ProfilingSettingsComponent(settings=settings.profiling, is_enabled=False)) + self.add_mode('Settings', [self._general, self._pad_settings]) + self.add_mode('Info', [self._info]) + if self.application.has_option('_Push2DeveloperMode'): + self.add_mode('Display Debug', [self._display_debug]) self.add_mode('Profiling', [self._profiling]) - self.add_mode('Experimental', [self._experimental]) self.selected_mode = 'Settings' self.category_radio_buttons.control_count = len(self.modes) self.category_radio_buttons.checked_index = 0 + return + + @make_it_go_boom_button.pressed + def make_it_go_boom_button(self, _button): + self.make_it_go_boom = True @property def general(self): return self._general + @property + def info(self): + return self._info + @property def pad_settings(self): return self._pad_settings @@ -203,10 +220,6 @@ def display_debug(self): def profiling(self): return self._profiling - @property - def experimental(self): - return self._experimental - @property def settings(self): return self._settings diff --git a/Push2/simpler_slice_nudging.py b/Push2/simpler_slice_nudging.py deleted file mode 100644 index bb3626ef..00000000 --- a/Push2/simpler_slice_nudging.py +++ /dev/null @@ -1,91 +0,0 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/midi-remote-scripts/Push2/simpler_slice_nudging.py -from __future__ import absolute_import, with_statement -from contextlib import contextmanager -import Live -from ableton.v2.base import SlotManager, find_if, liveobj_valid, clamp, listens -from .simpler_zoom import is_simpler -CENTERED_NUDGE_VALUE = 0.5 -MINIMUM_SLICE_DISTANCE = 2 - -class SimplerSliceNudging(SlotManager): - _simpler = None - _nudge_parameter = None - - def set_device(self, device): - self._simpler = device if is_simpler(device) else None - self.__on_selected_slice_changed.subject = self._simpler - with self._updating_nudge_parameter(): - self._nudge_parameter = find_if(lambda p: p.name == 'Nudge', self._simpler.parameters if liveobj_valid(self._simpler) else []) - - @contextmanager - def _updating_nudge_parameter(self): - if self._nudge_parameter: - self._nudge_parameter.set_display_value_conversion(None) - yield - if self._nudge_parameter: - self._nudge_parameter.set_display_value_conversion(self._display_value_conversion) - self.__on_nudge_delta.subject = self._nudge_parameter - - def _can_access_slicing_properties(self): - return liveobj_valid(self._simpler) and self._simpler.current_playback_mode == str(Live.SimplerDevice.PlaybackMode.slicing) and self._simpler.sample_length > 0 - - @listens('view.selected_slice') - def __on_selected_slice_changed(self): - if self._nudge_parameter: - self._nudge_parameter.notify_value() - - @listens('delta') - def __on_nudge_delta(self, delta): - if self._can_access_slicing_properties(): - old_slice_time = self._simpler.view.selected_slice - if old_slice_time >= 0: - if self._is_first_slice_at_time(old_slice_time): - new_start = self._new_start_marker_time(old_slice_time, delta) - self._simpler.start_marker = new_start - return - new_slice_time = self._new_slice_time(old_slice_time, delta) - if old_slice_time != new_slice_time: - original_slices = self._simpler.slices - self._simpler.insert_slice(new_slice_time) - self._simpler.remove_slice(old_slice_time) - try: - self._simpler.view.selected_slice = new_slice_time - except RuntimeError: - self._simpler.view.selected_slice = self._simpler.slices[list(original_slices).index(old_slice_time)] - - def _is_first_slice_at_time(self, slice_time): - start_sample = self._simpler.start_marker - return abs(slice_time - start_sample) < MINIMUM_SLICE_DISTANCE - - def _new_start_marker_time(self, old_slice_time, delta): - change_in_samples = self._sample_change_from_delta(delta) - new_start_marker_time = old_slice_time + change_in_samples - return clamp(new_start_marker_time, 0, self._simpler.sample_length - MINIMUM_SLICE_DISTANCE) - - def _sample_change_from_delta(self, delta): - sample_length = self._simpler.sample_length - change_in_samples = round(delta * sample_length / 10) - return int(change_in_samples) - - def _get_surrounding_slices(self, slice_time): - slices = list(self._simpler.slices) - index = slices.index(slice_time) - sample_length = self._simpler.sample_length - previous_slice = slices[index - 1] + MINIMUM_SLICE_DISTANCE if index > 0 else 0 - next_slice = slices[index + 1] if index < len(slices) - 1 else sample_length - return (previous_slice, next_slice) - - def _display_value_conversion(self, _value): - selected_slice = self._simpler.view.selected_slice if self._can_access_slicing_properties() else -1 - return str(selected_slice) if selected_slice >= 0 else '-' - - def _get_min_first_slice_length(self): - return Live.SimplerDevice.get_min_first_slice_length_in_samples(self._simpler.proxied_object) - - def _new_slice_time(self, old_slice_time, delta): - previous_slice, next_slice = self._get_surrounding_slices(old_slice_time) - change_in_samples = self._sample_change_from_delta(delta) - min_second_slice_start = self._simpler.start_marker + self._get_min_first_slice_length() - lower_bound = max(0, min_second_slice_start, previous_slice) - upper_bound = min(next_slice, self._simpler.end_marker, self._simpler.sample_length) - MINIMUM_SLICE_DISTANCE - return clamp(old_slice_time + change_in_samples, lower_bound, upper_bound) \ No newline at end of file diff --git a/Push2/simpler_zoom.py b/Push2/simpler_zoom.py deleted file mode 100644 index 4a6b165e..00000000 --- a/Push2/simpler_zoom.py +++ /dev/null @@ -1,134 +0,0 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/Push2/simpler_zoom.py -from __future__ import absolute_import, print_function -from contextlib import contextmanager -from ableton.v2.base import clamp, linear, SlotManager, Subject, listens, liveobj_valid, liveobj_changed -from pushbase.device_chain_utils import is_simpler - -def get_zoom_parameter(parameter_host): - parameters = parameter_host.parameters if liveobj_valid(parameter_host) else [] - results = filter(lambda p: p.name == 'Zoom', parameters) - if len(results) > 0: - return results[0] - - -class ZoomHandling(SlotManager, Subject): - __events__ = ('zoom',) - SCREEN_WIDTH = 960 - ZOOM_EXP = 10.0 - DEFAULT_ZOOM_START_FUDGE = 0.2 - _parameter_host = None - _zoom_parameter = None - - @property - def zoom(self): - if self._zoom_parameter: - return self._zoom_parameter.value - return 0.0 - - @property - def max_zoom(self): - return NotImplementedError - - @property - def zoom_factor(self): - factor = 1.0 - if self.zoom > 0.0: - minimum = 1.0 - factor = 1.0 / linear(minimum, self.max_zoom, self.zoom) - return factor - - @listens('value') - def _on_zoom_changed(self): - self.notify_zoom() - - def _get_zoom_start_fudge(self): - """ Return fudge for lowest zoom value, assuming ZOOM_EXP == 10.0. - - _internal_to_zoom and _zoom_to_internal adjust for some mysterious - behaviour elsewhere with a fudge to map the lowest non-zero internal - value to a higher zoom value, determined by the return value of this - funtion. - - The sensible return value of this depends on the exponent of the curve - used in those functions. To account for the fact that ZOOM_EXP is - can be changed in subclasses or in the future, this function should return - a value which assumes a ZOOM_EXP of 10.0. - """ - return self.DEFAULT_ZOOM_START_FUDGE - - def _internal_to_zoom(self, value, _parent): - fudge = self._get_zoom_start_fudge() ** 10 ** (1.0 / self.ZOOM_EXP) - if value > 0.0: - return (value * (1.0 - fudge) + fudge) ** self.ZOOM_EXP - return 0.0 - - def _zoom_to_internal(self, value, _parent): - fudge = self._get_zoom_start_fudge() ** 10 ** (1.0 / self.ZOOM_EXP) - linear_value = (value ** (1.0 / self.ZOOM_EXP) - fudge) / (1.0 - fudge) - return clamp(linear_value, 0.0, 1.0) - - def request_zoom(self, factor): - if self._zoom_parameter: - self._zoom_parameter.value = factor - - def _set_zoom_parameter(self): - return NotImplementedError - - def set_parameter_host(self): - return NotImplementedError - - -class SimplerZoomHandling(ZoomHandling): - - def __init__(self): - ZoomHandling.__init__(self) - self.ZOOM_EXP = 20.0 - - def set_parameter_host(self, parameter_host): - new_parameter_host = parameter_host if is_simpler(parameter_host) else None - if liveobj_changed(self._parameter_host, new_parameter_host): - old_zoom = self.zoom - self._parameter_host = new_parameter_host - with self._updating_zoom_scaling(): - self._set_zoom_parameter() - self._on_zoom_changed.subject = self._zoom_parameter - self._on_sample_changed.subject = self._parameter_host - if self.zoom != old_zoom: - self.notify_zoom() - - def _set_zoom_parameter(self): - self._zoom_parameter = get_zoom_parameter(self._parameter_host) - - @listens('sample') - def _on_sample_changed(self): - if self._zoom_parameter: - self._zoom_parameter.value = self._zoom_parameter.default_value - - @contextmanager - def _updating_zoom_scaling(self): - if self._zoom_parameter: - self._zoom_parameter.set_scaling_functions(None, None) - yield - if self._zoom_parameter: - self._zoom_parameter.set_scaling_functions(self._zoom_to_internal, self._internal_to_zoom) - - @property - def max_zoom(self): - has_sample = liveobj_valid(self._parameter_host) and liveobj_valid(self._parameter_host.sample) - length = float(self._parameter_host.sample.length if has_sample else self.SCREEN_WIDTH) - return float(length / self.SCREEN_WIDTH) - - def _get_zoom_start_fudge(self): - if liveobj_valid(self._parameter_host) and liveobj_valid(self._parameter_host.sample): - sample_length = self._parameter_host.sample.length - fudge_length_a = 200000 - fudge_factor_a = 0.4 - fudge_length_b = 2500000 - fudge_factor_b = 0.2 - if sample_length < fudge_length_a: - return fudge_factor_a - if sample_length > fudge_length_b: - return fudge_factor_b - return (sample_length - fudge_length_a) / (fudge_length_b - fudge_length_a) * (fudge_factor_b - fudge_factor_a) + fudge_factor_a - else: - return self.DEFAULT_ZOOM_START_FUDGE \ No newline at end of file diff --git a/Push2/skin_default.py b/Push2/skin_default.py index 264bcd4e..025f3739 100644 --- a/Push2/skin_default.py +++ b/Push2/skin_default.py @@ -1,27 +1,20 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/Push2/skin_default.py +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/Push2/skin_default.py +# Compiled at: 2016-09-29 19:13:24 from __future__ import absolute_import, print_function -from functools import partial from ableton.v2.control_surface import Skin -from ableton.v2.control_surface.elements import SelectedTrackColorFactory, SelectedClipColorFactory +from ableton.v2.control_surface.elements import SelectedClipColorFactory, SelectedTrackColorFactory from pushbase.colors import Blink, FallbackColor, Pulse from pushbase.skin_default import Colors as ColorsBase -from .colors import Basic, determine_shaded_color_index, Rgb, translate_color_index - -def shaded_color(color_index, shade_level = 1): - return determine_shaded_color_index(translate_color_index(color_index), shade_level) - - -shade_transform = partial(shaded_color, shade_level=1) -shade_transform2 = partial(shaded_color, shade_level=2) -SelectedTrackColor = SelectedTrackColorFactory(transformation=translate_color_index) -SelectedClipColor = SelectedClipColorFactory(transformation=translate_color_index) -SelectedTrackColorShade = SelectedTrackColorFactory(transformation=shade_transform) -SelectedTrackColorShade2 = SelectedTrackColorFactory(transformation=shade_transform2) -SelectedClipColorShade = SelectedClipColorFactory(transformation=shade_transform) -SelectedClipColorShade2 = SelectedClipColorFactory(transformation=shade_transform2) +from .colors import Basic, DISPLAY_BUTTON_SHADE_LEVEL, Rgb, SelectedDeviceChainColorFactory, SelectedDrumPadColorFactory, make_color_factory_func +make_selected_track_color = make_color_factory_func(SelectedTrackColorFactory) +make_selected_drum_pad_color = make_color_factory_func(SelectedDrumPadColorFactory) +make_selected_device_chain_color = make_color_factory_func(SelectedDeviceChainColorFactory) +make_selected_clip_color = make_color_factory_func(SelectedClipColorFactory) TRACK_SOLOED_COLOR = Rgb.OCEAN RECORDING_COLOR = Rgb.RED +CLIP_PLAYING_COLOR = Rgb.GREEN UNLIT_COLOR = Rgb.BLACK +SELECTION_PULSE_SPEED = 48 class Colors(ColorsBase): @@ -33,7 +26,7 @@ class DefaultButton: Transparent = Basic.TRANSPARENT class Instrument: - NoteBase = SelectedTrackColor + NoteBase = make_selected_track_color() NoteScale = Rgb.WHITE NoteNotScale = Rgb.BLACK NoteInvalid = Rgb.BLACK @@ -44,19 +37,20 @@ class Instrument: class DrumGroup: PadSelected = Rgb.WHITE PadSelectedNotSoloed = Rgb.WHITE - PadFilled = SelectedTrackColor - PadEmpty = SelectedTrackColorShade2 + PadFilled = make_selected_track_color() + PadEmpty = Rgb.DARK_GREY PadMuted = Rgb.LIGHT_GREY PadMutedSelected = Rgb.WHITE PadSoloed = Rgb.BLUE PadSoloedSelected = Rgb.WHITE PadInvisible = Rgb.BLACK PadAction = Rgb.WHITE + PadHotswapping = Pulse(Rgb.WHITE, Rgb.LIGHT_GREY, 48) class SlicedSimpler: SliceSelected = Rgb.WHITE - SliceUnselected = SelectedTrackColor - NoSlice = SelectedTrackColorShade2 + SliceUnselected = make_selected_track_color() + NoSlice = make_selected_track_color(shade_level=2) class Melodic: Playhead = Rgb.GREEN @@ -70,12 +64,21 @@ class LoopSelector: InsideLoop = Rgb.LIGHT_GREY OutsideLoop = Rgb.BLACK + class VelocityLevels: + LowLevel = Rgb.DARK_GREY + MidLevel = Rgb.LIGHT_GREY + HighLevel = Rgb.WHITE + SelectedLevel = make_selected_track_color() + + class DrumGroupVelocityLevels(VelocityLevels): + SelectedLevel = make_selected_drum_pad_color() + class NoteEditor: class Step: - Low = SelectedClipColorShade2 - High = SelectedClipColorShade - Full = SelectedClipColor + Low = make_selected_clip_color(shade_level=2) + High = make_selected_clip_color(shade_level=1) + Full = make_selected_clip_color(shade_level=0) Muted = Rgb.LIGHT_GREY class StepEditing: @@ -97,6 +100,14 @@ class StepEditing: NoteInvalid = Rgb.BLACK class DrumGroupNoteEditor(NoteEditor): + + class Step: + Low = make_selected_drum_pad_color(shade_level=2) + High = make_selected_drum_pad_color(shade_level=1) + Full = make_selected_drum_pad_color(shade_level=0) + Muted = Rgb.LIGHT_GREY + + class SlicingNoteEditor(NoteEditor): pass class Option: @@ -110,6 +121,7 @@ class Mixer: TrackSelected = Rgb.WHITE NoTrack = Rgb.BLACK MutedTrack = Rgb.DARK_GREY + FrozenChain = Rgb.DARK_GREY MuteOn = Rgb.YELLOW_SHADE MuteOff = Rgb.YELLOW SoloOn = TRACK_SOLOED_COLOR @@ -126,10 +138,20 @@ class TrackControlView: ButtonOff = Rgb.DARK_GREY ButtonDisabled = Rgb.BLACK + class MixOrRoutingChooser: + ModeActive = Rgb.WHITE + ModeInactive = make_selected_track_color(DISPLAY_BUTTON_SHADE_LEVEL) + class ItemNavigation: ItemSelected = Rgb.WHITE NoItem = Rgb.BLACK - ItemNotSelected = SelectedTrackColor + ItemNotSelected = make_selected_track_color(DISPLAY_BUTTON_SHADE_LEVEL) + + class EditModeOptions(ItemNavigation): + ItemNotSelected = make_selected_device_chain_color(DISPLAY_BUTTON_SHADE_LEVEL) + + class BankSelection(ItemNavigation): + ItemNotSelected = make_selected_device_chain_color(DISPLAY_BUTTON_SHADE_LEVEL) class Browser: Navigation = FallbackColor(Rgb.WHITE, Basic.ON) @@ -170,11 +192,9 @@ class Session: SceneTriggered = FallbackColor(Blink(Rgb.GREEN, Rgb.BLACK, 24), 24) NoScene = Rgb.BLACK ClipStopped = Rgb.AMBER - ClipStarted = Pulse(Rgb.GREEN_SHADE, Rgb.GREEN, 48) - ClipRecording = Pulse(Rgb.BLACK, Rgb.RED, 48) - ClipTriggeredPlay = Blink(Rgb.GREEN, Rgb.BLACK, 24) ClipTriggeredRecord = Blink(Rgb.RED, Rgb.BLACK, 24) ClipEmpty = Rgb.BLACK + EmptySlotTriggeredPlay = Blink(Rgb.GREEN, Rgb.BLACK, 24) RecordButton = Rgb.RED_SHADE StopClip = Rgb.RED StopClipTriggered = Blink(Rgb.RED, Rgb.BLACK, 24) @@ -202,6 +222,8 @@ class Metronome: class FixedLength: On = Basic.FULL_PULSE_SLOW Off = Basic.ON + PhraseAlignedOn = Rgb.WHITE + PhraseAlignedOff = Rgb.DARK_GREY class Accent: On = Basic.FULL_PULSE_SLOW diff --git a/Push2/sliced_simpler.py b/Push2/sliced_simpler.py new file mode 100644 index 00000000..11eba799 --- /dev/null +++ b/Push2/sliced_simpler.py @@ -0,0 +1,16 @@ +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/Push2/sliced_simpler.py +# Compiled at: 2016-09-29 19:13:24 +from __future__ import absolute_import, print_function +from pushbase.colors import Pulse +from pushbase.sliced_simpler_component import SlicedSimplerComponent +from .colors import IndexedColor +NEXT_SLICE_PULSE_SPEED = 48 + +def next_slice_color(track_color_index): + return Pulse(color1=IndexedColor.from_live_index(track_color_index, shade_level=2), color2=IndexedColor.from_live_index(track_color_index, shade_level=1), speed=NEXT_SLICE_PULSE_SPEED) + + +class Push2SlicedSimplerComponent(SlicedSimplerComponent): + + def _next_slice_color(self): + return next_slice_color(self.song.view.selected_track.color_index) \ No newline at end of file diff --git a/Push2/stop_clip_component.py b/Push2/stop_clip_component.py deleted file mode 100644 index b281eaba..00000000 --- a/Push2/stop_clip_component.py +++ /dev/null @@ -1,53 +0,0 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/midi-remote-scripts/Push2/stop_clip_component.py -from itertools import count -from ableton.v2.base import listens_group -from ableton.v2.control_surface.control import ButtonControl -from pushbase.actions import is_clip_stop_pending, StopClipComponent as StopClipComponentBase -from .colors import make_blinking_track_color, make_pulsing_track_color -from .skin_default import RECORDING_COLOR, UNLIT_COLOR - -def track_color_with_pending_stop(track): - return make_blinking_track_color(track, UNLIT_COLOR) - - -class StopClipComponent(StopClipComponentBase): - stop_selected_track_clip_button = ButtonControl() - - @stop_selected_track_clip_button.released_immediately - def stop_selected_track_clip_button(self, button): - self._stop_clip_in_selected_track() - - def _stop_clip_in_selected_track(self): - song = self.song - selected_track = song.view.selected_track - if selected_track != song.master_track and selected_track not in song.return_tracks: - selected_track.stop_all_clips() - - def _assign_listeners(self, tracks): - super(StopClipComponent, self)._assign_listeners(tracks) - self.__on_mute_changed.replace_subjects(tracks, count()) - self.__on_solo_changed.replace_subjects(tracks, count()) - - @listens_group('mute') - def __on_mute_changed(self, track_index): - self._update_stop_button_by_index(track_index) - - @listens_group('solo') - def __on_solo_changed(self, track_index): - self._update_stop_button_by_index(track_index) - - def _color_for_button(self, track): - if is_clip_stop_pending(track): - return track_color_with_pending_stop(track) - elif track.playing_slot_index >= 0: - if track.solo: - return 'StopClips.SoloedTrack' - elif track.mute: - return 'StopClips.MutedTrack' - elif track.clip_slots[track.playing_slot_index].is_recording: - pulse_to = RECORDING_COLOR - else: - pulse_to = UNLIT_COLOR - return make_pulsing_track_color(track, pulse_to) - else: - return super(StopClipComponent, self)._color_for_button(track) \ No newline at end of file diff --git a/Push2/sysex.py b/Push2/sysex.py index df71ffad..63a2d32d 100644 --- a/Push2/sysex.py +++ b/Push2/sysex.py @@ -1,4 +1,5 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/Push2/sysex.py +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/Push2/sysex.py +# Compiled at: 2016-09-29 19:13:24 from __future__ import absolute_import, print_function from ableton.v2.base import chunks from ableton.v2.control_surface import midi @@ -8,26 +9,28 @@ MODE_SWITCH_MESSAGE_ID = 10 def make_aftertouch_mode_message(mode_id): - raise mode_id in ('polyphonic', 'mono') or AssertionError + assert mode_id in ('polyphonic', 'mono') mode_byte = 0 if mode_id == 'mono' else 1 return make_message(30, (mode_byte,)) def make_mode_switch_messsage(mode_id): - raise mode_id[0] in (LIVE_MODE, USER_MODE) or AssertionError + assert mode_id[0] in (LIVE_MODE, USER_MODE) return make_message(MODE_SWITCH_MESSAGE_ID, mode_id) def make_rgb_palette_entry_message(index, hex_color, white_balance): r, g, b = _make_rgb_from_hex(hex_color) - return make_message(3, (index,) + to_7L1M(r) + to_7L1M(g) + to_7L1M(b) + to_7L1M(white_balance)) + return make_message(3, ( + index,) + to_7L1M(r) + to_7L1M(g) + to_7L1M(b) + to_7L1M(white_balance)) def _make_rgb_from_hex(hex_value): r = hex_value >> 16 g = hex_value >> 8 & 255 b = hex_value & 255 - return (r, g, b) + return ( + r, g, b) def make_reapply_palette_message(): @@ -72,8 +75,9 @@ def make_touch_strip_mode_message(mode): TOUCHSTRIP_STATE_TO_BRIGHTNESS = {TouchStripStates.STATE_OFF: 0, - TouchStripStates.STATE_HALF: 1, - TouchStripStates.STATE_FULL: 6} + TouchStripStates.STATE_HALF: 1, + TouchStripStates.STATE_FULL: 6 + } def _make_touch_strip_light(state): if len(state) == 2: @@ -98,7 +102,7 @@ def make_pad_velocity_curve_message(index, velocities): Updates a chunk of velocities in the voltage to velocity table. The index refers to the first entry in the velocities list. """ - raise len(velocities) == PAD_VELOCITY_CURVE_CHUNK_SIZE or AssertionError + assert len(velocities) == PAD_VELOCITY_CURVE_CHUNK_SIZE return make_message(32, (index,) + tuple(velocities)) @@ -118,7 +122,7 @@ def make_led_brightness_message(brightness): brightness may be limited to a maximum value (e.g. 32) internally when power supply is not connected. """ - raise 0 <= brightness <= 127 or AssertionError + assert 0 <= brightness <= 127 return make_message(6, (brightness,)) @@ -132,7 +136,7 @@ def make_display_brightness_message(brightness): via MIDI, the remaining values are set by the firmware, depending on the power source. """ - raise 0 <= brightness <= 255 or AssertionError + assert 0 <= brightness <= 255 return make_message(8, to_7L1M(brightness)) @@ -150,18 +154,33 @@ def extract_identity_response_info(data): build = from_7L7M(data[14], data[15]) sn = from_7L7777M(data[16:21]) board_revision = data[21] if len(data) > 22 else 0 - return (major, - minor, - build, - sn, - board_revision) + return ( + major, minor, build, sn, board_revision) + + +def make_pad_setting_message(scene_index, track_index, setting): + """ + This command allows to select one of N available sets of pad + parameter values called settings. + + If scene_index and track_index are 0, the settings for all pads are selected. + + scene_index - 1 (top) ... 8(bottom), or 0 for all pads + track_index - 1 (left) ... 8(right), or 0 for all pads + setting - (0-regular, 1-less sensitive) + """ + assert 0 <= scene_index <= 8 + assert 0 <= track_index <= 8 + assert 0 <= setting <= 2 + return make_message(40, (scene_index, track_index, setting)) MANUFACTURER_ID = (0, 33, 29) -MESSAGE_START = (midi.SYSEX_START,) + MANUFACTURER_ID + (1, 1) +MESSAGE_START = ( + midi.SYSEX_START,) + MANUFACTURER_ID + (1, 1) IDENTITY_RESPONSE_PRODUCT_ID_BYTES = MANUFACTURER_ID + (103, 50, 2, 0) -def make_message(command_id, arguments = tuple()): +def make_message(command_id, arguments=tuple()): """ Create a sysex message from a command id and the optional arguments @@ -183,13 +202,15 @@ def make_message_identifier(command_id): def to_7L1M(value): """ Returns a list with the 7 lower bits of the value followed by the 1 higher bit """ - return (value & 127, value >> 7 & 1) + return ( + value & 127, value >> 7 & 1) def to_7L5M(value): """ Returns a list with the 7 lower bits of the value followed by the 5 higher bits """ - return (value & 127, value >> 7 & 31) + return ( + value & 127, value >> 7 & 31) def from_7L7M(lsb, msb): diff --git a/Push2/track_list.py b/Push2/track_list.py index 7251a4d8..7a7f18d6 100644 --- a/Push2/track_list.py +++ b/Push2/track_list.py @@ -1,7 +1,8 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/Push2/track_list.py +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/Push2/track_list.py +# Compiled at: 2016-09-29 19:13:24 from __future__ import absolute_import, print_function from functools import partial -from itertools import chain, izip +from itertools import izip import Live from ableton.v2.base import nop, listenable_property, listens, listens_group, liveobj_valid from ableton.v2.control_surface.control import ButtonControl, control_list @@ -9,9 +10,12 @@ from pushbase.actions import is_clip_stop_pending from pushbase.consts import MessageBoxText from pushbase.message_box_component import Messenger -from pushbase.special_chan_strip_component import toggle_arm -from .colors import make_blinking_track_color, make_pulsing_track_color, translate_color_index +from pushbase.selected_track_parameter_provider import toggle_arm +from pushbase.song_utils import delete_track_or_return_track +from .colors import DISPLAY_BUTTON_SHADE_LEVEL, IndexedColor, make_blinking_track_color, make_pulsing_track_color from .mixable_utilities import can_play_clips, is_chain +from .mixer_control_component import find_parent_track +from .real_time_channel import RealTimeDataComponent from .skin_default import RECORDING_COLOR, UNLIT_COLOR from .track_selection import get_all_mixer_tracks, SelectedMixerTrackProvider DeviceType = Live.Device.DeviceType @@ -20,9 +24,11 @@ def track_color_with_pending_stop(track): return make_blinking_track_color(track, UNLIT_COLOR) -def mixable_button_color(mixer_track, song, selected_track = None): +def mixable_button_color(mixer_track, song, selected_track=None): color = 'Mixer.NoTrack' if mixer_track: + mixer_track_parent_track = find_parent_track(mixer_track.proxied_object) + is_frozen_chain = mixer_track_parent_track.is_frozen and not isinstance(mixer_track.proxied_object, Live.Track.Track) if can_play_clips(mixer_track) and is_clip_stop_pending(mixer_track): color = track_color_with_pending_stop(mixer_track) elif mixer_track.solo: @@ -31,8 +37,10 @@ def mixable_button_color(mixer_track, song, selected_track = None): color = 'Mixer.TrackSelected' elif mixer_track.mute or mixer_track.muted_via_solo: color = 'Mixer.MutedTrack' + elif is_frozen_chain: + color = 'Mixer.FrozenChain' else: - color = translate_color_index(mixer_track.color_index) + color = IndexedColor.from_live_index(mixer_track.color_index, DISPLAY_BUTTON_SHADE_LEVEL) return color @@ -40,18 +48,19 @@ def stop_clip_button_color(track, song, _): if liveobj_valid(track) and not is_chain(track) and bool(track.clip_slots): if is_clip_stop_pending(track): return track_color_with_pending_stop(track) - elif track.playing_slot_index >= 0: - if track.solo: - return 'StopClips.SoloedTrack' - if track.mute: - return 'StopClips.MutedTrack' - if track.clip_slots[track.playing_slot_index].is_recording: - pulse_to = RECORDING_COLOR - else: - pulse_to = UNLIT_COLOR - return make_pulsing_track_color(track, pulse_to) else: + if track.playing_slot_index >= 0: + if track.solo: + return 'StopClips.SoloedTrack' + if track.mute: + return 'StopClips.MutedTrack' + if track.clip_slots[track.playing_slot_index].is_recording: + pulse_to = RECORDING_COLOR + else: + pulse_to = UNLIT_COLOR + return make_pulsing_track_color(track, pulse_to) return 'Session.StoppedClip' + else: return 'Mixer.NoTrack' @@ -89,15 +98,18 @@ class TrackListComponent(ModesComponent, Messenger): Notifies whenever a track action is executed, e.g. deleting or duplicating. But selection does *not* count as an action. """ - __events__ = ('mute_solo_stop_cancel_action_performed',) + __events__ = ('mute_solo_stop_cancel_action_performed', ) track_action_buttons = control_list(ButtonControl, control_count=8) - def __init__(self, tracks_provider = None, trigger_recording_on_release_callback = nop, *a, **k): - raise tracks_provider is not None or AssertionError + def __init__(self, tracks_provider=None, trigger_recording_on_release_callback=nop, color_chooser=None, *a, **k): + assert tracks_provider is not None super(TrackListComponent, self).__init__(*a, **k) self.locked_mode = None self._button_handler = self._select_mixable self._button_feedback_provider = mixable_button_color + self._color_chooser = color_chooser + self._playheads_real_time_data = [ self.register_component(RealTimeDataComponent(channel_type='playhead', is_enabled=False)) for _ in xrange(8) + ] self._setup_action_mode('select', handler=self._select_mixable) self._setup_action_mode('lock_override', handler=self._select_mixable) self._setup_action_mode('delete', handler=self._delete_mixable) @@ -106,6 +118,7 @@ def __init__(self, tracks_provider = None, trigger_recording_on_release_callback self._setup_action_mode('mute', handler=partial(toggle_mixable_mute, song=self.song)) self._setup_action_mode('solo', handler=partial(toggle_mixable_solo, song=self.song)) self._setup_action_mode('stop', handler=self._stop_track_clip, feedback_provider=stop_clip_button_color) + self._setup_action_mode('select_color', handler=self._select_mixable_color, exit_handler=partial(self._select_mixable_color, None)) self.selected_mode = 'select' self._can_trigger_recording_callback = trigger_recording_on_release_callback self._track_provider = tracks_provider @@ -114,9 +127,15 @@ def __init__(self, tracks_provider = None, trigger_recording_on_release_callback self.__on_selected_item_changed.subject = self._track_provider self.__on_tracks_changed.subject = self.song self.__on_selected_track_changed.subject = self.song.view + self.__on_is_playing_changed.subject = self.song self._update_track_and_chain_listeners() - self._update_button_enabled_state() - self._update_all_button_colors() + self._update_playheads_real_time_data() + self._update_realtime_channels_ability() + return + + @listenable_property + def playhead_real_time_channels(self): + return self._playheads_real_time_data @listenable_property def tracks(self): @@ -133,8 +152,11 @@ def absolute_selected_track_index(self): selected_track = song.view.selected_track return list(tracks).index(selected_track) - def _setup_action_mode(self, name, handler, feedback_provider = mixable_button_color): - self.add_mode(name, partial(self._enter_action_mode, handler=handler, feedback_provider=feedback_provider), behaviour=TrackListBehaviour()) + def _setup_action_mode(self, name, handler, exit_handler=nop, feedback_provider=mixable_button_color): + self.add_mode(name, [ + ( + partial(self._enter_action_mode, handler=handler, feedback_provider=feedback_provider), + exit_handler)], behaviour=TrackListBehaviour()) self.get_mode_button(name).mode_selected_color = 'DefaultButton.Transparent' self.get_mode_button(name).mode_unselected_color = 'DefaultButton.Transparent' @@ -144,18 +166,31 @@ def _enter_action_mode(self, handler, feedback_provider): self._button_feedback_provider = feedback_provider self._update_all_button_colors() + def _playing_clip(self, track): + if hasattr(track, 'playing_slot_index'): + try: + if track.playing_slot_index >= 0: + playing_clip_slot = track.clip_slots[track.playing_slot_index] + if playing_clip_slot is not None: + return playing_clip_slot.clip + return + except RuntimeError: + pass + + return + @listens('tracks') def __on_tracks_changed(self): self._update_track_and_chain_listeners() - self._update_button_enabled_state() + self._update_playheads_real_time_data() @listens_group('mute') - def __on_track_mute_state_changed(self, track): - self._update_all_button_colors() + def __on_track_mute_state_changed(self, mixable): + self._update_mixable_color(self.tracks.index(mixable), mixable) @listens_group('solo') - def __on_track_solo_state_changed(self, track): - self._update_all_button_colors() + def __on_track_solo_state_changed(self, mixable): + self._update_mixable_color(self.tracks.index(mixable), mixable) @listens_group('fired_slot_index') def __on_track_fired_slot_changed(self, track): @@ -164,22 +199,44 @@ def __on_track_fired_slot_changed(self, track): @listens_group('playing_slot_index') def __on_track_playing_slot_changed(self, _): self._update_all_button_colors() + self._update_playheads_real_time_data() @listens('items') def __on_items_changed(self): self._update_track_and_chain_listeners() - self._update_button_enabled_state() + self._update_playheads_real_time_data() + + @listens('is_playing') + def __on_is_playing_changed(self): + self._update_playheads_real_time_data() + + @listens_group('is_frozen') + def __on_track_is_frozen_state_changed(self, track): + self._update_all_button_colors() + + def _update_playheads_real_time_data(self): + if self.song.is_playing: + for track, real_time_data in zip(self.tracks, self._playheads_real_time_data): + real_time_data.set_data(self._playing_clip(track)) + + else: + for track, real_time_data in zip(self.tracks, self._playheads_real_time_data): + real_time_data.set_data(None) + + self.notify_playhead_real_time_channels() + return def _update_track_and_chain_listeners(self): self.notify_tracks() - self.__on_track_color_index_changed.replace_subjects(self.tracks) - tracks_without_chains = filter(can_play_clips, self.tracks) + tracks = self.tracks + self.__on_track_color_index_changed.replace_subjects(tracks) + self.__on_track_mute_state_changed.replace_subjects(tracks) + self.__on_track_muted_via_solo_changed.replace_subjects(tracks) + self.__on_track_solo_state_changed.replace_subjects(tracks) + tracks_without_chains = filter(can_play_clips, tracks) self.__on_track_fired_slot_changed.replace_subjects(tracks_without_chains) self.__on_track_playing_slot_changed.replace_subjects(tracks_without_chains) - all_tracks = [ _ for _ in chain(self.song.tracks, self.tracks) ] - self.__on_track_mute_state_changed.replace_subjects(all_tracks) - self.__on_track_solo_state_changed.replace_subjects(all_tracks) - self.__on_track_muted_via_solo_changed.replace_subjects(all_tracks) + self.__on_track_is_frozen_state_changed.replace_subjects(tracks_without_chains) self._update_button_enabled_state() self._update_all_button_colors() @@ -189,8 +246,8 @@ def _update_button_enabled_state(self): control.enabled = liveobj_valid(track) @listens_group('color_index') - def __on_track_color_index_changed(self, _): - self._update_all_button_colors() + def __on_track_color_index_changed(self, mixable): + self._update_mixable_color(self.tracks.index(mixable), mixable) @listens('selected_item') def __on_selected_item_changed(self): @@ -203,12 +260,14 @@ def __on_selected_track_changed(self): @listens_group('muted_via_solo') def __on_track_muted_via_solo_changed(self, mixable): - self._update_all_button_colors() + self._update_mixable_color(self.tracks.index(mixable), mixable) + + def _update_mixable_color(self, button_index, mixable): + self.track_action_buttons[button_index].color = self._button_feedback_provider(mixable, self.song, self.selected_track) def _update_all_button_colors(self): - for index, mixer_track in enumerate(self.tracks): - color = self._button_feedback_provider(mixer_track, self.song, self.selected_track) - self.track_action_buttons[index].color = color + for index, mixable in enumerate(self.tracks): + self._update_mixable_color(index, mixable) @track_action_buttons.pressed def track_action_buttons(self, button): @@ -227,22 +286,21 @@ def _select_mixable(self, track): track.is_showing_chains = not track.is_showing_chains @staticmethod - def can_duplicate_or_delete(track_or_chain, return_tracks): + def can_duplicate(track_or_chain, return_tracks): unwrapped = track_or_chain.proxied_object return isinstance(unwrapped, Live.Track.Track) and unwrapped not in list(return_tracks) def _delete_mixable(self, track_or_chain): - if self.can_duplicate_or_delete(track_or_chain, self.song.return_tracks): + if liveobj_valid(track_or_chain) and not is_chain(track_or_chain): try: - track_index = list(self.song.tracks).index(track_or_chain) name = track_or_chain.name - self.song.delete_track(track_index) + delete_track_or_return_track(self.song, track_or_chain) self.show_notification(MessageBoxText.DELETE_TRACK % name) except RuntimeError: self.show_notification(MessageBoxText.TRACK_DELETE_FAILED) def _duplicate_mixable(self, track_or_chain): - if self.can_duplicate_or_delete(track_or_chain, self.song.return_tracks): + if self.can_duplicate(track_or_chain, self.song.return_tracks): try: track_index = list(self.song.tracks).index(track_or_chain) self.song.duplicate_track(track_index) @@ -263,10 +321,21 @@ def _stop_track_clip(self, mixable): if not is_chain(mixable): mixable.stop_all_clips() + def _select_mixable_color(self, mixable): + if self._color_chooser is not None: + self._color_chooser.object = mixable + return + + def _update_realtime_channels_ability(self): + for playhead in self._playheads_real_time_data: + playhead.set_enabled(self.is_enabled()) + def on_enabled_changed(self): super(TrackListComponent, self).on_enabled_changed() + self._update_realtime_channels_ability() if not self.is_enabled(): self.selected_mode = 'select' self.pop_unselected_modes() elif self.locked_mode is not None: - self.push_mode(self.locked_mode) \ No newline at end of file + self.push_mode(self.locked_mode) + return \ No newline at end of file diff --git a/Push2/track_mixer_control_component.py b/Push2/track_mixer_control_component.py index 7861922a..e6c25fff 100644 --- a/Push2/track_mixer_control_component.py +++ b/Push2/track_mixer_control_component.py @@ -1,12 +1,13 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/Push2/track_mixer_control_component.py +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/Push2/track_mixer_control_component.py +# Compiled at: 2016-11-16 18:13:20 from __future__ import absolute_import, print_function -from itertools import izip_longest from ableton.v2.base import clamp, depends, listens, liveobj_valid from ableton.v2.control_surface import CompoundComponent from ableton.v2.control_surface.control import control_list, ButtonControl from pushbase.mapped_control import MappedControl from .real_time_channel import RealTimeDataComponent from .item_lister_component import SimpleItemSlot +from .mixer_control_component import assign_parameters MAX_RETURN_TRACKS = 6 class TrackMixerControlComponent(CompoundComponent): @@ -17,9 +18,9 @@ class TrackMixerControlComponent(CompoundComponent): scroll_left_button = ButtonControl(**BUTTON_SKIN) @depends(tracks_provider=None, real_time_mapper=None, register_real_time_data=None) - def __init__(self, real_time_mapper = None, register_real_time_data = None, tracks_provider = None, *a, **k): - raise liveobj_valid(real_time_mapper) or AssertionError - raise tracks_provider is not None or AssertionError + def __init__(self, real_time_mapper=None, register_real_time_data=None, tracks_provider=None, *a, **k): + assert liveobj_valid(real_time_mapper) + assert tracks_provider is not None super(TrackMixerControlComponent, self).__init__(*a, **k) self._tracks_provider = tracks_provider self._on_return_tracks_changed.subject = self.song @@ -29,6 +30,7 @@ def __init__(self, real_time_mapper = None, register_real_time_data = None, trac self._number_return_tracks = self._number_sends() self._update_scroll_buttons() self.__on_selected_item_changed.subject = self._tracks_provider + return def set_controls(self, controls): self.controls.set_control_element(controls) @@ -51,10 +53,7 @@ def _update_real_time_channel_id(self): def _update_controls(self): if self.is_enabled(): - for control, parameter in izip_longest(self.controls, self.parameters[self.scroll_offset:]): - if control: - control.mapped_parameter = parameter - + assign_parameters(self.controls, self.parameters[self.scroll_offset:]) self.notify_parameters() @property diff --git a/Push2/track_selection.py b/Push2/track_selection.py index 3b452059..d4e6e35b 100644 --- a/Push2/track_selection.py +++ b/Push2/track_selection.py @@ -1,8 +1,9 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/Push2/track_selection.py +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/Push2/track_selection.py +# Compiled at: 2016-09-29 19:13:24 from __future__ import absolute_import, print_function from functools import partial import Live -from ableton.v2.base import SlotManager, Subject, const, depends, flatten, nop, listenable_property, listens, listens_group, liveobj_changed, liveobj_valid +from ableton.v2.base import EventObject, const, depends, flatten, nop, listenable_property, listens, listens_group, liveobj_changed, liveobj_valid from ableton.v2.control_surface.components import SessionRingComponent, right_align_return_tracks_track_assigner from ableton.v2.control_surface.components.view_control import has_next_item, next_item, TrackScroller as TrackScrollerBase, ViewControlComponent as ViewControlComponentBase from pushbase.device_chain_utils import find_instrument_devices @@ -15,10 +16,12 @@ def get_flattened_track(track): Returns a flat list of a track with its instrument chains (when visible), or just the original track """ - flat_track = [track] + flat_track = [ + track] if track.can_show_chains and track.is_showing_chains: instruments = list(find_instrument_devices(track)) - flat_track.extend([ c for c in instruments[0].chains ]) + if instruments: + flat_track.extend([ c for c in instruments[0].chains ]) return flat_track @@ -30,16 +33,17 @@ def get_all_mixer_tracks(song): return tracks + list(song.return_tracks) -class SelectedMixerTrackProvider(Subject, SlotManager): +class SelectedMixerTrackProvider(EventObject): @depends(song=None) - def __init__(self, song = None, *a, **k): + def __init__(self, song=None, *a, **k): super(SelectedMixerTrackProvider, self).__init__(*a, **k) self._view = song.view self._selected_mixer_track = None self._on_selected_track_changed.subject = self._view self._on_selected_chain_changed.subject = self._view self._on_selected_track_changed() + return @listens('selected_track') def _on_selected_track_changed(self): @@ -81,14 +85,14 @@ def _get_selected_chain_or_track(self): class SessionRingTrackProvider(SessionRingComponent, ItemProvider): @depends(set_session_highlight=const(nop)) - def __init__(self, set_session_highlight = nop, *a, **k): + def __init__(self, set_session_highlight=nop, *a, **k): self._decorator_factory = TrackDecoratorFactory() - super(SessionRingTrackProvider, self).__init__(set_session_highlight=partial(set_session_highlight, include_rack_chains=True), *a, **k) + super(SessionRingTrackProvider, self).__init__(set_session_highlight=partial(set_session_highlight, include_rack_chains=True), tracks_to_use=self._decorated_tracks_to_use, *a, **k) self._artificially_selected_item = None - self._on_tracklist_changed.subject = self.song self._update_listeners() self._selected_track = self.register_disconnectable(SelectedMixerTrackProvider()) self._on_selected_item_changed.subject = self._selected_track + return def scroll_into_view(self, mixable): mixable_index = self.tracks_to_use().index(mixable) @@ -109,6 +113,7 @@ def _set_selected_item(self, item): self._artificially_selected_item = None self._selected_track.selected_mixer_track = item self.notify_selected_item() + return selected_item = property(_get_selected_item, _set_selected_item) @@ -118,9 +123,17 @@ def items(self): def move(self, tracks, scenes): super(SessionRingTrackProvider, self).move(tracks, scenes) - self._on_tracklist_changed() + if tracks != 0: + self._ensure_valid_track_offset() + self.notify_items() - def tracks_to_use(self): + def _update_track_list(self): + super(SessionRingTrackProvider, self)._update_track_list() + self._ensure_valid_track_offset() + self.notify_items() + self._update_listeners() + + def _decorated_tracks_to_use(self): return self._decorator_factory.decorate_all_mixer_tracks(get_all_mixer_tracks(self.song)) def controlled_tracks(self): @@ -137,29 +150,19 @@ def synchronize_selection_with_live_view(self): if self._artificially_selected_item: self.selected_item = self._artificially_selected_item - @listens('visible_tracks') - def _on_tracklist_changed(self): - self._notify_and_update() - @listens_group('is_showing_chains') def _on_is_showing_chains_changed(self, _): - self._notify_and_update() + self._update_track_list() @listens_group('chains') def _on_chains_changed(self, _): if not self.song.view.selected_track.can_show_chains: self.selected_item = self.song.view.selected_track - self._notify_and_update() + self._update_track_list() @listens_group('devices') def _on_devices_changed(self, _): - self._notify_and_update() - - def _notify_and_update(self): - self._ensure_valid_track_offset() - self.notify_items() - self.notify_tracks() - self._update_listeners() + self._update_track_list() def _update_listeners(self): @@ -168,8 +171,10 @@ def flattened_list_of_instruments(instruments): tracks = self.song.tracks self._on_devices_changed.replace_subjects(tracks) - chain_listenable_tracks = [ track for track in tracks if isinstance(track, Live.Track.Track) and track ] - instruments = flattened_list_of_instruments([ find_instrument_devices(track) for track in chain_listenable_tracks if track ]) + chain_listenable_tracks = [ track for track in tracks if isinstance(track, Live.Track.Track) and track + ] + instruments = flattened_list_of_instruments([ find_instrument_devices(track) for track in chain_listenable_tracks if track + ]) instruments_with_chains = filter(lambda i: i.can_have_chains, instruments) self._on_is_showing_chains_changed.replace_subjects(chain_listenable_tracks) self._on_chains_changed.replace_subjects(instruments_with_chains) @@ -183,21 +188,22 @@ def _ensure_valid_track_offset(self): @listens_group('return_chains') def _on_instrument_return_chains_changed(self, _): - self._notify_and_update() + self._update_track_list() @listens('selected_mixer_track') def _on_selected_item_changed(self, _): self.notify_selected_item() -class TrackScroller(TrackScrollerBase, Subject): - __events__ = ('scrolled',) +class TrackScroller(TrackScrollerBase, EventObject): + __events__ = ('scrolled', ) @depends(tracks_provider=None) - def __init__(self, tracks_provider = None, *a, **k): - raise tracks_provider is not None or AssertionError + def __init__(self, tracks_provider=None, *a, **k): + assert tracks_provider is not None super(TrackScroller, self).__init__(*a, **k) self._track_provider = tracks_provider + return def _all_items(self): return self._track_provider.tracks_to_use() + [self._song.master_track] @@ -217,10 +223,10 @@ def _can_scroll(self, delta): class ViewControlComponent(ViewControlComponentBase): - __events__ = ('selection_changed',) + __events__ = ('selection_changed', ) @depends(tracks_provider=None) - def __init__(self, tracks_provider = None, *a, **k): + def __init__(self, tracks_provider=None, *a, **k): self._track_provider = tracks_provider super(ViewControlComponent, self).__init__(*a, **k) self._on_items_changed.subject = self._track_provider diff --git a/Push2/transport_state.py b/Push2/transport_state.py new file mode 100644 index 00000000..f4dc150b --- /dev/null +++ b/Push2/transport_state.py @@ -0,0 +1,73 @@ +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/Push2/transport_state.py +# Compiled at: 2016-09-29 19:13:24 +from __future__ import absolute_import, print_function +from ableton.v2.base import listenable_property, listens +from ableton.v2.control_surface import CompoundComponent +from .real_time_channel import RealTimeDataComponent +COUNT_IN_DURATION_IN_BARS = (0, 1, 2, 4) + +class TransportState(CompoundComponent): + count_in_duration = listenable_property.managed(0) + + def __init__(self, song=None, *a, **kw): + super(TransportState, self).__init__(*a, **kw) + self._song = song + self.__on_is_playing_changed.subject = song + self._count_in_time_real_time_data = self.register_component(RealTimeDataComponent(channel_type='count-in')) + self.__on_count_in_duration_changed.subject = song + self.__on_is_counting_in_changed.subject = song + self.__on_signature_numerator_changed.subject = song + self.__on_signature_denominator_changed.subject = song + self.__on_count_in_channel_changed.subject = self._count_in_time_real_time_data + self._update_count_in_duration() + + @listenable_property + def count_in_real_time_channel_id(self): + return self._count_in_time_real_time_data.channel_id + + @listenable_property + def is_counting_in(self): + return self._song.is_counting_in + + @listenable_property + def signature_numerator(self): + return self._song.signature_numerator + + @listenable_property + def signature_denominator(self): + return self._song.signature_denominator + + def _update_count_in_duration(self): + self.count_in_duration = COUNT_IN_DURATION_IN_BARS[self._song.count_in_duration] + + @listens('count_in_duration') + def __on_count_in_duration_changed(self): + if not self.is_counting_in: + self._update_count_in_duration() + + @listens('is_counting_in') + def __on_is_counting_in_changed(self): + self._count_in_time_real_time_data.set_data(self._song if self.is_counting_in else None) + self.notify_is_counting_in() + self._update_count_in_duration() + return + + @listens('signature_numerator') + def __on_signature_numerator_changed(self): + self.notify_signature_numerator() + + @listens('signature_denominator') + def __on_signature_denominator_changed(self): + self.notify_signature_denominator() + + @listenable_property + def is_playing(self): + return self._song.is_playing + + @listens('is_playing') + def __on_is_playing_changed(self): + self.notify_is_playing() + + @listens('channel_id') + def __on_count_in_channel_changed(self): + self.notify_count_in_real_time_channel_id() \ No newline at end of file diff --git a/Push2/user_component.py b/Push2/user_component.py index 848b239d..fa112c33 100644 --- a/Push2/user_component.py +++ b/Push2/user_component.py @@ -1,4 +1,5 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/Push2/user_component.py +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/Push2/user_component.py +# Compiled at: 2016-05-20 03:43:52 from __future__ import absolute_import, print_function from contextlib import contextmanager from ableton.v2.control_surface.mode import ModeButtonBehaviour @@ -7,11 +8,12 @@ class UserButtonBehavior(ModeButtonBehaviour): - def __init__(self, user_component = None, *a, **k): - raise user_component is not None or AssertionError + def __init__(self, user_component=None, *a, **k): + assert user_component is not None super(UserButtonBehavior, self).__init__(*a, **k) self._previous_mode = None self._user_component = user_component + return def press_immediate(self, component, mode): if component.selected_mode != 'user' and self._user_component.mode == sysex.LIVE_MODE: @@ -24,10 +26,11 @@ def release_delayed(self, component, mode): self._leave_user_mode(component) def _leave_user_mode(self, component): - if not (component.selected_mode == 'user' and self._user_component.mode == sysex.USER_MODE and self._previous_mode is not None): - raise AssertionError + if component.selected_mode == 'user' and self._user_component.mode == sysex.USER_MODE: + assert self._previous_mode is not None component.selected_mode = self._previous_mode self._previous_mode = None + return class UserComponent(UserComponentBase): diff --git a/Push2/waveform_navigation.py b/Push2/waveform_navigation.py index cd3fec9c..f33f792a 100644 --- a/Push2/waveform_navigation.py +++ b/Push2/waveform_navigation.py @@ -1,11 +1,15 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/Push2/waveform_navigation.py +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/Push2/waveform_navigation.py +# Compiled at: 2016-09-29 19:13:24 from __future__ import absolute_import, print_function +import logging import math from collections import namedtuple, OrderedDict from functools import partial -from itertools import imap -from ableton.v2.base import SlotManager, Subject, const, clamp, depends, find_if, index_if, lazy_attribute, listenable_property, listens, listens_group, liveobj_changed, task +from itertools import ifilter, imap +import Live +from ableton.v2.base import EventObject, const, clamp, depends, find_if, index_if, isclose, lazy_attribute, listenable_property, listens, listens_group, liveobj_valid, nop, task from ableton.v2.control_surface.control import EncoderControl +logger = logging.getLogger(__name__) FocusMarker = namedtuple('FocusMarker', ['name', 'position']) def ease_out(t, degree): @@ -43,43 +47,129 @@ def calc_easing_degree_for_proportion(proportion): return -math.log10(proportion) + 1 -def compare_region_length(r1, r2): +def interpolate_region(from_region, to_region, t, ease_out_degree): + return Region(interpolate(from_region.start, to_region.start, t, ease_out_degree), interpolate(from_region.end, to_region.end, t, ease_out_degree)) + + +def inverse_interpolate_region(from_region, to_region, current_region, ease_out_degree, prefer_end): + if prefer_end: + index = 0 if from_region.end == to_region.end else 1 + else: + index = 1 if from_region.start == to_region.start else 0 + return interpolate_inverse(from_region[index], to_region[index], current_region[index], ease_out_degree) + + +class Region(namedtuple('Region', ['start', 'end'])): + """ + A region with a start and end position in sample time. + """ + + def __eq__(self, region): + """ + Regions are considered equal, if their samples are equal, ignoring the + the decimal digits. + """ + return region is not None and isclose(region.start, self.start) and isclose(region.end, self.end) + + def __ne__(self, region): + return not region == self + + @property + def length(self): + return self.end - self.start + + def inside(self, outer): + return (isclose(self.start, outer.start) or self.start > outer.start) and (isclose(self.end, outer.end) or self.end < outer.end) and outer != self + + def clamp_position(self, position): + return clamp(position, self.start, self.end) + + def clamp_to_region(self, region): + return Region(region.clamp_position(self.start), region.clamp_position(self.end)) + + +class RegionOfInterest(object): """ - Returns a negative, zero or positive number, depending on whether r1 is considered - smaller than, equal to, or larger than r2 + A region of interest is responsible of fetching the current region from the getter + and which start and end objects are attached to it. If the start and end points are + not focusable, the identifiers should be None. """ - r1 = r1() - r2 = r2() - s1 = r1[1] - r1[0] - s2 = r2[1] - r2[0] - return int(s2 - s1) + def __init__(self, start_identifier=None, end_identifier=None, getter=None, add_margin=nop, *a, **k): + assert getter is not None + super(RegionOfInterest, self).__init__(*a, **k) + self.start_identifier = start_identifier + self.end_identifier = end_identifier + self.add_margin = add_margin + self._getter = getter + return -class WaveformNavigation(SlotManager, Subject): + def bound_by(self, object_identifier): + return object_identifier in (self.start_identifier, self.end_identifier) + + @property + def region(self): + return Region(*self._getter()) + + @property + def region_with_margin(self): + return self.add_margin(Region(*self._getter())) + + +class ObjectDescription(object): + + def __init__(self, regions, focus_name_or_getter, *a, **k): + super(ObjectDescription, self).__init__(*a, **k) + self._regions = ('waveform', ) + regions + ('focused_object', ) + self._focus_name_or_getter = focus_name_or_getter + + @property + def regions(self): + return self._regions + + @property + def focus_name(self): + if callable(self._focus_name_or_getter): + return self._focus_name_or_getter() + return self._focus_name_or_getter + + +class MarginType(object): + """ Enum for the zooming algorithm. """ + NONE, START, END = range(3) + + +class WaveformNavigation(EventObject): """ Class for managing a visible area of a waveform """ - visible_start = listenable_property.managed(0) - visible_end = listenable_property.managed(0) + visible_region = listenable_property.managed(Region(0, 1)) + visible_region_in_samples = listenable_property.managed(Region(0, 1)) animate_visible_region = listenable_property.managed(False) + focus_marker = listenable_property.managed(FocusMarker('', 0)) show_focus = listenable_property.managed(False) ZOOM_SENSITIVITY = 1.5 - MIN_VISIBLE_LENGTH = 1000 + MIN_VISIBLE_SAMPLES = 49 WAVEFORM_WIDTH_IN_PX = 933 MARGIN_IN_PX = 121 RELATIVE_FOCUS_MARGIN = float(MARGIN_IN_PX) / WAVEFORM_WIDTH_IN_PX + UNSNAPPING_THRESHOLD = 0.6 + CHANGE_OBJECT_TIME = 0.1 - def __init__(self, waveform_length = None, *a, **k): - raise waveform_length is not None or AssertionError - raise waveform_length > 0 or AssertionError + def __init__(self, *a, **k): super(WaveformNavigation, self).__init__(*a, **k) - self._length = waveform_length - self._focused_object = None - self._focus_marker = FocusMarker('', 0) - self._touched_objects = set() + self._waveform_region = Region(0, 1) + self.waveform_roi = self.make_region_of_interest(getter=lambda : self._waveform_region, with_margin=False) + self.focused_object_roi = self.make_region_of_interest(getter=self._make_region_for_focused_object, with_margin=False) + self._focused_identifier = None + self._touched_identifiers = set() + self._changed_identifiers = set() self._has_tasks = False - self._target_region_getter = self.get_start_end_region - self._source_region_getter = lambda : (0, self._length) + self._target_roi = self.waveform_roi + self._source_roi = self.waveform_roi self._request_select_region = False - self.set_visible_region(0, self._length) + self._unsnapping_value = 0 + self._locked_roi = None + self._last_action = None + return def disconnect(self): super(WaveformNavigation, self).disconnect() @@ -93,33 +183,90 @@ def get_object_identifier(self, obj): def get_zoom_object(self): raise NotImplementedError - def get_start_object_identifier(self): - raise NotImplementedError + def get_region_in_samples(self, region): + return region - def get_end_object_identifier(self): - raise NotImplementedError + def get_min_visible_length(self): + return self.MIN_VISIBLE_SAMPLES + + @listenable_property + def waveform_region(self): + return self._waveform_region - def get_start_end_region(self): - return self._make_region_from_region_identifiers(self.get_start_object_identifier(), self.get_end_object_identifier()) + @waveform_region.setter + def waveform_region(self, region): + if region != self._waveform_region: + self._waveform_region = region + self._request_select_region = True + self.set_visible_region(self._waveform_region) + self.notify_waveform_region() + + def make_region_of_interest(self, start_identifier=None, end_identifier=None, getter=None, with_margin=True): + return RegionOfInterest(start_identifier, end_identifier, getter, add_margin=self._add_margin_to_region if with_margin else nop) @lazy_attribute - def focusable_object_connections(self): + def regions_of_interest(self): + """ + The region of interests, that can be zoomed into. By default, the waveform + navigation zooms only between the full waveform and the focused objects + position. Override additional_regions_of_interest to add more regions to it. + """ + rois = {'waveform': self.waveform_roi, + 'focused_object': self.focused_object_roi + } + rois.update(self.additional_regions_of_interest) + return rois + + @lazy_attribute + def additional_regions_of_interest(self): return {} - @property - def visible_length(self): - """ Returns the length of the visible area """ - return self.visible_end - self.visible_start + def get_name_for_roi(self, roi): + """ + Returns the name for the given roi or None, if it doesn't have one + """ + item = find_if(lambda i: i[1] == roi, self.regions_of_interest.iteritems()) + if item is not None: + return item[0] + else: + return + + @lazy_attribute + def focusable_object_descriptions(self): + """ + Describes focusable objects and how they zoom into regions. + Returns a dictionary of identifier to ObjectDescriptions. + """ + return {} + + def get_object_description(self, identifier): + return self.focusable_object_descriptions.get(identifier, None) @property def visible_proportion(self): """ Returns the proportion between the visible length and the sample length """ - return self.visible_length / float(self._length) + return self.visible_region.length / float(self._waveform_region.length) + + def set_visible_region(self, region, source_action=None, force_animate=False): + """ + Set the current visible region in the current unit and samples. + The region is animated, if source action changes. + Animation is enforced, if force_animate is True. + """ + self.animate_visible_region = force_animate or source_action != self._last_action + self.visible_region = region.clamp_to_region(self._waveform_region) + self.visible_region_in_samples = self.get_region_in_samples(self.visible_region) + self._last_action = source_action - def set_visible_region(self, start, end, animate = False): - self.animate_visible_region = animate - self.visible_start = clamp(start, 0, self._length) - self.visible_end = clamp(end, 0, self._length) + def set_visible_length(self, length): + """ + Extends or reduces the visible end to show the given length. + If the end of the waveform is reached, the visible start is adapted. + """ + start = self.visible_region.start + end = min(start + length, self.waveform_region.end) + start = end - length + self.set_visible_region(Region(start, end)) def zoom(self, value): """ Zooms in or out of the waveform start @@ -127,82 +274,265 @@ def zoom(self, value): possible and -1.0 will zoom out completely. """ animate = self._request_select_region - if self._request_select_region: + if self._request_select_region or self._process_unsnapping(value): self._select_region(value > 0) - source = self._source_region_getter() - target = self._target_region_getter() - source_length = float(source[1] - source[0]) - target_length = float(target[1] - target[0]) - easing_degree = calc_easing_degree_for_proportion(target_length / source_length) - if source[0] != target[0]: - t = interpolate_inverse(source[0], target[0], self.visible_start, easing_degree) - else: - t = interpolate_inverse(source[1], target[1], self.visible_end, easing_degree) + source = self._source_roi.region_with_margin + target = self._target_roi.region_with_margin + easing_degree = calc_easing_degree_for_proportion(float(target.length) / float(source.length)) + focused_region, focus_marker, margin_type = self._get_zoom_info_for_focused_object() + t = inverse_interpolate_region(source, target, self.visible_region, easing_degree, prefer_end=margin_type == MarginType.START) t = clamp(t + value * self.ZOOM_SENSITIVITY, 0.0, 1.0) - self.set_visible_region(interpolate(source[0], target[0], t, easing_degree), interpolate(source[1], target[1], t, easing_degree), animate=animate) + region = interpolate_region(source, target, t, easing_degree) + region = self._add_margin_to_zoomed_region(region, focused_region, margin_type) + self.set_visible_region(region, force_animate=animate, source_action='zoom') + self.focus_marker = focus_marker self.show_focus = True self.try_hide_focus_delayed() + self._try_lock_region() + + def _get_zoom_info_for_focused_object(self): + """ + Returns a tuple of the region for the focused object and weather a + margin should be added to the zoom region. + """ + identifier = self._focused_identifier + roi = self._get_roi_for_object_identifier(identifier) + margin_type = MarginType.NONE + region = None + focus_marker = None + if roi is not None: + margin = self.waveform_region.length * self.RELATIVE_FOCUS_MARGIN + region = roi.region + is_start = roi.start_identifier == identifier + if is_start and region.start < margin: + margin_type = MarginType.START + elif not is_start and region.end > self.waveform_region.end - margin: + margin_type = MarginType.END + obj_description = self.focusable_object_descriptions.get(identifier, None) + if obj_description is not None: + focus_marker = FocusMarker(obj_description.focus_name, region.end if roi.end_identifier == identifier else region.start) + return ( + region, focus_marker, margin_type) + + def _add_margin_to_zoomed_region(self, zoom_region, focused_region, margin_type): + """ + Adds a margin to a zoom region, so that the focused object is shown with a margin + as soon as possible. This makes switching between zooming an focusing an object + seamless, as focusing will always add the margin as well. + """ + if focused_region is not None and margin_type != MarginType.NONE: + position = focused_region.start if margin_type == MarginType.START else focused_region.end + if zoom_region.start <= position <= zoom_region.end: + if margin_type == MarginType.START: + zoom_region = self._add_margin_to_zoomed_region_start(zoom_region, position) + else: + zoom_region = self._add_margin_to_zoomed_region_end(zoom_region, position) + else: + logger.warn("Focused object not visible. Couldn't add margin to zoomed region. %d not in %r" % ( + position, zoom_region)) + return zoom_region + + def _add_margin_to_zoomed_region_start(self, region, focused_position): + p = focused_position - self.waveform_region.start + samples_per_pixel = p / self.MARGIN_IN_PX + length = self.WAVEFORM_WIDTH_IN_PX * samples_per_pixel + if self.waveform_region.start + length < region.end: + region = Region(self.waveform_region.start, region.end) + else: + p = region.end - focused_position + samples_per_pixel = p / (self.WAVEFORM_WIDTH_IN_PX - self.MARGIN_IN_PX) + length = self.WAVEFORM_WIDTH_IN_PX * samples_per_pixel + start = region.end - length + if start < region.start: + region = Region(start, region.end) + return region + + def _add_margin_to_zoomed_region_end(self, region, focused_position): + p = self.waveform_region.end - focused_position + samples_per_pixel = p / self.MARGIN_IN_PX + length = self.WAVEFORM_WIDTH_IN_PX * samples_per_pixel + if self.waveform_region.end - length > region.start: + region = Region(region.start, self.waveform_region.end) + else: + p = focused_position - region.start + samples_per_pixel = p / (self.WAVEFORM_WIDTH_IN_PX - self.MARGIN_IN_PX) + length = self.WAVEFORM_WIDTH_IN_PX * samples_per_pixel + end = region.start + length + if end > region.end: + region = Region(region.start, end) + return region + + def _process_unsnapping(self, value): + """ + Process unsnapping for the given normalized value. + Returns true if the region should be unsnapped. + """ + if self.is_snapped: + self._unsnapping_value += value + return abs(self._unsnapping_value) >= self.UNSNAPPING_THRESHOLD + return False + + def _try_lock_region(self): + if self.visible_region == self._waveform_region: + self._locked_roi = None + elif self.visible_region == self._target_roi.region_with_margin: + self._locked_roi = self._target_roi + elif self.visible_region == self._source_roi.region_with_margin: + self._locked_roi = self._source_roi + else: + self._locked_roi = None + return + + @property + def is_snapped(self): + return self.visible_region == self._target_roi.region_with_margin or self.visible_region == self._source_roi.region_with_margin def focus_object(self, obj): if obj != self.get_zoom_object(): identifier = self.get_object_identifier(obj) - if identifier in self.focusable_object_connections: - connection = self.focusable_object_connections[identifier] - animate = liveobj_changed(self._focused_object, obj) - self._focused_object = obj - self._focus_connection(connection, animate=animate) + zoom_identifier = self.get_object_identifier(self.get_zoom_object()) + touched_identifiers = self._touched_identifiers - set([zoom_identifier]) + objects_to_show = self._changed_identifiers & touched_identifiers + if identifier in self.focusable_object_descriptions: + if len(objects_to_show) > 1: + logger.debug('Focus all objects %r' % objects_to_show) + self._focused_identifier = identifier + self._show_all_objects(objects_to_show) + else: + logger.debug('Focus object %r' % identifier) + animate = len(touched_identifiers) <= 1 and self.object_changed(self._focused_identifier, identifier) + self._focused_identifier = identifier + self._focus_object_by_identifier(identifier, animate=animate) return True return False - def _focus_connection(self, connection, animate = False): - """ Focuses the connection in the waveform and brings it into the visible range. + def object_changed(self, identifier1, identifier2): + return identifier1 != identifier2 + + def _get_roi_for_object_identifier(self, identifier): + return find_if(lambda roi: roi.bound_by(identifier), self.regions_of_interest.values()) + + def _get_position_for_identifier(self, identifier): + roi = self._get_roi_for_object_identifier(identifier) + if roi is not None: + if roi.start_identifier == identifier: + return roi.region.start + return roi.region.end + else: + return + + def _zoom_out_or_move_region(self, source_region, target_region): + """ + Zooms out the source region, if it's contained in the target region + or moves it left or right depending on where they overlap. + """ + new_region = None + if source_region.inside(target_region): + new_region = target_region + elif target_region.start < source_region.start: + new_region = Region(target_region.start, max(target_region.start + source_region.length, target_region.end)) + elif target_region.end > source_region.end: + new_region = Region(min(target_region.end - source_region.length, target_region.start), target_region.end) + return new_region + + def _show_all_objects(self, identifiers): + start = self.waveform_region.end + end = self.waveform_region.start + positions = imap(self._get_position_for_identifier, identifiers) + for position in ifilter(None, positions): + start = min(start, position) + end = max(end, position) + + margin = self.visible_region.length * self.RELATIVE_FOCUS_MARGIN + visible_region_without_margin = Region(self.visible_region.start + margin, self.visible_region.end - margin) + object_region = Region(start, end) + new_region = self._zoom_out_or_move_region(visible_region_without_margin, object_region) + if new_region is not None: + self.set_visible_region(self._add_margin_to_region(new_region), source_action='show_objects %r' % identifiers) + self._request_select_region = True + self._locked_roi = None + self.focus_marker = FocusMarker('', 0) + return + + def _focus_object_by_identifier(self, identifier, animate=False): + """ Focuses the object in the waveform and brings it into the visible range. The visible length is preserved. The position is aligned to the left or right of the visible range, with a certain margin defined by RELATIVE_FOCUS_MARGIN. - If the connections boundary is already in the visible range, the visible + If the objects region boundary is already in the visible range, the visible position is not changing. - :connection: the object connection to focus - :align_right: focuses the position on the left or right side of the + :identifier: the object identifier to focus :animate: should be set to True if, if it should animate to the new position """ - position = connection.getter() - visible_length = self.visible_length - visible_margin = visible_length * self.RELATIVE_FOCUS_MARGIN - length = self._length - if connection.align_right: - start = min(connection.boundary_getter() - visible_margin, self.visible_start) if connection else 0 - right = max(position + visible_margin, start + visible_length) - self.set_visible_region(clamp(right - visible_length, 0, length - visible_length), clamp(right, visible_length, length), animate) + roi = self._get_roi_for_object_identifier(identifier) + region = roi.region + if self._locked_roi is not None and self._locked_roi.bound_by(identifier): + if region.start < self.waveform_region.start: + start = self.waveform_region.start + new_visible_region = Region(start, start + self.visible_region.length) + elif region.end > self.waveform_region.end: + end = self.waveform_region.end + new_visible_region = Region(end - self.visible_region.length, end) + else: + new_visible_region = self._add_margin_to_region(region) + self.set_visible_region(new_visible_region, force_animate=animate) else: - end = max(connection.boundary_getter() + visible_margin, self.visible_end) if connection else length - left = min(position - visible_margin, end - visible_length) - self.set_visible_region(clamp(left, 0, length - visible_length), clamp(left + visible_length, visible_length, length), animate) - self._focus_marker = FocusMarker(connection.focus_name, position) - self.notify_focus_marker() - self._request_select_region = True - - @listenable_property - def focus_marker(self): - return self._focus_marker + visible_length = self.visible_region.length + visible_margin = visible_length * self.RELATIVE_FOCUS_MARGIN + waveform_start, waveform_end = self._waveform_region + if roi.end_identifier == identifier: + start = min(region.start - visible_margin, self.visible_region.start) + right = max(region.end + visible_margin, start + visible_length) + left = right - visible_length + else: + end = max(region.end + visible_margin, self.visible_region.end) + left = min(region.start - visible_margin, end - visible_length) + right = left + visible_length + self.set_visible_region(Region(clamp(left, waveform_start, waveform_end - visible_length), clamp(right, waveform_start + visible_length, waveform_end)), force_animate=animate) + self._request_select_region = True + self.focus_marker = FocusMarker(self.focusable_object_descriptions[identifier].focus_name, region.end if roi.end_identifier == identifier else region.start) + return def touch_object(self, obj): is_zoom_object = obj == self.get_zoom_object() - if is_zoom_object: - if self._visible_region_reaches(self._target_region_getter()) or self._visible_region_reaches(self._source_region_getter()): - self._request_select_region = True + if is_zoom_object and self.is_snapped: + self._request_select_region = True + self._touched_identifiers.add(self.get_object_identifier(obj)) if self.focus_object(obj) or is_zoom_object: - self._touched_objects.add(obj) self.show_focus = True def release_object(self, obj): - if obj in self._touched_objects: - self._touched_objects.remove(obj) + identifier = self.get_object_identifier(obj) + self._remove_changed_object(identifier) + if identifier in self._touched_identifiers: + self._touched_identifiers.remove(identifier) self.try_hide_focus() + def _remove_changed_object(self, identifier): + if identifier in self._changed_identifiers: + self._changed_identifiers.remove(identifier) + + def _remove_changed_object_delayed(self, identifier): + tasks = self._tasks + if tasks is not None: + tasks.add(task.sequence(task.wait(self.CHANGE_OBJECT_TIME), task.run(partial(self._remove_changed_object, identifier)))) + return + def change_object(self, obj): + identifier = self.get_object_identifier(obj) + self._changed_identifiers.add(identifier) + self._remove_changed_object_delayed(identifier) if self.focus_object(obj) or obj == self.get_zoom_object(): self.show_focus = True self.try_hide_focus_delayed() + def focus_region_of_interest(self, roi_identifier, focused_object): + roi = self.regions_of_interest[roi_identifier] + visible_region = roi.region_with_margin + self.set_visible_region(visible_region) + self.focus_object(focused_object) + if visible_region != self._waveform_region: + self._locked_roi = roi + def try_hide_focus(self): """ Hides the focus, if the focused object is not longer touched """ if self._should_hide_focus(): @@ -216,106 +546,117 @@ def try_hide_focus_delayed(self): self._hide_focus_task.restart() def _should_hide_focus(self): - return self.get_zoom_object() not in self._touched_objects and self._focused_object not in self._touched_objects + zoom_identifier = self.get_object_identifier(self.get_zoom_object()) + return zoom_identifier not in self._touched_identifiers and self._focused_identifier not in self._touched_identifiers def reset_focus_and_animation(self): self.show_focus = False self.animate_visible_region = False - self._touched_objects = set() + self._touched_identifiers = set() + self._changed_identifiers = set() + + def copy_state(self, navigation): + """ + Tries to replicate the state of the given waveform navigation. + The waveform regions need to be identical for this to make sense. + The focused identifier and all region of interests should be available + in both navigations, or the result will be undefined. + """ + if self._waveform_region == navigation.waveform_region: + self.set_visible_region(navigation.visible_region) + self._focused_identifier = navigation._focused_identifier + source_roi_name = navigation.get_name_for_roi(navigation._source_roi) + target_roi_name = navigation.get_name_for_roi(navigation._target_roi) + locked_roi_name = navigation.get_name_for_roi(navigation._locked_roi) + self._source_roi = self.regions_of_interest.get(source_roi_name, None) + self._target_roi = self.regions_of_interest.get(target_roi_name, None) + self._locked_roi = self.regions_of_interest.get(locked_roi_name, None) + return @lazy_attribute @depends(parent_task_group=const(None)) - def _tasks(self, parent_task_group = None): + def _tasks(self, parent_task_group=None): if parent_task_group is not None: tasks = parent_task_group.add(task.TaskGroup()) self._has_tasks = True return tasks + else: + return @lazy_attribute def _hide_focus_task(self): tasks = self._tasks if tasks is not None: return tasks.add(task.sequence(task.wait(EncoderControl.TOUCH_TIME), task.run(self.try_hide_focus))) + else: + return - def _add_margin_to_region(self, start, end): + def _add_margin_to_region(self, region): + start, end = region margin = self.RELATIVE_FOCUS_MARGIN start1 = (margin * start + end * margin - start) / (2 * margin - 1) + start1 = self._waveform_region.clamp_position(start1) end1 = (end - margin * start1) / (1 - margin) - start1 = clamp(start1, 0, self._length) - end1 = clamp(end1, 0, self._length) - return (start1, end1) - - def _get_position_for_identifier(self, identifier): - return self.focusable_object_connections[identifier].getter() - - def _make_region_from_region_identifiers(self, start_identifier, end_identifier): - return self._add_margin_to_region(self._get_position_for_identifier(start_identifier), self._get_position_for_identifier(end_identifier)) + end2 = (margin * start + end * margin - end) / (2 * margin - 1) + end2 = self._waveform_region.clamp_position(end2) + start2 = (start - margin * end2) / (1 - margin) + return Region(max(start1, start2), min(end1, end2)) def _make_region_from_position_identifier(self, identifier): - connection = self.focusable_object_connections[identifier] - align_right = connection.align_right - position = connection.getter() - length = self.MIN_VISIBLE_LENGTH + roi = self._get_roi_for_object_identifier(identifier) + align_right = roi.end_identifier == identifier + region = roi.region + position = region.end if align_right else region.start + length = self.get_min_visible_length() margin = self.RELATIVE_FOCUS_MARGIN * length if align_right: - right = min(position + margin, self._length) - left = max(right - length, 0) + right = self._waveform_region.clamp_position(position + margin) + left = self._waveform_region.clamp_position(right - length) else: - left = max(position - margin, 0) - right = min(left + length, self._length) - return (left, right) + left = self._waveform_region.clamp_position(position - margin) + right = self._waveform_region.clamp_position(left + length) + return Region(left, right) def _make_region_for_focused_object(self): - if self._focused_object is not None: - return self._make_region_from_position_identifier(self.get_object_identifier(self._focused_object)) - return (0, 0) - - def _region_inside(self, inner_region, outer_region): - outer_region = (int(outer_region[0]), int(outer_region[1])) - inner_region = (int(inner_region[0]), int(inner_region[1])) - return inner_region[0] >= outer_region[0] and inner_region[1] <= outer_region[1] and outer_region != inner_region - - def _visible_region_reaches(self, region): - region = (int(region[0]), int(region[1])) - visible_region = (int(self.visible_start), int(self.visible_end)) - return region == visible_region - - def _visible_region_inside(self, region): - return self._region_inside((self.visible_start, self.visible_end), region) + if self._focused_identifier is not None: + return self._make_region_from_position_identifier(self._focused_identifier) + else: + return Region(0, 0) - def _get_region_getters_for_focused_identifier(self): - focused_identifier = self.get_object_identifier(self._focused_object) - return self.focusable_object_connections[focused_identifier].region_getters + def _get_roi_for_focused_identifier(self): + if self._focused_identifier is not None: + return map(self.regions_of_interest.get, self.get_object_description(self._focused_identifier).regions) + else: + return [] - def _get_unique_region_getters(self): + def _get_unique_regions_of_interest(self): """ Eliminates duplicates of the current regions and returns the remaining getters sorted by the length of the regions. """ - getters = OrderedDict() - for region_getter in self._get_region_getters_for_focused_identifier(): - getters[region_getter()] = region_getter + rois = OrderedDict() + for roi in self._get_roi_for_focused_identifier(): + rois[roi.region_with_margin] = roi - return sorted(getters.values(), cmp=compare_region_length) + items = sorted(rois.items(), key=lambda (r, _): r.length, reverse=True) + return map(lambda item: item[1], items) def _select_region_around_visible_region(self): - region_getters = self._get_unique_region_getters() - source_getter = find_if(lambda g: self._visible_region_inside(g()), region_getters[1::-1]) - if source_getter is not None: - self._source_region_getter = source_getter - self._target_region_getter = region_getters[region_getters.index(source_getter) + 1] + regions_of_interest = self._get_unique_regions_of_interest() + source_roi = find_if(lambda roi: self.visible_region.inside(roi.region_with_margin), reversed(regions_of_interest[:-1])) + if source_roi is not None: + self._set_source_and_target_roi(source_roi, regions_of_interest[regions_of_interest.index(source_roi) + 1]) + return def _select_reached_region(self, zoom_in): - region_getters = self._get_unique_region_getters() - i = index_if(self._visible_region_reaches, imap(apply, region_getters)) - if i != len(region_getters): + rois = self._get_unique_regions_of_interest() + i = index_if(lambda roi: self.visible_region == roi.region_with_margin, rois) + if i != len(rois): if zoom_in: - if i < len(region_getters) - 1: - self._source_region_getter = region_getters[i] - self._target_region_getter = region_getters[i + 1] + if i < len(rois) - 1: + self._set_source_and_target_roi(rois[i], rois[i + 1]) elif i > 0: - self._source_region_getter = region_getters[i - 1] - self._target_region_getter = region_getters[i] + self._set_source_and_target_roi(rois[i - 1], rois[i]) return True return False @@ -323,114 +664,231 @@ def _select_region(self, zoom_in): if not self._select_reached_region(zoom_in): self._select_region_around_visible_region() self._request_select_region = False + self._unsnapping_value = 0 + def _set_source_and_target_roi(self, source_roi, target_roi): + self._source_roi = source_roi + self._target_roi = target_roi + if logger.isEnabledFor(logging.DEBUG): + self._report_current_source_and_target_roi() + + def _report_current_source_and_target_roi(self): + source_roi_name = '' + target_roi_name = '' + for name, roi in self.regions_of_interest.iteritems(): + if roi == self._source_roi: + source_roi_name = name + elif roi == self._target_roi: + target_roi_name = name + + logger.debug('Zooming between roi "%s" and "%s"' % ( + source_roi_name, target_roi_name)) -ObjectConnection = namedtuple('ObjectConnection', ['getter', - 'align_right', - 'focus_name', - 'region_getters', - 'boundary_getter']) class SimplerWaveformNavigation(WaveformNavigation): """ Extends the WaveformNavigation class by the concept of focusing parameters and slices. """ - selected_slice_focus = object() + selected_slice_focus = 'selected_slice' - def __init__(self, simpler = None, *a, **k): + def __init__(self, simpler=None, *a, **k): super(SimplerWaveformNavigation, self).__init__(*a, **k) self._simpler = simpler - focusable_parameters = [ self._simpler.get_parameter_by_name(n) for n in self.focusable_object_connections ] - self.__on_selected_slice_changed.subject = simpler.view + self._enable_focus_objects = True + focusable_parameters = [ self._simpler.get_parameter_by_name(n) for n in self.focusable_object_descriptions + ] + self.__on_playback_mode_changed.subject = simpler + self.__on_playing_position_enabled_changed.subject = simpler + self.__on_selected_slice_changed.subject = simpler.positions + self.__on_use_beat_time_changed.subject = simpler.positions + self.__on_warp_markers_changed.subject = simpler.positions + self.__on_before_update_all.subject = simpler.positions + self.__on_after_update_all.subject = simpler.positions self.__on_parameter_value_changed.replace_subjects(focusable_parameters) + self._update_waveform_region() + + def get_region_in_samples(self, region): + sample = self._simpler.sample + if liveobj_valid(sample) and sample.warping: + return Region(sample.beat_to_sample_time(region.start), sample.beat_to_sample_time(region.end)) + return region + + def get_min_visible_length(self): + """ + Tries to give a useful duration in beat time for warped samples. + """ + sample = self._simpler.sample + if sample.warping: + return sample.sample_to_beat_time(self.MIN_VISIBLE_SAMPLES) - sample.sample_to_beat_time(0) + return self.MIN_VISIBLE_SAMPLES + + @lazy_attribute + def additional_regions_of_interest(self): + return {'start_end_marker': self.make_region_of_interest(start_identifier='Start', end_identifier='End', getter=lambda : ( + self._simpler.positions.start_marker, + self._simpler.positions.end_marker)), + 'active_sample': self.make_region_of_interest(start_identifier='S Start', end_identifier='S Length', getter=lambda : ( + self._simpler.positions.active_start, + self._simpler.positions.active_end)), + 'loop': self.make_region_of_interest(start_identifier='S Loop Length', end_identifier='S Length', getter=lambda : ( + self._simpler.positions.loop_start, + self._simpler.positions.loop_end)), + 'selected_slice': self.make_region_of_interest(start_identifier=self.selected_slice_focus, end_identifier=None, getter=lambda : ( + self._simpler.positions.selected_slice.time, + self.get_next_slice_position())) + } @lazy_attribute - def focusable_object_connections(self): - return {'Start': ObjectConnection(lambda : self._simpler.sample.start_marker, False, 'start_marker', (lambda : (0, self._length), self.get_start_end_region, self._make_region_for_focused_object), partial(self._get_position_for_identifier, 'End')), - 'End': ObjectConnection(lambda : self._simpler.sample.end_marker, True, 'end_marker', (lambda : (0, self._length), self.get_start_end_region, self._make_region_for_focused_object), partial(self._get_position_for_identifier, 'Start')), - 'S Start': ObjectConnection(lambda : self._simpler.view.sample_start, False, 'position', (lambda : (0, self._length), - self.get_start_end_region, - self.get_sample_start_end_region, - self._make_region_for_focused_object), partial(self._get_position_for_identifier, 'S Length')), - 'S Length': ObjectConnection(lambda : self._simpler.view.sample_end, True, 'position', (lambda : (0, self._length), - self.get_start_end_region, - self.get_sample_start_end_region, - self._make_region_for_focused_object), partial(self._get_position_for_identifier, 'S Start')), - 'S Loop Length': ObjectConnection(lambda : self._simpler.view.sample_loop_start, False, 'position', (lambda : (0, self._length), - self.get_start_end_region, - self.get_sample_start_end_region, - self.get_sample_loop_start_end_region, - self._make_region_for_focused_object), partial(self._get_position_for_identifier, 'S Length')), - self.selected_slice_focus: ObjectConnection(lambda : self._simpler.view.selected_slice, False, '', (lambda : (0, self._length), - self.get_start_end_region, - self.get_slice_region, - self._make_region_for_focused_object), self.get_next_slice_position)} + def focusable_object_descriptions(self): + return {'Start': ObjectDescription(('start_end_marker', ), 'start_marker'), + 'End': ObjectDescription(('start_end_marker', ), 'end_marker'), + 'S Start': ObjectDescription(('start_end_marker', 'active_sample'), 'position'), + 'S Length': ObjectDescription(('start_end_marker', 'active_sample'), 'position'), + 'S Loop Length': ObjectDescription(('start_end_marker', 'active_sample', 'loop'), 'position'), + self.selected_slice_focus: ObjectDescription(('start_end_marker', 'selected_slice'), '') + } def get_object_identifier(self, obj): if hasattr(obj, 'name'): return obj.name - return self.selected_slice_focus + return obj def get_zoom_object(self): return self._simpler.zoom - def get_start_object_identifier(self): - return 'Start' - - def get_end_object_identifier(self): - return 'End' - - def get_sample_start_end_region(self): - return self._make_region_from_region_identifiers('S Start', 'S Length') + def get_next_slice_position(self): + positions = self._simpler.positions + slice_index = self._get_selected_slice_index() + min_visible_length = self.get_min_visible_length() + if slice_index == -1: + next_pos = positions.selected_slice.time + min_visible_length + elif slice_index + 1 < len(positions.slices): + next_pos = max(positions.slices[slice_index + 1].time, positions.selected_slice.time + min_visible_length) + else: + next_pos = max(positions.end_marker, positions.selected_slice.time + min_visible_length) + return next_pos - def get_sample_loop_start_end_region(self): - return self._make_region_from_region_identifiers('S Loop Length', 'S Length') + def object_changed(self, identifier1, identifier2): + if self.selected_slice_focus in (identifier1, identifier2) and self._get_selected_slice_index() == 0 and 'Start' in (identifier1, identifier2): + return False + return identifier1 != identifier2 - def get_next_slice_position(self): - selected_slice = self._get_selected_slice_index() - if selected_slice < 0 or selected_slice + 1 >= len(self._simpler.sample.slices): - return self._get_position_for_identifier(self.get_end_object_identifier()) - return self._simpler.sample.slices[selected_slice + 1] + def focus_object(self, obj): + if self._enable_focus_objects: + return super(SimplerWaveformNavigation, self).focus_object(obj) + return False - def get_slice_region(self): - return self._add_margin_to_region(self._get_position_for_identifier(self.selected_slice_focus), self.get_next_slice_position()) + @listens('playback_mode') + def __on_playback_mode_changed(self): + start_end_region = self.regions_of_interest['start_end_marker'].region + if start_end_region.inside(self.visible_region): + self.focus_object(self._simpler.get_parameter_by_name('Start')) + else: + self._focus_start_end_roi() @listens_group('value') def __on_parameter_value_changed(self, parameter): + self._simpler.positions.update_all() self.change_object(parameter) @listens('selected_slice') - def __on_selected_slice_changed(self): + def __on_selected_slice_changed(self, _): + self._focus_selected_slice() + + @listens('playing_position_enabled') + def __on_playing_position_enabled_changed(self): + slicing = self._simpler.playback_mode == Live.SimplerDevice.PlaybackMode.slicing + if slicing and self._simpler.playing_position_enabled: + self._focus_selected_slice() + + @listens('use_beat_time') + def __on_use_beat_time_changed(self, use_beat_time): + self._update_waveform_region_and_preserve_visible_region() + + @listens('warp_markers') + def __on_warp_markers_changed(self): + self._update_waveform_region_and_preserve_visible_region() + + @listens('before_update_all') + def __on_before_update_all(self): + self._enable_focus_objects = False + + @listens('after_update_all') + def __on_after_update_all(self): + self._enable_focus_objects = True + + def _update_waveform_region_and_preserve_visible_region(self): + sample = self._simpler.sample + region = self.visible_region_in_samples + self._update_waveform_region() + if sample.warping: + region = Region(sample.sample_to_beat_time(region.start), sample.sample_to_beat_time(region.end)) + self.set_visible_region(region) + + def _update_waveform_region(self): + self.waveform_region = Region(self._simpler.positions.start, self._simpler.positions.end) + + def _focus_selected_slice(self): slice_index = self._get_selected_slice_index() if slice_index != -1: - self.focus_object(slice_index) + self.focus_object(self.selected_slice_focus) + + def _focus_start_end_roi(self): + self.focus_region_of_interest('start_end_marker', self._simpler.get_parameter_by_name('Start')) def _get_selected_slice_index(self): + selected_slice_index = -1 try: - return self._simpler.sample.slices.index(self._simpler.view.selected_slice) + if liveobj_valid(self._simpler.sample): + selected_slice_index = self._simpler.sample.slices.index(self._simpler.view.selected_slice) except ValueError: pass - return -1 + return selected_slice_index class AudioClipWaveformNavigation(WaveformNavigation): """ WaveformNavigation that adds the concept of focus for audio clips to the. """ - zoom_focus = object() - start_marker_focus = object() - loop_start_focus = object() - loop_end_focus = object() + zoom_focus = 'zoom' + start_marker_focus = 'start_marker' + loop_start_focus = 'loop_start' + loop_end_focus = 'loop_end' - def __init__(self, clip = None, *a, **k): + def __init__(self, clip=None, *a, **k): super(AudioClipWaveformNavigation, self).__init__(*a, **k) self._clip = clip - self.__on_is_recording_changed.subject = clip + self._process_object_changes = True + self._connect_positions_property('loop_start', self.loop_start_focus) + self._connect_positions_property('loop_length', self.loop_end_focus) + self._connect_positions_property('start_marker', self.start_marker_focus) + self.__on_is_recording_changed.subject = clip.positions + self.__on_warp_markers_changed.subject = clip.positions + self.__on_use_beat_time_changed.subject = clip.positions + self.__on_before_update_all.subject = clip.positions + self.__on_after_update_all.subject = clip.positions + self._update_waveform_region() + + def _connect_positions_property(self, property_name, focus_object): + self.register_slot(self._clip.positions, lambda _: self.change_object(focus_object), property_name) + + @lazy_attribute + def additional_regions_of_interest(self): + return {'start_end_marker': self.make_region_of_interest(start_identifier=self.start_marker_focus, end_identifier=self.loop_end_focus, getter=lambda : ( + self._clip.positions.start_marker, + self._clip.positions.loop_end)), + 'loop': self.make_region_of_interest(start_identifier=self.loop_start_focus, end_identifier=self.loop_end_focus, getter=lambda : ( + self._clip.positions.loop_start, + self._clip.positions.loop_end)), + 'start_end': self.make_region_of_interest(getter=self._get_start_end_region) + } @lazy_attribute - def focusable_object_connections(self): - return {self.start_marker_focus: ObjectConnection(lambda : self._clip.view.sample_start_marker, False, 'start_marker', (lambda : (0, self._length), self.get_start_end_region, self._make_region_for_focused_object), partial(self._get_position_for_identifier, self.loop_end_focus)), - self.loop_start_focus: ObjectConnection(lambda : self._clip.view.sample_loop_start, False, 'position', (lambda : (0, self._length), self.get_start_end_region, self._make_region_for_focused_object), partial(self._get_position_for_identifier, self.loop_end_focus)), - self.loop_end_focus: ObjectConnection(lambda : self._clip.view.sample_loop_end, True, 'end_marker', (lambda : (0, self._length), self.get_start_end_region, self._make_region_for_focused_object), self._get_start_position)} + def focusable_object_descriptions(self): + return {self.start_marker_focus: ObjectDescription(('start_end', 'start_end_marker'), 'start_marker'), + self.loop_start_focus: ObjectDescription(('start_end', 'loop'), lambda : if self._clip.looping: +'position''start_marker'), + self.loop_end_focus: ObjectDescription(('start_end', ), 'end_marker') + } def get_object_identifier(self, obj): return obj @@ -438,21 +896,64 @@ def get_object_identifier(self, obj): def get_zoom_object(self): return self.zoom_focus - def get_start_object_identifier(self): - return self.loop_start_focus - - def get_end_object_identifier(self): - return self.loop_end_focus + def object_changed(self, identfier1, identifier2): + if self.start_marker_focus in (identfier1, identifier2) and self.loop_start_focus in (identfier1, identifier2) and self._clip.positions.start_marker == self._clip.positions.loop_start: + return False + return identfier1 != identifier2 - def _get_start_position(self): - start_marker_position = self._get_position_for_identifier(self.start_marker_focus) - loop_start_position = self._get_position_for_identifier(self.loop_start_focus) - return min(start_marker_position, loop_start_position) + def change_object(self, obj): + if self._process_object_changes: + self._clip.positions.update_all() + visible_length = self.visible_region.length + self._update_waveform_region() + self.set_visible_length(visible_length) + super(AudioClipWaveformNavigation, self).change_object(obj) + + def get_region_in_samples(self, region): + if self._clip.warping: + return Region(self._clip.beat_to_sample_time(region.start), self._clip.beat_to_sample_time(region.end)) + return region + + def get_min_visible_length(self): + """ + Tries to give a useful duration in beat time for warped audio clips. + """ + if self._clip.warping: + return self._clip.sample_to_beat_time(self.MIN_VISIBLE_SAMPLES) - self._clip.sample_to_beat_time(0) + return self.MIN_VISIBLE_SAMPLES - def get_start_end_region(self): - return self._add_margin_to_region(self._get_start_position(), self._get_position_for_identifier(self.loop_end_focus)) + def _get_start_end_region(self): + start_position = min(self._clip.positions.start_marker, self._clip.positions.loop_start) + return ( + start_position, self._clip.positions.loop_end) @listens('is_recording') def __on_is_recording_changed(self): - self._length = self._clip.view.sample_length - self.set_visible_region(0, self._length) \ No newline at end of file + self._update_waveform_region() + + @listens('warp_markers') + def __on_warp_markers_changed(self): + self._update_waveform_region_and_preserve_visible_region() + + @listens('use_beat_time') + def __on_use_beat_time_changed(self, use_beat_time): + self._update_waveform_region_and_preserve_visible_region() + + @listens('before_update_all') + def __on_before_update_all(self): + self._process_object_changes = False + + @listens('after_update_all') + def __on_after_update_all(self): + self._process_object_changes = True + self._request_select_region = True + + def _update_waveform_region_and_preserve_visible_region(self): + region = self.visible_region_in_samples + self._update_waveform_region() + if self._clip.warping: + region = Region(self._clip.sample_to_beat_time(region.start), self._clip.sample_to_beat_time(region.end)) + self.set_visible_region(region) + + def _update_waveform_region(self): + self.waveform_region = Region(min(self._clip.positions.start, self._clip.positions.start_marker, self._clip.positions.loop_start), max(self._clip.positions.end, self._clip.positions.loop_end)) \ No newline at end of file diff --git a/pushbase/__init__.py b/pushbase/__init__.py index e068acea..73f52bfd 100644 --- a/pushbase/__init__.py +++ b/pushbase/__init__.py @@ -1,2 +1,7 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/__init__.py +# uncompyle6 version 2.9.10 +# Python bytecode 2.7 (62211) +# Decompiled from: Python 2.7.13 (default, Dec 17 2016, 23:03:43) +# [GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.42.1)] +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/__init__.py +# Compiled at: 2016-05-20 03:43:52 from __future__ import absolute_import, print_function \ No newline at end of file diff --git a/pushbase/accent_component.py b/pushbase/accent_component.py index 9e5dbd72..5b5227c2 100644 --- a/pushbase/accent_component.py +++ b/pushbase/accent_component.py @@ -1,4 +1,9 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/accent_component.py +# uncompyle6 version 2.9.10 +# Python bytecode 2.7 (62211) +# Decompiled from: Python 2.7.13 (default, Dec 17 2016, 23:03:43) +# [GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.42.1)] +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/accent_component.py +# Compiled at: 2016-05-20 03:43:52 from __future__ import absolute_import, print_function from ableton.v2.control_surface.mode import ModesComponent @@ -12,9 +17,11 @@ def __init__(self, *a, **k): super(AccentComponent, self).__init__(*a, **k) self._full_velocity = None self.add_mode('disabled', None, 'Accent.Off') - self.add_mode('enabled', (self._on_accent_on, self._on_accent_off), 'Accent.On') + self.add_mode('enabled', ( + self._on_accent_on, self._on_accent_off), 'Accent.On') self.selected_mode = 'disabled' self.set_full_velocity(None) + return def set_full_velocity(self, full_velocity): full_velocity = full_velocity or DummyFullVelocity() @@ -22,6 +29,7 @@ def set_full_velocity(self, full_velocity): self._full_velocity.enabled = False self._full_velocity = full_velocity self._full_velocity.enabled = self.selected_mode == 'enabled' + return @property def activated(self): diff --git a/pushbase/action_with_options_component.py b/pushbase/action_with_options_component.py index 4736e69d..48c19e87 100644 --- a/pushbase/action_with_options_component.py +++ b/pushbase/action_with_options_component.py @@ -1,4 +1,9 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/action_with_options_component.py +# uncompyle6 version 2.9.10 +# Python bytecode 2.7 (62211) +# Decompiled from: Python 2.7.13 (default, Dec 17 2016, 23:03:43) +# [GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.42.1)] +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/action_with_options_component.py +# Compiled at: 2016-05-20 03:43:52 from __future__ import absolute_import, print_function from itertools import izip_longest from ableton.v2.base import in_range, clamp, task @@ -57,13 +62,13 @@ def action_button(self, button): class OptionsComponent(Component): - __events__ = ('selected_option',) + __events__ = ('selected_option', ) unselected_color = 'Option.Unselected' selected_color = 'Option.Selected' _selected_option = None select_buttons = control_list(ButtonControl, control_count=0) - def __init__(self, num_options = 8, num_labels = 4, num_display_segments = None, *a, **k): + def __init__(self, num_options=8, num_labels=4, num_display_segments=None, *a, **k): super(OptionsComponent, self).__init__(*a, **k) num_display_segments = num_display_segments or num_options self._label_data_sources = [ DisplayDataSource() for _ in xrange(num_labels) ] @@ -90,10 +95,11 @@ def _get_selected_option(self): return self._selected_option def _set_selected_option(self, selected_option): - raise in_range(selected_option, 0, len(self.option_names)) or selected_option is None or AssertionError + assert in_range(selected_option, 0, len(self.option_names)) or selected_option is None self._selected_option = selected_option self._update_select_buttons() self._update_data_sources() + return selected_option = property(_get_selected_option, _set_selected_option) @@ -152,7 +158,7 @@ def _update_data_sources(self): class ActionWithOptionsComponent(ActionWithSettingsComponent): - def __init__(self, num_options = 8, *a, **k): + def __init__(self, num_options=8, *a, **k): super(ActionWithOptionsComponent, self).__init__(*a, **k) self._options = self.register_component(OptionsComponent(num_options=num_options)) self._options.set_enabled(False) diff --git a/pushbase/actions.py b/pushbase/actions.py index da2e4eb0..590388c7 100644 --- a/pushbase/actions.py +++ b/pushbase/actions.py @@ -1,13 +1,18 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/actions.py +# uncompyle6 version 2.9.10 +# Python bytecode 2.7 (62211) +# Decompiled from: Python 2.7.13 (default, Dec 17 2016, 23:03:43) +# [GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.42.1)] +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/actions.py +# Compiled at: 2016-09-29 19:13:24 from __future__ import absolute_import, print_function from itertools import izip, count import Live -from ableton.v2.base import forward_property, listens, listens_group, liveobj_valid +from ableton.v2.base import forward_property, listens, listens_group, liveobj_changed, liveobj_valid from ableton.v2.control_surface import Component, CompoundComponent from ableton.v2.control_surface.control import control_list, ButtonControl from ableton.v2.control_surface.elements import DisplayDataSource from .action_with_options_component import ActionWithSettingsComponent -from .clip_control_component import convert_length_to_bars_beats_sixteenths +from .clip_control_component import convert_beat_length_to_bars_beats_sixteenths from .consts import MessageBoxText, SIDE_BUTTON_COLORS from .message_box_component import Messenger AutomationState = Live.DeviceParameter.AutomationState @@ -16,49 +21,74 @@ def convert_length_to_mins_secs(length_in_secs): if length_in_secs is None: return '-' - mins = int(length_in_secs / 60.0) - secs = int(length_in_secs % 60.0) - return str(mins) + ':' + str('%02d' % secs) + else: + mins = int(length_in_secs / 60.0) + secs = int(length_in_secs % 60.0) + return str(mins) + ':' + str('%02d' % secs) -def convert_beats_to_mins_secs(length_in_beats, tempo = 120.0): +def convert_beats_to_mins_secs(length_in_beats, tempo=120.0): if length_in_beats is None: return '-' - length_in_secs = length_in_beats / tempo * 60.0 - return convert_length_to_mins_secs(length_in_secs) + else: + length_in_secs = length_in_beats / tempo * 60.0 + return convert_length_to_mins_secs(length_in_secs) + + +def duplicate_arrangement_clip(clip, song_view, show_notification): + try: + track = clip.canonical_parent + song_view.detail_clip = track.duplicate_clip_to_arrangement(clip, clip.end_time) + show_notification(MessageBoxText.DUPLICATE_CLIP % clip.name) + except RuntimeError: + show_notification(MessageBoxText.CLIP_DUPLICATION_FAILED) class CaptureAndInsertSceneComponent(ActionWithSettingsComponent, Messenger): - def post_trigger_action(self): + def _capture_and_insert_scene(self): try: self.song.capture_and_insert_scene() self.show_notification(MessageBoxText.CAPTURE_AND_INSERT_SCENE % self.song.view.selected_scene.name.strip()) except Live.Base.LimitationError: self.expect_dialog(MessageBoxText.SCENE_LIMIT_REACHED) + def post_trigger_action(self): + view = self.song.view + clip = view.detail_clip + if liveobj_valid(clip) and clip.is_arrangement_clip: + duplicate_arrangement_clip(clip, view, self.show_notification) + else: + self._capture_and_insert_scene() + class DuplicateDetailClipComponent(ActionWithSettingsComponent, Messenger): + def _duplicate_session_clip(self, clip, view): + try: + slot = clip.canonical_parent + track = slot.canonical_parent + start_duplicate = clip.is_playing + target_index = list(track.clip_slots).index(slot) + destination_index = track.duplicate_clip_slot(target_index) + view.highlighted_clip_slot = track.clip_slots[destination_index] + view.detail_clip = view.highlighted_clip_slot.clip + if start_duplicate: + view.highlighted_clip_slot.fire(force_legato=True, launch_quantization=_Q.q_no_q) + self.show_notification(MessageBoxText.DUPLICATE_CLIP % clip.name) + except Live.Base.LimitationError: + self.expect_dialog(MessageBoxText.SCENE_LIMIT_REACHED) + except RuntimeError: + self.show_notification(MessageBoxText.CLIP_DUPLICATION_FAILED) + def post_trigger_action(self): view = self.song.view clip = view.detail_clip if liveobj_valid(clip): - slot = clip.canonical_parent - track = slot.canonical_parent - try: - start_duplicate = clip.is_playing - target_index = list(track.clip_slots).index(slot) - destination_index = track.duplicate_clip_slot(target_index) - view.highlighted_clip_slot = track.clip_slots[destination_index] - view.detail_clip = view.highlighted_clip_slot.clip - if start_duplicate: - view.highlighted_clip_slot.fire(force_legato=True, launch_quantization=_Q.q_no_q) - self.show_notification(MessageBoxText.DUPLICATE_CLIP % clip.name) - except Live.Base.LimitationError: - self.expect_dialog(MessageBoxText.SCENE_LIMIT_REACHED) - except RuntimeError: - self.show_notification(MessageBoxText.CLIP_DUPLICATION_FAILED) + if clip.is_arrangement_clip: + duplicate_arrangement_clip(clip, view, self.show_notification) + else: + self._duplicate_session_clip(clip, view) class DuplicateLoopComponent(ActionWithSettingsComponent, Messenger): @@ -83,7 +113,8 @@ def trigger_action(self): if liveobj_valid(clip): try: clip.duplicate_loop() - self.show_notification(MessageBoxText.DUPLICATE_LOOP % dict(length=convert_length_to_bars_beats_sixteenths(clip.loop_end - clip.loop_start))) + self.show_notification(MessageBoxText.DUPLICATE_LOOP % dict(length=convert_beat_length_to_bars_beats_sixteenths(( + clip.signature_numerator, clip.signature_denominator), clip.loop_end - clip.loop_start))) except RuntimeError: pass @@ -94,11 +125,21 @@ class DeleteSelectedClipComponent(ActionWithSettingsComponent, Messenger): """ def post_trigger_action(self): - slot = self.song.view.highlighted_clip_slot - if liveobj_valid(slot) and slot.has_clip: - name = slot.clip.name - slot.delete_clip() - self.show_notification(MessageBoxText.DELETE_CLIP % name) + clip = self.song.view.detail_clip + if liveobj_valid(clip) and clip.is_arrangement_clip: + try: + name = clip.name + self.song.view.selected_track.delete_clip(clip) + self.show_notification(MessageBoxText.DELETE_CLIP % name) + except RuntimeError: + pass + + else: + slot = self.song.view.highlighted_clip_slot + if liveobj_valid(slot) and slot.has_clip: + name = slot.clip.name + slot.delete_clip() + self.show_notification(MessageBoxText.DELETE_CLIP % name) class DeleteSelectedSceneComponent(ActionWithSettingsComponent, Messenger): @@ -137,7 +178,9 @@ def set_display_line(self, display_line): for idx in xrange(self.num_segments): display_line.segment(idx).set_data_source(self._data_sources[idx]) - def set_display_string(self, string, segment = 0): + return + + def set_display_string(self, string, segment=0): if segment < self.num_segments: self._data_sources[segment].set_display_string(string) @@ -151,14 +194,27 @@ def reset_display_right(self): def select_clip_and_get_name_from_slot(clip_slot, song): - clip_name = '[none]' if liveobj_valid(clip_slot): if song.view.highlighted_clip_slot != clip_slot: song.view.highlighted_clip_slot = clip_slot + if clip_slot.has_clip and liveobj_changed(song.view.detail_clip, clip_slot.clip): + song.view.detail_clip = clip_slot.clip + return clip_name_from_clip_slot(clip_slot) + + +def get_clip_name(clip): + if clip.name != '': + return clip.name + return '[unnamed]' + + +def clip_name_from_clip_slot(clip_slot): + clip_name = '[none]' + if liveobj_valid(clip_slot): clip = clip_slot.clip clip_name = '[empty slot]' if liveobj_valid(clip): - clip_name = clip.name if clip.name != '' else '[unnamed]' + clip_name = get_clip_name(clip) return clip_name @@ -182,6 +238,7 @@ def __init__(self, *a, **k): self._selected_clip = None self._selection_display = self.register_component(SelectionDisplayComponent()) self._selection_display.set_enabled(False) + return selection_display_layer = forward_property('_selection_display')('layer') @@ -208,7 +265,8 @@ def _do_show_time_remaining(self): if clip.is_recording: label = 'Record Count:' length = (clip.playing_position - clip.loop_start) * clip.signature_denominator / clip.signature_numerator - time = convert_length_to_bars_beats_sixteenths(length) + time = convert_beat_length_to_bars_beats_sixteenths(( + clip.signature_numerator, clip.signature_denominator), length) else: label = 'Time Remaining:' length = clip.loop_end - clip.playing_position @@ -254,6 +312,7 @@ def select_button(self, control): self._selection_display.set_enabled(False) self._selection_display.reset_display() self.set_selected_clip(None) + return class DeleteComponent(Component, Messenger): @@ -264,6 +323,7 @@ class DeleteComponent(Component, Messenger): def __init__(self, *a, **k): super(DeleteComponent, self).__init__(*a, **k) self._delete_button = None + return def set_delete_button(self, button): self._delete_button = button @@ -306,7 +366,7 @@ class StopClipComponent(Component): stop_all_clips_button = ButtonControl() stop_track_clips_buttons = control_list(ButtonControl, color='Session.StoppedClip') - def __init__(self, session_ring = None, *a, **k): + def __init__(self, session_ring=None, *a, **k): super(StopClipComponent, self).__init__(*a, **k) self._track_provider = session_ring self._on_tracks_changed.subject = self._track_provider @@ -333,7 +393,8 @@ def _on_tracks_changed(self): self._update_listeners() def _update_listeners(self): - tracks = [ track for track in self._track_provider.controlled_tracks() if isinstance(track, Live.Track.Track) ] + tracks = [ track for track in self._track_provider.controlled_tracks() if isinstance(track, Live.Track.Track) + ] self._assign_listeners(tracks) self._update_all_stop_buttons() @@ -369,7 +430,7 @@ def _color_for_button(self, track): return color def _update_stop_button(self, track, button): - has_clip_slots = isinstance(track, Live.Track.Track) and bool(track.clip_slots) + has_clip_slots = liveobj_valid(track) and isinstance(track, Live.Track.Track) and bool(track.clip_slots) if has_clip_slots: button.color = self._color_for_button(track) button.enabled = bool(has_clip_slots) diff --git a/pushbase/auto_arm_component.py b/pushbase/auto_arm_component.py index b96608d5..cb028c11 100644 --- a/pushbase/auto_arm_component.py +++ b/pushbase/auto_arm_component.py @@ -1,4 +1,9 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/auto_arm_component.py +# uncompyle6 version 2.9.10 +# Python bytecode 2.7 (62211) +# Decompiled from: Python 2.7.13 (default, Dec 17 2016, 23:03:43) +# [GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.42.1)] +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/auto_arm_component.py +# Compiled at: 2016-09-29 19:13:24 """ Component that automatically arms the selected track. """ @@ -21,11 +26,12 @@ class AutoArmRestoreBehaviour(LatchingBehaviour): condition changes. """ - def __init__(self, auto_arm = None, *a, **k): + def __init__(self, auto_arm=None, *a, **k): super(AutoArmRestoreBehaviour, self).__init__(*a, **k) self._auto_arm = auto_arm self._last_update_params = None self._skip_super = False + return def press_immediate(self, component, mode): called_super = False @@ -75,12 +81,13 @@ def __init__(self, *a, **k): self._on_tracks_changed() self._notification_reference = partial(nop, None) self._on_selected_track_changed.subject = self.song.view + return def auto_arm_restore_behaviour(self, *extra_classes, **extra_params): if not self._auto_arm_restore_behaviour: self._auto_arm_restore_behaviour = mixin(AutoArmRestoreBehaviour, *extra_classes)(auto_arm=self, **extra_params) else: - raise not extra_params and not extra_classes or AssertionError + assert not extra_params and not extra_classes return self._auto_arm_restore_behaviour def track_can_be_armed(self, track): @@ -102,6 +109,7 @@ def _update_notification(self): def _hide_notification(self): if self._notification_reference() is not None: self._notification_reference().hide() + return def update(self): super(AutoArmComponent, self).update() @@ -134,7 +142,7 @@ def needs_restore_auto_arm(self): def _on_tracks_changed(self): tracks = filter(lambda t: t.can_be_armed, self.song.tracks) self._on_arm_changed.replace_subjects(tracks) - self._on_current_input_routing_changed.replace_subjects(tracks) + self._on_input_routing_type_changed.replace_subjects(tracks) self._on_frozen_state_changed.replace_subjects(tracks) @listens('exclusive_arm') @@ -145,8 +153,8 @@ def _on_exclusive_arm_changed(self): def _on_arm_changed(self, track): self.update() - @listens_group('current_input_routing') - def _on_current_input_routing_changed(self, track): + @listens_group('input_routing_type') + def _on_input_routing_type_changed(self, track): self.update() @listens_group('is_frozen') diff --git a/pushbase/automation_component.py b/pushbase/automation_component.py index 677c808c..f9d0242c 100644 --- a/pushbase/automation_component.py +++ b/pushbase/automation_component.py @@ -1,4 +1,9 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/automation_component.py +# uncompyle6 version 2.9.10 +# Python bytecode 2.7 (62211) +# Decompiled from: Python 2.7.13 (default, Dec 17 2016, 23:03:43) +# [GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.42.1)] +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/automation_component.py +# Compiled at: 2016-05-20 03:43:52 from __future__ import absolute_import, print_function import Live from ableton.v2.base import clamp, task, liveobj_valid @@ -44,14 +49,16 @@ def _set_selected_time(self, value): @property def parameters(self): - return map(lambda info: (info.parameter if info else None), self._parameter_infos_to_use()) + return map(lambda info: if info: +info.parameterNone, self._parameter_infos_to_use()) @property def parameter_infos(self): return self._parameter_infos_to_use() def _parameter_infos_to_use(self): - return map(lambda info: (info if self.parameter_is_automateable(info.parameter if info else None) else None), self._parameter_provider.parameters) + return map(lambda info: if self.parameter_is_automateable(info.parameter if info else None): +infoNone, self._parameter_provider.parameters) @property def can_automate_parameters(self): @@ -120,10 +127,13 @@ def encoders(self, encoder): def _update_parameter_floats(self): if self._clip and self.is_enabled(): parameters = self.parameters - envelopes = [ (self._clip.automation_envelope(self._parameter_for_index(parameters, index)) if param != None else None) for index, param in enumerate(parameters) ] - self._parameter_floats = [ [ (self._value_at_time(envelope, step) if envelope != None else 0.0) for envelope in envelopes ] for step in self.selected_time ] + envelopes = [ (self._clip.automation_envelope(self._parameter_for_index(parameters, index)) if param != None else None) for index, param in enumerate(parameters) + ] + self._parameter_floats = [ [ (self._value_at_time(envelope, step) if envelope != None else 0.0) for envelope in envelopes ] for step in self.selected_time + ] else: self._parameter_floats = [] + return def _insert_step(self, time_range, time_index, param_index, envelope, value): param = self._parameter_for_index(self.parameters, param_index) diff --git a/pushbase/banking_util.py b/pushbase/banking_util.py index 713c9c44..bc4dcd40 100644 --- a/pushbase/banking_util.py +++ b/pushbase/banking_util.py @@ -1,4 +1,9 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/banking_util.py +# uncompyle6 version 2.9.10 +# Python bytecode 2.7 (62211) +# Decompiled from: Python 2.7.13 (default, Dec 17 2016, 23:03:43) +# [GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.42.1)] +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/banking_util.py +# Compiled at: 2016-05-20 03:43:52 from __future__ import absolute_import, print_function from math import ceil from copy import deepcopy @@ -49,7 +54,7 @@ def all_parameters(device): return [] -def device_bank_count(device, bank_size = 8, definition = None, definitions = None): +def device_bank_count(device, bank_size=8, definition=None, definitions=None): count = 0 if liveobj_valid(device): definition = definition or definitions.get(device.class_name, {}) @@ -68,7 +73,7 @@ def device_bank_definition(device, definitions): return definition -def device_bank_names(device, bank_size = 8, definitions = None): +def device_bank_names(device, bank_size=8, definitions=None): names = [] if liveobj_valid(device): class_name = device.class_name @@ -76,7 +81,8 @@ def device_bank_names(device, bank_size = 8, definitions = None): names = definitions[class_name].keys() elif has_bank_count(device) and has_bank_names(device, definitions): offset = int(has_main_bank(device, definitions)) - names = [ device.get_bank_name(index - offset) for index in xrange(device_bank_count(device, definitions=definitions)) ] + names = [ device.get_bank_name(index - offset) for index in xrange(device_bank_count(device, definitions=definitions)) + ] if has_main_bank(device, definitions) and not names[0]: names[0] = MAIN_KEY else: diff --git a/pushbase/browser_modes.py b/pushbase/browser_modes.py index c9c3aeb9..4b26b8d9 100644 --- a/pushbase/browser_modes.py +++ b/pushbase/browser_modes.py @@ -1,4 +1,9 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/browser_modes.py +# uncompyle6 version 2.9.10 +# Python bytecode 2.7 (62211) +# Decompiled from: Python 2.7.13 (default, Dec 17 2016, 23:03:43) +# [GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.42.1)] +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/browser_modes.py +# Compiled at: 2016-05-20 03:43:52 """ Different mode objects that turn live into different browsing modes. """ @@ -16,7 +21,7 @@ def can_browse_for_object(obj): class BrowserHotswapMode(Mode): @depends(selection=None) - def __init__(self, selection = None, application = None, *a, **k): + def __init__(self, selection=None, application=None, *a, **k): super(BrowserHotswapMode, self).__init__(*a, **k) self._selection = selection self._application = application @@ -35,6 +40,7 @@ def enter_mode(self): def leave_mode(self): self._application.browser.hotswap_target = None + return def _set_hotswap_target(self, hotswap_object): self._application.browser.hotswap_target = hotswap_object @@ -45,7 +51,7 @@ class BrowserAddEffectMode(Mode): insert_left = False @depends(selection=None) - def __init__(self, selection = None, browser = None, insert_left = None, application_view = None, *a, **k): + def __init__(self, selection=None, browser=None, insert_left=None, application_view=None, *a, **k): super(BrowserAddEffectMode, self).__init__(*a, **k) self._selection = selection self._browser = browser @@ -54,6 +60,7 @@ def __init__(self, selection = None, browser = None, insert_left = None, applica self._selection_for_insert = None if insert_left is not None: self.insert_left = insert_left + return def enter_mode(self): self._track_to_add_effect = self._selection.selected_track @@ -62,6 +69,7 @@ def enter_mode(self): self._browser.filter_type = self.get_filter_type() if self._application_view.browse_mode: self._browser.hotswap_target = None + return def leave_mode(self): disabled = Live.Track.DeviceInsertMode.default @@ -93,9 +101,10 @@ def get_filter_type(self): else: right = chain.devices[index + 1] if index < chain_len - 1 else None return filter_type_between(selected, right, midi_support, is_drum_pad, supports_instrument) + return -def filter_type_between(left, right, supports_midi = False, is_drum_pad = False, supports_instrument = False): +def filter_type_between(left, right, supports_midi=False, is_drum_pad=False, supports_instrument=False): """ Given 'left' and 'right' are two consecutive devices in a valid device chain, returns the appropriate browser filter type for valid @@ -122,4 +131,5 @@ def filter_type_between(left, right, supports_midi = False, is_drum_pad = False, return Types.instrument_hotswap else: return Types.midi_effect_hotswap + return Types.audio_effect_hotswap \ No newline at end of file diff --git a/pushbase/browser_util.py b/pushbase/browser_util.py index 37416078..a4f5f77c 100644 --- a/pushbase/browser_util.py +++ b/pushbase/browser_util.py @@ -1,10 +1,15 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/browser_util.py +# uncompyle6 version 2.9.10 +# Python bytecode 2.7 (62211) +# Decompiled from: Python 2.7.13 (default, Dec 17 2016, 23:03:43) +# [GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.42.1)] +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/browser_util.py +# Compiled at: 2016-05-20 03:43:52 from __future__ import absolute_import, print_function import Live FilterType = Live.Browser.FilterType DeviceType = Live.Device.DeviceType -def filter_type_for_hotswap_target(target, default = FilterType.disabled): +def filter_type_for_hotswap_target(target, default=FilterType.disabled): """ Returns the appropriate browser filter type for a given hotswap target. """ @@ -26,7 +31,7 @@ def filter_type_for_hotswap_target(target, default = FilterType.disabled): return default -def get_selection_for_new_device(selection, insert_left = False): +def get_selection_for_new_device(selection, insert_left=False): """ Returns a device, depending on the type of object that is selected at this moment. For drum pads, it returns the last device in the pads chain. diff --git a/pushbase/clip_control_component.py b/pushbase/clip_control_component.py index 67ba4eda..487efec8 100644 --- a/pushbase/clip_control_component.py +++ b/pushbase/clip_control_component.py @@ -1,7 +1,12 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/clip_control_component.py +# uncompyle6 version 2.9.10 +# Python bytecode 2.7 (62211) +# Decompiled from: Python 2.7.13 (default, Dec 17 2016, 23:03:43) +# [GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.42.1)] +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/clip_control_component.py +# Compiled at: 2016-05-20 03:43:52 from __future__ import absolute_import, print_function import Live -from ableton.v2.base import clamp, listens, liveobj_valid, nop, Subject, SlotManager, forward_property +from ableton.v2.base import clamp, listens, liveobj_valid, nop, EventObject, forward_property, listenable_property from ableton.v2.control_surface import Component from ableton.v2.control_surface.control import ButtonControl, control_list, EncoderControl, StepEncoderControl from ableton.v2.control_surface.mode import ModesComponent @@ -9,7 +14,8 @@ ONE_THIRTYSECOND_IN_BEATS = 0.125 ONE_SIXTEENTH_IN_BEATS = 0.25 ONE_YEAR_AT_120BPM_IN_BEATS = 63072000.0 -GRID_QUANTIZATION_LIST = [Live.Clip.GridQuantization.no_grid, +GRID_QUANTIZATION_LIST = [ + Live.Clip.GridQuantization.no_grid, Live.Clip.GridQuantization.g_thirtysecond, Live.Clip.GridQuantization.g_sixteenth, Live.Clip.GridQuantization.g_eighth, @@ -20,54 +26,64 @@ Live.Clip.GridQuantization.g_4_bars, Live.Clip.GridQuantization.g_8_bars] WARP_MODE_NAMES = {Live.Clip.WarpMode.beats: 'Beats', - Live.Clip.WarpMode.tones: 'Tones', - Live.Clip.WarpMode.texture: 'Texture', - Live.Clip.WarpMode.repitch: 'Repitch', - Live.Clip.WarpMode.complex: 'Complex', - Live.Clip.WarpMode.complex_pro: 'Pro', - Live.Clip.WarpMode.rex: 'Rex'} - -def convert_time_to_bars_beats_sixteenths(time): - if time is None: + Live.Clip.WarpMode.tones: 'Tones', + Live.Clip.WarpMode.texture: 'Texture', + Live.Clip.WarpMode.repitch: 'Repitch', + Live.Clip.WarpMode.complex: 'Complex', + Live.Clip.WarpMode.complex_pro: 'Pro', + Live.Clip.WarpMode.rex: 'Rex' + } + +def convert_beat_time_to_bars_beats_sixteenths((numerator, denominator), beat_time): + if beat_time is None: return '-' - if time >= 0: - bars = 1 + int(time / 4.0) else: - bars = int(time / 4.0) if time % 4.0 == 0 else int(time / 4.0) - 1 - beats = 1 + int(time % 4.0) - sixteenths = 1 + int(time % 1.0 * 4) - return str(bars) + '.' + str(beats) + '.' + str(sixteenths) + beats_per_bar = one_bar_in_note_values((numerator, denominator), 4.0) + musical_beats_per_beat = denominator / 4.0 + if beat_time >= 0: + bars = 1 + int(beat_time / beats_per_bar) + else: + bars = int(beat_time / beats_per_bar) if beat_time % beats_per_bar == 0 else int(beat_time / beats_per_bar) - 1 + beats = 1 + int(beat_time % beats_per_bar * musical_beats_per_beat) + sixteenths = 1 + int(beat_time % (1.0 / musical_beats_per_beat) * 4.0) + return '%i.%i.%i' % (bars, beats, sixteenths) -def convert_length_to_bars_beats_sixteenths(length): - if length is None: +def convert_beat_length_to_bars_beats_sixteenths((numerator, denominator), beat_length): + if beat_length is None: return '-' - bars = int(length / 4.0) - beats = int(length % 4.0) - sixteenths = int(length % 1.0 * 4) - return str(bars) + '.' + str(beats) + '.' + str(sixteenths) + else: + beats_per_bar = one_bar_in_note_values((numerator, denominator), 4.0) + musical_beats_per_beat = denominator / 4.0 + bars = int(beat_length / beats_per_bar) + beats = int(beat_length % beats_per_bar * musical_beats_per_beat) + sixteenths = int(beat_length % (1.0 / musical_beats_per_beat) * 4.0) + return '%i.%i.%i' % (bars, beats, sixteenths) def is_new_recording(clip): return clip.is_recording and not clip.is_overdubbing -def one_measure_in_note_values(clip, note_value = 4.0): - return note_value * clip.signature_numerator / clip.signature_denominator +def one_bar_in_note_values((numerator, denominator), note_value=4.0): + return note_value * numerator / denominator -class LoopSettingsModel(Subject, SlotManager): +class LoopSettingsModel(EventObject): __events__ = ('looping', 'loop_start', 'loop_end', 'loop_length', 'position', 'start_marker') def __init__(self, song, *a, **k): super(LoopSettingsModel, self).__init__(*a, **k) self.clip = None self._song = song + return - def _get_clip(self): + @listenable_property + def clip(self): return self._clip - def _set_clip(self, clip): + @clip.setter + def clip(self, clip): self._clip = clip self._loop_length = self._get_loop_length() self._on_looping_changed.subject = clip @@ -75,8 +91,8 @@ def _set_clip(self, clip): self._on_loop_start_changed.subject = clip self._on_loop_end_changed.subject = clip self._on_position_changed.subject = clip + self.notify_clip() - clip = property(_get_clip, _set_clip) loop_start = forward_property('clip')('loop_start') start_marker = forward_property('clip')('start_marker') loop_end = forward_property('clip')('loop_end') @@ -127,8 +143,10 @@ def can_loop(self): def move_start_marker(self, value, fine_grained): marker = self.clip.start_marker if self.looping else self.clip.loop_start new_value = marker + self._adjusted_offset(value, fine_grained) - measure_in_beats = one_measure_in_note_values(self.clip) - measure_in_sixteenths = one_measure_in_note_values(self.clip, 16.0) + signature = ( + self.clip.signature_numerator, self.clip.signature_denominator) + measure_in_beats = one_bar_in_note_values(signature) + measure_in_sixteenths = one_bar_in_note_values(signature, 16.0) additional_offset = measure_in_beats / measure_in_sixteenths * (measure_in_sixteenths - 1) if fine_grained else 0.0 new_value = min(new_value, self.clip.loop_end - measure_in_beats + additional_offset) if self.looping: @@ -150,11 +168,13 @@ def move_loop_end(self, value, fine_grained): self.clip.loop_end = new_end def _adjusted_offset(self, value, fine_grained): - return value * self._encoder_factor(fine_grained) * one_measure_in_note_values(self.clip) + return value * self._encoder_factor(fine_grained) * one_bar_in_note_values(( + self.clip.signature_numerator, + self.clip.signature_denominator)) def _encoder_factor(self, fine_grained): if fine_grained: - return 1.0 / one_measure_in_note_values(self.clip, 16.0) + return 1.0 / one_bar_in_note_values((self.clip.signature_numerator, self.clip.signature_denominator), 16.0) return 1.0 @@ -164,27 +184,33 @@ class LoopSettingsControllerComponent(Component): def __init__(self, *a, **k): super(LoopSettingsControllerComponent, self).__init__(*a, **k) - self._encoder_callbacks_looped = [self._on_clip_position_value, + self._encoder_callbacks_looped = [ + self._on_clip_position_value, self._on_clip_end_value, self._on_clip_start_marker_value, self._on_clip_looping_value] - self._encoder_callbacks_unlooped = [self._on_clip_start_marker_value, + self._encoder_callbacks_unlooped = [ + self._on_clip_start_marker_value, self._on_clip_end_value, nop, self._on_clip_looping_value] - self._touched_encoder_callbacks_looped = [self._on_clip_position_touched, + self._touched_encoder_callbacks_looped = [ + self._on_clip_position_touched, self._on_clip_end_touched, self._on_clip_start_marker_touched, self._on_clip_looping_touched] - self._touched_encoder_callbacks_unlooped = [self._on_clip_start_marker_touched, + self._touched_encoder_callbacks_unlooped = [ + self._on_clip_position_touched, self._on_clip_end_touched, nop, self._on_clip_looping_touched] - self._released_encoder_callbacks_looped = [self._on_clip_position_released, + self._released_encoder_callbacks_looped = [ + self._on_clip_position_released, self._on_clip_end_released, self._on_clip_start_marker_released, self._on_clip_looping_released] - self._released_encoder_callbacks_unlooped = [self._on_clip_start_marker_released, + self._released_encoder_callbacks_unlooped = [ + self._on_clip_position_released, self._on_clip_end_released, nop, self._on_clip_looping_released] @@ -287,6 +313,34 @@ def set_value_display(self, display): if display: display.set_data_sources(self._value_sources) + def convert_beat_time_to_bars_beats_sixteenths(self, clip, beat_time): + return convert_beat_time_to_bars_beats_sixteenths(( + clip.signature_numerator, clip.signature_denominator), beat_time) + + def convert_beat_length_to_bars_beats_sixteenths(self, clip, beat_length): + return convert_beat_length_to_bars_beats_sixteenths(( + clip.signature_numerator, clip.signature_denominator), beat_length) + + def _on_clip_changed(self): + self.__on_signature_denominator_changed.subject = self._loop_model.clip + self.__on_signature_denominator_changed() + self.__on_signature_numerator_changed.subject = self._loop_model.clip + self.__on_signature_numerator_changed() + + @listens('signature_denominator') + def __on_signature_denominator_changed(self): + self.__update_position_sources() + + @listens('signature_numerator') + def __on_signature_numerator_changed(self): + self.__update_position_sources() + + def __update_position_sources(self): + self._update_start_marker_source() + self._update_loop_start_source() + self._update_loop_end_source() + self._update_position_source() + @listens('looping') def __on_looping_changed(self): if self.is_enabled(): @@ -311,7 +365,7 @@ def __on_loop_end_changed(self): def _update_start_marker_source(self): looping = self._loop_model.looping if liveobj_valid(self.clip) else False - self._value_sources[2].set_display_string(convert_time_to_bars_beats_sixteenths(self._loop_model.start_marker) if looping else '') + self._value_sources[2].set_display_string(self.convert_beat_time_to_bars_beats_sixteenths(self.clip, self._loop_model.start_marker) if looping else '') def _update_is_looping_source(self): looping = self._loop_model.looping if liveobj_valid(self.clip) else False @@ -320,27 +374,24 @@ def _update_is_looping_source(self): self._name_sources[2].set_display_string('Offset' if looping else '') def _update_loop_start_source(self): - self._value_sources[0].set_display_string(convert_time_to_bars_beats_sixteenths(self._loop_model.loop_start) if self.clip else '-') + self._value_sources[0].set_display_string(self.convert_beat_time_to_bars_beats_sixteenths(self.clip, self._loop_model.loop_start) if self.clip else '-') def _update_loop_end_source(self): if liveobj_valid(self.clip) and not is_new_recording(self.clip): looping = self._loop_model.looping - self._value_sources[1].set_display_string(convert_length_to_bars_beats_sixteenths(self._loop_model.loop_length) if looping else convert_time_to_bars_beats_sixteenths(self._loop_model.loop_end)) + self._value_sources[1].set_display_string(self.convert_beat_length_to_bars_beats_sixteenths(self.clip, self._loop_model.loop_length) if looping else self.convert_beat_time_to_bars_beats_sixteenths(self.clip, self._loop_model.loop_end)) self._value_sources[3].set_display_string('On' if looping else 'Off') else: self._value_sources[1].set_display_string('-') self._value_sources[3].set_display_string('-') def _update_position_source(self): - self._value_sources[0].set_display_string(convert_time_to_bars_beats_sixteenths(self._loop_model.position) if liveobj_valid(self.clip) else '-') + self._value_sources[0].set_display_string(self.convert_beat_time_to_bars_beats_sixteenths(self.clip, self._loop_model.position) if liveobj_valid(self.clip) else '-') def update(self): super(LoopSettingsComponent, self).update() if self.is_enabled(): - for index, label in enumerate(['Position', - 'Length', - 'Offset', - 'Loop']): + for index, label in enumerate(['Position', 'Length', 'Offset', 'Loop']): self._name_sources[index].set_display_string(label) self.__on_loop_start_changed() @@ -349,12 +400,13 @@ def update(self): self.__on_start_marker_changed() -class AudioClipSettingsModel(Subject, SlotManager): +class AudioClipSettingsModel(EventObject): __events__ = ('pitch_fine', 'pitch_coarse', 'gain', 'warp_mode', 'warping') def __init__(self, *a, **k): super(AudioClipSettingsModel, self).__init__(*a, **k) self.clip = None + return def _get_clip(self): return self._clip @@ -549,10 +601,7 @@ def _update_pitch_coarse_source(self): def update(self): super(AudioClipSettingsComponent, self).update() if self.is_enabled(): - for index, label in enumerate(['WarpMode', - 'Transpose', - 'Detune', - 'Gain']): + for index, label in enumerate(['WarpMode', 'Transpose', 'Detune', 'Gain']): self._name_sources[index].set_display_string(label) self._update_warp_mode_source() @@ -570,8 +619,10 @@ class ClipNameComponent(Component): def __init__(self, *a, **k): super(ClipNameComponent, self).__init__(*a, **k) self._clip = None - self._name_data_sources = [ DisplayDataSource() for _ in xrange(self.num_label_segments) ] + self._name_data_sources = [ DisplayDataSource() for _ in xrange(self.num_label_segments) + ] self._name_data_sources[0].set_display_string('Clip Selection:') + return def _get_clip(self): return self._clip @@ -617,12 +668,15 @@ class ClipControlComponent(ModesComponent): Component that modifies clip properties """ - def __init__(self, loop_layer = None, audio_layer = None, clip_name_layer = None, *a, **k): + def __init__(self, loop_layer=None, audio_layer=None, clip_name_layer=None, *a, **k): super(ClipControlComponent, self).__init__(*a, **k) self._audio_clip_settings, self._loop_settings, self._clip_name = self.register_components(AudioClipSettingsComponent(is_enabled=False, layer=audio_layer), LoopSettingsComponent(is_enabled=False, layer=loop_layer), ClipNameComponent(is_enabled=False, layer=clip_name_layer)) self.add_mode('no_clip', (self._clip_name,)) - self.add_mode('midi', (self._loop_settings, self._clip_name)) - self.add_mode('audio', (self._loop_settings, self._audio_clip_settings, self._clip_name)) + self.add_mode('midi', (self._loop_settings, + self._clip_name)) + self.add_mode('audio', (self._loop_settings, + self._audio_clip_settings, + self._clip_name)) self.selected_mode = 'no_clip' self._update_clip() self._on_detail_clip_changed.subject = self.song.view @@ -659,4 +713,5 @@ def _update_clip(self): audio_clip = clip if clip and clip.is_audio_clip else None self._clip_name.clip = clip self._loop_settings.clip = clip - self._audio_clip_settings.clip = audio_clip \ No newline at end of file + self._audio_clip_settings.clip = audio_clip + return \ No newline at end of file diff --git a/pushbase/colors.py b/pushbase/colors.py index cef18cb9..329f527b 100644 --- a/pushbase/colors.py +++ b/pushbase/colors.py @@ -1,4 +1,9 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/colors.py +# uncompyle6 version 2.9.10 +# Python bytecode 2.7 (62211) +# Decompiled from: Python 2.7.13 (default, Dec 17 2016, 23:03:43) +# [GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.42.1)] +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/colors.py +# Compiled at: 2016-11-16 18:13:20 """ Module for the color interfaces defining all posible ways of turning on buttons on Push. @@ -14,7 +19,7 @@ def can_draw_on_interface(self, interface): return not self.needs_rgb_interface or interface.is_rgb def draw(self, interface): - raise self.can_draw_on_interface(interface) or AssertionError + assert self.can_draw_on_interface(interface) super(PushColor, self).draw(interface) @@ -25,16 +30,17 @@ class RgbColor(PushColor): needs_rgb_interface = True _rgb_value = (0, 0, 0) - def __init__(self, midi_value = None, rgb_value = None, *a, **k): + def __init__(self, midi_value=None, rgb_value=None, *a, **k): super(RgbColor, self).__init__(midi_value=midi_value, *a, **k) if rgb_value is not None: self._rgb_value = rgb_value + return def shade(self, shade_level): """ Generate a new shaded RGB from this color. """ - raise shade_level > 0 and shade_level <= 2 or AssertionError + assert shade_level > 0 and shade_level <= 2 shade_factor = 1.0 / 2.0 * (2 - shade_level) return RgbColor(self.midi_value + shade_level, [ a * b for a, b in izip(self._rgb_value, repeat(shade_factor)) ]) @@ -58,7 +64,7 @@ class FallbackColor(PushColor): or rgb button. """ - def __init__(self, default_color = None, fallback_color = None, *a, **k): + def __init__(self, default_color=None, fallback_color=None, *a, **k): super(FallbackColor, self).__init__(midi_value=to_midi_value(fallback_color), *a, **k) self.default_color = default_color @@ -79,7 +85,7 @@ class AnimatedColor(PushColor): def midi_value(self): return self.convert_to_midi_value() - def __init__(self, color1 = RgbColor(), color2 = RgbColor(), channel2 = 7, *a, **k): + def __init__(self, color1=RgbColor(), color2=RgbColor(), channel2=7, *a, **k): super(AnimatedColor, self).__init__(*a, **k) self.color1 = color1 self.color2 = color2 @@ -89,7 +95,7 @@ def can_draw_on_interface(self, interface): return self.color1.can_draw_on_interface(interface) and self.color2.can_draw_on_interface(interface) def draw(self, interface): - raise interface.num_delayed_messages >= 2 or AssertionError + assert interface.num_delayed_messages >= 2 interface.send_value(self.color1.midi_value) interface.send_value(self.color2.midi_value, channel=self.channel2) @@ -102,12 +108,9 @@ class Pulse(AnimatedColor): Smoothly pulsates between two colors. """ - def __init__(self, color1 = RgbColor(), color2 = RgbColor(), speed = 6, *a, **k): - channel2 = [4, - 6, - 12, - 24, - 48].index(speed) + 6 + def __init__(self, color1=RgbColor(), color2=RgbColor(), speed=6, *a, **k): + channel2 = [ + 4, 6, 12, 24, 48].index(speed) + 6 super(Pulse, self).__init__(color1=color1, color2=color2, channel2=channel2, *a, **k) @@ -116,15 +119,20 @@ class Blink(AnimatedColor): Blinks jumping between two colors. """ - def __init__(self, color1 = 0, color2 = 0, speed = 6, *a, **k): - channel2 = [4, - 6, - 12, - 24, - 48].index(speed) + 11 + def __init__(self, color1=0, color2=0, speed=6, *a, **k): + channel2 = [4, 6, 12, 24, 48].index(speed) + 11 super(Blink, self).__init__(color1=color1, color2=color2, channel2=channel2, *a, **k) +class TransparentColor(object): + """ + Color that does not transmit any MIDI data. + """ + + def draw(self, interface): + pass + + class Rgb: """ Table of RgbColors for main matrix. @@ -161,6 +169,7 @@ class Basic: FULL_BLINK_FAST = FallbackColor(Blink(Rgb.WHITE, Rgb.BLACK, 24), 6) OFF = FallbackColor(Rgb.BLACK, 0) ON = FallbackColor(Rgb.WHITE, 127) + TRANSPARENT = TransparentColor() class BiLed: @@ -187,67 +196,68 @@ class BiLed: ON = FallbackColor(Rgb.WHITE, 127) -CLIP_COLOR_TABLE = {15549221: 60, - 12411136: 61, - 11569920: 62, - 8754719: 63, - 5480241: 64, - 695438: 65, - 31421: 66, - 197631: 67, - 3101346: 68, - 6441901: 69, - 8092539: 70, - 3947580: 71, - 16712965: 72, - 12565097: 73, - 10927616: 74, - 8046132: 75, - 4047616: 76, - 49071: 77, - 1090798: 78, - 5538020: 79, - 8940772: 80, - 10701741: 81, - 12008809: 82, - 9852725: 83, - 16149507: 84, - 12581632: 85, - 8912743: 86, - 1769263: 87, - 2490280: 88, - 5111762: 89, - 1698303: 90, - 9160191: 91, - 9611263: 92, - 12094975: 93, - 14183652: 94, - 16726484: 95, - 16753961: 96, - 16773172: 97, - 14939139: 98, - 14402304: 99, - 12492131: 100, - 9024637: 101, - 8962746: 102, - 10204100: 103, - 8758722: 104, - 13011836: 105, - 15810688: 106, - 16749734: 107, - 16753524: 108, - 16772767: 109, - 13821080: 110, - 12243060: 111, - 11119017: 112, - 13958625: 113, - 13496824: 114, - 12173795: 115, - 13482980: 116, - 13684944: 117, - 14673637: 118, - 16777215: Rgb.WHITE} -RGB_COLOR_TABLE = ((0, 0), +CLIP_COLOR_TABLE = {15549221: 60,12411136: 61, + 11569920: 62, + 8754719: 63, + 5480241: 64, + 695438: 65, + 31421: 66, + 197631: 67, + 3101346: 68, + 6441901: 69, + 8092539: 70, + 3947580: 71, + 16712965: 72, + 12565097: 73, + 10927616: 74, + 8046132: 75, + 4047616: 76, + 49071: 77, + 1090798: 78, + 5538020: 79, + 8940772: 80, + 10701741: 81, + 12008809: 82, + 9852725: 83, + 16149507: 84, + 12581632: 85, + 8912743: 86, + 1769263: 87, + 2490280: 88, + 5111762: 89, + 1698303: 90, + 9160191: 91, + 9611263: 92, + 12094975: 93, + 14183652: 94, + 16726484: 95, + 16753961: 96, + 16773172: 97, + 14939139: 98, + 14402304: 99, + 12492131: 100, + 9024637: 101, + 8962746: 102, + 10204100: 103, + 8758722: 104, + 13011836: 105, + 15810688: 106, + 16749734: 107, + 16753524: 108, + 16772767: 109, + 13821080: 110, + 12243060: 111, + 11119017: 112, + 13958625: 113, + 13496824: 114, + 12173795: 115, + 13482980: 116, + 13684944: 117, + 14673637: 118, + 16777215: Rgb.WHITE + } +RGB_COLOR_TABLE = ( + (0, 0), (1, 1973790), (2, 8355711), (3, 16777215), diff --git a/pushbase/configurable_button_element.py b/pushbase/configurable_button_element.py index 0e1b5ae8..ea7d1df2 100644 --- a/pushbase/configurable_button_element.py +++ b/pushbase/configurable_button_element.py @@ -1,6 +1,11 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/configurable_button_element.py +# uncompyle6 version 2.9.10 +# Python bytecode 2.7 (62211) +# Decompiled from: Python 2.7.13 (default, Dec 17 2016, 23:03:43) +# [GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.42.1)] +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/configurable_button_element.py +# Compiled at: 2016-09-29 19:13:24 from __future__ import absolute_import, print_function -from ableton.v2.base import in_range +from ableton.v2.base import const, in_range from ableton.v2.control_surface import Skin, SkinColorMissingError from ableton.v2.control_surface.elements import ButtonElement, ON_VALUE, OFF_VALUE from .colors import Basic @@ -26,11 +31,12 @@ class DefaultButton: default_skin = Skin(Colors) default_states = {True: 'DefaultButton.On', - False: 'DefaultButton.Off'} + False: 'DefaultButton.Off' + } num_delayed_messages = 2 send_depends_on_forwarding = False - def __init__(self, is_momentary, msg_type, channel, identifier, skin = None, is_rgb = False, default_states = None, *a, **k): + def __init__(self, is_momentary, msg_type, channel, identifier, skin=None, is_rgb=False, default_states=None, *a, **k): super(ConfigurableButtonElement, self).__init__(is_momentary, msg_type, channel, identifier, skin=(skin or self.default_skin), *a, **k) if default_states is not None: self.default_states = default_states @@ -38,6 +44,7 @@ def __init__(self, is_momentary, msg_type, channel, identifier, skin = None, is_ self.is_rgb = is_rgb self._force_next_value = False self.set_channel(NON_FEEDBACK_CHANNEL) + return @property def _on_value(self): @@ -115,12 +122,16 @@ class PadButtonElement(ConfigurableButtonElement): parameter defines the Pad coordine id used in the sysex protocol. """ - def __init__(self, pad_id = None, pad_sensitivity_update = None, *a, **k): - raise pad_id is not None or AssertionError + class ProxiedInterface(ConfigurableButtonElement.ProxiedInterface): + sensitivity_profile = const(None) + + def __init__(self, pad_id=None, pad_sensitivity_update=None, *a, **k): + assert pad_id is not None super(PadButtonElement, self).__init__(*a, **k) self._sensitivity_profile = 'default' self._pad_id = pad_id self._pad_sensitivity_update = pad_sensitivity_update + return def _get_sensitivity_profile(self): return self._sensitivity_profile @@ -129,6 +140,7 @@ def _set_sensitivity_profile(self, profile): if profile != self._sensitivity_profile and self._pad_sensitivity_update is not None: self._sensitivity_profile = profile self._pad_sensitivity_update.set_pad(self._pad_id, profile) + return sensitivity_profile = property(_get_sensitivity_profile, _set_sensitivity_profile) diff --git a/pushbase/consts.py b/pushbase/consts.py index 86657bb8..158334dd 100644 --- a/pushbase/consts.py +++ b/pushbase/consts.py @@ -1,4 +1,9 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/consts.py +# uncompyle6 version 2.9.10 +# Python bytecode 2.7 (62211) +# Decompiled from: Python 2.7.13 (default, Dec 17 2016, 23:03:43) +# [GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.42.1)] +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/consts.py +# Compiled at: 2016-06-20 08:10:51 from __future__ import absolute_import, print_function import sys import Live @@ -18,10 +23,6 @@ DIALOG_PRIORITY = DEFAULT_PRIORITY + 2 NOTIFICATION_PRIORITY = DEFAULT_PRIORITY + 1 BACKGROUND_PRIORITY = DEFAULT_PRIORITY - 3 -ENCODER_SENSITIVITY = 0.5 -CONTINUOUS_MAPPING_SENSITIVITY = 2.0 -FINE_GRAINED_CONTINUOUS_MAPPING_SENSITIVITY = 0.01 -QUANTIZED_MAPPING_SENSITIVITY = 1.0 / 15.0 GLOBAL_MAP_MODE = Live.MidiMap.MapMode.relative_smooth_two_compliment CHAR_ARROW_UP = '\x00' CHAR_ARROW_DOWN = '\x01' @@ -38,9 +39,32 @@ CHAR_ELLIPSIS = '\x1c' CHAR_FULL_BLOCK = '\x1d' CHAR_SELECT = '\x7f' -GRAPH_VOL = ('\x03\x06\x06\x06\x06\x06\x06\x06', '\x05\x06\x06\x06\x06\x06\x06\x06', '\x05\x03\x06\x06\x06\x06\x06\x06', '\x05\x05\x06\x06\x06\x06\x06\x06', '\x05\x05\x03\x06\x06\x06\x06\x06', '\x05\x05\x05\x06\x06\x06\x06\x06', '\x05\x05\x05\x03\x06\x06\x06\x06', '\x05\x05\x05\x05\x06\x06\x06\x06', '\x05\x05\x05\x05\x03\x06\x06\x06', '\x05\x05\x05\x05\x05\x06\x06\x06', '\x05\x05\x05\x05\x05\x03\x06\x06', '\x05\x05\x05\x05\x05\x05\x06\x06', '\x05\x05\x05\x05\x05\x05\x03\x06', '\x05\x05\x05\x05\x05\x05\x05\x06', '\x05\x05\x05\x05\x05\x05\x05\x03', '\x05\x05\x05\x05\x05\x05\x05\x05') -GRAPH_PAN = ('\x05\x05\x05\x05\x06\x06\x06\x06', '\x04\x05\x05\x05\x06\x06\x06\x06', '\x06\x05\x05\x05\x06\x06\x06\x06', '\x06\x04\x05\x05\x06\x06\x06\x06', '\x06\x06\x05\x05\x06\x06\x06\x06', '\x06\x06\x04\x05\x06\x06\x06\x06', '\x06\x06\x06\x05\x06\x06\x06\x06', '\x06\x06\x06\x04\x06\x06\x06\x06', '\x06\x06\x06\x04\x03\x06\x06\x06', '\x06\x06\x06\x06\x03\x06\x06\x06', '\x06\x06\x06\x06\x05\x06\x06\x06', '\x06\x06\x06\x06\x05\x03\x06\x06', '\x06\x06\x06\x06\x05\x05\x06\x06', '\x06\x06\x06\x06\x05\x05\x03\x06', '\x06\x06\x06\x06\x05\x05\x05\x06', '\x06\x06\x06\x06\x05\x05\x05\x03', '\x06\x06\x06\x06\x05\x05\x05\x05') -GRAPH_SIN = ('\x03\x06\x06\x06\x06\x06\x06\x06', '\x04\x06\x06\x06\x06\x06\x06\x06', '\x06\x03\x06\x06\x06\x06\x06\x06', '\x06\x04\x06\x06\x06\x06\x06\x06', '\x06\x06\x03\x06\x06\x06\x06\x06', '\x06\x06\x04\x06\x06\x06\x06\x06', '\x06\x06\x06\x03\x06\x06\x06\x06', '\x06\x06\x06\x04\x06\x06\x06\x06', '\x06\x06\x06\x06\x03\x06\x06\x06', '\x06\x06\x06\x06\x04\x06\x06\x06', '\x06\x06\x06\x06\x06\x03\x06\x06', '\x06\x06\x06\x06\x06\x04\x06\x06', '\x06\x06\x06\x06\x06\x06\x03\x06', '\x06\x06\x06\x06\x06\x06\x04\x06', '\x06\x06\x06\x06\x06\x06\x06\x03', '\x06\x06\x06\x06\x06\x06\x06\x04') +GRAPH_VOL = ('\x03\x06\x06\x06\x06\x06\x06\x06', '\x05\x06\x06\x06\x06\x06\x06\x06', + '\x05\x03\x06\x06\x06\x06\x06\x06', '\x05\x05\x06\x06\x06\x06\x06\x06', + '\x05\x05\x03\x06\x06\x06\x06\x06', '\x05\x05\x05\x06\x06\x06\x06\x06', + '\x05\x05\x05\x03\x06\x06\x06\x06', '\x05\x05\x05\x05\x06\x06\x06\x06', + '\x05\x05\x05\x05\x03\x06\x06\x06', '\x05\x05\x05\x05\x05\x06\x06\x06', + '\x05\x05\x05\x05\x05\x03\x06\x06', '\x05\x05\x05\x05\x05\x05\x06\x06', + '\x05\x05\x05\x05\x05\x05\x03\x06', '\x05\x05\x05\x05\x05\x05\x05\x06', + '\x05\x05\x05\x05\x05\x05\x05\x03', '\x05\x05\x05\x05\x05\x05\x05\x05') +GRAPH_PAN = ('\x05\x05\x05\x05\x06\x06\x06\x06', '\x04\x05\x05\x05\x06\x06\x06\x06', + '\x06\x05\x05\x05\x06\x06\x06\x06', '\x06\x04\x05\x05\x06\x06\x06\x06', + '\x06\x06\x05\x05\x06\x06\x06\x06', '\x06\x06\x04\x05\x06\x06\x06\x06', + '\x06\x06\x06\x05\x06\x06\x06\x06', '\x06\x06\x06\x04\x06\x06\x06\x06', + '\x06\x06\x06\x04\x03\x06\x06\x06', '\x06\x06\x06\x06\x03\x06\x06\x06', + '\x06\x06\x06\x06\x05\x06\x06\x06', '\x06\x06\x06\x06\x05\x03\x06\x06', + '\x06\x06\x06\x06\x05\x05\x06\x06', '\x06\x06\x06\x06\x05\x05\x03\x06', + '\x06\x06\x06\x06\x05\x05\x05\x06', '\x06\x06\x06\x06\x05\x05\x05\x03', + '\x06\x06\x06\x06\x05\x05\x05\x05') +GRAPH_SIN = ('\x03\x06\x06\x06\x06\x06\x06\x06', '\x04\x06\x06\x06\x06\x06\x06\x06', + '\x06\x03\x06\x06\x06\x06\x06\x06', '\x06\x04\x06\x06\x06\x06\x06\x06', + '\x06\x06\x03\x06\x06\x06\x06\x06', '\x06\x06\x04\x06\x06\x06\x06\x06', + '\x06\x06\x06\x03\x06\x06\x06\x06', '\x06\x06\x06\x04\x06\x06\x06\x06', + '\x06\x06\x06\x06\x03\x06\x06\x06', '\x06\x06\x06\x06\x04\x06\x06\x06', + '\x06\x06\x06\x06\x06\x03\x06\x06', '\x06\x06\x06\x06\x06\x04\x06\x06', + '\x06\x06\x06\x06\x06\x06\x03\x06', '\x06\x06\x06\x06\x06\x06\x04\x06', + '\x06\x06\x06\x06\x06\x06\x06\x03', '\x06\x06\x06\x06\x06\x06\x06\x04') +DISTANT_FUTURE = 999999 class MessageBoxText: LIVE_DIALOG = '\n Live is showing a dialog' + '\n that needs your attention.' @@ -56,7 +80,7 @@ class MessageBoxText: DELETE_CLIP = ' Clip deleted: %s' DUPLICATE_CLIP = ' Clip duplicated: %s' QUANTIZE_CLIP = ' Quantized to: %(to)s, %(amount)s' - QUANTIZE_CLIP_PITCH = ' Quantized pad to: %(to)s, %(amount)s' + QUANTIZE_CLIP_PITCH = ' Quantized %(source)s to: %(to)s, %(amount)s' DELETE_NOTES = ' Notes deleted: %s' CAPTURE_AND_INSERT_SCENE = ' Duplicated to scene %s' DUPLICATE_LOOP = ' New loop length: %(length)s' @@ -65,6 +89,7 @@ class MessageBoxText: DELETE_ENVELOPE = ' Delete automation %(automation)s' DEFAULT_PARAMETER_VALUE = ' Reset to default: %(automation)s' DELETE_DRUM_RACK_PAD = ' Drum Pad deleted: %s' + DELETE_SLICE = ' Slice %s deleted' FIXED_LENGTH = ' Fixed Length: %s' EMPTY_DEVICE_CHAIN = '\n\n No Devices. Press [Browse] to add a device.' STUCK_PAD_WARNING = ' Warning: Low threshold may cause stuck pads' @@ -76,10 +101,32 @@ class MessageBoxText: PLAYING_CLIP_BELOW_SELECTED_CLIP = ' Press Down Arrow to edit playing clip' TOUCHSTRIP_PITCHBEND_MODE = ' Touchstrip Mode: Pitchbend' TOUCHSTRIP_MODWHEEL_MODE = ' Touchstrip Mode: Modwheel' - COPIED_DRUM_PAD = ' Pad %s copied. Press destination pad to paste' - PASTED_DRUM_PAD = ' Pad %s duplicated to %s' + COPIED_DRUM_PAD = ' Pad %len=8,s copied. Press destination pad to paste' + PASTED_DRUM_PAD = ' Pad %len=8,s duplicated to %len=8,s' CANNOT_COPY_EMPTY_DRUM_PAD = ' Cannot copy empty drum pad' CANNOT_PASTE_TO_SOURCE_DRUM_PAD = ' Cannot paste to source drum pad' + COPIED_STEP = ' Note(s) copied. Press destination step to paste' + PASTED_STEP = ' Note(s) duplicated.' + CANNOT_COPY_EMPTY_STEP = ' Cannot copy empty step' + CANNOT_PASTE_TO_SOURCE_STEP = ' Cannot paste to source step' + COPIED_CLIP = ' %len=8,s copied. Press destination clip slot to paste' + PASTED_CLIP = ' %len=8,s duplicated to: %len=17,s' + CANNOT_COPY_EMPTY_CLIP = ' Cannot copy from empty clip slot' + CANNOT_COPY_GROUP_SLOT = ' Group clips cannot be copied' + CANNOT_COPY_RECORDING_CLIP = ' Cannot copy recording clip' + CANNOT_COPY_AUDIO_CLIP_TO_MIDI_TRACK = ' Please paste this audio clip into an audio track' + CANNOT_COPY_MIDI_CLIP_TO_AUDIO_TRACK = ' Please paste this MIDI clip into a MIDI track' + CANNOT_PASTE_INTO_GROUP_SLOT = ' A clip cannot be pasted into a group track' + LAYOUT_DRUMS_LOOP = ' Drums: Loop Selector' + LAYOUT_DRUMS_LEVELS = ' Drums: 16 Velocities' + LAYOUT_DRUMS_64_PADS = ' Drums: 64 Pads' + LAYOUT_SLICING_LOOP = ' Slicing: Loop Selector' + LAYOUT_SLICING_LEVELS = ' Slicing: 16 Velocities' + LAYOUT_SLICING_64_PADS = ' Slicing: 64 Slices' + LAYOUT_MELODIC_PLAYING = ' Melodic: 64 Notes' + LAYOUT_MELODIC_SEQUENCER = ' Melodic: Sequencer' + LAYOUT_SESSION_VIEW = ' Session View' + LAYOUT_SESSION_OVERVIEW = ' Session Overview' _test_mode = __builtins__.get('TEST_MODE', False) diff --git a/pushbase/control_element_factory.py b/pushbase/control_element_factory.py index a6c52050..8643194d 100644 --- a/pushbase/control_element_factory.py +++ b/pushbase/control_element_factory.py @@ -1,4 +1,9 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/control_element_factory.py +# uncompyle6 version 2.9.10 +# Python bytecode 2.7 (62211) +# Decompiled from: Python 2.7.13 (default, Dec 17 2016, 23:03:43) +# [GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.42.1)] +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/control_element_factory.py +# Compiled at: 2016-05-20 03:43:52 from __future__ import absolute_import, print_function from ableton.v2.base import depends from ableton.v2.control_surface import MIDI_CC_TYPE, MIDI_NOTE_TYPE, PrioritizedResource, midi @@ -6,7 +11,7 @@ from .configurable_button_element import ConfigurableButtonElement @depends(skin=None) -def create_button(note, name, skin = None, **k): +def create_button(note, name, skin=None, **k): return ConfigurableButtonElement(True, MIDI_CC_TYPE, 0, note, name=name, skin=skin, **k) @@ -15,7 +20,7 @@ def create_modifier_button(note, name, **k): @depends(skin=None) -def create_note_button(note, name, skin = None, **k): +def create_note_button(note, name, skin=None, **k): return ConfigurableButtonElement(True, MIDI_NOTE_TYPE, 0, note, skin=skin, name=name, **k) @@ -23,5 +28,5 @@ def make_send_message_generator(prefix): return lambda value_bytes: prefix + value_bytes + (midi.SYSEX_END,) -def create_sysex_element(message_prefix, enquire_message = None, default_value = None): +def create_sysex_element(message_prefix, enquire_message=None, default_value=None): return SysexElement(sysex_identifier=message_prefix, send_message_generator=make_send_message_generator(message_prefix), enquire_message=enquire_message, default_value=default_value) \ No newline at end of file diff --git a/pushbase/decoration.py b/pushbase/decoration.py index 75d5a323..f7000705 100644 --- a/pushbase/decoration.py +++ b/pushbase/decoration.py @@ -1,4 +1,9 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/decoration.py +# uncompyle6 version 2.9.10 +# Python bytecode 2.7 (62211) +# Decompiled from: Python 2.7.13 (default, Dec 17 2016, 23:03:43) +# [GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.42.1)] +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/decoration.py +# Compiled at: 2016-05-20 03:43:52 from __future__ import absolute_import, print_function from itertools import ifilter from ableton.v2.base import CompoundDisconnectable, Proxy @@ -24,7 +29,7 @@ def get(self, key, *default): return super(LiveObjectDict, self).get(self._transform_key(key), *default) def _transform_key(self, key): - raise hasattr(key, '_live_ptr') or AssertionError + assert hasattr(key, '_live_ptr') return key._live_ptr def update(self, *a, **k): @@ -43,13 +48,15 @@ def prune(self, keys): class LiveObjectDecorator(CompoundDisconnectable, Proxy): - def __init__(self, live_object = None, additional_properties = {}): - raise live_object is not None or AssertionError + def __init__(self, live_object=None, additional_properties={}): + assert live_object is not None super(LiveObjectDecorator, self).__init__(proxied_object=live_object) self._live_object = live_object for name, value in additional_properties.iteritems(): setattr(self, name, value) + return + def __eq__(self, other): return id(self) == id(other) or self._live_object == other @@ -70,7 +77,7 @@ def __init__(self, *a, **k): super(DecoratorFactory, self).__init__(*a, **k) self.decorated_objects = LiveObjectDict() - def decorate(self, live_object, additional_properties = {}, **k): + def decorate(self, live_object, additional_properties={}, **k): if self._should_be_decorated(live_object): if not self.decorated_objects.get(live_object, None): self.decorated_objects[live_object] = self.register_disconnectable(self._get_decorated_object(live_object, additional_properties, **k)) diff --git a/pushbase/device_chain_utils.py b/pushbase/device_chain_utils.py index d92bce21..e567d173 100644 --- a/pushbase/device_chain_utils.py +++ b/pushbase/device_chain_utils.py @@ -1,4 +1,9 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/device_chain_utils.py +# uncompyle6 version 2.9.10 +# Python bytecode 2.7 (62211) +# Decompiled from: Python 2.7.13 (default, Dec 17 2016, 23:03:43) +# [GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.42.1)] +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/device_chain_utils.py +# Compiled at: 2016-06-08 13:13:04 from __future__ import absolute_import, print_function import Live from itertools import imap, chain @@ -17,22 +22,25 @@ def find_instrument_devices(track_or_chain): """ Returns a list with all instruments from a track or chain. """ - instrument = find_if(lambda d: d.type == Live.Device.DeviceType.instrument, track_or_chain.devices) - if liveobj_valid(instrument): - if not instrument.can_have_drum_pads and instrument.can_have_chains: - return chain([instrument], *imap(find_instrument_devices, instrument.chains)) - return [instrument] + if liveobj_valid(track_or_chain): + instrument = find_if(lambda d: d.type == Live.Device.DeviceType.instrument, track_or_chain.devices) + if liveobj_valid(instrument): + if not instrument.can_have_drum_pads and instrument.can_have_chains: + return chain([instrument], *imap(find_instrument_devices, instrument.chains)) + return [instrument] return [] def find_instrument_meeting_requirement(requirement, track_or_chain): - instrument = find_if(lambda d: d.type == Live.Device.DeviceType.instrument, track_or_chain.devices) - if liveobj_valid(instrument): - if requirement(instrument): - return instrument - if instrument.can_have_chains: - recursive_call = partial(find_instrument_meeting_requirement, requirement) - return find_if(bool, imap(recursive_call, instrument.chains)) + if liveobj_valid(track_or_chain): + instrument = find_if(lambda d: d.type == Live.Device.DeviceType.instrument, track_or_chain.devices) + if liveobj_valid(instrument): + if requirement(instrument): + return instrument + if instrument.can_have_chains: + recursive_call = partial(find_instrument_meeting_requirement, requirement) + return find_if(bool, imap(recursive_call, instrument.chains)) + return None def is_simpler(device): diff --git a/pushbase/device_component.py b/pushbase/device_component.py index 03414e9d..b34afe27 100644 --- a/pushbase/device_component.py +++ b/pushbase/device_component.py @@ -1,9 +1,14 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/device_component.py +# uncompyle6 version 2.9.10 +# Python bytecode 2.7 (62211) +# Decompiled from: Python 2.7.13 (default, Dec 17 2016, 23:03:43) +# [GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.42.1)] +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/device_component.py +# Compiled at: 2016-05-20 03:43:52 from __future__ import absolute_import, print_function from ableton.v2.base import depends, listens, liveobj_valid, liveobj_changed from ableton.v2.control_surface import CompoundComponent from .device_parameter_bank import create_device_bank -from .parameter_provider import ParameterProvider, generate_info +from .parameter_provider import ParameterProvider from .simpler_slice_nudging import SimplerSliceNudging class DeviceComponent(ParameterProvider, CompoundComponent): @@ -11,11 +16,11 @@ class DeviceComponent(ParameterProvider, CompoundComponent): Device component that serves as parameter provider for the DeviceParameterComponent. """ - __events__ = ('device',) + __events__ = ('device', ) _provided_parameters = tuple() @depends(device_provider=None) - def __init__(self, device_decorator_factory = None, banking_info = None, device_bank_registry = None, device_provider = None, *a, **k): + def __init__(self, device_decorator_factory=None, banking_info=None, device_bank_registry=None, device_provider=None, *a, **k): self._bank = None self._banking_info = banking_info self._decorated_device = None @@ -27,6 +32,7 @@ def __init__(self, device_decorator_factory = None, banking_info = None, device_ self.__on_bank_changed.subject = device_bank_registry self.__on_provided_device_changed.subject = device_provider self.__on_provided_device_changed() + return def set_device(self, device): self._device_provider.device = device @@ -48,22 +54,25 @@ def __on_bank_changed(self, device, bank): def _set_bank_index(self, device, bank): if self._bank is not None: self._bank.index = bank + return def _update_parameters(self): self._provided_parameters = self._get_provided_parameters() self.notify_parameters() - def _setup_bank(self, device, bank_factory = create_device_bank): + def _setup_bank(self, device, bank_factory=create_device_bank): if self._bank is not None: self.disconnect_disconnectable(self._bank) self._bank = None if liveobj_valid(device): self._bank = self.register_disconnectable(bank_factory(device, self._banking_info)) + return def _get_decorated_device(self, device): if self._decorator_factory is not None: return self._decorator_factory.decorate(device) - return device + else: + return device def _device_changed(self, device): current_device = getattr(self.device(), '_live_object', self.device()) @@ -103,13 +112,19 @@ def _on_bank_parameters_changed(self): def _current_bank_details(self): if self._bank is not None: return (self._bank.name, self._bank.parameters) - return ('', [None] * 8) + else: + return ( + '', [None] * 8) def _number_of_parameter_banks(self): if self._bank is not None: return self._bank.bank_count() - return 0 + else: + return 0 def _get_provided_parameters(self): _, parameters = self._current_bank_details() if self.device() else (None, ()) - return [ generate_info(p) for p in parameters ] \ No newline at end of file + return [ self._create_parameter_info(p) for p in parameters ] + + def _create_parameter_info(self, parameter, name): + raise NotImplementedError() \ No newline at end of file diff --git a/pushbase/device_parameter_bank.py b/pushbase/device_parameter_bank.py index e2622ffc..3395f377 100644 --- a/pushbase/device_parameter_bank.py +++ b/pushbase/device_parameter_bank.py @@ -1,12 +1,17 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/device_parameter_bank.py +# uncompyle6 version 2.9.10 +# Python bytecode 2.7 (62211) +# Decompiled from: Python 2.7.13 (default, Dec 17 2016, 23:03:43) +# [GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.42.1)] +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/device_parameter_bank.py +# Compiled at: 2016-05-20 03:43:52 from __future__ import absolute_import, print_function -from ableton.v2.base import SlotManager, Subject, find_if, listens, listens_group, listenable_property, liveobj_valid, clamp +from ableton.v2.base import EventObject, find_if, listens, listens_group, listenable_property, liveobj_valid, clamp from .banking_util import PARAMETERS_KEY, MAIN_KEY, BANK_FORMAT, all_parameters -class DeviceParameterBank(SlotManager, Subject): +class DeviceParameterBank(EventObject): - def __init__(self, size = None, device = None, banking_info = None, *a, **k): - raise size is not None or AssertionError + def __init__(self, size=None, device=None, banking_info=None, *a, **k): + assert size is not None super(DeviceParameterBank, self).__init__(*a, **k) self._size = size self._device = device @@ -15,6 +20,7 @@ def __init__(self, size = None, device = None, banking_info = None, *a, **k): self._parameters = None self._on_parameters_changed.subject = device self._update_parameters() + return def bank_count(self): return self._banking_info.device_bank_count(self._device, bank_size=self._size) @@ -74,7 +80,7 @@ def _update_parameters(self): class DescribedDeviceParameterBank(DeviceParameterBank): - def __init__(self, device = None, banking_info = None, *a, **k): + def __init__(self, device=None, banking_info=None, *a, **k): self._definition = banking_info.device_bank_definition(device) self._dynamic_slots = [] super(DescribedDeviceParameterBank, self).__init__(device=device, banking_info=banking_info, *a, **k) @@ -101,6 +107,7 @@ def _setup_dynamic_slots(self): slot.set_parameter_host(self.device) self._on_slot_content_changed.replace_subjects(self._dynamic_slots) + return def _calc_name(self): return self._definition.key_by_index(self.index) @@ -108,7 +115,9 @@ def _calc_name(self): def _collect_parameters(self): parameters = self._device.parameters bank_slots = self._current_parameter_slots() - return [ find_if(lambda p: p.original_name == str(slot_definition), parameters) for slot_definition in bank_slots ] + return [ find_if(lambda p: p.original_name == str(slot_definition), parameters) + for slot_definition in bank_slots + ] def _update_parameters(self): self._setup_dynamic_slots() @@ -119,7 +128,7 @@ class MaxDeviceParameterBank(DeviceParameterBank): def __init__(self, *a, **k): super(MaxDeviceParameterBank, self).__init__(*a, **k) - raise hasattr(self._device, 'get_bank_count') or AssertionError + assert hasattr(self._device, 'get_bank_count') def _calc_name(self): if self.bank_count() == 0: @@ -133,10 +142,11 @@ def _calc_name(self): def _collect_parameters(self): if self.bank_count() == 0: return [None] * self._size - parameters = self._device.parameters - mx_index = self.index - int(self._banking_info.has_main_bank(self._device)) - indices = self.device.get_bank_parameters(mx_index) - return [ (parameters[index] if index >= 0 else None) for index in indices ] + else: + parameters = self._device.parameters + mx_index = self.index - int(self._banking_info.has_main_bank(self._device)) + indices = self.device.get_bank_parameters(mx_index) + return [ (parameters[index] if index >= 0 else None) for index in indices ] def create_device_bank(device, banking_info): diff --git a/pushbase/device_parameter_component.py b/pushbase/device_parameter_component.py index 05ed7c5a..ee83a4b2 100644 --- a/pushbase/device_parameter_component.py +++ b/pushbase/device_parameter_component.py @@ -1,4 +1,9 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/device_parameter_component.py +# uncompyle6 version 2.9.10 +# Python bytecode 2.7 (62211) +# Decompiled from: Python 2.7.13 (default, Dec 17 2016, 23:03:43) +# [GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.42.1)] +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/device_parameter_component.py +# Compiled at: 2016-05-20 03:43:52 from __future__ import absolute_import, print_function from itertools import chain, repeat, izip_longest import Live @@ -19,7 +24,7 @@ def graphic_bar_for_parameter(parameter): return consts.GRAPH_VOL -def convert_parameter_value_to_graphic(param, param_to_value = lambda p: p.value): +def convert_parameter_value_to_graphic(param, param_to_value=lambda p: p.value): if param != None: param_range = param.max - param.min param_bar = graphic_bar_for_parameter(param) @@ -41,7 +46,7 @@ def update_encoder_sensitivity(encoder, parameter_info): class DeviceParameterComponentBase(Component): controls = ControlList(MappedControl, 8) - def __init__(self, parameter_provider = None, *a, **k): + def __init__(self, parameter_provider=None, *a, **k): super(DeviceParameterComponentBase, self).__init__(*a, **k) self._parameter_controls = [] self.parameter_provider = parameter_provider @@ -53,6 +58,7 @@ def _set_parameter_provider(self, provider): self._parameter_provider = provider or ParameterProvider() self._on_parameters_changed.subject = self._parameter_provider self._update_parameters() + self._on_parameter_provider_changed(provider) parameter_provider = property(_get_parameter_provider, _set_parameter_provider) @@ -68,6 +74,8 @@ def _connect_parameters(self): if parameter: control.update_sensitivities(parameter_info.default_encoder_sensitivity, parameter_info.fine_grain_encoder_sensitivity) + return + @property def parameters(self): return map(lambda p: p and p.parameter, self._parameter_provider.parameters) @@ -84,6 +92,9 @@ def _update_parameters(self): def _on_parameters_changed(self): self._update_parameters() + def _on_parameter_provider_changed(self, provider): + pass + def update(self): super(DeviceParameterComponentBase, self).update() self._update_parameters() @@ -96,9 +107,14 @@ class DeviceParameterComponent(DeviceParameterComponentBase): """ def __init__(self, *a, **k): - self._parameter_name_data_sources = map(DisplayDataSource, ('', '', '', '', '', '', '', '')) - self._parameter_value_data_sources = map(DisplayDataSource, ('', '', '', '', '', '', '', '')) - self._parameter_graphic_data_sources = map(DisplayDataSource, ('', '', '', '', '', '', '', '')) + self._parameter_name_data_sources = map(DisplayDataSource, ('', '', '', '', + '', '', '', '')) + self._parameter_value_data_sources = map(DisplayDataSource, ('', '', '', '', + '', '', '', + '')) + self._parameter_graphic_data_sources = map(DisplayDataSource, ('', '', '', + '', '', '', + '', '')) super(DeviceParameterComponent, self).__init__(*a, **k) def set_name_display_line(self, line): @@ -153,6 +169,8 @@ def _update_parameter_names(self): name = consts.CHAR_FULL_BLOCK + name name_data_source.set_display_string(name or '') + return + def _update_parameter_values(self): if self.is_enabled(): for parameter, data_source in izip_longest(self.parameters, self._parameter_value_data_sources): @@ -170,7 +188,8 @@ def _update_parameter_values(self): def parameter_to_string(self, parameter): if parameter == None: return '' - return unicode(parameter) + else: + return unicode(parameter) def parameter_to_value(self, parameter): return parameter.value \ No newline at end of file diff --git a/pushbase/drum_group_component.py b/pushbase/drum_group_component.py index 57bcae4d..7f25bf90 100644 --- a/pushbase/drum_group_component.py +++ b/pushbase/drum_group_component.py @@ -1,29 +1,35 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/drum_group_component.py +# uncompyle6 version 2.9.10 +# Python bytecode 2.7 (62211) +# Decompiled from: Python 2.7.13 (default, Dec 17 2016, 23:03:43) +# [GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.42.1)] +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/drum_group_component.py +# Compiled at: 2016-06-13 08:15:55 from __future__ import absolute_import, print_function from functools import partial -from ableton.v2.base import nop, listens, liveobj_valid, listenable_property +from ableton.v2.base import find_if, nop, listens, liveobj_valid, listenable_property, NamedTuple from ableton.v2.control_surface.control import control_matrix, ButtonControl from ableton.v2.control_surface.components import DrumGroupComponent -from .consts import MessageBoxText -from .matrix_maps import PAD_FEEDBACK_CHANNEL +from .consts import MessageBoxText, DISTANT_FUTURE +from .matrix_maps import PAD_FEEDBACK_CHANNEL, NON_FEEDBACK_CHANNEL from .message_box_component import Messenger from .pad_control import PadControl from .slideable_touch_strip_component import SlideableTouchStripComponent class DrumPadCopyHandler(object): - def __init__(self, show_notification = None, notification_formatter = None, *a, **k): + def __init__(self, show_notification=None, *a, **k): super(DrumPadCopyHandler, self).__init__(*a, **k) self.is_copying = False self._source_pad = None self._show_notification = show_notification - self._format_notification_string = notification_formatter if notification_formatter is not None else nop + return def _start_copying(self, source_pad): if len(source_pad.chains) > 0: self._source_pad = source_pad self.is_copying = True - message = MessageBoxText.COPIED_DRUM_PAD % self._format_notification_string(source_pad.name) + message = ( + MessageBoxText.COPIED_DRUM_PAD, source_pad.name) else: message = MessageBoxText.CANNOT_COPY_EMPTY_DRUM_PAD return self._show_notification(message) @@ -35,7 +41,10 @@ def _finish_copying(self, drum_group_device, destination_pad): destination_pad.delete_all_chains() drum_group_device.copy_pad(self._source_pad.note, destination_pad.note) self.is_copying = False - message = MessageBoxText.PASTED_DRUM_PAD % (self._format_notification_string(self._source_pad.name), self._format_notification_string(destination_pad_name)) + message = ( + MessageBoxText.PASTED_DRUM_PAD, + self._source_pad.name, + destination_pad_name) else: message = MessageBoxText.CANNOT_PASTE_TO_SOURCE_DRUM_PAD return self._show_notification(message) @@ -56,11 +65,12 @@ class DrumGroupComponent(SlideableTouchStripComponent, DrumGroupComponent, Messe matrix = control_matrix(PadControl) duplicate_button = ButtonControl() - def __init__(self, notification_formatter = None, quantizer = None, *a, **k): + def __init__(self, quantizer=None, *a, **k): super(DrumGroupComponent, self).__init__(touch_slideable=self, translation_channel=PAD_FEEDBACK_CHANNEL, dragging_enabled=True, *a, **k) - self._copy_handler = self._make_copy_handler(notification_formatter) + self._copy_handler = self._make_copy_handler() self._notification_reference = partial(nop, None) self._quantizer = quantizer + return position_count = 32 page_length = 4 @@ -77,14 +87,19 @@ def set_drum_group_device(self, drum_group_device): self.notify_contents() def quantize_pitch(self, note): - self._quantizer.quantize_pitch(note) + self._quantizer.quantize_pitch(note, 'pad') def _update_selected_drum_pad(self): super(DrumGroupComponent, self)._update_selected_drum_pad() self.notify_selected_note() + self.notify_selected_target_note() - def _make_copy_handler(self, notification_formatter): - return DrumPadCopyHandler(self.show_notification, notification_formatter) + def _update_assigned_drum_pads(self): + super(DrumGroupComponent, self)._update_assigned_drum_pads() + self.notify_selected_target_note() + + def _make_copy_handler(self): + return DrumPadCopyHandler(self.show_notification) @matrix.pressed def matrix(self, pad): @@ -125,6 +140,7 @@ def duplicate_button(self, button): self._copy_handler.stop_copying() if self._notification_reference() is not None: self._notification_reference().hide() + return @listens('chains') def _on_chains_changed(self): @@ -133,8 +149,8 @@ def _on_chains_changed(self): def delete_pitch(self, drum_pad): clip = self.song.view.detail_clip - if clip and any(clip.get_notes(0, drum_pad.note, 999999, 1)): - clip.remove_notes(0, drum_pad.note, 999999, 1) + if clip and any(clip.get_notes(0, drum_pad.note, DISTANT_FUTURE, 1)): + clip.remove_notes(0, drum_pad.note, DISTANT_FUTURE, 1) self.show_notification(MessageBoxText.DELETE_NOTES % drum_pad.name) else: self.show_notification(MessageBoxText.DELETE_DRUM_RACK_PAD % drum_pad.name) @@ -163,7 +179,22 @@ def _update_sensitivity_profile(self): @listenable_property def selected_note(self): - selected_drum_pad = self._drum_group_device.view.selected_drum_pad if liveobj_valid(self._drum_group_device) else None - if liveobj_valid(selected_drum_pad): - return selected_drum_pad.note - return -1 \ No newline at end of file + if liveobj_valid(self._selected_drum_pad): + return self._selected_drum_pad.note + return -1 + + @listenable_property + def selected_target_note(self): + note_and_channel = (-1, -1) + if liveobj_valid(self._drum_group_device) and liveobj_valid(self._selected_drum_pad): + if self._selected_drum_pad in self.assigned_drum_pads: + predicate = lambda b: self._pad_for_button(b) == self._selected_drum_pad + button = find_if(predicate, self.matrix) + if button != None and None not in (button.identifier, button.channel): + note_and_channel = ( + button.identifier, button.channel) + else: + note_and_channel = ( + self._selected_drum_pad.note, + NON_FEEDBACK_CHANNEL) + return NamedTuple(note=note_and_channel[0], channel=note_and_channel[1]) \ No newline at end of file diff --git a/pushbase/elements.py b/pushbase/elements.py index 02bdd30c..6a71cca2 100644 --- a/pushbase/elements.py +++ b/pushbase/elements.py @@ -1,20 +1,30 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/elements.py +# uncompyle6 version 2.9.10 +# Python bytecode 2.7 (62211) +# Decompiled from: Python 2.7.13 (default, Dec 17 2016, 23:03:43) +# [GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.42.1)] +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/elements.py +# Compiled at: 2016-09-29 19:13:24 from __future__ import absolute_import, print_function from ableton.v2.base import depends, recursive_map -from ableton.v2.control_surface import PrioritizedResource, MIDI_NOTE_TYPE +from ableton.v2.control_surface import CompoundElement, MIDI_NOTE_TYPE, PrioritizedResource from ableton.v2.control_surface.elements import ButtonMatrixElement, DoublePressElement, FineGrainWithModifierEncoderElement, MultiElement from . import consts from .configurable_button_element import PadButtonElement from .control_element_factory import create_button, create_modifier_button, create_note_button from .playhead_element import PlayheadElement +from .velocity_levels_element import VelocityLevelsElement from .touch_encoder_element import TouchEncoderElement +BASE_ENCODER_SENSITIVITY = 0.5 class Elements(object): - def __init__(self, deleter = None, undo_handler = None, pad_sensitivity_update = None, playhead = None, continuous_mapping_sensitivity = consts.CONTINUOUS_MAPPING_SENSITIVITY, fine_grained_continuous_mapping_sensitivity = consts.FINE_GRAINED_CONTINUOUS_MAPPING_SENSITIVITY, *a, **k): - raise deleter is not None or AssertionError - raise undo_handler is not None or AssertionError - raise playhead is not None or AssertionError + def __init__(self, deleter=None, undo_handler=None, pad_sensitivity_update=None, playhead=None, velocity_levels=None, continuous_mapping_sensitivity=None, fine_grained_continuous_mapping_sensitivity=None, *a, **k): + assert deleter is not None + assert undo_handler is not None + assert playhead is not None + assert velocity_levels is not None + assert continuous_mapping_sensitivity is not None + assert fine_grained_continuous_mapping_sensitivity is not None super(Elements, self).__init__(*a, **k) self.foot_pedal_button = DoublePressElement(create_button(69, 'Foot_Pedal', is_rgb=True)) self.nav_up_button = create_button(46, 'Up_Arrow') @@ -57,36 +67,50 @@ def __init__(self, deleter = None, undo_handler = None, pad_sensitivity_update = self.create_track_button = create_button(53, 'Create_Track_Button', undo_step_handler=undo_handler) self.double_button = create_button(117, 'Double_Button', undo_step_handler=undo_handler) self.user_button = create_button(59, 'User_Button', undo_step_handler=undo_handler) - self.select_buttons_raw = [ create_button(20 + idx, 'Track_Select_Button' + str(idx)) for idx in xrange(8) ] - self.select_buttons = ButtonMatrixElement(name='Track_Select_Buttons', rows=[self.select_buttons_raw]) - self.track_state_buttons_raw = [ create_button(102 + idx, 'Track_State_Button' + str(idx), is_rgb=True) for idx in xrange(8) ] - self.track_state_buttons = ButtonMatrixElement(name='Track_State_Buttons', rows=[self.track_state_buttons_raw]) - self.side_buttons_raw = [ create_button(36 + idx, 'Scene_Launch_Button' + str(idx)) for idx in reversed(xrange(8)) ] - self.side_buttons = ButtonMatrixElement(name='Scene_Launch_Buttons', rows=[self.side_buttons_raw]) + self.select_buttons_raw = [ create_button(20 + idx, 'Track_Select_Button' + str(idx)) for idx in xrange(8) + ] + self.select_buttons = ButtonMatrixElement(name='Track_Select_Buttons', rows=[ + self.select_buttons_raw]) + self.track_state_buttons_raw = [ create_button(102 + idx, 'Track_State_Button' + str(idx), is_rgb=True) for idx in xrange(8) + ] + self.track_state_buttons = ButtonMatrixElement(name='Track_State_Buttons', rows=[ + self.track_state_buttons_raw]) + self.side_buttons_raw = [ create_button(36 + idx, 'Scene_Launch_Button' + str(idx)) for idx in reversed(xrange(8)) + ] + self.side_buttons = ButtonMatrixElement(name='Scene_Launch_Buttons', rows=[ + self.side_buttons_raw]) @depends(skin=None) - def create_pad_button(pad_id, name, skin = None, **k): + def create_pad_button(pad_id, name, skin=None, **k): return PadButtonElement(pad_id, pad_sensitivity_update, True, MIDI_NOTE_TYPE, 0, (36 + pad_id), skin=skin, name=name, **k) - self.matrix_rows_raw = [ [ create_pad_button((7 - row) * 8 + column, str(column) + '_Clip_' + str(row) + '_Button', is_rgb=True, default_states={True: 'DefaultMatrix.On', - False: 'DefaultMatrix.Off'}) for column in xrange(8) ] for row in xrange(8) ] + self.matrix_rows_raw = [ [ create_pad_button((7 - row) * 8 + column, str(column) + '_Clip_' + str(row) + '_Button', is_rgb=True, default_states={True: 'DefaultMatrix.On',False: 'DefaultMatrix.Off'}) for column in xrange(8) ] for row in xrange(8) + ] double_press_rows = recursive_map(DoublePressElement, self.matrix_rows_raw) self.matrix = ButtonMatrixElement(name='Button_Matrix', rows=self.matrix_rows_raw) self.double_press_matrix = ButtonMatrixElement(name='Double_Press_Matrix', rows=double_press_rows) self.single_press_event_matrix = ButtonMatrixElement(name='Single_Press_Event_Matrix', rows=recursive_map(lambda x: x.single_press, double_press_rows)) self.double_press_event_matrix = ButtonMatrixElement(name='Double_Press_Event_Matrix', rows=recursive_map(lambda x: x.double_press, double_press_rows)) self.tempo_control_tap = create_note_button(10, 'Tempo_Control_Tap') - self.tempo_control = TouchEncoderElement(channel=0, identifier=14, map_mode=consts.GLOBAL_MAP_MODE, name='Tempo_Control', undo_step_handler=undo_handler, delete_handler=deleter, encoder_sensitivity=consts.ENCODER_SENSITIVITY, touch_element=self.tempo_control_tap) + self.tempo_control = TouchEncoderElement(channel=0, identifier=14, map_mode=consts.GLOBAL_MAP_MODE, name='Tempo_Control', undo_step_handler=undo_handler, delete_handler=deleter, encoder_sensitivity=BASE_ENCODER_SENSITIVITY, touch_element=self.tempo_control_tap) self.swing_control_tap = create_note_button(9, 'Swing_Control_Tap') - self.swing_control = TouchEncoderElement(channel=0, identifier=15, map_mode=consts.GLOBAL_MAP_MODE, name='Swing_Control', undo_step_handler=undo_handler, delete_handler=deleter, encoder_sensitivity=consts.ENCODER_SENSITIVITY, touch_element=self.swing_control_tap) + self.swing_control = TouchEncoderElement(channel=0, identifier=15, map_mode=consts.GLOBAL_MAP_MODE, name='Swing_Control', undo_step_handler=undo_handler, delete_handler=deleter, encoder_sensitivity=BASE_ENCODER_SENSITIVITY, touch_element=self.swing_control_tap) self.master_volume_control_tap = create_note_button(8, 'Master_Volume_Tap') - self.master_volume_control = TouchEncoderElement(channel=0, identifier=79, map_mode=consts.GLOBAL_MAP_MODE, undo_step_handler=undo_handler, delete_handler=deleter, name='Master_Volume_Control', encoder_sensitivity=consts.ENCODER_SENSITIVITY, touch_element=self.master_volume_control_tap) + self.master_volume_control = TouchEncoderElement(channel=0, identifier=79, map_mode=consts.GLOBAL_MAP_MODE, undo_step_handler=undo_handler, delete_handler=deleter, name='Master_Volume_Control', encoder_sensitivity=BASE_ENCODER_SENSITIVITY, touch_element=self.master_volume_control_tap) self.master_volume_control.mapping_sensitivity = continuous_mapping_sensitivity - self.global_param_touch_buttons_raw = [ create_note_button(index, 'Track_Control_Touch_' + str(index), resource_type=PrioritizedResource) for index in range(8) ] - self.global_param_touch_buttons = ButtonMatrixElement(name='Track_Control_Touches', rows=[self.global_param_touch_buttons_raw]) - self.parameter_controls_raw = [ TouchEncoderElement(channel=0, identifier=71 + index, map_mode=consts.GLOBAL_MAP_MODE, undo_step_handler=undo_handler, delete_handler=deleter, encoder_sensitivity=consts.ENCODER_SENSITIVITY, name='Track_Control_' + str(index), touch_element=self.global_param_touch_buttons_raw[index]) for index in xrange(8) ] - self.global_param_controls = ButtonMatrixElement(name='Track_Controls', rows=[self.parameter_controls_raw]) - self.fine_grain_param_controls_raw = [ FineGrainWithModifierEncoderElement(encoder, self.shift_button, fine_grained_continuous_mapping_sensitivity, continuous_mapping_sensitivity) for encoder in self.parameter_controls_raw ] - self.fine_grain_param_controls = ButtonMatrixElement(rows=[self.fine_grain_param_controls_raw]) + self.global_param_touch_buttons_raw = [ create_note_button(index, 'Track_Control_Touch_' + str(index), resource_type=PrioritizedResource) for index in range(8) + ] + self.global_param_touch_buttons = ButtonMatrixElement(name='Track_Control_Touches', rows=[ + self.global_param_touch_buttons_raw]) + self.parameter_controls_raw = [ TouchEncoderElement(channel=0, identifier=71 + index, map_mode=consts.GLOBAL_MAP_MODE, undo_step_handler=undo_handler, delete_handler=deleter, encoder_sensitivity=BASE_ENCODER_SENSITIVITY, name='Track_Control_' + str(index), touch_element=self.global_param_touch_buttons_raw[index]) for index in xrange(8) + ] + self.global_param_controls = ButtonMatrixElement(name='Track_Controls', rows=[ + self.parameter_controls_raw]) + self.fine_grain_param_controls_raw = [ FineGrainWithModifierEncoderElement(encoder, self.shift_button, fine_grained_continuous_mapping_sensitivity, continuous_mapping_sensitivity) for encoder in self.parameter_controls_raw + ] + self.fine_grain_param_controls = ButtonMatrixElement(rows=[ + self.fine_grain_param_controls_raw]) self.any_touch_button = MultiElement(*self.global_param_touch_buttons.nested_control_elements()) - self.playhead_element = PlayheadElement(playhead) \ No newline at end of file + self.playhead_element = PlayheadElement(playhead) + self.velocity_levels_element = VelocityLevelsElement(velocity_levels) + return \ No newline at end of file diff --git a/pushbase/fixed_length.py b/pushbase/fixed_length.py index 2bcb311a..21709f59 100644 --- a/pushbase/fixed_length.py +++ b/pushbase/fixed_length.py @@ -1,14 +1,20 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/fixed_length.py +# uncompyle6 version 2.9.10 +# Python bytecode 2.7 (62211) +# Decompiled from: Python 2.7.13 (default, Dec 17 2016, 23:03:43) +# [GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.42.1)] +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/fixed_length.py +# Compiled at: 2016-09-29 19:13:24 from __future__ import absolute_import, print_function from functools import partial import Live -from ableton.v2.base import Subject, listens, listenable_property, task +from ableton.v2.base import EventObject, listens, listenable_property, task from ableton.v2.control_surface import CompoundComponent, Component from ableton.v2.control_surface.control import RadioButtonControl, TextDisplayControl, ToggleButtonControl, ButtonControl, control_list from . import consts from .message_box_component import Messenger Quantization = Live.Song.Quantization -LENGTH_OPTIONS = (Quantization.q_quarter, +LENGTH_OPTIONS = ( + Quantization.q_quarter, Quantization.q_half, Quantization.q_bar, Quantization.q_2_bars, @@ -16,14 +22,16 @@ Quantization.q_8_bars, Quantization.q_8_bars, Quantization.q_8_bars) -LENGTH_OPTION_NAMES = ('1 Beat', '2 Beats', '1 Bar', '2 Bars', '4 Bars', '8 Bars', '16 Bars', '32 Bars') +LENGTH_OPTION_NAMES = ('1 Beat', '2 Beats', '1 Bar', '2 Bars', '4 Bars', '8 Bars', + '16 Bars', '32 Bars') LENGTH_LABELS = ('Recording length:', '', '', '') DEFAULT_LENGTH_OPTION_INDEX = list(LENGTH_OPTIONS).index(Quantization.q_2_bars) -class FixedLengthSetting(Subject): +class FixedLengthSetting(EventObject): option_names = LENGTH_OPTION_NAMES selected_index = listenable_property.managed(0) enabled = listenable_property.managed(False) + legato_launch = listenable_property.managed(False) def get_selected_length(self, song): index = self.selected_index @@ -37,17 +45,20 @@ def get_selected_length(self, song): class FixedLengthSettingComponent(Component): length_option_buttons = control_list(RadioButtonControl, checked_color='Option.Selected', unchecked_color='Option.Unselected', control_count=len(LENGTH_OPTIONS)) fixed_length_toggle_button = ToggleButtonControl(toggled_color='Option.On', untoggled_color='Option.Off') + legato_launch_toggle_button = ToggleButtonControl(toggled_color='FixedLength.PhraseAlignedOn', untoggled_color='FixedLength.PhraseAlignedOff') label_display_line = TextDisplayControl(LENGTH_LABELS) option_display_line = TextDisplayControl(LENGTH_OPTION_NAMES) - def __init__(self, fixed_length_setting = None, *a, **k): - raise fixed_length_setting is not None or AssertionError + def __init__(self, fixed_length_setting=None, *a, **k): + assert fixed_length_setting is not None super(FixedLengthSettingComponent, self).__init__(*a, **k) self._fixed_length_setting = fixed_length_setting self.length_option_buttons.connect_property(fixed_length_setting, 'selected_index') self.fixed_length_toggle_button.connect_property(fixed_length_setting, 'enabled') + self.legato_launch_toggle_button.connect_property(fixed_length_setting, 'legato_launch') self.__on_setting_selected_index_changes.subject = fixed_length_setting self.__on_setting_selected_index_changes(fixed_length_setting.selected_index) + return @listens('selected_index') def __on_setting_selected_index_changes(self, index): @@ -62,15 +73,16 @@ def _update_option_display(self): class FixedLengthComponent(CompoundComponent, Messenger): fixed_length_toggle_button = ButtonControl() - def __init__(self, settings_component = None, fixed_length_setting = None, *a, **k): - raise settings_component is not None or AssertionError - raise fixed_length_setting is not None or AssertionError + def __init__(self, settings_component=None, fixed_length_setting=None, *a, **k): + assert settings_component is not None + assert fixed_length_setting is not None super(FixedLengthComponent, self).__init__(*a, **k) self._fixed_length_setting = fixed_length_setting self._settings_component = self.register_component(settings_component) self._length_press_state = None self.__on_setting_enabled_changes.subject = fixed_length_setting self.__on_setting_enabled_changes(fixed_length_setting.enabled) + return @fixed_length_toggle_button.released_immediately def fixed_length_toggle_button(self, button): @@ -95,34 +107,38 @@ def fixed_length_toggle_button(self, button): slot = song.view.highlighted_clip_slot if slot is None: return - clip = slot.clip - if slot.is_recording and not clip.is_overdubbing: - self._length_press_state = (slot, clip.playing_position) + else: + clip = slot.clip + if slot.is_recording and not clip.is_overdubbing: + self._length_press_state = ( + slot, clip.playing_position) + return def _set_loop(self): song = self.song slot = song.view.highlighted_clip_slot if slot is None: return - clip = slot.clip - loop_set = False - if self._length_press_state is not None: - press_slot, press_position = self._length_press_state - if press_slot == slot and slot.is_recording and not clip.is_overdubbing: - length, _ = self._fixed_length_setting.get_selected_length(song) - one_bar = 4.0 * song.signature_numerator / song.signature_denominator - loop_end = int(press_position / one_bar) * one_bar - loop_start = loop_end - length - if loop_start >= 0.0: - clip.loop_end = loop_end - clip.end_marker = loop_end - clip.loop_start = loop_start - clip.start_marker = loop_start - self._tasks.add(task.sequence(task.delay(0), task.run(partial(slot.fire, force_legato=True, launch_quantization=Quantization.q_no_q)))) - self.song.overdub = False - loop_set = True - self._length_press_state = None - return loop_set + else: + clip = slot.clip + loop_set = False + if self._length_press_state is not None: + press_slot, press_position = self._length_press_state + if press_slot == slot and slot.is_recording and not clip.is_overdubbing: + length, _ = self._fixed_length_setting.get_selected_length(song) + one_bar = 4.0 * song.signature_numerator / song.signature_denominator + loop_end = int(press_position / one_bar) * one_bar + loop_start = loop_end - length + if loop_start >= 0.0: + clip.loop_end = loop_end + clip.end_marker = loop_end + clip.loop_start = loop_start + clip.start_marker = loop_start + self._tasks.add(task.sequence(task.delay(0), task.run(partial(slot.fire, force_legato=True, launch_quantization=Quantization.q_no_q)))) + self.song.overdub = False + loop_set = True + self._length_press_state = None + return loop_set @listens('enabled') def __on_setting_enabled_changes(self, enabled): diff --git a/pushbase/grid_resolution.py b/pushbase/grid_resolution.py index c7387386..e2436ea2 100644 --- a/pushbase/grid_resolution.py +++ b/pushbase/grid_resolution.py @@ -1,10 +1,16 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/grid_resolution.py +# uncompyle6 version 2.9.10 +# Python bytecode 2.7 (62211) +# Decompiled from: Python 2.7.13 (default, Dec 17 2016, 23:03:43) +# [GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.42.1)] +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/grid_resolution.py +# Compiled at: 2016-06-08 13:13:04 from __future__ import absolute_import, print_function import Live -from ableton.v2.base import product, listens, SlotManager, Subject +from ableton.v2.base import EventObject, product, listens GridQuantization = Live.Clip.GridQuantization QUANTIZATION_FACTOR = 24 -QUANTIZATION_LIST = [2.0, +QUANTIZATION_LIST = [ + 2.0, 3.0, 4.0, 6.0, @@ -15,8 +21,11 @@ CLIP_VIEW_GRID_LIST = tuple(product([GridQuantization.g_thirtysecond, GridQuantization.g_sixteenth, GridQuantization.g_eighth, - GridQuantization.g_quarter], [True, False])) -CLIP_LENGTH_LIST = [2.0, + GridQuantization.g_quarter], [ + True, + False])) +CLIP_LENGTH_LIST = [ + 2.0, 4.0, 4.0, 8.0, @@ -26,14 +35,14 @@ 32.0] DEFAULT_INDEX = 3 -class GridResolution(SlotManager, Subject): - __events__ = ('index',) +class GridResolution(EventObject): + __events__ = ('index', ) def __init__(self, *a, **k): super(GridResolution, self).__init__(*a, **k) self._index = DEFAULT_INDEX self._quantization_buttons = [] - self._quantization_button_slots = self.register_slot_manager() + self._quantization_button_slots = self.register_disconnectable(EventObject()) def _get_index(self): return self._index @@ -80,5 +89,7 @@ def _update_quantization_buttons(self): else: button.turn_off() + return + def update(self): self._update_quantization_buttons() \ No newline at end of file diff --git a/pushbase/instrument_component.py b/pushbase/instrument_component.py index 549cccea..2dd9f388 100644 --- a/pushbase/instrument_component.py +++ b/pushbase/instrument_component.py @@ -1,6 +1,11 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/instrument_component.py +# uncompyle6 version 2.9.10 +# Python bytecode 2.7 (62211) +# Decompiled from: Python 2.7.13 (default, Dec 17 2016, 23:03:43) +# [GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.42.1)] +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/instrument_component.py +# Compiled at: 2016-06-13 08:15:55 from __future__ import absolute_import, print_function -from ableton.v2.base import Subject, index_if, listenable_property, listens, liveobj_valid, find_if +from ableton.v2.base import EventObject, index_if, listenable_property, listens, liveobj_valid, find_if from ableton.v2.control_surface import CompoundComponent from ableton.v2.control_surface.control import control_matrix from ableton.v2.control_surface.components import PlayableComponent, Slideable, SlideComponent @@ -11,18 +16,18 @@ from .slideable_touch_strip_component import SlideableTouchStripComponent DEFAULT_SCALE = SCALES[0] -class NoteLayout(Subject): - is_horizontal = listenable_property.managed(True) - interval = listenable_property.managed(3) +class NoteLayout(EventObject): - def __init__(self, song = None, preferences = dict(), *a, **k): - raise liveobj_valid(song) or AssertionError + def __init__(self, song=None, preferences=dict(), *a, **k): + assert liveobj_valid(song) super(NoteLayout, self).__init__(*a, **k) self._song = song self._scale = self._get_scale_from_name(self._song.scale_name) self._preferences = preferences self._is_in_key = self._preferences.setdefault('is_in_key', True) self._is_fixed = self._preferences.setdefault('is_fixed', False) + self._interval = self._song.get_data('push-note-layout-interval', 3) + self._is_horizontal = self._song.get_data('push-note-layout-horizontal', True) @property def notes(self): @@ -67,6 +72,28 @@ def is_fixed(self, is_fixed): self._preferences['is_fixed'] = self._is_fixed self.notify_is_fixed(self._is_fixed) + @listenable_property + def interval(self): + return self._interval + + @interval.setter + def interval(self, interval): + if interval != self._interval: + self._interval = interval + self._song.set_data('push-note-layout-interval', interval) + self.notify_interval(interval) + + @listenable_property + def is_horizontal(self): + return self._is_horizontal + + @is_horizontal.setter + def is_horizontal(self, is_horizontal): + if is_horizontal != self._is_horizontal: + self._is_horizontal = is_horizontal + self._song.set_data('push-note-layout-horizontal', is_horizontal) + self.notify_is_horizontal(is_horizontal) + def _get_scale_from_name(self, name): return find_if(lambda scale: scale.name == name, SCALES) or DEFAULT_SCALE @@ -76,11 +103,11 @@ class InstrumentComponent(PlayableComponent, CompoundComponent, Slideable, Messe Class that sets up the button matrix as a piano, using different selectable layouts for the notes. """ - __events__ = ('pattern',) + __events__ = ('pattern', ) matrix = control_matrix(PadControl, pressed_color='Instrument.NoteAction') - def __init__(self, note_layout = None, *a, **k): - raise note_layout is not None or AssertionError + def __init__(self, note_layout=None, *a, **k): + assert note_layout is not None super(InstrumentComponent, self).__init__(*a, **k) self._note_layout = note_layout self._delete_button = None @@ -94,10 +121,12 @@ def __init__(self, note_layout = None, *a, **k): self._aftertouch_control = None self._slider = self.register_component(SlideComponent(self)) self._touch_slider = self.register_component(SlideableTouchStripComponent(self)) - for event in ('scale', 'root_note', 'is_in_key', 'is_fixed', 'is_horizontal', 'interval'): + for event in ('scale', 'root_note', 'is_in_key', 'is_fixed', 'is_horizontal', + 'interval'): self.register_slot(self._note_layout, self._on_note_layout_changed, event) self._update_pattern() + return def set_detail_clip(self, clip): if clip != self._detail_clip: @@ -110,7 +139,8 @@ def set_detail_clip(self, clip): @listens('notes') def _on_clip_notes_changed(self): if self._detail_clip: - self._has_notes = [False] * 128 + self._has_notes = [ + False] * 128 loop_start = self._detail_clip.loop_start loop_length = self._detail_clip.loop_end - loop_start notes = self._detail_clip.get_notes(loop_start, 0, loop_length, 128) @@ -136,7 +166,8 @@ def contents(self, index): if note is not None: return self._has_notes[note] return False - return False + else: + return False @property def page_length(self): @@ -156,9 +187,9 @@ def position_count(self): def _first_scale_note_offset(self): if not self._note_layout.is_in_key: return self._note_layout.notes[0] - elif self._note_layout.notes[0] == 0: - return 0 else: + if self._note_layout.notes[0] == 0: + return 0 return len(self._note_layout.notes) - index_if(lambda n: n >= 12, self._note_layout.notes) @property @@ -261,7 +292,8 @@ def _update_pattern(self): self.notify_pattern() def _invert_and_swap_coordinates(self, coordinates): - return (coordinates[1], self.width - 1 - coordinates[0]) + return ( + coordinates[1], self.width - 1 - coordinates[0]) def _get_note_info_for_coordinate(self, coordinate): x, y = self._invert_and_swap_coordinates(coordinate) @@ -276,7 +308,8 @@ def _button_should_be_enabled(self, button): def _note_translation_for_button(self, button): note_info = self._get_note_info_for_coordinate(button.coordinate) - return (note_info.index, note_info.channel) + return ( + note_info.index, note_info.channel) def _set_button_control_properties(self, button): super(InstrumentComponent, self)._set_button_control_properties(button) @@ -287,32 +320,38 @@ def _update_matrix(self): self._update_led_feedback() self._update_note_translations() - def _get_pattern(self, first_note = None): + def _get_pattern(self, first_note=None): if first_note is None: first_note = int(round(self._first_note)) interval = self._note_layout.interval notes = self._note_layout.notes + width = None + height = None octave = first_note / self.page_length offset = first_note % self.page_length - self._first_scale_note_offset() if interval == None: - interval = 8 + if self._note_layout.is_in_key: + interval = len(self._note_layout.notes) + if self._note_layout.is_horizontal: + width = interval + 1 + else: + height = interval + 1 + else: + interval = 8 elif not self._note_layout.is_in_key: - interval = [0, - 2, - 4, - 5, - 7, - 9, - 10, - 11][interval] + interval = [ + 0, 2, 4, 5, 7, 9, 10, 11][interval] if self._note_layout.is_horizontal: - steps = [1, interval] + steps = [ + 1, interval] origin = [offset, 0] else: - steps = [interval, 1] + steps = [ + interval, 1] origin = [0, offset] - return MelodicPattern(steps=steps, scale=notes, origin=origin, root_note=octave * 12, chromatic_mode=not self._note_layout.is_in_key) + return MelodicPattern(steps=steps, scale=notes, origin=origin, root_note=octave * 12, chromatic_mode=not self._note_layout.is_in_key, width=width, height=height) def _update_aftertouch(self): if self.is_enabled() and self._aftertouch_control != None: - self._aftertouch_control.send_value('mono') \ No newline at end of file + self._aftertouch_control.send_value('mono') + return \ No newline at end of file diff --git a/pushbase/internal_parameter.py b/pushbase/internal_parameter.py index 3c98e665..f347542e 100644 --- a/pushbase/internal_parameter.py +++ b/pushbase/internal_parameter.py @@ -1,7 +1,12 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/internal_parameter.py +# uncompyle6 version 2.9.10 +# Python bytecode 2.7 (62211) +# Decompiled from: Python 2.7.13 (default, Dec 17 2016, 23:03:43) +# [GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.42.1)] +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/internal_parameter.py +# Compiled at: 2016-06-08 13:13:04 from __future__ import absolute_import, print_function from Live import DeviceParameter -from ableton.v2.base import listenable_property, liveobj_valid, nop, Slot, SlotError, SlotManager, Subject +from ableton.v2.base import listenable_property, liveobj_valid, nop, EventError, EventObject, Slot def identity(value, _parent): return value @@ -17,14 +22,15 @@ def to_percentage_display(value): return unicode(percentage_str + ' %') -class InternalParameterBase(Subject): +class InternalParameterBase(EventObject): is_enabled = True is_quantized = False - def __init__(self, name = None, *a, **k): - raise name is not None or AssertionError + def __init__(self, name=None, *a, **k): + assert name is not None super(InternalParameterBase, self).__init__(*a, **k) self._name = name + return def _has_valid_parent(self): return liveobj_valid(self._parent) @@ -82,14 +88,15 @@ class InternalParameter(InternalParameterBase): Class implementing the DeviceParameter interface. Using instances of this class, we can mix script-internal values with DeviceParameter instances. """ - __events__ = ('value',) + __events__ = ('value', ) - def __init__(self, parent = None, display_value_conversion = None, *a, **k): + def __init__(self, parent=None, display_value_conversion=None, *a, **k): super(InternalParameter, self).__init__(*a, **k) self._value = 0.0 self._parent = parent self.set_display_value_conversion(display_value_conversion) self.set_scaling_functions(None, None) + return def set_display_value_conversion(self, display_value_conversion): self._display_value_conversion = display_value_conversion or to_percentage_display @@ -109,7 +116,7 @@ def _get_value(self): return self.min def _set_value(self, new_value): - raise self.min <= new_value <= self.max or AssertionError('Invalid value %f' % new_value) + assert self.min <= new_value <= self.max, 'Invalid value %f' % new_value self.linear_value = self._to_internal(new_value, self._parent) value = property(_get_value, _set_value) @@ -137,17 +144,27 @@ def display_value(self): return self._display_value_conversion(self.value) -class WrappingParameter(InternalParameter, SlotManager): +class PropertyHostMixin(object): + """ + This is only used to document the set_property_host API + """ + + def set_property_host(self, new_host): + raise NotImplementedError - def __init__(self, property_host = None, source_property = None, from_property_value = None, to_property_value = None, display_value_conversion = nop, value_items = [], *a, **k): - raise source_property is not None or AssertionError + +class WrappingParameter(InternalParameter, PropertyHostMixin): + + def __init__(self, property_host=None, source_property=None, from_property_value=None, to_property_value=None, display_value_conversion=nop, value_items=[], *a, **k): + assert source_property is not None super(WrappingParameter, self).__init__(display_value_conversion=display_value_conversion, *a, **k) self._property_host = property_host - raise self._property_host == None or hasattr(self._property_host, source_property) or source_property in dir(self._property_host) or AssertionError + assert self._property_host == None or hasattr(self._property_host, source_property) or source_property in dir(self._property_host) self._source_property = source_property self._value_items = value_items self.set_scaling_functions(to_property_value, from_property_value) - self._property_slot = self.register_slot(Slot(listener=self.notify_value, event=source_property, subject=self._property_host)) + self._property_slot = self.register_slot(Slot(listener=self.notify_value, event_name=source_property, subject=self._property_host)) + return def set_property_host(self, new_host): self._property_host = new_host @@ -167,7 +184,7 @@ def _get_value(self): return self.min def _set_value(self, new_value): - raise self.min <= new_value <= self.max or AssertionError('Invalid value %f' % new_value) + assert self.min <= new_value <= self.max, 'Invalid value %f' % new_value if liveobj_valid(self._property_host): try: setattr(self._property_host, self._source_property, self._to_internal(new_value, self._property_host)) @@ -194,17 +211,17 @@ def value_items(self): return self._value_items -class EnumWrappingParameter(InternalParameterBase, SlotManager): +class EnumWrappingParameter(InternalParameterBase, PropertyHostMixin): is_enabled = True is_quantized = True - def __init__(self, parent = None, index_property_host = None, values_property_host = None, values_property = None, index_property = None, value_type = int, to_index_conversion = None, from_index_conversion = None, *a, **k): - raise parent is not None or AssertionError - raise values_property is not None or AssertionError - raise index_property is not None or AssertionError + def __init__(self, parent=None, index_property_host=None, values_host=None, values_property=None, index_property=None, value_type=int, to_index_conversion=None, from_index_conversion=None, *a, **k): + assert parent is not None + assert values_property is not None + assert index_property is not None super(EnumWrappingParameter, self).__init__(*a, **k) self._parent = parent - self._values_property_host = values_property_host + self._values_host = values_host self._index_property_host = index_property_host self._values_property = values_property self._index_property = index_property @@ -213,11 +230,13 @@ def __init__(self, parent = None, index_property_host = None, values_property_ho self.value_type = value_type self._index_property_slot = self.register_slot(index_property_host, self.notify_value, index_property) try: - self.register_slot(self._values_property_host, self.notify_value_items, values_property) - except SlotError: + self.register_slot(self._values_host, self.notify_value_items, values_property) + except EventError: pass - def set_index_property_host(self, new_host): + return + + def set_property_host(self, new_host): self._index_property_host = new_host self._index_property_slot.subject = self._index_property_host @@ -243,8 +262,8 @@ def value(self, new_value): self._set_index(new_value) def _get_values(self): - if liveobj_valid(self._values_property_host): - return getattr(self._values_property_host, self._values_property) + if liveobj_valid(self._values_host): + return getattr(self._values_host, self._values_property) return [] def _get_index(self): @@ -269,7 +288,7 @@ def min(self): class RelativeInternalParameter(InternalParameter): - __events__ = ('delta',) + __events__ = ('delta', ) @property def default_value(self): diff --git a/pushbase/live_util.py b/pushbase/live_util.py new file mode 100644 index 00000000..da2eadbb --- /dev/null +++ b/pushbase/live_util.py @@ -0,0 +1,26 @@ +# uncompyle6 version 2.9.10 +# Python bytecode 2.7 (62211) +# Decompiled from: Python 2.7.13 (default, Dec 17 2016, 23:03:43) +# [GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.42.1)] +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/live_util.py +# Compiled at: 2016-05-20 03:43:52 +from __future__ import absolute_import, print_function + +def get_position_for_new_track(song, selected_track_index): + """ + Returns the index for a new track. The track will always be added to the + right of the selected track. If a group track is selected, it will be added + after the group. + """ + if not -1 <= selected_track_index < len(song.tracks): + raise IndexError('Index %i needs to be in [-1..%i]' % ( + selected_track_index, len(song.tracks))) + if selected_track_index == -1: + index = -1 + else: + index = selected_track_index + 1 + if song.tracks[selected_track_index].is_foldable: + while index < len(song.tracks) and song.tracks[index].is_grouped: + index += 1 + + return index \ No newline at end of file diff --git a/pushbase/loop_selector_component.py b/pushbase/loop_selector_component.py index 0a33f393..bec893ac 100644 --- a/pushbase/loop_selector_component.py +++ b/pushbase/loop_selector_component.py @@ -1,13 +1,18 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/loop_selector_component.py +# uncompyle6 version 2.9.10 +# Python bytecode 2.7 (62211) +# Decompiled from: Python 2.7.13 (default, Dec 17 2016, 23:03:43) +# [GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.42.1)] +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/loop_selector_component.py +# Compiled at: 2016-09-29 19:13:24 from __future__ import absolute_import, print_function from contextlib import contextmanager from functools import partial -from itertools import izip -from ableton.v2.base import clamp, listens, liveobj_changed, liveobj_valid, Subject, task +from itertools import ifilter, izip +from ableton.v2.base import EventObject, clamp, first, listens, liveobj_changed, liveobj_valid, task from ableton.v2.control_surface import defaults, Component from ableton.v2.control_surface.control import ButtonControl -def create_clip_in_selected_slot(creator, song, clip_length = None): +def create_clip_in_selected_slot(creator, song, clip_length=None): """ Create a new clip in the selected slot of if none exists, using a given creator object. Fires it if the song is playing and @@ -15,7 +20,7 @@ def create_clip_in_selected_slot(creator, song, clip_length = None): """ selected_slot = song.view.highlighted_clip_slot if creator and selected_slot and not selected_slot.has_clip: - creator.create(selected_slot, clip_length) + creator.create(selected_slot, clip_length, legato_launch=True) song.view.detail_clip = selected_slot.clip return selected_slot.clip @@ -24,7 +29,7 @@ def clip_is_new_recording(clip): return clip.is_recording and not clip.is_overdubbing -class Paginator(Subject): +class Paginator(EventObject): """ Paginator interface for objects that split continuous time into discrete pages. This can be used as trivial paginator splits time @@ -66,9 +71,9 @@ class LoopSelectorComponent(Component): """ next_page_button = ButtonControl() prev_page_button = ButtonControl() - __events__ = ('is_following',) + __events__ = ('is_following', ) - def __init__(self, clip_creator = None, measure_length = 4.0, follow_detail_clip = False, paginator = None, *a, **k): + def __init__(self, clip_creator=None, measure_length=4.0, follow_detail_clip=False, paginator=None, default_size=1, *a, **k): super(LoopSelectorComponent, self).__init__(*a, **k) self._clip_creator = clip_creator self._sequencer_clip = None @@ -81,6 +86,7 @@ def __init__(self, clip_creator = None, measure_length = 4.0, follow_detail_clip self._select_button = None self._short_loop_selector_matrix = None self._loop_selector_matrix = None + self._default_size = default_size self._pressed_pages = [] self._page_colors = [] self._measure_length = measure_length @@ -93,6 +99,7 @@ def __init__(self, clip_creator = None, measure_length = 4.0, follow_detail_clip self._on_song_playback_status_changed.subject = self.song if paginator is not None: self.set_paginator(paginator) + return def _get_is_following(self): return self._can_follow and self._is_following @@ -117,7 +124,7 @@ def _on_page_index_changed(self): def _on_page_length_changed(self): self._update_page_colors() self._update_follow_button() - self._select_start_page_if_out_of_loop_range() + self._select_start_page() def set_follow_button(self, button): self._follow_button = button @@ -140,20 +147,20 @@ def set_detail_clip(self, clip): self._on_loop_end_changed.subject = clip self._on_is_recording_changed.subject = clip self._sequencer_clip = clip - self._select_start_page_if_out_of_loop_range() + self._select_start_page() self._on_loop_changed() def _update_follow_button(self): if self.is_enabled() and self._follow_button: self._follow_button.set_light(self.is_following) - def _select_start_page_if_out_of_loop_range(self): - if self._sequencer_clip: + def _select_start_page(self): + if liveobj_valid(self._sequencer_clip): page_start = self._paginator.page_index * self._paginator.page_length - if self._sequencer_clip and (page_start <= self._sequencer_clip.loop_start or page_start >= self._sequencer_clip.loop_end): + if page_start <= self._sequencer_clip.loop_start or page_start >= self._sequencer_clip.loop_end: self._paginator.select_page_in_point(self._sequencer_clip.loop_start) - else: - self._paginator.select_page_in_point(0) + else: + self._paginator.select_page_in_point(page_start) @listens('loop_start') def _on_loop_start_changed(self): @@ -164,7 +171,7 @@ def _on_loop_end_changed(self): self._on_loop_changed() def _on_loop_changed(self): - if self._sequencer_clip: + if liveobj_valid(self._sequencer_clip): self._loop_start = self._sequencer_clip.loop_start self._loop_end = self._sequencer_clip.loop_end self._loop_length = self._loop_end - self._loop_start @@ -179,6 +186,9 @@ def set_loop_selector_matrix(self, matrix): self._on_loop_selector_matrix_value.subject = matrix if matrix: matrix.reset() + for button, _ in ifilter(first, matrix.iterbuttons()): + button.sensitivity_profile = 'loop' + self._update_page_colors() def set_short_loop_selector_matrix(self, matrix): @@ -256,14 +266,15 @@ def replace_and_restore_tail_colors(page_colors, page): self._update_page_leds() def _get_size(self): - return max(len(self._loop_selector_matrix or []), len(self._short_loop_selector_matrix or []), 1) + return max(len(self._loop_selector_matrix or []), len(self._short_loop_selector_matrix or []), self._default_size) def _get_loop_in_pages(self): page_length = self._page_length_in_beats loop_start = int(self._loop_start / page_length) loop_end = int(self._loop_end / page_length) loop_length = loop_end - loop_start + int(self._loop_end % page_length != 0) - return (loop_start, loop_length) + return ( + loop_start, loop_length) def _selected_pages_range(self): size = self._get_size() @@ -271,7 +282,8 @@ def _selected_pages_range(self): seq_page_length = max(self._paginator.page_length / page_length, 1) seq_page_start = int(self._paginator.page_index * self._paginator.page_length / page_length) seq_page_end = int(min(seq_page_start + seq_page_length, self.page_offset + size)) - return (seq_page_start, seq_page_end) + return ( + seq_page_start, seq_page_end) def _update_page_colors(self): """ @@ -356,7 +368,8 @@ def _on_short_loop_selector_matrix_value(self, value, x, y, is_momentary): page = x + y * self._short_loop_selector_matrix.width() if self.is_enabled(): if value or not is_momentary: - self._pressed_pages = [page] + self._pressed_pages = [ + page] self._try_set_loop() self._pressed_pages = [] @@ -393,7 +406,7 @@ def handle_page_press_on_clip(page): self._pressed_pages.append(page) absolute_page = page + self.page_offset if not self._select_button or not self._select_button.is_pressed(): - if self._sequencer_clip == None and not self.song.view.highlighted_clip_slot.has_clip: + if not liveobj_valid(self._sequencer_clip) and not self.song.view.highlighted_clip_slot.has_clip: create_clip(absolute_page) elif liveobj_valid(self._sequencer_clip): handle_page_press_on_clip(absolute_page) @@ -409,7 +422,7 @@ def _try_select_page(self, page): def _try_set_loop(self): did_set_loop = False - if self._sequencer_clip: + if liveobj_valid(self._sequencer_clip): if not clip_is_new_recording(self._sequencer_clip): lowest_page = min(self._pressed_pages) + self.page_offset if self._try_select_page(lowest_page): @@ -427,14 +440,16 @@ def _set_loop_in_live(self): loop_end = self._quantize_page_index(end_page, quant) + quant if loop_start >= self._sequencer_clip.loop_end: self._sequencer_clip.loop_end = loop_end - self._sequencer_clip.loop_start = loop_start - self._sequencer_clip.end_marker = loop_end - self._sequencer_clip.start_marker = loop_start + if self._sequencer_clip.loop_end == loop_end: + self._sequencer_clip.loop_start = loop_start + self._sequencer_clip.end_marker = loop_end + self._sequencer_clip.start_marker = loop_start else: self._sequencer_clip.loop_start = loop_start - self._sequencer_clip.loop_end = loop_end - self._sequencer_clip.start_marker = loop_start - self._sequencer_clip.end_marker = loop_end + if self._sequencer_clip.loop_start == loop_start: + self._sequencer_clip.loop_end = loop_end + self._sequencer_clip.end_marker = loop_end + self._sequencer_clip.start_marker = loop_start self._sequencer_clip.view.show_loop() @property @@ -443,7 +458,7 @@ def _can_follow(self): @property def _page_length_in_beats(self): - return clamp(self._paginator.page_length, 0.5, self._one_measure_in_beats) + return clamp(self._paginator.page_length, 0.25, self._one_measure_in_beats) @property def _one_measure_in_beats(self): diff --git a/pushbase/mapped_control.py b/pushbase/mapped_control.py index d16e6dfb..481a20ff 100644 --- a/pushbase/mapped_control.py +++ b/pushbase/mapped_control.py @@ -1,4 +1,9 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/mapped_control.py +# uncompyle6 version 2.9.10 +# Python bytecode 2.7 (62211) +# Decompiled from: Python 2.7.13 (default, Dec 17 2016, 23:03:43) +# [GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.42.1)] +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/mapped_control.py +# Compiled at: 2016-05-20 03:43:52 from __future__ import absolute_import, print_function from ableton.v2.base import clamp, listens, liveobj_valid from ableton.v2.control_surface.control import MappedControl as MappedControlBase @@ -41,6 +46,7 @@ def _update_direct_connection(self): self._control_value.subject = None self._update_control_element() self._quantized_stepper.reset() + return def _update_control_element(self): if liveobj_valid(self.mapped_parameter): @@ -59,7 +65,7 @@ def _update_control_sensitivity(self): @listens('normalized_value') def _control_value(self, value): if is_zoom_parameter(self.mapped_parameter): - self.mapped_parameter.zoom(value) + self.mapped_parameter.zoom(value * self._control_element.mapping_sensitivity) if self.mapped_parameter.is_quantized: steps = self._quantized_stepper.advance(value) if steps != 0: diff --git a/pushbase/matrix_maps.py b/pushbase/matrix_maps.py index 1bae9006..f742780e 100644 --- a/pushbase/matrix_maps.py +++ b/pushbase/matrix_maps.py @@ -1,24 +1,18 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/matrix_maps.py +# uncompyle6 version 2.9.10 +# Python bytecode 2.7 (62211) +# Decompiled from: Python 2.7.13 (default, Dec 17 2016, 23:03:43) +# [GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.42.1)] +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/matrix_maps.py +# Compiled at: 2016-05-20 03:43:52 """ Pad Translations for Drum Rack (pad_x, pad_y, note, channel) """ from __future__ import absolute_import, print_function -PAD_TRANSLATIONS = ((0, 0, 60, 13), - (1, 0, 61, 13), - (2, 0, 62, 13), - (3, 0, 63, 13), - (0, 1, 52, 13), - (1, 1, 53, 13), - (2, 1, 54, 13), - (3, 1, 55, 13), - (0, 2, 44, 13), - (1, 2, 45, 13), - (2, 2, 46, 13), - (3, 2, 47, 13), - (0, 3, 36, 13), - (1, 3, 37, 13), - (2, 3, 38, 13), - (3, 3, 39, 13)) +PAD_TRANSLATIONS = ( + (0, 0, 60, 13), (1, 0, 61, 13), (2, 0, 62, 13), (3, 0, 63, 13), + (0, 1, 52, 13), (1, 1, 53, 13), (2, 1, 54, 13), (3, 1, 55, 13), + (0, 2, 44, 13), (1, 2, 45, 13), (2, 2, 46, 13), (3, 2, 47, 13), + (0, 3, 36, 13), (1, 3, 37, 13), (2, 3, 38, 13), (3, 3, 39, 13)) NON_FEEDBACK_CHANNEL = 0 FEEDBACK_CHANNELS = range(8, 16) PAD_FEEDBACK_CHANNEL = FEEDBACK_CHANNELS[-1] diff --git a/pushbase/melodic_component.py b/pushbase/melodic_component.py index 149bd3b4..10057b5c 100644 --- a/pushbase/melodic_component.py +++ b/pushbase/melodic_component.py @@ -1,40 +1,52 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/melodic_component.py +# uncompyle6 version 2.9.10 +# Python bytecode 2.7 (62211) +# Decompiled from: Python 2.7.13 (default, Dec 17 2016, 23:03:43) +# [GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.42.1)] +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/melodic_component.py +# Compiled at: 2016-11-16 18:13:20 from __future__ import absolute_import, print_function from itertools import izip_longest from ableton.v2.base import forward_property, find_if, listens from ableton.v2.control_surface.elements import to_midi_value -from ableton.v2.control_surface.mode import ModesComponent, LayerMode +from ableton.v2.control_surface.mode import LayerMode +from .consts import MessageBoxText from .instrument_component import InstrumentComponent from .loop_selector_component import LoopSelectorComponent from .matrix_maps import PLAYHEAD_FEEDBACK_CHANNELS, NON_FEEDBACK_CHANNEL from .melodic_pattern import pitch_index_to_string -from .message_box_component import Messenger +from .messenger_mode_component import MessengerModesComponent from .note_editor_component import NoteEditorComponent from .note_editor_paginator import NoteEditorPaginator from .playhead_component import PlayheadComponent +from .step_duplicator import StepDuplicatorComponent NUM_NOTE_EDITORS = 7 -class MelodicComponent(ModesComponent, Messenger): +class MelodicComponent(MessengerModesComponent): - def __init__(self, clip_creator = None, parameter_provider = None, grid_resolution = None, note_layout = None, note_editor_settings = None, note_editor_class = NoteEditorComponent, velocity_range_thresholds = None, skin = None, instrument_play_layer = None, instrument_sequence_layer = None, pitch_mod_touch_strip_mode = None, layer = None, *a, **k): + def __init__(self, clip_creator=None, parameter_provider=None, grid_resolution=None, note_layout=None, note_editor_settings=None, note_editor_class=NoteEditorComponent, velocity_range_thresholds=None, skin=None, instrument_play_layer=None, instrument_sequence_layer=None, pitch_mod_touch_strip_mode=None, layer=None, *a, **k): super(MelodicComponent, self).__init__(*a, **k) self._matrices = None self._grid_resolution = grid_resolution - self._instrument = self.register_component(InstrumentComponent(note_layout=note_layout)) - self._note_editors = self.register_components(*[ note_editor_class(clip_creator=clip_creator, grid_resolution=self._grid_resolution, velocity_range_thresholds=velocity_range_thresholds, is_enabled=False) for _ in xrange(NUM_NOTE_EDITORS) ]) + self._instrument, self._step_duplicator = self.register_components(InstrumentComponent(note_layout=note_layout), StepDuplicatorComponent()) + self._note_editors = self.register_components(*[ note_editor_class(clip_creator=clip_creator, grid_resolution=self._grid_resolution, velocity_range_thresholds=velocity_range_thresholds, is_enabled=False) for _ in xrange(NUM_NOTE_EDITORS) + ]) for editor in self._note_editors: note_editor_settings.add_editor(editor) + editor.set_step_duplicator(self._step_duplicator) self._paginator = NoteEditorPaginator(self._note_editors) self._loop_selector = self.register_component(LoopSelectorComponent(clip_creator=clip_creator, paginator=self._paginator, is_enabled=False)) self._playhead = None self._playhead_component = self.register_component(PlayheadComponent(grid_resolution=grid_resolution, paginator=self._paginator, follower=self._loop_selector, feedback_channels=PLAYHEAD_FEEDBACK_CHANNELS, is_enabled=False)) - self.add_mode('play', [LayerMode(self._instrument, instrument_play_layer), pitch_mod_touch_strip_mode]) - self.add_mode('sequence', [LayerMode(self._instrument, instrument_sequence_layer), + self.add_mode('play', [ + LayerMode(self._instrument, instrument_play_layer), + pitch_mod_touch_strip_mode], message=MessageBoxText.LAYOUT_MELODIC_PLAYING) + self.add_mode('sequence', [ + LayerMode(self._instrument, instrument_sequence_layer), self._loop_selector, note_editor_settings, LayerMode(self, layer), - self._playhead_component] + self._note_editors) + self._playhead_component] + self._note_editors, message=MessageBoxText.LAYOUT_MELODIC_SEQUENCER) self.selected_mode = 'play' self._on_detail_clip_changed.subject = self.song.view self._on_pattern_changed.subject = self._instrument @@ -44,6 +56,7 @@ def __init__(self, clip_creator = None, parameter_provider = None, grid_resoluti self._skin = skin self._playhead_color = 'Melodic.Playhead' self._update_playhead_color() + return def set_playhead(self, playhead): self._playhead = playhead @@ -61,12 +74,15 @@ def set_short_loop_selector_matrix(self, matrix): next_loop_page_button = forward_property('_loop_selector')('next_page_button') prev_loop_page_button = forward_property('_loop_selector')('prev_page_button') + def set_duplicate_button(self, button): + self._step_duplicator.button.set_control_element(button) + def set_note_editor_matrices(self, matrices): - raise not matrices or len(matrices) <= NUM_NOTE_EDITORS or AssertionError + assert not matrices or len(matrices) <= NUM_NOTE_EDITORS self._matrices = matrices for editor, matrix in izip_longest(self._note_editors, matrices or []): if editor: - editor.set_button_matrix(matrix) + editor.set_matrix(matrix) self._update_matrix_channels_for_playhead() @@ -90,6 +106,7 @@ def _on_detail_clip_changed(self): self._loop_selector.set_detail_clip(clip) self._playhead_component.set_clip(clip) self._instrument.set_detail_clip(clip) + return def _set_full_velocity(self, enable): for note_editor in self._note_editors: @@ -105,7 +122,7 @@ def set_quantization_buttons(self, buttons): def set_mute_button(self, button): for e in self._note_editors: - e.set_mute_button(button) + e.mute_button.set_control_element(button) @listens('position') def _on_notes_changed(self, *args): @@ -138,6 +155,8 @@ def _update_matrix_channels_for_playhead(self): button.set_identifier(button._original_identifier) button.set_channel(NON_FEEDBACK_CHANNEL) + return + def _update_playhead_color(self): if self.is_enabled() and self._skin and self._playhead: self._playhead.velocity = to_midi_value(self._skin[self._playhead_color]) @@ -147,7 +166,7 @@ def update(self): self._on_detail_clip_changed() self._update_playhead_color() - def _show_notes_information(self, mode = None): + def _show_notes_information(self, mode=None): if self.is_enabled(): if mode is None: mode = self.selected_mode @@ -161,4 +180,6 @@ def _show_notes_information(self, mode = None): message = u'Play %s to %s' start_note = self._instrument._pattern.note(0, 0).index end_note = self._instrument._pattern.note(7, 7).index - self.show_notification(message % (pitch_index_to_string(start_note), pitch_index_to_string(end_note))) \ No newline at end of file + self.show_notification(message % (pitch_index_to_string(start_note), + pitch_index_to_string(end_note))) + return \ No newline at end of file diff --git a/pushbase/melodic_pattern.py b/pushbase/melodic_pattern.py index c34cdc3e..42a26dcc 100644 --- a/pushbase/melodic_pattern.py +++ b/pushbase/melodic_pattern.py @@ -1,11 +1,17 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/melodic_pattern.py +# uncompyle6 version 2.9.10 +# Python bytecode 2.7 (62211) +# Decompiled from: Python 2.7.13 (default, Dec 17 2016, 23:03:43) +# [GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.42.1)] +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/melodic_pattern.py +# Compiled at: 2016-05-20 03:43:52 from __future__ import absolute_import, print_function from ableton.v2.base import NamedTuple, lazy_attribute, memoize, find_if from . import consts from .matrix_maps import FEEDBACK_CHANNELS CIRCLE_OF_FIFTHS = tuple([ 7 * k % 12 for k in range(12) ]) ROOT_NOTES = CIRCLE_OF_FIFTHS[0:6] + CIRCLE_OF_FIFTHS[-1:5:-1] -NOTE_NAMES = (u'C', u'D\u266d', u'D', u'E\u266d', u'E', u'F', u'G\u266d', u'G', u'A\u266d', u'A', u'B\u266d', u'B') +NOTE_NAMES = (u'C', u'D\u266d', u'D', u'E\u266d', u'E', u'F', u'G\u266d', u'G', u'A\u266d', + u'A', u'B\u266d', u'B') def pitch_index_to_string(index): if 0 <= index < 128: @@ -31,169 +37,32 @@ def __str__(self): return unicode(self).encode('utf-8') -SCALES = (Scale(name='Major', notes=[0, - 2, - 4, - 5, - 7, - 9, - 11]), - Scale(name='Minor', notes=[0, - 2, - 3, - 5, - 7, - 8, - 10]), - Scale(name='Dorian', notes=[0, - 2, - 3, - 5, - 7, - 9, - 10]), - Scale(name='Mixolydian', notes=[0, - 2, - 4, - 5, - 7, - 9, - 10]), - Scale(name='Lydian', notes=[0, - 2, - 4, - 6, - 7, - 9, - 11]), - Scale(name='Phrygian', notes=[0, - 1, - 3, - 5, - 7, - 8, - 10]), - Scale(name='Locrian', notes=[0, - 1, - 3, - 5, - 6, - 8, - 10]), - Scale(name='Diminished', notes=[0, - 1, - 3, - 4, - 6, - 7, - 9, - 10]), - Scale(name='Whole-half', notes=[0, - 2, - 3, - 5, - 6, - 8, - 9, - 11]), - Scale(name='Whole Tone', notes=[0, - 2, - 4, - 6, - 8, - 10]), - Scale(name='Minor Blues', notes=[0, - 3, - 5, - 6, - 7, - 10]), - Scale(name='Minor Pentatonic', notes=[0, - 3, - 5, - 7, - 10]), - Scale(name='Major Pentatonic', notes=[0, - 2, - 4, - 7, - 9]), - Scale(name='Harmonic Minor', notes=[0, - 2, - 3, - 5, - 7, - 8, - 11]), - Scale(name='Melodic Minor', notes=[0, - 2, - 3, - 5, - 7, - 9, - 11]), - Scale(name='Super Locrian', notes=[0, - 1, - 3, - 4, - 6, - 8, - 10]), - Scale(name='Bhairav', notes=[0, - 1, - 4, - 5, - 7, - 8, - 11]), - Scale(name='Hungarian Minor', notes=[0, - 2, - 3, - 6, - 7, - 8, - 11]), - Scale(name='Minor Gypsy', notes=[0, - 1, - 4, - 5, - 7, - 8, - 10]), - Scale(name='Hirojoshi', notes=[0, - 2, - 3, - 7, - 8]), - Scale(name='In-Sen', notes=[0, - 1, - 5, - 7, - 10]), - Scale(name='Iwato', notes=[0, - 1, - 5, - 6, - 10]), - Scale(name='Kumoi', notes=[0, - 2, - 3, - 7, - 9]), - Scale(name='Pelog', notes=[0, - 1, - 3, - 4, - 7, - 8]), - Scale(name='Spanish', notes=[0, - 1, - 3, - 4, - 5, - 6, - 8, - 10])) +SCALES = ( + Scale(name='Major', notes=[0, 2, 4, 5, 7, 9, 11]), + Scale(name='Minor', notes=[0, 2, 3, 5, 7, 8, 10]), + Scale(name='Dorian', notes=[0, 2, 3, 5, 7, 9, 10]), + Scale(name='Mixolydian', notes=[0, 2, 4, 5, 7, 9, 10]), + Scale(name='Lydian', notes=[0, 2, 4, 6, 7, 9, 11]), + Scale(name='Phrygian', notes=[0, 1, 3, 5, 7, 8, 10]), + Scale(name='Locrian', notes=[0, 1, 3, 5, 6, 8, 10]), + Scale(name='Diminished', notes=[0, 1, 3, 4, 6, 7, 9, 10]), + Scale(name='Whole-half', notes=[0, 2, 3, 5, 6, 8, 9, 11]), + Scale(name='Whole Tone', notes=[0, 2, 4, 6, 8, 10]), + Scale(name='Minor Blues', notes=[0, 3, 5, 6, 7, 10]), + Scale(name='Minor Pentatonic', notes=[0, 3, 5, 7, 10]), + Scale(name='Major Pentatonic', notes=[0, 2, 4, 7, 9]), + Scale(name='Harmonic Minor', notes=[0, 2, 3, 5, 7, 8, 11]), + Scale(name='Melodic Minor', notes=[0, 2, 3, 5, 7, 9, 11]), + Scale(name='Super Locrian', notes=[0, 1, 3, 4, 6, 8, 10]), + Scale(name='Bhairav', notes=[0, 1, 4, 5, 7, 8, 11]), + Scale(name='Hungarian Minor', notes=[0, 2, 3, 6, 7, 8, 11]), + Scale(name='Minor Gypsy', notes=[0, 1, 4, 5, 7, 8, 10]), + Scale(name='Hirojoshi', notes=[0, 2, 3, 7, 8]), + Scale(name='In-Sen', notes=[0, 1, 5, 7, 10]), + Scale(name='Iwato', notes=[0, 1, 5, 6, 10]), + Scale(name='Kumoi', notes=[0, 2, 3, 7, 9]), + Scale(name='Pelog', notes=[0, 1, 3, 4, 7, 8]), + Scale(name='Spanish', notes=[0, 1, 3, 4, 5, 6, 8, 10])) def scale_by_name(name): return find_if(lambda m: m.name == name, SCALES) @@ -206,11 +75,14 @@ class NoteInfo(NamedTuple): class MelodicPattern(NamedTuple): - steps = [0, 0] + steps = [ + 0, 0] scale = range(12) root_note = 0 origin = [0, 0] chromatic_mode = False + width = None + height = None @lazy_attribute def extended_scale(self): @@ -225,7 +97,9 @@ def is_aligned(self): return not self.origin[0] and not self.origin[1] and abs(self.root_note) % 12 == self.extended_scale[0] def note(self, x, y): - return self._get_note_info(self._octave_and_note(x, y), self.root_note, x + FEEDBACK_CHANNELS[0]) + if not self._boundary_reached(x, y): + return self._get_note_info(self._octave_and_note(x, y), self.root_note, x + FEEDBACK_CHANNELS[0]) + return NoteInfo() def __getitem__(self, i): root_note = self.root_note @@ -233,12 +107,16 @@ def __getitem__(self, i): root_note = 0 if self.is_aligned else -12 return self._get_note_info(self._octave_and_note_linear(i), root_note) + def _boundary_reached(self, x, y): + return self.width is not None and x >= self.width or self.height is not None and y >= self.height + def _octave_and_note_by_index(self, index): scale = self.extended_scale scale_size = len(scale) octave = index / scale_size note = scale[index % scale_size] - return (octave, note) + return ( + octave, note) def _octave_and_note(self, x, y): index = self.steps[0] * (self.origin[0] + x) + self.steps[1] * (self.origin[1] + y) @@ -247,12 +125,12 @@ def _octave_and_note(self, x, y): def _color_for_note(self, note): if note == self.scale[0]: return 'NoteBase' - elif note in self.scale: - return 'NoteScale' else: + if note in self.scale: + return 'NoteScale' return 'NoteNotScale' - def _get_note_info(self, (octave, note), root_note, channel = 0): + def _get_note_info(self, (octave, note), root_note, channel=0): note_index = 12 * octave + note + root_note if 0 <= note_index <= 127: return NoteInfo(index=note_index, channel=channel, color=self._color_for_note(note)) diff --git a/pushbase/message_box_component.py b/pushbase/message_box_component.py index 8cb46f15..953ef153 100644 --- a/pushbase/message_box_component.py +++ b/pushbase/message_box_component.py @@ -1,5 +1,11 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/message_box_component.py +# uncompyle6 version 2.9.10 +# Python bytecode 2.7 (62211) +# Decompiled from: Python 2.7.13 (default, Dec 17 2016, 23:03:43) +# [GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.42.1)] +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/message_box_component.py +# Compiled at: 2016-06-08 13:13:04 from __future__ import absolute_import, print_function +import re from itertools import izip_longest from ableton.v2.base import forward_property, const, nop, listens, listenable_property from ableton.v2.base.dependency import dependency @@ -7,6 +13,17 @@ from ableton.v2.control_surface.elements import DisplayDataSource from ableton.v2.control_surface.components import BackgroundComponent from .consts import DISPLAY_LENGTH, MessageBoxText +FORMAT_SPECIFIER_WITH_MARKUP_PATTERN = re.compile('[%](len=([0-9]+),)?([^%]*?[diouxXeEfFgGcrs])') + +def strip_restriction_markup_and_format(text_or_text_spec): + if isinstance(text_or_text_spec, tuple): + format_string = text_or_text_spec[0] + stripped_format_string = re.sub(FORMAT_SPECIFIER_WITH_MARKUP_PATTERN, '%\\g<3>', format_string) + arguments = text_or_text_spec[1:] + return stripped_format_string % arguments + else: + return text_or_text_spec + class Notification(object): @@ -28,15 +45,16 @@ class MessageBoxComponent(BackgroundComponent): """ Component showing a temporary message in the display """ - __events__ = ('cancel',) + __events__ = ('cancel', ) num_lines = 4 def __init__(self, *a, **k): super(MessageBoxComponent, self).__init__(*a, **k) self._current_text = None self._can_cancel = False - self.data_sources = map(DisplayDataSource, ('',) * self.num_lines) + self.data_sources = map(DisplayDataSource, ('', ) * self.num_lines) self._notification_display = None + return def _set_display_line(self, n, display_line): if display_line: @@ -65,6 +83,7 @@ def _update_cancel_button(self): button.reset() if self._can_cancel and button: button.set_light('MessageBox.Cancel') + return def _update_display(self): if self._current_text != None: @@ -75,6 +94,7 @@ def _update_display(self): if self._can_cancel: self.data_sources[-1].set_display_string('[ Ok ]'.rjust(DISPLAY_LENGTH - 1)) + return @listens('value') def _on_cancel_button_value(self, value): @@ -121,8 +141,9 @@ def __init__(self, *a, **k): self._message_box = self.register_component(MessageBoxComponent()) self._message_box.set_enabled(False) self._next_message = None - self._on_open_dialog_count.subject = self.application() + self._on_open_dialog_count.subject = self.application self._on_message_cancel.subject = self._message_box + return message_box_layer = forward_property('_message_box')('layer') @@ -140,23 +161,26 @@ def expect_dialog(self, message): def _on_open_dialog_count(self): self._update_dialog(open_dialog_changed=True) self._next_message = None + return @listens('cancel') def _on_message_cancel(self): self._next_message = None try: - self.application().press_current_dialog_button(0) + self.application.press_current_dialog_button(0) except RuntimeError: pass self._update_dialog() + return - def _update_dialog(self, open_dialog_changed = False): + def _update_dialog(self, open_dialog_changed=False): message = self._next_message or MessageBoxText.LIVE_DIALOG can_cancel = self._next_message != None self._message_box.text = message self._message_box.can_cancel = can_cancel - self._message_box.set_enabled(self.application().open_dialog_count > 0 or not open_dialog_changed and self._next_message) + self._message_box.set_enabled(self.application.open_dialog_count > 0 or not open_dialog_changed and self._next_message) + return class InfoComponent(BackgroundComponent): @@ -164,7 +188,7 @@ class InfoComponent(BackgroundComponent): Component that will show an info text and grab all components that should be unusable. """ - def __init__(self, info_text = '', *a, **k): + def __init__(self, info_text='', *a, **k): super(InfoComponent, self).__init__(*a, **k) self._data_source = DisplayDataSource() self._data_source.set_display_string(info_text) diff --git a/pushbase/messenger_mode_component.py b/pushbase/messenger_mode_component.py new file mode 100644 index 00000000..05f2d523 --- /dev/null +++ b/pushbase/messenger_mode_component.py @@ -0,0 +1,34 @@ +# uncompyle6 version 2.9.10 +# Python bytecode 2.7 (62211) +# Decompiled from: Python 2.7.13 (default, Dec 17 2016, 23:03:43) +# [GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.42.1)] +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/messenger_mode_component.py +# Compiled at: 2016-06-08 13:13:04 +from __future__ import absolute_import, print_function +from ableton.v2.base import BooleanContext +from ableton.v2.control_surface.mode import ModesComponent +from .message_box_component import Messenger + +class MessengerModesComponent(ModesComponent, Messenger): + notify_when_enabled = False + + def __init__(self, *a, **k): + super(MessengerModesComponent, self).__init__(*a, **k) + self._mode_message_map = {} + self._is_being_enabled = BooleanContext() + + def add_mode(self, name, mode_or_component, message=None, **k): + super(MessengerModesComponent, self).add_mode(name, mode_or_component, **k) + self._mode_message_map[name] = message + + def on_enabled_changed(self): + with self._is_being_enabled(): + super(MessengerModesComponent, self).on_enabled_changed() + + def _do_enter_mode(self, name): + super(MessengerModesComponent, self)._do_enter_mode(name) + if not self._is_being_enabled or self.notify_when_enabled: + message = self._mode_message_map.get(name, None) + if message: + self.show_notification(message) + return \ No newline at end of file diff --git a/pushbase/note_editor_component.py b/pushbase/note_editor_component.py index 75f4e19b..d46b408e 100644 --- a/pushbase/note_editor_component.py +++ b/pushbase/note_editor_component.py @@ -1,23 +1,51 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/note_editor_component.py +# uncompyle6 version 2.9.10 +# Python bytecode 2.7 (62211) +# Decompiled from: Python 2.7.13 (default, Dec 17 2016, 23:03:43) +# [GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.42.1)] +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/note_editor_component.py +# Compiled at: 2016-11-16 18:13:20 from __future__ import absolute_import, print_function +from bisect import bisect from contextlib import contextmanager from functools import partial from itertools import chain, imap, ifilter -from ableton.v2.base import clamp, first, index_if, in_range, listens, liveobj_valid, product, sign, Subject, task +from ableton.v2.base import clamp, first, index_if, in_range, listens, listenable_property, liveobj_valid, product, sign, EventObject, task from ableton.v2.control_surface import CompoundComponent, defaults +from ableton.v2.control_surface.control import ButtonControl, control_matrix from .loop_selector_component import create_clip_in_selected_slot from .matrix_maps import PLAYHEAD_FEEDBACK_CHANNELS +from .pad_control import PadControl + +def note_pitch(note): + return note[0] + + +def note_start_time(note): + return note[1] + + +def note_length(note): + return note[2] + + +def note_velocity(note): + return note[3] + + +def note_muted(note): + return note[4] + + DEFAULT_VELOCITY = 100 -DEFAULT_VELOCITY_RANGE_THRESHOLDS = [127, 100, 0] +DEFAULT_VELOCITY_RANGE_THRESHOLDS = [ + 127, 100, 0] VELOCITY_RANGE_INDEX_TO_COLOR = ['Full', 'High', 'Low'] BEAT_TIME_EPSILON = 1e-05 -def color_for_note(note, velocity_range_thresholds = None): +def color_for_note(note, velocity_range_thresholds=None): thresholds = velocity_range_thresholds or DEFAULT_VELOCITY_RANGE_THRESHOLDS - velocity = note[3] - muted = note[4] - if not muted: - velocity_range_index = index_if(lambda threshold: velocity >= threshold, thresholds) + if not note_muted(note): + velocity_range_index = index_if(lambda threshold: note_velocity(note) >= threshold, thresholds) return VELOCITY_RANGE_INDEX_TO_COLOR[velocity_range_index] else: return 'Muted' @@ -27,20 +55,22 @@ def most_significant_note(notes): return max(notes, key=lambda n: n[3]) +def is_triplet_quantization(triplet_factor): + return triplet_factor == 0.75 + + MAX_CLIP_LENGTH = 100000000 -RELATIVE_OFFSET = 0.25 +RELATIVE_OFFSET = 0.24 class TimeStep(object): """ A fixed step (time range) for the step sequencer """ - def __init__(self, start, length, clip_start = 0.0, clip_end = MAX_CLIP_LENGTH, *a, **k): + def __init__(self, start, length, *a, **k): super(TimeStep, self).__init__(*a, **k) self.start = start self.length = length - self.clip_start = clip_start - self.clip_end = clip_end @property def offset(self): @@ -53,10 +83,11 @@ def right_boundary(self): return max(0, self.start - self.offset + self.length - BEAT_TIME_EPSILON) def includes_note(self, note): - return self.includes_time(note[1]) + return self.includes_time(note_start_time(note)) def overlaps_note(self, note): - time, length = note[1:3] + time = note_start_time(note) + length = note_length(note) step_start = self.left_boundary() step_end = self.start + self.length - BEAT_TIME_EPSILON note_start = int((time + self.offset) / self.length) * self.length @@ -69,63 +100,69 @@ def overlaps_note(self, note): def filter_notes(self, notes): return filter(self.includes_note, notes) - def clamp(self, time, extra_time = 0.0): + def clamp(self, time, extra_time=0.0): return clamp(time + extra_time, self.left_boundary(), self.right_boundary()) def includes_time(self, time): return in_range(time - self.start + self.offset, 0, self.length) def connected_time_ranges(self): - return [(self.start - self.offset, self.length)] + return [ + ( + self.start - self.offset, self.length)] -class LoopingTimeStep(TimeStep): +class NullStepDuplicator(object): - def clamp(self, time, extra_time = 0.0): - result = clamp(self._looped_time(time, extra_time), self.left_boundary(), self.start - self.offset + self.length - BEAT_TIME_EPSILON) - if result < self.clip_start: - return result - self.clip_start + self.clip_end - else: - return result + @property + def is_duplicating(self): + return False - def connected_time_ranges(self): - """ - Returns a list of (start_time, length) ranges representing the - step in terms of continuous time ranges that can be used by - functions like clip.remove_notes - """ - if self.start - self.offset < self.clip_start: - return [(self.clip_start, self.length - self.offset), (self.clip_end - self.offset, self.offset)] - else: - return [(self.start - self.offset, self.length)] + def set_clip(self, _): + pass - def _looped_time(self, time, extra_time = 0.0): - if in_range(time, self.clip_end - self.offset, self.clip_end): - time = time - self.clip_end + self.clip_start - return time + extra_time - def includes_time(self, time): - return in_range(self._looped_time(time) + self.offset - self.start, 0, self.length) and in_range(time, self.clip_start, self.clip_end) +class NullVelocityProvider(EventObject): + @listenable_property + def velocity(self): + return DEFAULT_VELOCITY + + def set_velocities_playable(self, playable): + pass -class NoteEditorComponent(CompoundComponent, Subject): - __events__ = ('page_length', 'active_steps', 'notes_changed', 'modify_all_notes') - def __init__(self, clip_creator = None, grid_resolution = None, skin_base_key = 'NoteEditor', velocity_range_thresholds = None, *a, **k): - raise skin_base_key is not None or AssertionError +def min_max_for_notes(notes, start_time, min_max_values=None): + for note in notes: + note_values = list(note[:4]) + note_values[1] -= start_time + for index, value in enumerate(note_values): + if not min_max_values: + min_max_values = [ + (99999, -99999)] * 4 + min_value, max_value = min_max_values[index] + min_max_values[index] = (min(value, min_value), max(value, max_value)) + + return min_max_values + + +class NoteEditorComponent(CompoundComponent): + __events__ = ('page_length', 'active_note_regions', 'active_steps', 'notes_changed', + 'modify_all_notes') + matrix = control_matrix(PadControl, channel=PLAYHEAD_FEEDBACK_CHANNELS[0], sensitivity_profile='default') + mute_button = ButtonControl(color='DefaultButton.Transparent') + + def __init__(self, clip_creator=None, grid_resolution=None, skin_base_key='NoteEditor', velocity_range_thresholds=None, velocity_provider=None, *a, **k): + assert skin_base_key is not None super(NoteEditorComponent, self).__init__(*a, **k) self._skin_base_key = skin_base_key - self.loop_steps = False self.full_velocity = False + self._provided_velocity = None self._selected_page_point = 0 self._page_index = 0 self._clip_creator = clip_creator - self._matrix = None - self._width = 0 - self._height = 0 self._sequencer_clip = None self._step_colors = [] - self._mute_button = None self._pressed_steps = [] self._modified_steps = [] self._pressed_step_callback = None @@ -137,6 +174,7 @@ def __init__(self, clip_creator = None, grid_resolution = None, skin_base_key = self._note_index = 36 self._grid_resolution = grid_resolution self._on_resolution_changed.subject = self._grid_resolution + self.set_step_duplicator(None) self._nudge_offset = 0 self._length_offset = 0 self._velocity_offset = 0 @@ -144,6 +182,9 @@ def __init__(self, clip_creator = None, grid_resolution = None, skin_base_key = self._update_from_grid() self.background_color = self._skin_base_key + '.StepEmpty' self._velocity_range_thresholds = velocity_range_thresholds or DEFAULT_VELOCITY_RANGE_THRESHOLDS + self._velocity_provider = velocity_provider or NullVelocityProvider() + self.__on_provided_velocity_changed.subject = self._velocity_provider + return @property def page_index(self): @@ -158,11 +199,11 @@ def can_change_page(self): return not self._pressed_steps and not self._modified_steps def set_selected_page_point(self, point): - if not self.can_change_page: - raise AssertionError - self._selected_page_point = point - index = int(point / self.page_length) if self.page_length != 0 else 0 - self._page_index = index != self._page_index and index + assert self.can_change_page + self._selected_page_point = point + index = int(point / self.page_length) if self.page_length != 0 else 0 + if index != self._page_index: + self._page_index = index self._on_clip_notes_changed() def _get_modify_all_notes_enabled(self): @@ -178,6 +219,7 @@ def _set_modify_all_notes_enabled(self, enabled): def set_detail_clip(self, clip): self._sequencer_clip = clip + self._step_duplicator.set_clip(clip) self._on_clip_notes_changed.subject = clip self._on_clip_notes_changed() @@ -190,20 +232,22 @@ def _set_editing_note(self, note_index): editing_note = property(_get_editing_note, _set_editing_note) - def set_mute_button(self, button): - self._mute_button = button - self._on_mute_value.subject = button + def _get_width(self): + if self.matrix.width: + return self.matrix.width + return 4 - def set_button_matrix(self, matrix): + def _get_height(self): + if self.matrix.height: + return self.matrix.height + return 4 + + def set_matrix(self, matrix): last_page_length = self.page_length - self._matrix = matrix - self._on_matrix_value.subject = matrix + self.matrix.set_control_element(matrix) if matrix: - self._width = matrix.width() - self._height = matrix.height() - matrix.reset() - for button, _ in ifilter(first, matrix.iterbuttons()): - button.set_channel(PLAYHEAD_FEEDBACK_CHANNELS[0]) + for control in self.matrix: + control.set_playable(False) for t in self._step_tap_tasks.itervalues(): t.kill() @@ -212,13 +256,18 @@ def trigger_modification_task(x, y): trigger = partial(self._trigger_modification, (x, y), done=True) return self._tasks.add(task.sequence(task.wait(defaults.MOMENTARY_DELAY), task.run(trigger))).kill() - self._step_tap_tasks = dict([ ((x, y), trigger_modification_task(x, y)) for x, y in product(xrange(self._width), xrange(self._height)) ]) + self._step_tap_tasks = dict([ ((x, y), trigger_modification_task(x, y)) for x, y in product(xrange(self._get_width()), xrange(self._get_height())) + ]) if matrix and last_page_length != self.page_length: self._on_clip_notes_changed() self.notify_page_length() else: self._update_editor_matrix() + def set_step_duplicator(self, duplicator): + self._step_duplicator = duplicator or NullStepDuplicator() + self._step_duplicator.set_clip(self._sequencer_clip) + def update(self): super(NoteEditorComponent, self).update() self._update_editor_matrix_leds() @@ -243,15 +292,19 @@ def _on_clip_notes_changed(self): self._clip_notes = [] self._update_editor_matrix() self.notify_notes_changed() + return def _update_editor_matrix(self): """ update offline array of button LED values, based on note velocity and mute states """ - step_colors = [self._skin_base_key + '.StepDisabled'] * self._get_step_count() - width = self._width - coords_to_index = lambda (x, y): x + y * width + step_colors = [ + self._skin_base_key + '.StepDisabled'] * self._get_step_count() + + def coords_to_index(coord): + return coord[0] + coord[1] * self._get_width() + editing_indices = set(map(coords_to_index, self._modified_steps)) selected_indices = set(map(coords_to_index, self._pressed_steps)) last_editing_notes = [] @@ -289,27 +342,25 @@ def _visible_steps(self): steps_per_page = self._get_step_count() step_length = self._get_step_length() indices = range(steps_per_page) - if self._is_triplet_quantization(): + if is_triplet_quantization(self._triplet_factor): indices = filter(lambda k: k % 8 not in (6, 7), indices) - return [ (self._time_step(first_time + k * step_length), index) for k, index in enumerate(indices) ] + return [ (self._time_step(first_time + k * step_length), index) for k, index in enumerate(indices) + ] def _update_editor_matrix_leds(self): - """ update hardware LEDS to match offline array values """ - if self.is_enabled() and self._matrix: - for row, col in product(xrange(self._height), xrange(self._width)): - index = row * self._width + col - color = self._step_colors[index] - self._matrix.set_light(col, row, color) + if self.is_enabled(): + for control in self.matrix: + control.color = self._step_colors[control.index] def _get_step_count(self): - return self._width * self._height + return self._get_width() * self._get_height() - def _get_step_start_time(self, x, y): - """ returns step starttime in beats, based on step coordinates """ - raise in_range(x, 0, self._width) or AssertionError - raise in_range(y, 0, self._height) or AssertionError + def _get_step_start_time(self, step): + x, y = step + assert in_range(x, 0, self._get_width()) + assert in_range(y, 0, self._get_height()) page_time = self._page_index * self._get_step_count() * self._triplet_factor - step_time = x + y * self._width * self._triplet_factor + step_time = x + y * self._get_width() * self._triplet_factor return (page_time + step_time) * self._get_step_length() def _get_step_length(self): @@ -321,14 +372,13 @@ def _update_from_grid(self): if self._clip_creator: self._clip_creator.grid_quantization = quantization self._clip_creator.is_grid_triplet = is_triplet - if self._sequencer_clip: + if liveobj_valid(self._sequencer_clip): self._sequencer_clip.view.grid_quantization = quantization self._sequencer_clip.view.grid_is_triplet = is_triplet - @listens('value') - def _on_mute_value(self, value): - if self.is_enabled() and value: - self._trigger_modification(immediate=True) + @mute_button.pressed + def mute_button(self, button): + self._trigger_modification(immediate=True) @listens('index') def _on_resolution_changed(self): @@ -338,42 +388,74 @@ def _on_resolution_changed(self): self.notify_page_length() self._on_clip_notes_changed() - @listens('value') - def _on_matrix_value(self, value, x, y, is_momentary): - self._on_pad_pressed(value, x, y, is_momentary) + @matrix.pressed + def matrix(self, button): + self._on_pad_pressed(button.coordinate) - def _on_pad_pressed(self, value, x, y, is_momentary): + @matrix.released + def matrix(self, button): + self._on_pad_released(button.coordinate) + + def _on_pad_pressed(self, coordinate): + y, x = coordinate if self.is_enabled(): - if self._sequencer_clip == None and value or not is_momentary: - clip = create_clip_in_selected_slot(self._clip_creator, self.song) - self.set_detail_clip(clip) - if self._note_index != None: - width = self._width * self._triplet_factor if self._is_triplet_quantization() else self._width - if x < width and y < self._height: - if value or not is_momentary: - self._on_press_step((x, y)) - else: - self._on_release_step((x, y)) - self._update_editor_matrix() - - @listens('value') - def _on_any_touch_value(self, value, x, y, is_momentary): - pass + if not liveobj_valid(self._sequencer_clip): + self.set_detail_clip(create_clip_in_selected_slot(self._clip_creator, self.song)) + if self._can_press_or_release_step(x, y): + self._on_press_step((x, y)) + self._update_editor_matrix() + + def _on_pad_released(self, coordinate): + y, x = coordinate + if self.is_enabled() and self._can_press_or_release_step(x, y): + self._on_release_step((x, y)) + self._update_editor_matrix() + + def _can_press_or_release_step(self, x, y): + width = self._get_width() * self._triplet_factor if is_triplet_quantization(self._triplet_factor) else self._get_width() + return self._note_index != None and x < width and y < self._get_height() + + @listens('velocity') + def __on_provided_velocity_changed(self): + if len(self._pressed_steps) + len(self._modified_steps) > 0: + self._provided_velocity = self._velocity_provider.velocity + self._trigger_modification(immediate=True) + self._provided_velocity = None + return + + def _get_step_time_range(self, step): + time = self._get_step_start_time(step) + return ( + time, time + self._get_step_length()) @property def active_steps(self): + return imap(self._get_step_time_range, chain(self._pressed_steps, self._modified_steps)) - def get_time_range((x, y)): - time = self._get_step_start_time(x, y) - return (time, time + self._get_step_length()) - - return imap(get_time_range, chain(self._pressed_steps, self._modified_steps)) + @property + def active_note_regions(self): + return imap(self._get_time_range, chain(self._pressed_steps, self._modified_steps)) + + def _get_time_range(self, step): + time = self._get_step_start_time(step) + notes = self._time_step(time).filter_notes(self._clip_notes) + if notes: + beginning_note = notes[0] + start = note_start_time(beginning_note) + end = start + note_length(beginning_note) + if len(notes) > 1: + end_note = notes[-1] + end = note_start_time(end_note) + note_length(end_note) + return (start, end) + else: + return ( + time, time + self._get_step_length()) def _release_active_steps(self): for step in self._pressed_steps + self._modified_steps: self._on_release_step(step, do_delete_notes=False) - def _on_release_step(self, step, do_delete_notes = True): + def _on_release_step(self, step, do_delete_notes=True): self._step_tap_tasks[step].kill() if step in self._pressed_steps: if do_delete_notes: @@ -382,53 +464,92 @@ def _on_release_step(self, step, do_delete_notes = True): self._add_note_in_step(step) if step in self._modified_steps: self._modified_steps.remove(step) + if len(self._modified_steps) + len(self._pressed_steps) == 0: + self._velocity_provider.set_velocities_playable(True) self.notify_active_steps() + self.notify_active_note_regions() + + def _is_continuation_of_existing_note(self, step): + + def steps_to_note_start(steps): + return map(lambda step: self._get_step_start_time(step), steps) + + time = self._get_step_start_time(step) + all_note_starts = map(note_start_time, self._clip_notes) + if all_note_starts: + insert_point = bisect(all_note_starts, time) + if insert_point > 0: + prev_note = self._clip_notes[insert_point - 1] + notes_in_modified_steps = [] + for start in steps_to_note_start(self._modified_steps): + notes_in_modified_steps.extend(TimeStep(start, self._get_step_length()).filter_notes(self._clip_notes)) + + if note_start_time(prev_note) in map(note_start_time, notes_in_modified_steps): + return prev_note + return False + + def _add_step_to_duplicator(self, step): + nudge_offset = 0 + time = self._get_step_start_time(step) + notes = self._time_step(time).filter_notes(self._clip_notes) + step_start, step_end = self._get_time_range(step) + if notes: + nudge_offset = note_start_time(notes[0]) - time + self._step_duplicator.add_step(self.editing_note, step_start, step_end, nudge_offset) def _on_press_step(self, step): if liveobj_valid(self._sequencer_clip) and step not in self._pressed_steps and step not in self._modified_steps: - self._step_tap_tasks[step].restart() - self._pressed_steps.append(step) + if self._step_duplicator.is_duplicating: + self._add_step_to_duplicator(step) + else: + self._step_tap_tasks[step].restart() + note_continuation = self._is_continuation_of_existing_note(step) + if note_continuation: + self._modify_length_of_existing_note(note_continuation, step) + else: + self._pressed_steps.append(step) + self._velocity_provider.set_velocities_playable(False) self.notify_active_steps() + self.notify_active_note_regions() + + def _modify_length_of_existing_note(self, existing_note, new_step): + time = note_start_time(existing_note) + old_end = note_length(existing_note) + note_start_time(existing_note) + new_end = self._get_step_start_time(new_step) + self._get_step_length() + new_notes = self._modify_notes_in_time(self._time_step(time), self._clip_notes, new_end - old_end) + self._replace_notes(new_notes) def _time_step(self, time): - if self.loop_steps and liveobj_valid(self._sequencer_clip) and self._sequencer_clip.looping: - return LoopingTimeStep(time, self._get_step_length(), self._sequencer_clip.loop_start, self._sequencer_clip.loop_end) - else: - return TimeStep(time, self._get_step_length()) + return TimeStep(time, self._get_step_length()) - def _add_note_in_step(self, step, modify_existing = True): + def _add_note_in_step(self, step, modify_existing=True): """ Add note in given step if there are none in there, otherwise select the step for potential deletion or modification """ if liveobj_valid(self._sequencer_clip): - x, y = step - time = self._get_step_start_time(x, y) + time = self._get_step_start_time(step) notes = self._time_step(time).filter_notes(self._clip_notes) if notes: if modify_existing: most_significant_velocity = most_significant_note(notes)[3] - if self._mute_button and self._mute_button.is_pressed() or most_significant_velocity != 127 and self.full_velocity: + if self.mute_button.is_pressed or most_significant_velocity != 127 and self.full_velocity: self._trigger_modification(step, immediate=True) else: pitch = self._note_index - mute = self._mute_button and self._mute_button.is_pressed() - velocity = 127 if self.full_velocity else DEFAULT_VELOCITY - note = (pitch, - time, - self._get_step_length(), - velocity, - mute) + mute = self.mute_button.is_pressed + velocity = 127 if self.full_velocity else self._velocity_provider.velocity + note = (pitch, time, self._get_step_length(), velocity, mute) self._sequencer_clip.set_notes((note,)) self._sequencer_clip.deselect_all_notes() self._trigger_modification(step, done=True) return True return False - def _delete_notes_in_step(self, (x, y)): + def _delete_notes_in_step(self, step): """ Delete all notes in the given step """ - if self._sequencer_clip: - time_step = self._time_step(self._get_step_start_time(x, y)) + if liveobj_valid(self._sequencer_clip): + time_step = self._time_step(self._get_step_start_time(step)) for time, length in time_step.connected_time_ranges(): self._sequencer_clip.remove_notes(time, self._note_index, length, 1) @@ -443,19 +564,20 @@ def set_velocity_offset(self, value): def _modify_note_property(self, note_property, value): if self.is_enabled(): - setattr(self, note_property, getattr(self, note_property) + value) - self._trigger_modification() + with self._full_velocity_context(False): + setattr(self, note_property, getattr(self, note_property) + value) + self._trigger_modification(immediate=True) @contextmanager - def _full_velocity_context(self): + def _full_velocity_context(self, desired_full_velocity_state): saved_velocity = self.full_velocity - self.full_velocity = True + self.full_velocity = desired_full_velocity_state yield self.full_velocity = saved_velocity def set_full_velocity(self): if self.is_enabled(): - with self._full_velocity_context(): + with self._full_velocity_context(True): self._trigger_modification() def notify_modification(self): @@ -465,7 +587,7 @@ def notify_modification(self): """ self._trigger_modification(done=True) - def _trigger_modification(self, step = None, done = False, immediate = False): + def _trigger_modification(self, step=None, done=False, immediate=False): """ Because the modification of notes is slow, we accumulate modification events and perform all of them @@ -495,6 +617,7 @@ def _trigger_modification(self, step = None, done = False, immediate = False): self._modify_task.restart() if needs_update: self._update_editor_matrix() + return def _reset_modifications(self): self._velocity_offset = 0 @@ -524,15 +647,16 @@ def _replace_notes(self, new_notes): def _modify_all_notes(self): """ modify all notes in the current pitch """ - return self._modify_notes_in_time(TimeStep(0.0, MAX_CLIP_LENGTH), self._clip_notes) + return self._modify_notes_in_time(TimeStep(0.0, MAX_CLIP_LENGTH), self._clip_notes, self._length_offset) def _limited_nudge_offset(self, steps, notes, nudge_offset): limited_nudge_offset = MAX_CLIP_LENGTH - for x, y in steps: - time_step = self._time_step(self._get_step_start_time(x, y)) + for step in steps: + time_step = self._time_step(self._get_step_start_time(step)) for note in time_step.filter_notes(notes): - time_after_nudge = time_step.clamp(note[1], nudge_offset) - limited_nudge_offset = min(limited_nudge_offset, abs(note[1] - time_after_nudge)) + start_time = note_start_time(note) + time_after_nudge = time_step.clamp(start_time, nudge_offset) + limited_nudge_offset = min(limited_nudge_offset, abs(start_time - time_after_nudge)) return sign(nudge_offset) * limited_nudge_offset @@ -540,18 +664,18 @@ def _modify_step_notes(self, steps): """ Return a new list with all notes within steps modified. """ notes = self._clip_notes self._nudge_offset = self._limited_nudge_offset(steps, notes, self._nudge_offset) - for x, y in steps: - time_step = self._time_step(self._get_step_start_time(x, y)) - notes = self._modify_notes_in_time(time_step, notes) + for step in steps: + time_step = self._time_step(self._get_step_start_time(step)) + notes = self._modify_notes_in_time(time_step, notes, self._length_offset) return notes - def _modify_notes_in_time(self, time_step, notes): + def _modify_notes_in_time(self, time_step, notes, length_offset): step_notes = time_step.filter_notes(self._clip_notes) - step_mute = all(map(lambda note: note[4], step_notes)) - return map(partial(self._modify_single_note, step_mute, time_step), notes) + step_mute = all(map(lambda note: note_muted(note), step_notes)) + return map(partial(self._modify_single_note, step_mute, time_step, length_offset), notes) - def _modify_single_note(self, step_mute, time_step, (pitch, time, length, velocity, mute)): + def _modify_single_note(self, step_mute, time_step, length_offset, note): """ Return a modified version of the passed in note taking into account current modifiers. If the note is not within @@ -562,43 +686,33 @@ def _modify_single_note(self, step_mute, time_step, (pitch, time, length, veloci loop, so the resulting note may, in this case, jump between the beginning and the end. """ + pitch, time, length, velocity, mute = note if time_step.includes_time(time): time = time_step.clamp(time, self._nudge_offset) - if self._length_offset <= -time_step.length and length + self._length_offset < time_step.length: + if length_offset <= -time_step.length and length + length_offset < time_step.length: if length > time_step.length: length = time_step.length else: - length = max(0, length + self._length_offset) - velocity = 127 if self.full_velocity else clamp(velocity + self._velocity_offset, 1, 127) - mute = not step_mute if self._mute_button and self._mute_button.is_pressed() else mute - return (pitch, - time, - length, - velocity, - mute) - - def _min_max_for_notes(self, notes, start_time, min_max_values = None): - for note in notes: - note_values = list(note[:4]) - note_values[1] -= start_time - for index, value in enumerate(note_values): - if not min_max_values: - min_max_values = [(99999, -99999)] * 4 - min_value, max_value = min_max_values[index] - min_max_values[index] = (min(value, min_value), max(value, max_value)) - - return min_max_values + length = max(0, length + length_offset) + if self._provided_velocity: + velocity = self._provided_velocity + elif self.full_velocity: + velocity = 127 + else: + velocity = clamp(velocity + self._velocity_offset, 1, 127) + mute = not step_mute if self.mute_button.is_pressed else mute + return ( + pitch, time, length, velocity, mute) def get_min_max_note_values(self): if self._modify_all_notes_enabled and len(self._clip_notes) > 0: - return self._min_max_for_notes(self._clip_notes, 0.0) - if len(self._pressed_steps) + len(self._modified_steps) > 0: - min_max_values = None - for x, y in chain(self._modified_steps, self._pressed_steps): - start_time = self._get_step_start_time(x, y) - min_max_values = self._min_max_for_notes(self._time_step(start_time).filter_notes(self._clip_notes), start_time, min_max_values) - - return min_max_values - - def _is_triplet_quantization(self): - return self._triplet_factor == 0.75 \ No newline at end of file + return min_max_for_notes(self._clip_notes, 0.0) + else: + if len(self._pressed_steps) + len(self._modified_steps) > 0: + min_max_values = None + for step in chain(self._modified_steps, self._pressed_steps): + start_time = self._get_step_start_time(step) + min_max_values = min_max_for_notes(self._time_step(start_time).filter_notes(self._clip_notes), start_time, min_max_values) + + return min_max_values + return \ No newline at end of file diff --git a/pushbase/note_editor_paginator.py b/pushbase/note_editor_paginator.py index d27d6f85..1227b25a 100644 --- a/pushbase/note_editor_paginator.py +++ b/pushbase/note_editor_paginator.py @@ -1,11 +1,16 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/note_editor_paginator.py +# uncompyle6 version 2.9.10 +# Python bytecode 2.7 (62211) +# Decompiled from: Python 2.7.13 (default, Dec 17 2016, 23:03:43) +# [GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.42.1)] +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/note_editor_paginator.py +# Compiled at: 2016-05-20 03:43:52 from __future__ import absolute_import, print_function -from ableton.v2.base import forward_property, SlotManager, listens, listens_group +from ableton.v2.base import forward_property, listens, listens_group from .loop_selector_component import Paginator -class NoteEditorPaginator(Paginator, SlotManager): +class NoteEditorPaginator(Paginator): - def __init__(self, note_editors = None, *a, **k): + def __init__(self, note_editors=None, *a, **k): super(NoteEditorPaginator, self).__init__(*a, **k) self._note_editors = note_editors self._last_page_index = -1 diff --git a/pushbase/note_repeat_component.py b/pushbase/note_repeat_component.py index 7a7f2980..1e81d3a4 100644 --- a/pushbase/note_repeat_component.py +++ b/pushbase/note_repeat_component.py @@ -1,17 +1,15 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/note_repeat_component.py +# uncompyle6 version 2.9.10 +# Python bytecode 2.7 (62211) +# Decompiled from: Python 2.7.13 (default, Dec 17 2016, 23:03:43) +# [GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.42.1)] +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/note_repeat_component.py +# Compiled at: 2016-05-20 03:43:52 from __future__ import absolute_import, print_function from ableton.v2.base import listens, task from ableton.v2.control_surface import CompoundComponent from .action_with_options_component import OptionsComponent t = 3.0 / 2.0 -NOTE_REPEAT_FREQUENCIES = [32 * t, - 32, - 16 * t, - 16, - 8 * t, - 8, - 4 * t, - 4] +NOTE_REPEAT_FREQUENCIES = [32 * t, 32, 16 * t, 16, 8 * t, 8, 4 * t, 4] del t class DummyNoteRepeat(object): @@ -36,6 +34,7 @@ def __init__(self, *a, **k): self._options.selected_option = 5 self._on_selected_option_changed.subject = self._options self.set_note_repeat(None) + return def on_enabled_changed(self): if self.is_enabled(): @@ -64,6 +63,7 @@ def set_note_repeat(self, note_repeat): self._note_repeat.enabled = False self._note_repeat = note_repeat self._update_note_repeat(enabled=self.is_enabled()) + return def set_pad_parameters(self, element): if element: @@ -91,6 +91,6 @@ def _on_selected_option_changed(self, option): frequency = NOTE_REPEAT_FREQUENCIES[option] self._note_repeat.repeat_rate = 1.0 / frequency * 4.0 - def _update_note_repeat(self, enabled = False): + def _update_note_repeat(self, enabled=False): self._on_selected_option_changed(self._options.selected_option) self._note_repeat.enabled = self.is_enabled() \ No newline at end of file diff --git a/pushbase/note_settings_component.py b/pushbase/note_settings_component.py index 0719d8d0..1f1fffd5 100644 --- a/pushbase/note_settings_component.py +++ b/pushbase/note_settings_component.py @@ -1,9 +1,14 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/note_settings_component.py +# uncompyle6 version 2.9.10 +# Python bytecode 2.7 (62211) +# Decompiled from: Python 2.7.13 (default, Dec 17 2016, 23:03:43) +# [GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.42.1)] +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/note_settings_component.py +# Compiled at: 2016-06-08 13:13:04 from __future__ import absolute_import, print_function import math from functools import partial from itertools import imap, chain, izip_longest -from ableton.v2.base import clamp, find_if, forward_property, listens, listens_group, Subject, task +from ableton.v2.base import clamp, find_if, forward_property, listens, listens_group, task from ableton.v2.control_surface import defaults, Component from ableton.v2.control_surface.control import ButtonControl, ControlManager, EncoderControl, StepEncoderControl from ableton.v2.control_surface.elements import DisplayDataSource @@ -11,15 +16,16 @@ from .action_with_options_component import OptionsComponent from .consts import CHAR_ELLIPSIS, GRAPH_VOL -class NoteSettingBase(ControlManager, Subject): - __events__ = ('setting_changed',) +class NoteSettingBase(ControlManager): + __events__ = ('setting_changed', ) attribute_index = -1 encoder = EncoderControl() - def __init__(self, grid_resolution = None, *a, **k): + def __init__(self, grid_resolution=None, *a, **k): super(NoteSettingBase, self).__init__(*a, **k) self._min_max_value = None self._grid_resolution = grid_resolution + return def encoder_value_to_attribute(self, value): raise NotImplementedError @@ -112,7 +118,8 @@ def format_string(value): if min_value == max_value: return (format_string(min_value) + ' stp') % min_value - return (format_string(min_value) + CHAR_ELLIPSIS + format_string(max_value)) % (min_value, max_value) + return (format_string(min_value) + CHAR_ELLIPSIS + format_string(max_value)) % ( + min_value, max_value) def encoder_value_to_attribute(self, value): return self.step_length * value @@ -155,7 +162,7 @@ class NoteSettingsComponentBase(Component): __events__ = ('setting_changed', 'full_velocity') full_velocity_button = ButtonControl() - def __init__(self, grid_resolution = None, *a, **k): + def __init__(self, grid_resolution=None, *a, **k): super(NoteSettingsComponentBase, self).__init__(*a, **k) self._settings = [] self._encoders = [] @@ -168,7 +175,7 @@ def _create_settings(self, grid_resolution): self._add_setting(NoteVelocitySetting(grid_resolution=grid_resolution)) def _add_setting(self, setting): - raise len(self._settings) < 8 or AssertionError('Cannot show more than 8 settings') + assert len(self._settings) < 8, 'Cannot show more than 8 settings' self._settings.append(setting) self._update_encoders() self.register_disconnectable(setting) @@ -243,10 +250,11 @@ class DetailViewRestorerMode(Mode): Has no effect if the detail view is hidden at the point the mode is entered. """ - def __init__(self, application = None, *a, **k): + def __init__(self, application=None, *a, **k): super(DetailViewRestorerMode, self).__init__(*a, **k) self._app = application self._view_to_restore = None + return def enter_mode(self): clip_view_visible = self._app.view.is_view_visible('Detail/Clip', False) @@ -262,12 +270,14 @@ def leave_mode(self): except RuntimeError: pass + return + class NoteEditorSettingsComponent(ModesComponent): - def __init__(self, note_settings_component = None, automation_component = None, initial_encoder_layer = None, encoder_layer = None, *a, **k): + def __init__(self, note_settings_component=None, automation_component=None, initial_encoder_layer=None, encoder_layer=None, *a, **k): super(NoteEditorSettingsComponent, self).__init__(*a, **k) - raise encoder_layer or AssertionError + assert encoder_layer self._request_hide = False self.settings = self.register_component(note_settings_component) self.settings.set_enabled(False) @@ -283,11 +293,13 @@ def __init__(self, note_settings_component = None, automation_component = None, self._update_infos_task = self._tasks.add(task.run(self._update_note_infos)).kill() self._settings_modes = self.register_component(ModesComponent()) self._settings_modes.set_enabled(False) - self._settings_modes.add_mode('automation', [self._automation, + self._settings_modes.add_mode('automation', [ + self._automation, self._mode_selector, partial(self._set_envelope_view_visible, True), self._show_clip_view]) - self._settings_modes.add_mode('note_settings', [self.settings, + self._settings_modes.add_mode('note_settings', [ + self.settings, self._update_note_infos, self._mode_selector, partial(self._set_envelope_view_visible, False), @@ -295,8 +307,12 @@ def __init__(self, note_settings_component = None, automation_component = None, self._encoders = None self._initial_encoders = None self.add_mode('disabled', []) - self.add_mode('about_to_show', [AddLayerMode(self, initial_encoder_layer), (self._show_settings_task.restart, self._show_settings_task.kill)]) - self.add_mode('enabled', [DetailViewRestorerMode(self.application()), + self.add_mode('about_to_show', [ + AddLayerMode(self, initial_encoder_layer), + ( + self._show_settings_task.restart, self._show_settings_task.kill)]) + self.add_mode('enabled', [ + DetailViewRestorerMode(self.application), AddLayerMode(self, encoder_layer), self._update_available_modes, self._settings_modes]) @@ -306,6 +322,7 @@ def __init__(self, note_settings_component = None, automation_component = None, self._on_selected_track_changed.subject = self.song.view self.__on_full_velocity_changed.subject = self.settings self.__on_setting_changed.subject = self.settings + return automation_layer = forward_property('_automation')('layer') mode_selector_layer = forward_property('_mode_selector')('layer') @@ -320,11 +337,12 @@ def editors(self): return self._editors def add_editor(self, editor): - raise editor != None or AssertionError + assert editor != None self._editors.append(editor) - self._on_active_steps_changed.add_subject(editor) + self._on_active_note_regions_changed.add_subject(editor) self._on_notes_changed.replace_subjects(self._editors) self.__on_modify_all_notes_changed.add_subject(editor) + return def set_display_line(self, line): self._mode_selector.set_display_line(line) @@ -377,9 +395,9 @@ def _update_available_modes(self): def _show_clip_view(self): try: - view = self.application().view + view = self.application.view if view.is_view_visible('Detail/DeviceChain', False) and not view.is_view_visible('Detail/Clip', False): - self.application().view.show_view('Detail/Clip') + self.application.view.show_view('Detail/Clip') except RuntimeError: pass @@ -395,10 +413,10 @@ def _try_immediate_show_settings(self): if self.selected_mode == 'about_to_show' and any(imap(lambda e: e and e.is_pressed(), self._initial_encoders or [])): self._show_settings() - @listens_group('active_steps') - def _on_active_steps_changed(self, editor): + @listens_group('active_note_regions') + def _on_active_note_regions_changed(self, _): if self.is_enabled(): - all_steps = list(set(chain.from_iterable(imap(lambda e: e.active_steps, self._editors)))) + all_steps = list(set(chain.from_iterable(imap(lambda e: e.active_note_regions, self._editors)))) self._automation.selected_time = all_steps self._update_note_infos() if len(all_steps) > 0: @@ -422,6 +440,7 @@ def _on_notes_changed(self, editor): def _on_detail_clip_changed(self): clip = self.song.view.detail_clip if self.is_enabled() else None self._automation.clip = clip + return @listens('selected_track') def _on_selected_track_changed(self): @@ -469,6 +488,7 @@ def min_max((l_min, l_max), (r_min, r_max)): edit_all_notes_active = find_if(lambda e: e.modify_all_notes_enabled, self._editors) != None self.settings.set_info_message('Tweak to add note' if not edit_all_notes_active and not min_max_values else '') + return def _show_settings(self): if self.selected_mode != 'enabled': @@ -480,7 +500,9 @@ def _update_selected_setting(self, option): if option == 0: self.selected_setting = 'note_settings' elif option == 1: + self._on_active_note_regions_changed(None) self.selected_setting = 'automation' + return def _try_hide_settings(self): if self._request_hide and not any(imap(lambda e: e and e.is_pressed(), self._encoders or [])): diff --git a/pushbase/pad_control.py b/pushbase/pad_control.py index ef2b3eee..78f55e02 100644 --- a/pushbase/pad_control.py +++ b/pushbase/pad_control.py @@ -1,4 +1,9 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/pad_control.py +# uncompyle6 version 2.9.10 +# Python bytecode 2.7 (62211) +# Decompiled from: Python 2.7.13 (default, Dec 17 2016, 23:03:43) +# [GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.42.1)] +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/pad_control.py +# Compiled at: 2016-11-16 18:13:20 from __future__ import absolute_import, print_function from ableton.v2.control_surface.control import PlayableControl @@ -6,9 +11,9 @@ class PadControl(PlayableControl): class State(PlayableControl.State): - def __init__(self, *a, **k): + def __init__(self, sensitivity_profile=None, *a, **k): super(PadControl.State, self).__init__(*a, **k) - self._sensitivity_profile = None + self._sensitivity_profile = sensitivity_profile def _get_sensitivity_profile(self): return self._sensitivity_profile diff --git a/pushbase/pad_sensitivity.py b/pushbase/pad_sensitivity.py new file mode 100644 index 00000000..60801e0c --- /dev/null +++ b/pushbase/pad_sensitivity.py @@ -0,0 +1,83 @@ +# uncompyle6 version 2.9.10 +# Python bytecode 2.7 (62211) +# Decompiled from: Python 2.7.13 (default, Dec 17 2016, 23:03:43) +# [GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.42.1)] +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/pad_sensitivity.py +# Compiled at: 2016-06-13 08:15:55 +from __future__ import absolute_import, print_function +from itertools import repeat +from ableton.v2.base import find_if, second, nop, task +from ableton.v2.control_surface import Component + +class PadUpdateComponent(Component): + """ + Sets a set of parameters for different pads. It keeps a set of + profiles, and maps a profile to each pad. It caches all + modifications to the pad profiles, updating later optimally. + + The all_pads parameter contains the pads identifiers. + + The parameter_sender is a function that is used to update the + pads. It takes the parameters as first value and a second optional + value indicating the pad to update, or None to update all possible + pads. + """ + + def __init__(self, all_pads=tuple(), parameter_sender=nop, default_profile=None, update_delay=0, *a, **k): + assert find_if(lambda pad: pad < 0 or pad > 63, all_pads or []) == None + super(PadUpdateComponent, self).__init__(*a, **k) + self.parameter_sender = parameter_sender + self._all_pads = set(all_pads) + self._modified_pads = set(all_pads) + self._profiles = {'default': default_profile} + self._profile_for = dict(zip(all_pads, repeat('default'))) + self._profile_count = {'default': len(all_pads)} + self._update_task = self._tasks.add(task.sequence(task.wait(update_delay), task.run(self._update_modified))) + self._update_task.restart() + return + + def set_profile(self, profile_id, parameters): + self._profiles[profile_id] = parameters + self._profile_count.setdefault(profile_id, 0) + affected = [ k for k, v in self._profile_for.iteritems() if v == profile_id ] + self._add_modified_pads(affected) + + def get_profile(self, profile_id): + return self._profiles[profile_id] + + def set_pad(self, pad, new_profile): + assert pad in self._all_pads + assert new_profile in self._profile_count + old_profile = self._profile_for[pad] + if old_profile != new_profile: + self._add_modified_pads([pad]) + self._profile_for[pad] = new_profile + self._profile_count[old_profile] -= 1 + self._profile_count[new_profile] += 1 + + def update(self): + super(PadUpdateComponent, self).update() + self._add_modified_pads(self._all_pads) + self._update_modified() + + def _update_modified(self): + if self.is_enabled() and self._modified_pads: + assert sum(self._profile_count.itervalues()) == len(self._all_pads) + largest_profile, largest_count = max(self._profile_count.iteritems(), key=second) + if len(self._all_pads) - largest_count + 1 < len(self._modified_pads): + self.parameter_sender(self._profiles[largest_profile]) + for pad in self._all_pads: + profile = self._profile_for[pad] + if profile != largest_profile: + self.parameter_sender(self._profiles[profile], pad) + + else: + for pad in self._modified_pads: + self.parameter_sender(self._profiles[self._profile_for[pad]], pad) + + self._modified_pads.clear() + self._update_task.kill() + + def _add_modified_pads(self, pads): + self._modified_pads.update(pads) + self._update_task.restart() \ No newline at end of file diff --git a/pushbase/parameter_provider.py b/pushbase/parameter_provider.py index 39fcb3a7..f268c472 100644 --- a/pushbase/parameter_provider.py +++ b/pushbase/parameter_provider.py @@ -1,8 +1,13 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/parameter_provider.py +# uncompyle6 version 2.9.10 +# Python bytecode 2.7 (62211) +# Decompiled from: Python 2.7.13 (default, Dec 17 2016, 23:03:43) +# [GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.42.1)] +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/parameter_provider.py +# Compiled at: 2016-05-20 03:43:52 from __future__ import absolute_import, print_function -from ableton.v2.base import liveobj_valid, NamedTuple, Subject -from . import consts -DISCRETE_PARAMETERS_DICT = {'GlueCompressor': ('Ratio', 'Attack', 'Release', 'Peak Clip In')} +from ableton.v2.base import liveobj_valid, NamedTuple, EventObject +DISCRETE_PARAMETERS_DICT = {'GlueCompressor': ('Ratio', 'Attack', 'Release', 'Peak Clip In') + } def is_parameter_quantized(parameter, parent_device): is_quantized = False @@ -12,26 +17,12 @@ def is_parameter_quantized(parameter, parent_device): return is_quantized -def parameter_mapping_sensitivity(parameter): - is_quantized = is_parameter_quantized(parameter, parameter and parameter.canonical_parent) - if is_quantized: - return consts.QUANTIZED_MAPPING_SENSITIVITY - return consts.CONTINUOUS_MAPPING_SENSITIVITY - - -def fine_grain_parameter_mapping_sensitivity(parameter): - is_quantized = is_parameter_quantized(parameter, parameter and parameter.canonical_parent) - if is_quantized: - return consts.QUANTIZED_MAPPING_SENSITIVITY - return consts.FINE_GRAINED_CONTINUOUS_MAPPING_SENSITIVITY - - class ParameterInfo(NamedTuple): parameter = None - default_encoder_sensitivity = (None,) - fine_grain_encoder_sensitivity = (None,) + default_encoder_sensitivity = None + fine_grain_encoder_sensitivity = None - def __init__(self, name = None, *a, **k): + def __init__(self, name=None, *a, **k): super(ParameterInfo, self).__init__(_overriden_name=name, *a, **k) @property @@ -39,12 +30,8 @@ def name(self): return self._overriden_name or getattr(self.parameter, 'name', '') -def generate_info(parameter, name = None, default_sens_factory = parameter_mapping_sensitivity, fine_sens_factory = fine_grain_parameter_mapping_sensitivity): - return ParameterInfo(name=name, parameter=parameter, default_encoder_sensitivity=default_sens_factory(parameter), fine_grain_encoder_sensitivity=fine_sens_factory(parameter)) - - -class ParameterProvider(Subject): - __events__ = ('parameters',) +class ParameterProvider(EventObject): + __events__ = ('parameters', ) @property def parameters(self): diff --git a/pushbase/parameter_slot_description.py b/pushbase/parameter_slot_description.py index 36df17f0..2d03a8c2 100644 --- a/pushbase/parameter_slot_description.py +++ b/pushbase/parameter_slot_description.py @@ -1,6 +1,11 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/parameter_slot_description.py +# uncompyle6 version 2.9.10 +# Python bytecode 2.7 (62211) +# Decompiled from: Python 2.7.13 (default, Dec 17 2016, 23:03:43) +# [GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.42.1)] +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/parameter_slot_description.py +# Compiled at: 2016-05-20 03:43:52 from __future__ import absolute_import, print_function -from ableton.v2.base import find_if, listens_group, liveobj_valid, Subject, SlotManager +from ableton.v2.base import find_if, listens_group, liveobj_valid, EventObject RESULTING_NAME_KEY = 'ResultingName' CONDITION_NAME_KEY = 'ConditionName' CONDITIONS_LIST_NAME_KEY = 'ConditionsListName' @@ -14,7 +19,7 @@ def find_parameter(name, host): return find_if(lambda p: p.original_name == name, parameters) -class ParameterSlotDescription(Subject, SlotManager): +class ParameterSlotDescription(EventObject): """ Description class that allows chosing a parameter (name) based on the values of other parameters. To retrieve the chosen parameter name @@ -26,7 +31,7 @@ class ParameterSlotDescription(Subject, SlotManager): - slot = use('A').if_parameter('B').has_value('1.0') .and_parameter('C').has_value('0.5').else_use('D') - parameter_name = str(slot) """ - __events__ = ('content',) + __events__ = ('content', ) def __init__(self, *a, **k): super(ParameterSlotDescription, self).__init__(*a, **k) @@ -34,13 +39,17 @@ def __init__(self, *a, **k): self._default_parameter_name = '' self._conditions = [] self._cached_content = None + return def _calc_content(self): content = self._default_parameter_name for condition in self._conditions: result = True for subcond in condition[CONDITIONS_LIST_NAME_KEY]: - result = eval('%s %s %s' % (result, subcond[OPERAND_NAME_KEY], subcond[PREDICATE_KEY](find_parameter(subcond[CONDITION_NAME_KEY], self._parameter_host)))) + result = eval('%s %s %s' % ( + result, + subcond[OPERAND_NAME_KEY], + subcond[PREDICATE_KEY](find_parameter(subcond[CONDITION_NAME_KEY], self._parameter_host)))) if not result: continue @@ -68,15 +77,19 @@ def set_parameter_host(self, host): def if_parameter(self, parameter_name): self._conditions.append({RESULTING_NAME_KEY: self._default_parameter_name, - CONDITIONS_LIST_NAME_KEY: [{CONDITION_NAME_KEY: parameter_name, - OPERAND_NAME_KEY: AND}]}) + CONDITIONS_LIST_NAME_KEY: [ + {CONDITION_NAME_KEY: parameter_name, + OPERAND_NAME_KEY: AND + }] + }) self._default_parameter_name = '' return self def chain_condition(self, operand, parameter_name): - raise len(self._conditions) > 0 and len(self._conditions[-1][CONDITIONS_LIST_NAME_KEY]) > 0 and not self._default_parameter_name or AssertionError + assert len(self._conditions) > 0 and len(self._conditions[-1][CONDITIONS_LIST_NAME_KEY]) > 0 and not self._default_parameter_name self._conditions[-1][CONDITIONS_LIST_NAME_KEY].append({CONDITION_NAME_KEY: parameter_name, - OPERAND_NAME_KEY: operand}) + OPERAND_NAME_KEY: operand + }) return self def and_parameter(self, parameter_name): @@ -86,7 +99,7 @@ def or_parameter(self, parameter_name): return self.chain_condition(OR, parameter_name) def _add_condition_predicate(self, predicate): - raise len(self._conditions) > 0 and PREDICATE_KEY not in self._conditions[-1][CONDITIONS_LIST_NAME_KEY][-1] or AssertionError + assert len(self._conditions) > 0 and PREDICATE_KEY not in self._conditions[-1][CONDITIONS_LIST_NAME_KEY][-1] self._conditions[-1][CONDITIONS_LIST_NAME_KEY][-1][PREDICATE_KEY] = predicate def has_value(self, value): diff --git a/pushbase/percussion_instrument_finder_component.py b/pushbase/percussion_instrument_finder.py similarity index 72% rename from pushbase/percussion_instrument_finder_component.py rename to pushbase/percussion_instrument_finder.py index 16abcb34..4b33cb19 100644 --- a/pushbase/percussion_instrument_finder_component.py +++ b/pushbase/percussion_instrument_finder.py @@ -1,26 +1,48 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/percussion_instrument_finder_component.py +# uncompyle6 version 2.9.10 +# Python bytecode 2.7 (62211) +# Decompiled from: Python 2.7.13 (default, Dec 17 2016, 23:03:43) +# [GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.42.1)] +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/percussion_instrument_finder.py +# Compiled at: 2016-05-20 03:43:52 from __future__ import absolute_import, print_function import Live from itertools import chain -from ableton.v2.base import Subject, listens_group, liveobj_changed, liveobj_valid -from ableton.v2.control_surface import Component +from ableton.v2.base import EventObject, listens_group, liveobj_changed, liveobj_valid +from ableton.v2.control_surface.mode import Mode from .device_chain_utils import find_instrument_devices, find_instrument_meeting_requirement -class PercussionInstrumentFinderComponent(Component, Subject): +class PercussionInstrumentFinder(Mode, EventObject): """ Looks in the hierarchy of devices of the selected track, looking for the first available drum-rack or sliced simpler (depth-first), updating as the device list changes. """ - __events__ = ('instrument',) + __events__ = ('instrument', ) _drum_group = None _simpler = None - def __init__(self, device_parent = None, *a, **k): - raise liveobj_valid(device_parent) or AssertionError - super(PercussionInstrumentFinderComponent, self).__init__(*a, **k) + def __init__(self, device_parent=None, is_enabled=True, *a, **k): + assert liveobj_valid(device_parent) + super(PercussionInstrumentFinder, self).__init__(*a, **k) + self._is_enabled = is_enabled self._device_parent = None self.device_parent = device_parent + return + + @property + def is_enabled(self): + return self._is_enabled + + @is_enabled.setter + def is_enabled(self, enabled): + self._is_enabled = enabled + self.update() + + def enter_mode(self): + self.is_enabled = True + + def leave_mode(self): + self.is_enabled = False @property def drum_group(self): @@ -62,8 +84,7 @@ def __on_slicing_changed(self, _simpler): self.update() def update(self): - super(PercussionInstrumentFinderComponent, self).update() - if self.is_enabled(): + if self.is_enabled: self._update_listeners() self._update_instruments() diff --git a/pushbase/playhead_component.py b/pushbase/playhead_component.py index e2aed205..e2f63cc1 100644 --- a/pushbase/playhead_component.py +++ b/pushbase/playhead_component.py @@ -1,6 +1,11 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/playhead_component.py +# uncompyle6 version 2.9.10 +# Python bytecode 2.7 (62211) +# Decompiled from: Python 2.7.13 (default, Dec 17 2016, 23:03:43) +# [GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.42.1)] +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/playhead_component.py +# Compiled at: 2016-09-29 19:13:24 from __future__ import absolute_import, print_function -from ableton.v2.base import listens +from ableton.v2.base import listens, liveobj_valid from ableton.v2.control_surface import Component class PlayheadComponent(Component): @@ -8,7 +13,7 @@ class PlayheadComponent(Component): Updates the contents of the Live playhead object. """ - def __init__(self, paginator = None, grid_resolution = None, follower = None, notes = range(8), triplet_notes = range(6), feedback_channels = [], *a, **k): + def __init__(self, paginator=None, grid_resolution=None, follower=None, notes=range(8), triplet_notes=range(6), feedback_channels=[], *a, **k): super(PlayheadComponent, self).__init__(*a, **k) self._playhead = None self._clip = None @@ -21,6 +26,7 @@ def __init__(self, paginator = None, grid_resolution = None, follower = None, no self._on_page_changed.subject = self._paginator self._on_grid_resolution_changed.subject = self._grid_resolution self._on_follower_is_following_changed.subject = self._follower + return def set_playhead(self, playhead): self._playhead = playhead @@ -31,6 +37,7 @@ def set_clip(self, clip): self._on_playing_status_changed.subject = clip self._on_song_is_playing_changed.subject = self.song if clip else None self.update() + return @listens('page') def _on_page_changed(self): @@ -55,17 +62,17 @@ def _on_follower_is_following_changed(self, value): def update(self): super(PlayheadComponent, self).update() if self._playhead: - if self.is_enabled() and self.song.is_playing and self._clip and self._clip.is_playing: - clip_slot = self._clip.canonical_parent - track = clip_slot.canonical_parent if clip_slot else None - else: - track = None - self._playhead.track = track + clip = None + if self.is_enabled() and self.song.is_playing and liveobj_valid(self._clip): + if self._clip.is_arrangement_clip or self._clip.is_playing: + clip = self._clip + self._playhead.clip = clip self._playhead.set_feedback_channels(self._feedback_channels) - if track: + if clip: is_triplet = self._grid_resolution.clip_grid[1] notes = self._triplet_notes if is_triplet else self._notes self._playhead.notes = list(notes) self._playhead.wrap_around = self._follower.is_following and self._paginator.can_change_page self._playhead.start_time = self._paginator.page_length * self._paginator.page_index - self._playhead.step_length = self._paginator.page_length / len(notes) \ No newline at end of file + self._playhead.step_length = self._paginator.page_length / len(notes) + return \ No newline at end of file diff --git a/pushbase/playhead_element.py b/pushbase/playhead_element.py index 50136fe8..86e19430 100644 --- a/pushbase/playhead_element.py +++ b/pushbase/playhead_element.py @@ -1,7 +1,12 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/playhead_element.py +# uncompyle6 version 2.9.10 +# Python bytecode 2.7 (62211) +# Decompiled from: Python 2.7.13 (default, Dec 17 2016, 23:03:43) +# [GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.42.1)] +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/playhead_element.py +# Compiled at: 2016-09-29 19:13:24 from __future__ import absolute_import, print_function -from ableton.v2.base import Proxy, nop -from ableton.v2.control_surface import ControlElement +from ableton.v2.base import nop +from .proxy_element import ProxyElement class NullPlayhead(object): notes = [] @@ -10,22 +15,15 @@ class NullPlayhead(object): velocity = 0.0 wrap_around = False track = None + clip = None set_feedback_channels = nop -class ProxyElement(Proxy, ControlElement): - - def reset(self): - try: - super(ProxyElement, self).__getattr__('reset')() - except AttributeError: - pass - - class PlayheadElement(ProxyElement): - def __init__(self, playhead = None, *a, **k): + def __init__(self, playhead=None, *a, **k): super(PlayheadElement, self).__init__(proxied_object=playhead, proxied_interface=NullPlayhead()) def reset(self): - self.track = None \ No newline at end of file + self.track = None + return \ No newline at end of file diff --git a/pushbase/provider_device_component.py b/pushbase/provider_device_component.py deleted file mode 100644 index 13f9c2e7..00000000 --- a/pushbase/provider_device_component.py +++ /dev/null @@ -1,40 +0,0 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/midi-remote-scripts/pushbase/provider_device_component.py -from __future__ import absolute_import, with_statement -from ableton.v2.base import BooleanContext -from ableton.v2.control_surface.components import DeviceComponent -from .parameter_provider import ParameterProvider, generate_info - -class ProviderDeviceComponent(ParameterProvider, DeviceComponent): - """ - Device component that serves as parameter provider for the - DeviceParameterComponent. - """ - _provided_parameters = tuple() - - def __init__(self, *a, **k): - super(ProviderDeviceComponent, self).__init__(*a, **k) - self.set_parameter_controls([]) - self._suppress_parameter_notification = BooleanContext() - - @property - def parameters(self): - return self._provided_parameters - - def set_device(self, device): - with self._suppress_parameter_notification(): - super(ProviderDeviceComponent, self).set_device(device) - self._provided_parameters = self._get_provided_parameters() - self.notify_parameters() - - def _is_banking_enabled(self): - return True - - def _assign_parameters(self): - super(ProviderDeviceComponent, self)._assign_parameters() - self._provided_parameters = self._get_provided_parameters() - if not self._suppress_parameter_notification: - self.notify_parameters() - - def _get_provided_parameters(self): - _, parameters = self._current_bank_details() if self._device else (None, ()) - return [ generate_info(p) for p in parameters ] \ No newline at end of file diff --git a/pushbase/proxy_element.py b/pushbase/proxy_element.py new file mode 100644 index 00000000..e021c229 --- /dev/null +++ b/pushbase/proxy_element.py @@ -0,0 +1,17 @@ +# uncompyle6 version 2.9.10 +# Python bytecode 2.7 (62211) +# Decompiled from: Python 2.7.13 (default, Dec 17 2016, 23:03:43) +# [GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.42.1)] +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/proxy_element.py +# Compiled at: 2016-06-13 12:41:02 +from __future__ import absolute_import, print_function +from ableton.v2.base import Proxy +from ableton.v2.control_surface import ControlElement + +class ProxyElement(Proxy, ControlElement): + + def reset(self): + try: + super(ProxyElement, self).__getattr__('reset')() + except AttributeError: + pass \ No newline at end of file diff --git a/pushbase/push_base.py b/pushbase/push_base.py index b10d174d..3a756891 100644 --- a/pushbase/push_base.py +++ b/pushbase/push_base.py @@ -1,12 +1,17 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/push_base.py +# uncompyle6 version 2.9.10 +# Python bytecode 2.7 (62211) +# Decompiled from: Python 2.7.13 (default, Dec 17 2016, 23:03:43) +# [GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.42.1)] +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/push_base.py +# Compiled at: 2016-11-16 18:13:21 from __future__ import absolute_import, print_function from contextlib import contextmanager from functools import partial from itertools import imap -from ableton.v2.base import inject, clamp, nop, const, NamedTuple, listens, listens_group +from ableton.v2.base import clamp, const, inject, listens, listens_group, NamedTuple, nop from ableton.v2.control_surface import BackgroundLayer, ClipCreator, ControlSurface, DeviceBankRegistry, Layer, midi from ableton.v2.control_surface.components import BackgroundComponent, M4LInterfaceComponent, ModifierBackgroundComponent, SessionNavigationComponent, SessionRingComponent, SessionOverviewComponent, ViewControlComponent -from ableton.v2.control_surface.elements import adjust_string, ButtonElement, ButtonMatrixElement, ChoosingElement, ComboElement, DoublePressContext, MultiElement, OptionalElement, to_midi_value +from ableton.v2.control_surface.elements import ButtonElement, ButtonMatrixElement, ChoosingElement, ComboElement, DoublePressContext, MultiElement, OptionalElement, to_midi_value from ableton.v2.control_surface.mode import AddLayerMode, LayerMode, LazyComponentMode, ReenterBehaviour, ModesComponent, EnablingModesComponent from .accent_component import AccentComponent from .actions import CaptureAndInsertSceneComponent, DeleteAndReturnToDefaultComponent, DeleteComponent, DeleteSelectedClipComponent, DeleteSelectedSceneComponent, DuplicateDetailClipComponent, DuplicateLoopComponent, UndoRedoComponent @@ -22,21 +27,21 @@ from .matrix_maps import FEEDBACK_CHANNELS from .melodic_component import MelodicComponent from .message_box_component import DialogComponent, InfoComponent +from .messenger_mode_component import MessengerModesComponent from .note_editor_component import DEFAULT_VELOCITY_RANGE_THRESHOLDS from .note_repeat_component import NoteRepeatComponent from .note_settings_component import NoteEditorSettingsComponent -from .selected_track_parameter_provider import SelectedTrackParameterProvider from .selection import PushSelection from .select_playing_clip_component import SelectPlayingClipComponent +from .session_recording_component import FixedLengthRecording from .skin_default import make_default_skin -from .sliced_simpler_component import SlicedSimplerComponent -from .special_session_component import SpecialSessionComponent from .step_seq_component import StepSeqComponent -from .percussion_instrument_finder_component import PercussionInstrumentFinderComponent +from .percussion_instrument_finder import PercussionInstrumentFinder from .touch_strip_controller import TouchStripControllerComponent, TouchStripEncoderConnection, TouchStripPitchModComponent from .track_frozen_mode import TrackFrozenModesComponent from .transport_component import TransportComponent from .value_component import ValueComponent, ParameterValueComponent +from .velocity_levels_component import VelocityLevelsComponent from . import consts from . import sysex NUM_TRACKS = 8 @@ -49,18 +54,23 @@ def tracks_to_use_from_song(song): class PushBase(ControlSurface): preferences_key = 'Push' - session_component_type = SpecialSessionComponent drum_group_note_editor_skin = 'NoteEditor' + slicing_note_editor_skin = 'NoteEditor' + drum_group_velocity_levels_skin = 'VelocityLevels' + slicing_velocity_levels_skin = 'VelocityLevels' note_editor_velocity_range_thresholds = DEFAULT_VELOCITY_RANGE_THRESHOLDS device_component_class = None + selected_track_parameter_provider_class = None bank_definitions = None note_editor_class = None + sliced_simpler_class = None def __init__(self, *a, **k): super(PushBase, self).__init__(*a, **k) self.register_slot(self.song.view, self._on_selected_track_changed, 'selected_track') self._device_decorator_factory = self._create_device_decorator_factory() self.register_disconnectable(self._device_decorator_factory) + self._percussion_instrument_finder = self.register_disconnectable(PercussionInstrumentFinder(device_parent=self.song.view.selected_track)) self._double_press_context = DoublePressContext() injecting = self._create_injector() self._push_injector = injecting.everywhere() @@ -75,6 +85,7 @@ def __init__(self, *a, **k): with inject(skin=const(self._skin)).everywhere(): self._create_controls() self._element_injector = inject(element_container=const(self.elements)).everywhere() + return def initialize(self): self._setup_accidental_touch_prevention() @@ -91,6 +102,7 @@ def disconnect(self): with self.component_guard(): self._user.mode = sysex.USER_MODE super(PushBase, self).disconnect() + return @contextmanager def _component_guard(self): @@ -134,7 +146,8 @@ def _create_components(self): self._init_automation_component() self._init_note_settings_component() self._init_note_editor_settings_component() - self._init_step_sequencer() + self._init_drum_step_sequencer() + self._init_slicing_step_sequencer() self._init_instrument() self._init_scales() self._init_note_repeat() @@ -143,7 +156,7 @@ def _create_components(self): self._init_m4l_interface() def _create_injector(self): - return inject(double_press_context=const(self._double_press_context), expect_dialog=const(self.expect_dialog), show_notification=const(self.show_notification), selection=lambda : PushSelection(application=self.application(), device_component=self._device_component, navigation_component=self._device_navigation)) + return inject(double_press_context=const(self._double_press_context), expect_dialog=const(self.expect_dialog), show_notification=const(self.show_notification), selection=lambda : PushSelection(application=self.application, device_component=self._device_component, navigation_component=self._device_navigation)) def _create_skin(self): return make_default_skin() @@ -195,13 +208,13 @@ def _init_background(self): self._mod_background.layer = Layer(shift_button='shift_button', select_button='select_button', delete_button='delete_button', duplicate_button='duplicate_button', quantize_button='quantize_button') def _create_background_layer(self): - return Layer(top_buttons='select_buttons', bottom_buttons='track_state_buttons', scales_button='scale_presets_button', octave_up='octave_up_button', octave_down='octave_down_button', side_buttons='side_buttons', repeat_button='repeat_button', accent_button='accent_button', double_button='double_button', param_controls='global_param_controls', param_touch='global_param_touch_buttons', touch_strip='touch_strip_control', nav_up_button='nav_up_button', nav_down_button='nav_down_button', nav_left_button='nav_left_button', nav_right_button='nav_right_button', aftertouch='aftertouch_control', _notification=self._notification.use_single_line(2), priority=consts.BACKGROUND_PRIORITY) + return Layer(top_buttons='select_buttons', bottom_buttons='track_state_buttons', scales_button='scale_presets_button', octave_up='octave_up_button', octave_down='octave_down_button', side_buttons='side_buttons', repeat_button='repeat_button', accent_button='accent_button', double_button='double_button', param_controls='global_param_controls', param_touch='global_param_touch_buttons', touch_strip='touch_strip_control', nav_up_button='nav_up_button', nav_down_button='nav_down_button', nav_left_button='nav_left_button', nav_right_button='nav_right_button', new_button='new_button', aftertouch='aftertouch_control', _notification=self._notification.use_single_line(2), priority=consts.BACKGROUND_PRIORITY) def _init_track_list(self): pass def _can_auto_arm_track(self, track): - routing = track.current_input_routing + routing = track.input_routing_type.display_name return routing == 'Ext: All Ins' or routing == 'All Ins' or routing.startswith(self.input_target_name_for_auto_arm) def _init_touch_strip_controller(self): @@ -223,9 +236,21 @@ def _create_session_mode(self): raise NotImplementedError def _create_slicing_modes(self): - slicing_modes = ModesComponent(name='Slicing_Modes', is_enabled=False) - slicing_modes.add_mode('64pads', [AddLayerMode(self._slicing_component, Layer(matrix='matrix')), LayerMode(self._pitch_mod_touch_strip, self._pitch_mod_touch_strip_layer)]) - slicing_modes.add_mode('sequencer', [self._slice_step_sequencer, self._note_editor_settings_component, AddLayerMode(self._slicing_component, Layer(matrix=self.elements.matrix.submatrix[:4, 4:8], page_strip='touch_strip_control', scroll_strip=self._with_shift('touch_strip_control')))]) + slicing_modes = MessengerModesComponent(name='Slicing_Modes', is_enabled=False) + slicing_modes.add_mode('64pads', [ + AddLayerMode(self._slicing_component, Layer(matrix='matrix')), + LayerMode(self._pitch_mod_touch_strip, self._pitch_mod_touch_strip_layer)], message=consts.MessageBoxText.LAYOUT_SLICING_64_PADS) + slicing_modes.add_mode('sequencer_loop', [ + self._slice_step_sequencer, + self._note_editor_settings_component, + AddLayerMode(self._slice_step_sequencer, Layer(loop_selector_matrix=self.elements.double_press_matrix.submatrix[4:8, 4:8], short_loop_selector_matrix=self.elements.double_press_event_matrix.submatrix[4:8, 4:8])), + AddLayerMode(self._slicing_component, Layer(matrix=self.elements.matrix.submatrix[:4, 4:8], page_strip='touch_strip_control', scroll_strip=self._with_shift('touch_strip_control')))], message=consts.MessageBoxText.LAYOUT_SLICING_LOOP) + slicing_modes.add_mode('sequencer_velocity_levels', [ + self._slice_step_sequencer, + self._note_editor_settings_component, + self._slicing_velocity_levels, + AddLayerMode(self._slicing_component, Layer(matrix=self.elements.matrix.submatrix[:4, 4:8], page_strip='touch_strip_control', scroll_strip=self._with_shift('touch_strip_control'))), + AddLayerMode(self._slicing_velocity_levels, Layer(matrix=self.elements.matrix.submatrix[4:8, 4:8]))], message=consts.MessageBoxText.LAYOUT_SLICING_LEVELS) slicing_modes.selected_mode = '64pads' return slicing_modes @@ -235,24 +260,35 @@ def _init_matrix_modes(self): self._auto_arm.layer = Layer(_notification=self._notification.use_single_line(2)) self._select_playing_clip = SelectPlayingClipComponent(name='Select_Playing_Clip', playing_clip_above_layer=Layer(action_button='nav_up_button'), playing_clip_below_layer=Layer(action_button='nav_down_button')) self._select_playing_clip.layer = Layer(_notification=self._notification.use_single_line(2)) - self._percussion_instrument_finder = PercussionInstrumentFinderComponent(device_parent=self.song.view.selected_track) - self.__on_percussion_instrument_changed.subject = self._percussion_instrument_finder - self._drum_modes = ModesComponent(name='Drum_Modes', is_enabled=False) - self._drum_modes.add_mode('sequencer', [self._drum_step_sequencer, self._note_editor_settings_component, AddLayerMode(self._drum_component, Layer(matrix=self.elements.matrix.submatrix[:4, 4:8]))]) - self._drum_modes.add_mode('64pads', [AddLayerMode(self._drum_component, Layer(matrix='matrix'))]) - self._drum_modes.selected_mode = 'sequencer' + self._drum_modes = MessengerModesComponent(name='Drum_Modes', is_enabled=False) + self._drum_modes.add_mode('sequencer_loop', [ + self._drum_step_sequencer, + self._note_editor_settings_component, + AddLayerMode(self._drum_component, Layer(matrix=self.elements.matrix.submatrix[:4, 4:8])), + AddLayerMode(self._drum_step_sequencer, Layer(loop_selector_matrix=self.elements.double_press_matrix.submatrix[4:8, 4:8], short_loop_selector_matrix=self.elements.double_press_event_matrix.submatrix[4:8, 4:8]))], message=consts.MessageBoxText.LAYOUT_DRUMS_LOOP) + self._drum_modes.add_mode('sequencer_velocity_levels', [ + self._drum_step_sequencer, + self._note_editor_settings_component, + self._drum_velocity_levels, + AddLayerMode(self._drum_component, Layer(matrix=self.elements.matrix.submatrix[:4, 4:8])), + AddLayerMode(self._drum_velocity_levels, Layer(matrix=self.elements.matrix.submatrix[4:8, 4:8]))], message=consts.MessageBoxText.LAYOUT_DRUMS_LEVELS) + self._drum_modes.add_mode('64pads', AddLayerMode(self._drum_component, Layer(matrix='matrix')), message=consts.MessageBoxText.LAYOUT_DRUMS_64_PADS) + self._drum_modes.selected_mode = 'sequencer_loop' self._slicing_modes = self._create_slicing_modes() self._note_modes = ModesComponent(name='Note_Modes') - self._note_modes.add_mode('drums', [self._drum_component, + self._note_modes.add_mode('drums', [ + self._drum_component, self._note_repeat_enabler, self._accent_component, self._drum_modes]) - self._note_modes.add_mode('slicing', [self._slicing_component, + self._note_modes.add_mode('slicing', [ + self._slicing_component, self._note_repeat_enabler, self._accent_component, self._slicing_modes]) self._note_modes.add_mode('looper', self._audio_loop if consts.PROTO_AUDIO_NOTE_MODE else self._matrix_background) - self._note_modes.add_mode('instrument', [self._note_repeat_enabler, + self._note_modes.add_mode('instrument', [ + self._note_repeat_enabler, self._accent_component, self._instrument, self._scales_enabler]) @@ -266,15 +302,19 @@ def _init_matrix_modes(self): self._matrix_modes.layer = Layer(session_button='session_mode_button', note_button='note_mode_button') self.__on_matrix_mode_changed.subject = self._matrix_modes self._matrix_modes.selected_mode = 'note' + self.__on_percussion_instrument_changed.subject = self._percussion_instrument_finder def _switch_note_mode_layout(self): cyclable_mode = {'instrument': self._instrument, - 'drums': self._drum_modes, - 'slicing': self._slicing_modes}.get(self._note_modes.selected_mode, None) + 'drums': self._drum_modes, + 'slicing': self._slicing_modes + }.get(self._note_modes.selected_mode, None) getattr(cyclable_mode, 'cycle_mode', nop)() + return def _create_note_mode(self): - return [self._percussion_instrument_finder, + return [ + self._percussion_instrument_finder, self._view_control, self._note_modes, self._delete_clip, @@ -305,8 +345,14 @@ def _create_session_layer(self): def _set_session_skin(self, session): pass + def _create_fixed_length_recording(self): + return self.register_disconnectable(FixedLengthRecording(self.song, self._clip_creator, fixed_length_setting=self._fixed_length_setting)) + + def _instantiate_session(self): + raise NotImplementedError + def _create_session(self): - session = self.session_component_type(session_ring=self._session_ring, enable_skinning=True, is_enabled=False, auto_name=True, layer=self._create_session_layer()) + session = self._instantiate_session() self._set_session_skin(session) for scene_index in xrange(8): scene = session.scene(scene_index) @@ -361,21 +407,33 @@ def _create_track_modes_layer(self): return Layer(stop_button='global_track_stop_button', mute_button='global_mute_button', solo_button='global_solo_button') def _when_track_is_not_frozen(self, *modes): - return TrackFrozenModesComponent(default_mode=[modes], frozen_mode=self._track_frozen_info, is_enabled=False) + return TrackFrozenModesComponent(default_mode=[ + modes], frozen_mode=self._track_frozen_info, is_enabled=False) def _create_device_mode(self): raise NotImplementedError def _create_main_mixer_modes(self): - self._main_modes.add_mode('volumes', [self._track_modes, (self._mixer, self._mixer_volume_layer), self._track_note_editor_mode]) - self._main_modes.add_mode('pan_sends', [self._track_modes, (self._mixer, self._mixer_pan_send_layer), self._track_note_editor_mode]) - self._main_modes.add_mode('track', [self._track_modes, + self._main_modes.add_mode('volumes', [ + self._track_modes, + ( + self._mixer, self._mixer_volume_layer), + self._track_note_editor_mode]) + self._main_modes.add_mode('pan_sends', [ + self._track_modes, + ( + self._mixer, self._mixer_pan_send_layer), + self._track_note_editor_mode]) + self._main_modes.add_mode('track', [ + self._track_modes, self._track_mixer, - (self._mixer, self._mixer_track_layer), + ( + self._mixer, self._mixer_track_layer), self._track_note_editor_mode]) def _create_clip_mode(self): - return [self._when_track_is_not_frozen(partial(self._view_control.show_view, 'Detail/Clip'), LazyComponentMode(self._create_clip_control))] + return [ + self._when_track_is_not_frozen(partial(self._view_control.show_view, 'Detail/Clip'), LazyComponentMode(self._create_clip_control))] def _init_main_modes(self): @@ -411,7 +469,7 @@ def _init_mixer(self): pass def _init_track_mixer(self): - self._track_parameter_provider = self.register_disconnectable(SelectedTrackParameterProvider()) + self._track_parameter_provider = self.register_disconnectable(self.selected_track_parameter_provider_class()) self._track_mixer = DeviceParameterComponent(parameter_provider=self._track_parameter_provider, is_enabled=False, layer=self._create_track_mixer_layer()) def _create_track_mixer_layer(self): @@ -443,22 +501,38 @@ def _init_fixed_length(self): self._fixed_length_setting = FixedLengthSetting() self._fixed_length_setting.enabled = self.preferences.setdefault('fixed_length_enabled', False) self._fixed_length_setting.selected_index = self.preferences.setdefault('fixed_length_option', DEFAULT_LENGTH_OPTION_INDEX) + self._fixed_length_setting.legato_launch = self.preferences.setdefault('fixed_length_legato_launch', False) self.__on_fixed_length_enabled_changed.subject = self._fixed_length_setting self.__on_fixed_length_selected_index_changed.subject = self._fixed_length_setting + self.__on_fixed_length_legato_launch_changed.subject = self._fixed_length_setting self._fixed_length_settings_component = FixedLengthSettingComponent(fixed_length_setting=self._fixed_length_setting, is_enabled=False) self._fixed_length = FixedLengthComponent(settings_component=self._fixed_length_settings_component, fixed_length_setting=self._fixed_length_setting) self._fixed_length.layer = Layer(fixed_length_toggle_button='fixed_length_button') + length, _ = self._fixed_length_setting.get_selected_length(self.song) + self._clip_creator.fixed_length = length def _create_session_recording(self): raise NotImplementedError + @listens('focused_document_view') + def __on_session_visible_changed(self): + is_showing_session = self.application.view.focused_document_view == 'Session' + self._session_recording.layer = self._session_recording_session_layer if is_showing_session else self._session_recording_arrangement_layer + self._view_control.layer = self._view_control_session_layer if is_showing_session else self._view_control_arrangement_layer + self._session_recording.footswitch_toggles_arrangement_recording = not is_showing_session + def _init_transport_and_recording(self): self._view_control = self._create_view_control_component() self._view_control.set_enabled(False) - self._view_control.layer = Layer(prev_track_button='nav_left_button', next_track_button='nav_right_button', prev_scene_button=OptionalElement('nav_up_button', self._settings['workflow'], False), next_scene_button=OptionalElement('nav_down_button', self._settings['workflow'], False), prev_scene_list_button=OptionalElement('nav_up_button', self._settings['workflow'], True), next_scene_list_button=OptionalElement('nav_down_button', self._settings['workflow'], True)) + self._view_control_arrangement_layer = Layer(prev_track_button='nav_left_button', next_track_button='nav_right_button') + self._view_control_session_layer = Layer(prev_scene_button=OptionalElement('nav_up_button', self._settings['workflow'], False), next_scene_button=OptionalElement('nav_down_button', self._settings['workflow'], False), prev_scene_list_button=OptionalElement('nav_up_button', self._settings['workflow'], True), next_scene_list_button=OptionalElement('nav_down_button', self._settings['workflow'], True)) + self._view_control_arrangement_layer self._session_recording = self._create_session_recording() new_button = MultiElement(self.elements.new_button, self.elements.foot_pedal_button.double_press) - self._session_recording.layer = Layer(new_button=OptionalElement(new_button, self._settings['workflow'], False), scene_list_new_button=OptionalElement(new_button, self._settings['workflow'], True), record_button='record_button', arrangement_record_button=self._with_shift('record_button'), automation_button='automation_button', new_scene_button=self._with_shift('new_button'), re_enable_automation_button=self._with_shift('automation_button'), delete_automation_button=ComboElement('automation_button', 'delete_button'), foot_switch_button=self.elements.foot_pedal_button.single_press, _uses_foot_pedal='foot_pedal_button') + session_recording_base_layer = Layer(automation_button='automation_button', new_scene_button=self._with_shift('new_button'), re_enable_automation_button=self._with_shift('automation_button'), delete_automation_button=ComboElement('automation_button', 'delete_button'), foot_switch_button=self.elements.foot_pedal_button.single_press, _uses_foot_pedal='foot_pedal_button') + self._session_recording_arrangement_layer = Layer(record_button=self._with_shift('record_button'), arrangement_record_button='record_button') + session_recording_base_layer + self._session_recording_session_layer = Layer(record_button='record_button', arrangement_record_button=self._with_shift('record_button'), new_button=OptionalElement(new_button, self._settings['workflow'], False), scene_list_new_button=OptionalElement(new_button, self._settings['workflow'], True)) + session_recording_base_layer + self.__on_session_visible_changed.subject = self.application.view + self.__on_session_visible_changed() self._transport = TransportComponent(name='Transport', is_root=True) self._transport.layer = Layer(play_button='play_button', stop_button=self._with_shift('play_button'), tap_tempo_button='tap_tempo_button', metronome_button='metronome_button') @@ -498,10 +572,10 @@ def _create_note_editor_device_automation_layer(self): return Layer(priority=consts.MOMENTARY_DIALOG_PRIORITY) def _create_instrument_layer(self): - return Layer(playhead='playhead_element', mute_button='global_mute_button', quantization_buttons='side_buttons', loop_selector_matrix=self.elements.double_press_matrix.submatrix[:, 0], short_loop_selector_matrix=self.elements.double_press_event_matrix.submatrix[:, 0], note_editor_matrices=ButtonMatrixElement([[ self.elements.matrix.submatrix[:, 7 - row] for row in xrange(7) ]])) + return Layer(playhead='playhead_element', mute_button='global_mute_button', quantization_buttons='side_buttons', loop_selector_matrix=self.elements.double_press_matrix.submatrix[:, 0], short_loop_selector_matrix=self.elements.double_press_event_matrix.submatrix[:, 0], note_editor_matrices=ButtonMatrixElement([[ self.elements.matrix.submatrix[:, 7 - row] for row in xrange(7) ]]), duplicate_button='duplicate_button') def _init_instrument(self): - self._note_layout = NoteLayout(song=self.song, preferences=self.preferences) + self._note_layout = self.register_disconnectable(NoteLayout(song=self.song, preferences=self.preferences)) instrument_basic_layer = Layer(octave_strip=self._with_shift('touch_strip_control'), octave_up_button='octave_up_button', octave_down_button='octave_down_button', scale_up_button=self._with_shift('octave_up_button'), scale_down_button=self._with_shift('octave_down_button')) self._instrument = MelodicComponent(skin=self._skin, is_enabled=False, clip_creator=self._clip_creator, name='Melodic_Component', grid_resolution=self._grid_resolution, note_layout=self._note_layout, note_editor_settings=self._note_editor_settings_component, note_editor_class=self.note_editor_class, velocity_range_thresholds=self.note_editor_velocity_range_thresholds, layer=self._create_instrument_layer(), instrument_play_layer=instrument_basic_layer + Layer(matrix='matrix', aftertouch_control='aftertouch_control', delete_button='delete_button'), instrument_sequence_layer=instrument_basic_layer + Layer(note_strip='touch_strip_control'), pitch_mod_touch_strip_mode=LayerMode(self._pitch_mod_touch_strip, self._pitch_mod_touch_strip_layer)) self.__on_note_editor_layout_changed.subject = self._instrument @@ -512,25 +586,29 @@ def _create_scales_enabler(self): def _init_scales(self): self._scales_enabler = self._create_scales_enabler() - def _create_step_sequencer_layer(self): - return Layer(playhead='playhead_element', button_matrix=self.elements.matrix.submatrix[:8, :4], loop_selector_matrix=self.elements.double_press_matrix.submatrix[4:8, 4:8], short_loop_selector_matrix=self.elements.double_press_event_matrix.submatrix[4:8, 4:8], quantization_buttons='side_buttons', solo_button='global_solo_button', select_button='select_button', delete_button='delete_button', shift_button='shift_button', mute_button='global_mute_button') + def _create_drum_step_sequencer_layer(self): + return Layer(playhead='playhead_element', button_matrix=self.elements.matrix.submatrix[:8, :4], quantization_buttons='side_buttons', solo_button='global_solo_button', select_button='select_button', delete_button='delete_button', mute_button='global_mute_button', duplicate_button='duplicate_button') - def _init_step_sequencer(self): - drum_note_editor = self.note_editor_class(clip_creator=self._clip_creator, grid_resolution=self._grid_resolution, skin_base_key=self.drum_group_note_editor_skin, velocity_range_thresholds=self.note_editor_velocity_range_thresholds) + def _init_drum_step_sequencer(self): + self._drum_velocity_levels = VelocityLevelsComponent(target_note_provider=self._drum_component, skin_base_key=self.drum_group_velocity_levels_skin, is_enabled=False, layer=Layer(velocity_levels='velocity_levels_element', select_button='select_button')) + drum_note_editor = self.note_editor_class(clip_creator=self._clip_creator, grid_resolution=self._grid_resolution, skin_base_key=self.drum_group_note_editor_skin, velocity_provider=self._drum_velocity_levels, velocity_range_thresholds=self.note_editor_velocity_range_thresholds) self._note_editor_settings_component.add_editor(drum_note_editor) - self._drum_step_sequencer = StepSeqComponent(self._clip_creator, self._skin, name='Drum_Step_Sequencer', grid_resolution=self._grid_resolution, note_editor_component=drum_note_editor, instrument_component=self._drum_component) + self._drum_step_sequencer = StepSeqComponent(self._clip_creator, self._skin, name='Drum_Step_Sequencer', grid_resolution=self._grid_resolution, note_editor_component=drum_note_editor, instrument_component=self._drum_component, is_enabled=False) self._drum_step_sequencer.set_enabled(False) - self._drum_step_sequencer.layer = self._create_step_sequencer_layer() - self._audio_loop = LoopSelectorComponent(follow_detail_clip=True, measure_length=1.0, name='Loop_Selector') + self._drum_step_sequencer.layer = self._create_drum_step_sequencer_layer() + self._audio_loop = LoopSelectorComponent(follow_detail_clip=True, name='Loop_Selector') self._audio_loop.set_enabled(False) self._audio_loop.layer = Layer(loop_selector_matrix='matrix') - slice_note_editor = self.note_editor_class(clip_creator=self._clip_creator, grid_resolution=self._grid_resolution, skin_base_key=self.drum_group_note_editor_skin, velocity_range_thresholds=self.note_editor_velocity_range_thresholds) + + def _create_slice_step_sequencer_layer(self): + return Layer(playhead='playhead_element', button_matrix=self.elements.matrix.submatrix[:8, :4], quantization_buttons='side_buttons', select_button='select_button', duplicate_button='duplicate_button') + + def _init_slicing_step_sequencer(self): + self._slicing_velocity_levels = VelocityLevelsComponent(target_note_provider=self._slicing_component, skin_base_key=self.slicing_velocity_levels_skin, is_enabled=False, layer=Layer(velocity_levels='velocity_levels_element', select_button='select_button')) + slice_note_editor = self.note_editor_class(clip_creator=self._clip_creator, grid_resolution=self._grid_resolution, skin_base_key=self.slicing_note_editor_skin, velocity_provider=self._slicing_velocity_levels, velocity_range_thresholds=self.note_editor_velocity_range_thresholds) self._note_editor_settings_component.add_editor(slice_note_editor) self._slice_step_sequencer = StepSeqComponent(self._clip_creator, self._skin, name='Slice_Step_Sequencer', grid_resolution=self._grid_resolution, note_editor_component=slice_note_editor, instrument_component=self._slicing_component, is_enabled=False) - self._slice_step_sequencer.layer = Layer(playhead='playhead_element', button_matrix=self.elements.matrix.submatrix[:8, :4], loop_selector_matrix=self.elements.double_press_matrix.submatrix[4:8, 4:8], short_loop_selector_matrix=self.elements.double_press_event_matrix.submatrix[4:8, 4:8], quantization_buttons='side_buttons', select_button='select_button') - - def _drum_pad_notification_formatter(self): - return lambda x: adjust_string(x, 8) + self._slice_step_sequencer.layer = self._create_slice_step_sequencer_layer() def _create_drum_component(self): raise NotImplementedError @@ -540,8 +618,8 @@ def _init_drum_component(self): self._drum_component.layer = Layer(page_strip='touch_strip_control', scroll_strip=self._with_shift('touch_strip_control'), solo_button='global_solo_button', select_button='select_button', delete_button='delete_button', scroll_page_up_button='octave_up_button', scroll_page_down_button='octave_down_button', quantize_button='quantize_button', duplicate_button='duplicate_button', mute_button='global_mute_button', scroll_up_button=self._with_shift('octave_up_button'), scroll_down_button=self._with_shift('octave_down_button')) def _init_slicing_component(self): - self._slicing_component = SlicedSimplerComponent(is_enabled=False) - self._slicing_component.layer = Layer(scroll_page_up_button='octave_up_button', scroll_page_down_button='octave_down_button', scroll_up_button=self._with_shift('octave_up_button'), scroll_down_button=self._with_shift('octave_down_button'), delete_button='delete_button', select_button='select_button') + self._slicing_component = self.sliced_simpler_class(quantizer=self._quantize, is_enabled=False) + self._slicing_component.layer = Layer(scroll_page_up_button='octave_up_button', scroll_page_down_button='octave_down_button', scroll_up_button=self._with_shift('octave_up_button'), scroll_down_button=self._with_shift('octave_down_button'), delete_button='delete_button', select_button='select_button', quantize_button='quantize_button') def _init_note_repeat(self): self._note_repeat = NoteRepeatComponent(name='Note_Repeat') @@ -569,7 +647,9 @@ def _create_message_box_layer(self): def _init_message_box(self): self._dialog = DialogComponent(is_enabled=True, is_root=True) - self._dialog.message_box_layer = (self._create_message_box_background_layer(), self._create_message_box_layer()) + self._dialog.message_box_layer = ( + self._create_message_box_background_layer(), + self._create_message_box_layer()) def _for_non_frozen_tracks(self, component, **k): """ Wrap component into a mode that will only enable it when @@ -614,7 +694,8 @@ def _init_quantize_actions(self): def _init_value_components(self): self._swing_amount = ValueComponent('swing_amount', self.song, display_label='Swing Amount:', display_format='%d%%', model_transform=lambda x: clamp(x / 200.0, 0.0, 0.5), view_transform=lambda x: x * 200.0, encoder_factor=100.0, encoder_touch_delay=TEMPO_SWING_TOUCH_DELAY, is_root=True) self._swing_amount.layer = Layer(encoder='swing_control') - self._tempo = ValueComponent('tempo', self.song, display_label='Tempo:', display_format='%0.2f BPM', encoder_factor=128.0, encoder_touch_delay=TEMPO_SWING_TOUCH_DELAY, is_root=True) + tempo_param = self.song.master_track.mixer_device.song_tempo + self._tempo = ValueComponent('tempo', self.song, display_label='Tempo:', display_format='%0.2f BPM', encoder_factor=128.0, encoder_touch_delay=TEMPO_SWING_TOUCH_DELAY, model_transform=lambda x: clamp(x, tempo_param.min, tempo_param.max), is_root=True) self._tempo.layer = Layer(encoder='tempo_control', shift_button='shift_button') self._master_vol = ParameterValueComponent(self.song.master_track.mixer_device.volume, display_label='Master Volume:', display_seg_start=3, name='Master_Volume_Display', is_root=True) self._master_vol.layer = Layer(encoder='master_volume_control') @@ -632,13 +713,14 @@ def _init_m4l_interface(self): def __on_note_editor_layout_changed(self, mode): self.reset_controlled_track(mode) - def reset_controlled_track(self, mode = None): + def reset_controlled_track(self, mode=None): if mode == None: mode = self._instrument.selected_mode if self._instrument.is_enabled() and mode == 'sequence': self.release_controlled_track() else: self.set_controlled_track(self.song.view.selected_track) + return @listens('selected_track.is_frozen') def __on_selected_track_is_frozen_changed(self): @@ -649,7 +731,7 @@ def _on_selected_track_changed(self): self._select_note_mode() self._note_repeat_enabler.selected_mode = 'disabled' - def _send_midi(self, midi_event_bytes, optimized = True): + def _send_midi(self, midi_event_bytes, optimized=True): if not self._suppress_sysex or not midi.is_sysex(midi_event_bytes): return super(PushBase, self)._send_midi(midi_event_bytes, optimized) @@ -681,6 +763,10 @@ def __on_fixed_length_enabled_changed(self, enabled): def __on_fixed_length_selected_index_changed(self, index): self.preferences['fixed_length_option'] = index + @listens('legato_launch') + def __on_fixed_length_legato_launch_changed(self, value): + self.preferences['fixed_length_legato_launch'] = value + @listens('before_mode_sent') def __on_before_hardware_mode_sent(self, mode): self._suppress_sysex = False @@ -694,13 +780,17 @@ def __on_after_hardware_mode_sent(self, mode): def __on_hardware_mode_changed(self, mode): if mode == sysex.USER_MODE: self._suppress_sysex = True + self.request_rebuild_midi_map() + for control in self.controls: + control.clear_send_cache() + self._update_auto_arm() @listens('selected_mode') def __on_matrix_mode_changed(self, mode): self._update_auto_arm(selected_mode=mode) - def _update_auto_arm(self, selected_mode = None): + def _update_auto_arm(self, selected_mode=None): self._auto_arm.set_enabled(self._user.mode == sysex.LIVE_MODE and (selected_mode or self._matrix_modes.selected_mode) == 'note') @listens('instrument') @@ -727,12 +817,14 @@ def _select_note_mode(self): else: self._note_modes.selected_mode = 'instrument' self.reset_controlled_track() + return def _percussion_instruments_for_track(self, track): self._percussion_instrument_finder.device_parent = track drum_device = self._percussion_instrument_finder.drum_group sliced_simpler = self._percussion_instrument_finder.sliced_simpler - return (drum_device, sliced_simpler) + return ( + drum_device, sliced_simpler) def _setup_accidental_touch_prevention(self): self._accidental_touch_prevention_layer = BackgroundLayer('tempo_control_tap', 'swing_control_tap', 'master_volume_control_tap', priority=consts.MOMENTARY_DIALOG_PRIORITY) @@ -757,7 +849,7 @@ def get_matrix_button(self, column, row): def expect_dialog(self, message): self.schedule_message(1, partial(self._dialog.expect_dialog, message)) - def show_notification(self, message, blink_text = None, notification_time = None): + def show_notification(self, message, blink_text=None, notification_time=None): return self._notification.show_notification(message, blink_text, notification_time) def process_midi_bytes(self, midi_bytes, midi_processor): @@ -765,4 +857,5 @@ def process_midi_bytes(self, midi_bytes, midi_processor): recipient = self.get_recipient_for_nonsysex_midi_message(midi_bytes) if isinstance(recipient, ButtonElement) and midi.extract_value(midi_bytes) != 0 and self._notification is not None: self._notification.hide_notification() - super(PushBase, self).process_midi_bytes(midi_bytes, midi_processor) \ No newline at end of file + super(PushBase, self).process_midi_bytes(midi_bytes, midi_processor) + return \ No newline at end of file diff --git a/pushbase/quantization_component.py b/pushbase/quantization_component.py index ca1f37ee..f90794c2 100644 --- a/pushbase/quantization_component.py +++ b/pushbase/quantization_component.py @@ -1,4 +1,9 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/quantization_component.py +# uncompyle6 version 2.9.10 +# Python bytecode 2.7 (62211) +# Decompiled from: Python 2.7.13 (default, Dec 17 2016, 23:03:43) +# [GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.42.1)] +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/quantization_component.py +# Compiled at: 2016-06-13 08:15:56 from __future__ import absolute_import, print_function import Live from ableton.v2.base import clamp, listenable_property, listens @@ -7,7 +12,8 @@ from .consts import MessageBoxText, SIDE_BUTTON_COLORS from .message_box_component import Messenger RecordingQuantization = Live.Song.RecordingQuantization -QUANTIZATION_OPTIONS = [RecordingQuantization.rec_q_quarter, +QUANTIZATION_OPTIONS = [ + RecordingQuantization.rec_q_quarter, RecordingQuantization.rec_q_eight, RecordingQuantization.rec_q_eight_triplet, RecordingQuantization.rec_q_eight_eight_triplet, @@ -16,8 +22,9 @@ RecordingQuantization.rec_q_sixtenth_sixtenth_triplet, RecordingQuantization.rec_q_thirtysecond] DEFAULT_QUANTIZATION_INDEX = QUANTIZATION_OPTIONS.index(RecordingQuantization.rec_q_sixtenth) -QUANTIZATION_NAMES = ('1/4', '1/8', '1/8t', '1/8+t', '1/16', '1/16t', '1/16+t', '1/32') -QUANTIZATION_NAMES_UNICODE = (u'\xbc', u'\u215b', u'\u215bt', u'\u215b+t', u'\ue001', u'\ue001t', u'\ue001+t', u'\ue002') +QUANTIZATION_NAMES = ('1/4', '1/8', '1/8T', '1/8+T', '1/16', '1/16T', '1/16+T', '1/32') +QUANTIZATION_NAMES_UNICODE = (u'\xbc', u'\u215b', u'\u215bT', u'\u215b+T', u'\ue001', + u'\ue001T', u'\ue001+T', u'\ue002') def quantize_amount_to_string(amount): """ @@ -36,7 +43,7 @@ class QuantizationSettingsComponent(Component): quantize_to_index = listenable_property.managed(DEFAULT_QUANTIZATION_INDEX) record_quantization_index = listenable_property.managed(DEFAULT_QUANTIZATION_INDEX) - def __init__(self, quantization_names = QUANTIZATION_NAMES, *a, **k): + def __init__(self, quantization_names=QUANTIZATION_NAMES, *a, **k): super(QuantizationSettingsComponent, self).__init__(*a, **k) self._quantization_names = quantization_names self.__on_swing_amount_changed.subject = self.song @@ -109,18 +116,19 @@ def _update_record_quantization(self): class QuantizationComponent(CompoundComponent, Messenger): action_button = ButtonControl(**SIDE_BUTTON_COLORS) - def __init__(self, settings = None, *a, **k): - raise settings is not None or AssertionError + def __init__(self, settings=None, *a, **k): + assert settings is not None super(QuantizationComponent, self).__init__(*a, **k) self._settings = self.register_component(settings) self._settings.set_enabled(False) self._cancel_quantize = False + return - def quantize_pitch(self, note): + def quantize_pitch(self, note, source=None): clip = self.song.view.detail_clip if clip: clip.quantize_pitch(note, self._settings.quantize_to, self._settings.quantize_amount) - self.show_notification(MessageBoxText.QUANTIZE_CLIP_PITCH % dict(amount=quantize_amount_to_string(self._settings.quantize_amount), to=self._settings.selected_quantization_name)) + self.show_notification(MessageBoxText.QUANTIZE_CLIP_PITCH % dict(source=source, amount=quantize_amount_to_string(self._settings.quantize_amount), to=self._settings.selected_quantization_name)) self._cancel_quantize = True @action_button.pressed_delayed diff --git a/pushbase/scrollable_list.py b/pushbase/scrollable_list.py index 16f9b9b3..7aeb59a1 100644 --- a/pushbase/scrollable_list.py +++ b/pushbase/scrollable_list.py @@ -1,7 +1,12 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/scrollable_list.py +# uncompyle6 version 2.9.10 +# Python bytecode 2.7 (62211) +# Decompiled from: Python 2.7.13 (default, Dec 17 2016, 23:03:43) +# [GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.42.1)] +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/scrollable_list.py +# Compiled at: 2016-05-20 03:43:52 from __future__ import absolute_import, print_function from functools import partial -from ableton.v2.base import BooleanContext, clamp, forward_property, in_range, index_if, Subject, listens, task +from ableton.v2.base import BooleanContext, EventObject, clamp, forward_property, in_range, index_if, listens, task from ableton.v2.control_surface import CompoundComponent, defaults from ableton.v2.control_surface.control import ButtonControl, EncoderControl, control_list from ableton.v2.control_surface.components import ScrollComponent, Scrollable @@ -12,7 +17,7 @@ class ScrollableListItem(object): Wrapper of an item of a scrollable list. """ - def __init__(self, index = None, content = None, scrollable_list = None, *a, **k): + def __init__(self, index=None, content=None, scrollable_list=None, *a, **k): super(ScrollableListItem, self).__init__(*a, **k) self._content = content self._index = index @@ -41,7 +46,7 @@ def select(self): return self._scrollable_list and self._scrollable_list.select_item(self) -class ScrollableList(Subject, Scrollable): +class ScrollableList(EventObject, Scrollable): """ Class for managing a visual subset of a list of items. @@ -51,7 +56,7 @@ class ScrollableList(Subject, Scrollable): item_type = ScrollableListItem fixed_offset = None - def __init__(self, num_visible_items = 1, item_type = None, *a, **k): + def __init__(self, num_visible_items=1, item_type=None, *a, **k): super(ScrollableList, self).__init__(*a, **k) if item_type != None: self.item_type = item_type @@ -65,6 +70,7 @@ def __init__(self, num_visible_items = 1, item_type = None, *a, **k): self._pager.scroll_down = self.next_page self._pager.can_scroll_up = self.can_scroll_up self._pager.can_scroll_down = self.can_scroll_down + return @property def pager(self): @@ -90,7 +96,7 @@ def _get_num_visible_items(self): return self._num_visible_items def _set_num_visible_items(self, num_items): - raise num_items >= 0 or AssertionError + assert num_items >= 0 self._num_visible_items = num_items self._normalize_offset(self._selected_item_index) @@ -106,8 +112,8 @@ def select_item_index_with_offset(self, index, offset): if possible, 'offset' number of elements visible before the selected one. Does nothing if the item was already selected. """ - if not (index != self.selected_item_index and index >= 0 and index < len(self._items) and self.selected_item_index != -1): - raise AssertionError + if index != self.selected_item_index and index >= 0 and index < len(self._items): + assert self.selected_item_index != -1 self._offset = clamp(index - offset, 0, len(self._items)) self._normalize_offset(index) self._do_set_selected_item_index(index) @@ -125,6 +131,7 @@ def select_item_index_with_border(self, index, border_size): self._offset = clamp(offset, 0, len(self._items)) self._normalize_offset(index) self._do_set_selected_item_index(index) + return def next_page(self): if self.can_scroll_down(): @@ -147,8 +154,8 @@ def prev_page(self): self.select_item_index_with_offset(index, 0) def _set_selected_item_index(self, index): - if not (index >= 0 and index < len(self._items) and self.selected_item_index != -1): - raise AssertionError + if index >= 0 and index < len(self._items): + assert self.selected_item_index != -1 self._normalize_offset(index) self._do_set_selected_item_index(index) @@ -169,6 +176,8 @@ def _normalize_offset(self, index): def selected_item(self): if in_range(self._selected_item_index, 0, len(self._items)): return self._items[self.selected_item_index] + else: + return None @property def items(self): @@ -179,7 +188,8 @@ def assign_items(self, items): for item in self._items: item._scrollable_list = None - self._items = tuple([ self.item_type(index=index, content=item, scrollable_list=self) for index, item in enumerate(items) ]) + self._items = tuple([ self.item_type(index=index, content=item, scrollable_list=self) for index, item in enumerate(items) + ]) if self._items: new_selection = index_if(lambda item: unicode(item) == old_selection, self._items) self._selected_item_index = new_selection if in_range(new_selection, 0, len(self._items)) else 0 @@ -190,6 +200,7 @@ def assign_items(self, items): self._last_activated_item_index = None self.notify_selected_item() self.request_notify_item_activated() + return def select_item(self, item): self.selected_item_index = item.index @@ -254,7 +265,7 @@ class ListComponent(CompoundComponent): action_button = ButtonControl(color='Browser.Load') encoders = control_list(EncoderControl) - def __init__(self, scrollable_list = None, data_sources = tuple(), *a, **k): + def __init__(self, scrollable_list=None, data_sources=tuple(), *a, **k): super(ListComponent, self).__init__(*a, **k) self._data_sources = data_sources self._activation_task = task.Task() @@ -280,6 +291,7 @@ def __init__(self, scrollable_list = None, data_sources = tuple(), *a, **k): self._in_encoder_selection = BooleanContext(False) self._execute_action_task = self._tasks.add(task.sequence(task.delay(1), task.run(self._execute_action))) self._execute_action_task.kill() + return @property def _trigger_action_on_scrolling(self): @@ -302,6 +314,7 @@ def _set_scrollable_list(self, new_list): self._scroller.scrollable = ScrollComponent.default_pager self._on_selected_item_changed.subject = new_list self.update_all() + return scrollable_list = property(_get_scrollable_list, _set_scrollable_list) @@ -359,6 +372,7 @@ def _add_offset_to_selected_index(self, offset): def action_button(self, button): if self._current_action_item == None: self._trigger_action(self.next_item if self._action_target_is_next_item() else self.selected_item) + return def do_trigger_action(self, item): item.action() @@ -371,6 +385,7 @@ def _trigger_action(self, item): self._current_action_item = item self.update() self._execute_action_task.restart() + return def _execute_action(self): """ Is called by the execute action task and should not be called directly @@ -380,11 +395,14 @@ def _execute_action(self): self._last_action_item = self._current_action_item self._current_action_item = None self.update() + return @property def selected_item(self): if self._scrollable_list != None: return self._scrollable_list.selected_item + else: + return @property def next_item(self): @@ -411,6 +429,7 @@ def _update_action_feedback(self): else: color = 'Browser.LoadNotPossible' self.action_button.color = color + return def _update_display(self): visible_items = self._scrollable_list.visible_items if self._scrollable_list else [] @@ -422,6 +441,7 @@ def _update_display(self): if not visible_items and self._data_sources and self.empty_list_message: self._data_sources[0].set_display_string(self.empty_list_message) + return def update(self): super(ListComponent, self).update() diff --git a/pushbase/scrollable_list_component.py b/pushbase/scrollable_list_component.py index e08e1db1..04b3cdd8 100644 --- a/pushbase/scrollable_list_component.py +++ b/pushbase/scrollable_list_component.py @@ -1,10 +1,15 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/scrollable_list_component.py +# uncompyle6 version 2.9.10 +# Python bytecode 2.7 (62211) +# Decompiled from: Python 2.7.13 (default, Dec 17 2016, 23:03:43) +# [GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.42.1)] +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/scrollable_list_component.py +# Compiled at: 2016-06-08 13:13:04 """ Scrollable list component. """ from __future__ import absolute_import, print_function from functools import partial -from ableton.v2.base import in_range, Event +from ableton.v2.base import EventObject, in_range, Event from ableton.v2.base.signal import short_circuit_signal from ableton.v2.control_surface import Component from ableton.v2.control_surface.elements import DisplayDataSource @@ -15,7 +20,9 @@ class ScrollableListComponent(Component): Component for handling a list of options with a limtied set of buttons and display segments. """ - __events__ = (Event(name='change_option', doc=' Event signaled when the selected option changes '), Event(name='press_option', signal=short_circuit_signal, doc='\n Event signaled when an option is pressed getting the option as\n parameter. The ScrollableListComponent is connected to it, if\n you want to override selection behaviour, connect to the front\n and return True from your handler.\n ')) + __events__ = ( + Event(name='change_option', doc=' Event signaled when the selected option changes '), + Event(name='press_option', signal=short_circuit_signal, doc='\n Event signaled when an option is pressed getting the option as\n parameter. The ScrollableListComponent is connected to it, if\n you want to override selection behaviour, connect to the front\n and return True from your handler.\n ')) num_segments = 8 display_line = 3 jump_size = 3 @@ -24,13 +31,15 @@ class ScrollableListComponent(Component): def __init__(self, *a, **k): super(ScrollableListComponent, self).__init__(*a, **k) - self._data_sources = [ DisplayDataSource() for _ in range(self.num_segments) ] + self._data_sources = [ DisplayDataSource() for _ in range(self.num_segments) + ] self._selected_option = None self._offset_index = 0 self._option_names = [] self._select_buttons = [] - self._select_button_slots = self.register_slot_manager() + self._select_button_slots = self.register_disconnectable(EventObject()) self.register_slot(self, self._set_selected_option, 'press_option') + return def set_display_line(self, line): if line: @@ -73,11 +82,12 @@ def _get_selected_option(self): return self._selected_option def _set_selected_option(self, selected_option): - if not (selected_option != self._selected_option and (selected_option == None or in_range(selected_option, 0, self._option_names))): - raise AssertionError + if selected_option != self._selected_option: + assert selected_option == None or in_range(selected_option, 0, self._option_names) self._selected_option = selected_option self.notify_change_option(selected_option) self.update() + return selected_option = property(_get_selected_option, _set_selected_option) @@ -106,15 +116,17 @@ def _scroll(self, delta): def _on_select_value(self, value, sender): if not self.is_enabled() or not value: return - index = list(self._select_buttons).index(sender) - if index == 0 and self._offset_index != 0: - self.scroll_left() - elif index == self.num_segments - 1 and self._offset_index < self._maximal_offset(): - self.scroll_right() - elif self._offset_index == 0: - self.notify_press_option(index if index < len(self._option_names) else None) else: - self.notify_press_option(index + self._offset_index - 1) + index = list(self._select_buttons).index(sender) + if index == 0 and self._offset_index != 0: + self.scroll_left() + elif index == self.num_segments - 1 and self._offset_index < self._maximal_offset(): + self.scroll_right() + elif self._offset_index == 0: + self.notify_press_option(index if index < len(self._option_names) else None) + else: + self.notify_press_option(index + self._offset_index - 1) + return def _get_display_string(self, option_index): if option_index < len(self._option_names): @@ -155,7 +167,7 @@ class ScrollableListWithTogglesComponent(ScrollableListComponent): Scrollable list that has a toggle button associated with every available option. """ - __events__ = ('toggle_option',) + __events__ = ('toggle_option', ) def __init__(self, *a, **k): super(ScrollableListWithTogglesComponent, self).__init__(*a, **k) @@ -174,6 +186,7 @@ def set_state_buttons(self, state_buttons): slot.subject = button self._update_state_buttons() + return def option_state(self, index): return self._option_states[index] @@ -197,6 +210,7 @@ def _on_state_button_value(self, index, value): self.notify_press_option(None) else: self.notify_press_option(None) + return def _set_option_names(self, names): """ overrides """ @@ -211,22 +225,25 @@ def update(self): def _update_state_buttons(self): if not self.is_enabled(): return - buttons = [ slot.subject for slot in self._state_button_slots ] - if buttons[0]: - first_button, max_button = 0, len(buttons) - if self._offset_index > 0: - buttons[0].turn_off() - first_button = 1 - if self._offset_index < self._maximal_offset(): - buttons[-1].turn_off() - max_button -= 1 - for state, button in zip(self._option_states[self._offset_index:], buttons[first_button:max_button]): - if button != None: - if state: - button.turn_on() - else: + else: + buttons = [ slot.subject for slot in self._state_button_slots ] + if buttons[0]: + first_button, max_button = 0, len(buttons) + if self._offset_index > 0: + buttons[0].turn_off() + first_button = 1 + if self._offset_index < self._maximal_offset(): + buttons[-1].turn_off() + max_button -= 1 + for state, button in zip(self._option_states[self._offset_index:], buttons[first_button:max_button]): + if button != None: + if state: + button.turn_on() + else: + button.turn_off() + + for button in buttons[len(self._option_states):]: + if button != None: button.turn_off() - for button in buttons[len(self._option_states):]: - if button != None: - button.turn_off() \ No newline at end of file + return \ No newline at end of file diff --git a/pushbase/select_playing_clip_component.py b/pushbase/select_playing_clip_component.py index 71b8be71..d57d940e 100644 --- a/pushbase/select_playing_clip_component.py +++ b/pushbase/select_playing_clip_component.py @@ -1,28 +1,38 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/select_playing_clip_component.py +# uncompyle6 version 2.9.10 +# Python bytecode 2.7 (62211) +# Decompiled from: Python 2.7.13 (default, Dec 17 2016, 23:03:43) +# [GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.42.1)] +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/select_playing_clip_component.py +# Compiled at: 2016-06-08 13:13:04 """ Component that automatically selects the playing clip in the selected track. """ from __future__ import absolute_import, print_function -from ableton.v2.base import index_if, partial, nop, listens, task +from functools import partial +from ableton.v2.base import index_if, nop, listens, task from ableton.v2.control_surface.control import ButtonControl -from ableton.v2.control_surface.mode import ModesComponent, AddLayerMode +from ableton.v2.control_surface.mode import AddLayerMode from .consts import MessageBoxText -from .message_box_component import Messenger +from .messenger_mode_component import MessengerModesComponent -class SelectPlayingClipComponent(ModesComponent, Messenger): +class SelectPlayingClipComponent(MessengerModesComponent): action_button = ButtonControl(color='DefaultButton.Alert') - def __init__(self, playing_clip_above_layer = None, playing_clip_below_layer = None, *a, **k): + def __init__(self, playing_clip_above_layer=None, playing_clip_below_layer=None, *a, **k): super(SelectPlayingClipComponent, self).__init__(*a, **k) self._update_mode_task = self._tasks.add(task.sequence(task.delay(1), task.run(self._update_mode))) self._update_mode_task.kill() self.add_mode('default', None) - self.add_mode('above', [AddLayerMode(self, playing_clip_above_layer), partial(self._show_notification, MessageBoxText.PLAYING_CLIP_ABOVE_SELECTED_CLIP)]) - self.add_mode('below', [AddLayerMode(self, playing_clip_below_layer), partial(self._show_notification, MessageBoxText.PLAYING_CLIP_BELOW_SELECTED_CLIP)]) + self.add_mode('above', [ + AddLayerMode(self, playing_clip_above_layer)], message=MessageBoxText.PLAYING_CLIP_ABOVE_SELECTED_CLIP) + self.add_mode('below', [ + AddLayerMode(self, playing_clip_below_layer)], message=MessageBoxText.PLAYING_CLIP_BELOW_SELECTED_CLIP) self.selected_mode = 'default' + self.notify_when_enabled = True self._on_detail_clip_changed.subject = self.song.view self._on_playing_slot_index_changed.subject = self.song.view.selected_track self._notification_reference = partial(nop, None) + return @action_button.pressed def action_button(self, button): @@ -47,9 +57,10 @@ def _go_to_playing_clip(self): def _hide_notification(self): if self._notification_reference() is not None: self._notification_reference().hide() + return - def _show_notification(self, display_text): - self._notification_reference = self.show_notification(display_text, blink_text=MessageBoxText.SELECTED_CLIP_BLINK, notification_time=-1) + def show_notification(self, display_text): + self._notification_reference = super(SelectPlayingClipComponent, self).show_notification(display_text, blink_text=MessageBoxText.SELECTED_CLIP_BLINK, notification_time=-1) def _selected_track_clip_is_playing(self): playing_clip_slot = self._playing_clip_slot() @@ -64,6 +75,8 @@ def _playing_clip_slot(self): except RuntimeError: pass + return + def _selected_track_clip_is_above_playing_clip(self): song_view = self.song.view track = song_view.selected_track diff --git a/pushbase/selected_track_parameter_provider.py b/pushbase/selected_track_parameter_provider.py index 4c8de9f7..da353b9b 100644 --- a/pushbase/selected_track_parameter_provider.py +++ b/pushbase/selected_track_parameter_provider.py @@ -1,26 +1,49 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/selected_track_parameter_provider.py +# uncompyle6 version 2.9.10 +# Python bytecode 2.7 (62211) +# Decompiled from: Python 2.7.13 (default, Dec 17 2016, 23:03:43) +# [GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.42.1)] +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/selected_track_parameter_provider.py +# Compiled at: 2016-05-20 03:43:52 from __future__ import absolute_import, print_function -from ableton.v2.base import depends, listens, SlotManager -from .special_chan_strip_component import TRACK_PARAMETER_NAMES -from .parameter_provider import ParameterProvider, generate_info +from ableton.v2.base import depends, listens +from .parameter_provider import ParameterProvider +TRACK_PARAMETER_NAMES = ('Volume', 'Pan', 'Send A', 'Send B', 'Send C', 'Send D', 'Send E', + 'Send F', 'Send G', 'Send H', 'Send I', 'Send J', 'Send K', + 'Send L') -class SelectedTrackParameterProvider(ParameterProvider, SlotManager): +def toggle_arm(track_to_arm, song, exclusive=False): + if track_to_arm.can_be_armed: + track_to_arm.arm = not track_to_arm.arm + if exclusive and (track_to_arm.implicit_arm or track_to_arm.arm): + for track in song.tracks: + if track.can_be_armed and track != track_to_arm: + track.arm = False + + +class SelectedTrackParameterProvider(ParameterProvider): @depends(song=None) - def __init__(self, song = None, *a, **k): + def __init__(self, song=None, *a, **k): super(SelectedTrackParameterProvider, self).__init__(*a, **k) self._track = None self._on_selected_track.subject = song.view self._on_visible_tracks.subject = song self._on_selected_track() + return @property def parameters(self): if self._track: - params = [self._track.mixer_device.volume, self._track.mixer_device.panning] + list(self._track.mixer_device.sends) - return [ generate_info(p, name=n) for n, p in zip(TRACK_PARAMETER_NAMES, params) ] + params = [ + self._track.mixer_device.volume, + self._track.mixer_device.panning] + list(self._track.mixer_device.sends) + return [ self._create_parameter_info(p, n) for n, p in zip(TRACK_PARAMETER_NAMES, params) + ] return [] + def _create_parameter_info(self, parameter, name): + raise NotImplementedError() + @listens('visible_tracks') def _on_visible_tracks(self): self.notify_parameters() diff --git a/pushbase/selection.py b/pushbase/selection.py index 1715625c..ea7b6e4e 100644 --- a/pushbase/selection.py +++ b/pushbase/selection.py @@ -1,4 +1,9 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/selection.py +# uncompyle6 version 2.9.10 +# Python bytecode 2.7 (62211) +# Decompiled from: Python 2.7.13 (default, Dec 17 2016, 23:03:43) +# [GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.42.1)] +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/selection.py +# Compiled at: 2016-05-20 03:43:52 """ Object that encapsulates selection in the Push controller. """ @@ -60,7 +65,7 @@ class PushSelection(Selection): and not otherwise. """ - def __init__(self, application = None, device_component = None, navigation_component = None, *a, **k): + def __init__(self, application=None, device_component=None, navigation_component=None, *a, **k): super(PushSelection, self).__init__(*a, **k) self._device_component = device_component self._navigation_component = navigation_component diff --git a/pushbase/session_recording_component.py b/pushbase/session_recording_component.py index 9487c025..f94f0d45 100644 --- a/pushbase/session_recording_component.py +++ b/pushbase/session_recording_component.py @@ -1,7 +1,13 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/session_recording_component.py +# uncompyle6 version 2.9.10 +# Python bytecode 2.7 (62211) +# Decompiled from: Python 2.7.13 (default, Dec 17 2016, 23:03:43) +# [GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.42.1)] +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/session_recording_component.py +# Compiled at: 2016-11-16 18:13:21 from __future__ import absolute_import, print_function +from itertools import ifilter import Live -from ableton.v2.base import listens +from ableton.v2.base import listens, liveobj_valid, EventObject from ableton.v2.control_surface.control import ButtonControl from ableton.v2.control_surface.components import SessionRecordingComponent from .consts import MessageBoxText @@ -12,36 +18,140 @@ def track_can_overdub(track): return not track.has_audio_input +def track_can_record(track): + return track.can_be_armed and (track.arm or track.implicit_arm) + + +def get_clip_slot_from_index(song, track, clip_slot_index): + clip_slots = track.clip_slots + if clip_slot_index < len(clip_slots): + return clip_slots[clip_slot_index] + else: + return None + + +def have_other_recording_clips(tracks, recording_clip): + for track in ifilter(lambda t: t.can_be_armed and (t.arm or t.implicit_arm), tracks): + index = track.playing_slot_index + slot = track.clip_slots[index] if 0 <= index < len(track.clip_slots) else None + clip = getattr(slot, 'clip', None) + if getattr(clip, 'is_recording', False) and clip is not recording_clip: + return True + + return False + + +class FixedLengthRecording(EventObject): + + def __init__(self, song=None, clip_creator=None, fixed_length_setting=None, *a, **k): + assert song is not None + assert clip_creator is not None + assert fixed_length_setting is not None + super(FixedLengthRecording, self).__init__(*a, **k) + self._song = song + self._clip_creator = clip_creator + self._fixed_length_setting = fixed_length_setting + self._clip_creator.legato_launch = self._fixed_length_setting.legato_launch + self.__on_setting_selected_index_changes.subject = self._fixed_length_setting + self.__on_setting_legato_launch_changes.subject = self._fixed_length_setting + return + + def should_start_fixed_length_recording(self, clip_slot): + return track_can_record(clip_slot.canonical_parent) and not clip_slot.is_recording and not clip_slot.has_clip and self._fixed_length_setting.enabled + + def start_recording_in_slot(self, track, clip_slot_index): + song = self._song + song.overdub = True + if track_can_record(track): + self._record_in_slot(track, track.clip_slots[clip_slot_index]) + if not song.is_playing: + song.is_playing = True + + def _is_infinite_recording(self, clip): + return not clip.is_overdubbing + + def stop_recording(self, clip): + if have_other_recording_clips(self._song.tracks, clip): + clip.fire() + elif self._is_infinite_recording(clip): + self._song.session_record = False + else: + self._song.overdub = False + + def _record_in_slot(self, track, clip_slot): + if self.should_start_fixed_length_recording(clip_slot): + length, quant = self._fixed_length_setting.get_selected_length(self._song) + if track_can_overdub(track): + self._clip_creator.create(clip_slot, length=length, launch_quantization=self._song.clip_trigger_quantization) + else: + clip_slot.fire(record_length=length, launch_quantization=quant) + elif not clip_slot.is_playing or not self._song.is_playing: + if clip_slot.has_clip: + clip_slot.stop() + clip_slot.fire(force_legato=True, launch_quantization=Quantization.q_no_q) + else: + clip_slot.fire() + + @listens('selected_index') + def __on_setting_selected_index_changes(self, _): + length, _ = self._fixed_length_setting.get_selected_length(self._song) + self._clip_creator.fixed_length = length + + @listens('legato_launch') + def __on_setting_legato_launch_changes(self, value): + self._clip_creator.legato_launch = value + + class FixedLengthSessionRecordingComponent(SessionRecordingComponent, Messenger): foot_switch_button = ButtonControl() arrangement_record_button = ButtonControl() - def __init__(self, fixed_length_setting = None, *a, **k): - raise fixed_length_setting is not None or AssertionError + def __init__(self, clip_creator=None, fixed_length_setting=None, *a, **k): + assert clip_creator is not None + assert fixed_length_setting is not None super(FixedLengthSessionRecordingComponent, self).__init__(*a, **k) - self._fixed_length_setting = fixed_length_setting - self.__on_setting_selected_index_changes.subject = self._fixed_length_setting + self._fixed_length_recording = self.register_disconnectable(FixedLengthRecording(song=self.song, clip_creator=clip_creator, fixed_length_setting=fixed_length_setting)) + self.footswitch_toggles_arrangement_recording = False self.__on_record_mode_changed.subject = self.song self.__on_record_mode_changed() + return @foot_switch_button.pressed def foot_switch_button(self, button): - self._trigger_recording() + if self.footswitch_toggles_arrangement_recording: + self._toggle_arrangement_recording() + else: + self._trigger_recording() @arrangement_record_button.pressed - def arrangement_record_button(self, button): + def arrangement_record_button(self, _): + self._on_arrangement_record_button_pressed() + + @arrangement_record_button.released + def arrangement_record_button(self, _): + self._on_arrangement_record_button_released() + + def _on_arrangement_record_button_pressed(self): + self._toggle_arrangement_recording() + + def _on_arrangement_record_button_released(self): + pass + + def _toggle_arrangement_recording(self): self.song.record_mode = not self.song.record_mode - def _trigger_recording(self): - if self.is_enabled(): - if self.song.record_mode: - self.song.record_mode = False - else: - super(FixedLengthSessionRecordingComponent, self)._trigger_recording() + def _clip_slot_index_to_record_into(self): + song = self.song + selected_scene = song.view.selected_scene + return list(song.scenes).index(selected_scene) def _update_record_button(self): if self.is_enabled(): - if self.song.record_mode: + song = self.song + clip_slot = get_clip_slot_from_index(song, song.view.selected_track, self._clip_slot_index_to_record_into()) + if liveobj_valid(clip_slot) and clip_slot.is_triggered and song.overdub and not clip_slot.is_recording: + self._record_button.color = 'Recording.Transition' + elif song.record_mode: self._record_button.color = 'Recording.ArrangementRecordingOn' else: super(FixedLengthSessionRecordingComponent, self)._update_record_button() @@ -52,29 +162,11 @@ def __on_record_mode_changed(self): self._update_record_button() def _start_recording(self): - song = self.song - song.overdub = True - selected_scene = song.view.selected_scene - scene_index = list(song.scenes).index(selected_scene) track = self.song.view.selected_track - if track.can_be_armed and (track.arm or track.implicit_arm): - self._record_in_slot(track, track.clip_slots[scene_index]) - self._ensure_slot_is_visible(track, scene_index) - if not song.is_playing: - song.is_playing = True - - def _record_in_slot(self, track, clip_slot): - if self._fixed_length_setting.enabled and not clip_slot.has_clip: - length, quant = self._fixed_length_setting.get_selected_length(self.song) - if track_can_overdub(track): - self._clip_creator.create(clip_slot, length) - else: - clip_slot.fire(record_length=length, launch_quantization=quant) - elif not clip_slot.is_playing: - if clip_slot.has_clip: - clip_slot.fire(force_legato=True, launch_quantization=Quantization.q_no_q) - else: - clip_slot.fire() + clip_slot_index = self._clip_slot_index_to_record_into() + self._fixed_length_recording.start_recording_in_slot(track, clip_slot_index) + if track_can_record(track): + self._ensure_slot_is_visible(self.song.view.selected_track, clip_slot_index) def _ensure_slot_is_visible(self, track, scene_index): song = self.song @@ -82,10 +174,5 @@ def _ensure_slot_is_visible(self, track, scene_index): song.view.selected_scene = song.scenes[scene_index] self._view_selected_clip_detail() - @listens('selected_index') - def __on_setting_selected_index_changes(self, _): - length, _ = self._fixed_length_setting.get_selected_length(self.song) - self._clip_creator.fixed_length = length - def _handle_limitation_error_on_scene_creation(self): self.expect_dialog(MessageBoxText.SCENE_LIMIT_REACHED) \ No newline at end of file diff --git a/pushbase/setting.py b/pushbase/setting.py index 8f643160..1fe477f3 100644 --- a/pushbase/setting.py +++ b/pushbase/setting.py @@ -1,16 +1,22 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/setting.py +# uncompyle6 version 2.9.10 +# Python bytecode 2.7 (62211) +# Decompiled from: Python 2.7.13 (default, Dec 17 2016, 23:03:43) +# [GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.42.1)] +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/setting.py +# Compiled at: 2016-09-29 19:13:24 from __future__ import absolute_import, print_function from math import fabs -from ableton.v2.base import sign, clamp, Subject, Event +from ableton.v2.base import sign, clamp, EventObject, Event -class Setting(Subject): +class Setting(EventObject): """ Setting interface for writing to the preferences and all information for changing and displaying it. """ - __events__ = (Event(name='value', doc=' Called when the value of the\n setting changes '),) + __events__ = ( + Event(name='value', doc=' Called when the value of the setting changes '),) - def __init__(self, name = '', values = None, default_value = None, preferences = None, *a, **k): + def __init__(self, name='', values=None, default_value=None, preferences=None, *a, **k): super(Setting, self).__init__(*a, **k) self.name = name self.values = values or [] @@ -19,14 +25,15 @@ def __init__(self, name = '', values = None, default_value = None, preferences = default_value = self._preferences[name] self._preferences[name] = None self.value = default_value + return def __str__(self): return self.value_to_string(self.value) def _set_value(self, value): - if not value in self.values: - raise AssertionError - self._preferences[self.name] = self._preferences[self.name] != value and value + assert value in self.values + if self._preferences[self.name] != value: + self._preferences[self.name] = value self.on_value_changed(value) self.notify_value(self.value) @@ -50,7 +57,8 @@ class OnOffSetting(Setting): """ Simple on/off setting represented by a boolean value """ THRESHOLD = 0.01 - def __init__(self, value_labels = ['On', 'Off'], *a, **k): + def __init__(self, value_labels=[ + 'On', 'Off'], *a, **k): super(OnOffSetting, self).__init__(values=[True, False], *a, **k) self._value_labels = value_labels @@ -67,7 +75,7 @@ class EnumerableSetting(Setting): """ Setting to go through a list of values """ STEP_SIZE = 0.1 - def __init__(self, value_formatter = str, *a, **k): + def __init__(self, value_formatter=str, *a, **k): super(EnumerableSetting, self).__init__(*a, **k) self._relative_value = 0.0 self._value_formatter = value_formatter @@ -80,6 +88,8 @@ def change_relative(self, value): relative_position = int(sign(self._relative_value)) self._relative_value -= self.STEP_SIZE return self._jump_relative(relative_position) != None + else: + return None def _jump_relative(self, relative_position): current_position = self.values.index(self.value) @@ -87,6 +97,8 @@ def _jump_relative(self, relative_position): self.value = self.values[new_position] if current_position != new_position: return new_position + else: + return None def on_value_changed(self, value): self._relative_value = 0.0 diff --git a/pushbase/simpler_decoration.py b/pushbase/simpler_decoration.py index c17f6f9f..e9311637 100644 --- a/pushbase/simpler_decoration.py +++ b/pushbase/simpler_decoration.py @@ -1,12 +1,19 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/simpler_decoration.py +# uncompyle6 version 2.9.10 +# Python bytecode 2.7 (62211) +# Decompiled from: Python 2.7.13 (default, Dec 17 2016, 23:03:43) +# [GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.42.1)] +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/simpler_decoration.py +# Compiled at: 2016-09-29 19:13:24 from __future__ import absolute_import, print_function from functools import partial import Live -from ableton.v2.base import clamp, liveobj_valid, listenable_property, listens, SlotManager, Subject +from ableton.v2.base import clamp, liveobj_valid, listenable_property, listens, EventObject from ableton.v2.base.collection import IndexedDict from .decoration import DecoratorFactory, LiveObjectDecorator from .internal_parameter import EnumWrappingParameter, RelativeInternalParameter, to_percentage_display, WrappingParameter -BoolWrappingParameter = partial(WrappingParameter, to_property_value=lambda integer, _simpler: bool(integer), from_property_value=lambda boolean, _simpler: int(boolean), value_items=['off', 'on'], display_value_conversion=lambda val: ('on' if val else 'off')) +BoolWrappingParameter = partial(WrappingParameter, to_property_value=lambda integer, _simpler: bool(integer), from_property_value=lambda boolean, _simpler: int(boolean), value_items=[ + 'off', 'on'], display_value_conversion=lambda val: if val: +'on''off') def from_user_range(minv, maxv): return lambda v, s: (v - minv) / float(maxv - minv) @@ -16,6 +23,11 @@ def to_user_range(minv, maxv): return lambda v, s: clamp(v * (maxv - minv) + minv, minv, maxv) +def to_user_range_quantized(minv, maxv): + user_range_transform = to_user_range(minv, maxv) + return lambda v, s: int(round(user_range_transform(v, s))) + + def from_sample_count(value, sample): return float(value) / sample.length @@ -24,14 +36,21 @@ def to_sample_count(value, sample): return clamp(int(value * sample.length), 0, sample.length - 1) -SimplerWarpModes = IndexedDict(((Live.Clip.WarpMode.beats, 'Beats'), - (Live.Clip.WarpMode.tones, 'Tones'), - (Live.Clip.WarpMode.texture, 'Texture'), - (Live.Clip.WarpMode.repitch, 'Re-Pitch'), - (Live.Clip.WarpMode.complex, 'Complex'), - (Live.Clip.WarpMode.complex_pro, 'Pro'))) +SimplerWarpModes = IndexedDict(( + ( + Live.Clip.WarpMode.beats, 'Beats'), + ( + Live.Clip.WarpMode.tones, 'Tones'), + ( + Live.Clip.WarpMode.texture, 'Texture'), + ( + Live.Clip.WarpMode.repitch, 'Re-Pitch'), + ( + Live.Clip.WarpMode.complex, 'Complex'), + ( + Live.Clip.WarpMode.complex_pro, 'Pro'))) -class SimplerDeviceDecorator(Subject, SlotManager, LiveObjectDecorator): +class SimplerDeviceDecorator(EventObject, LiveObjectDecorator): def __init__(self, *a, **k): super(SimplerDeviceDecorator, self).__init__(*a, **k) @@ -47,24 +66,29 @@ def setup_parameters(self): self.start = WrappingParameter(name='Start', parent=self, property_host=self._live_object.sample, source_property='start_marker', from_property_value=from_sample_count, to_property_value=to_sample_count) self.end = WrappingParameter(name='End', parent=self, property_host=self._live_object.sample, source_property='end_marker', from_property_value=from_sample_count, to_property_value=to_sample_count) self.sensitivity = WrappingParameter(name='Sensitivity', parent=self, property_host=self._live_object.sample, source_property='slicing_sensitivity', display_value_conversion=to_percentage_display) - self.mode = EnumWrappingParameter(name='Mode', parent=self, values_property_host=self, index_property_host=self, values_property='available_playback_modes', index_property='playback_mode') - self.slicing_playback_mode_param = EnumWrappingParameter(name='Playback', parent=self, values_property_host=self, index_property_host=self, values_property='available_slicing_playback_modes', index_property='slicing_playback_mode') + self.mode = EnumWrappingParameter(name='Mode', parent=self, values_host=self, index_property_host=self, values_property='available_playback_modes', index_property='playback_mode') + self.slicing_playback_mode_param = EnumWrappingParameter(name='Playback', parent=self, values_host=self, index_property_host=self, values_property='available_slicing_playback_modes', index_property='slicing_playback_mode') self.pad_slicing_param = BoolWrappingParameter(name='Pad Slicing', parent=self, property_host=self._live_object, source_property='pad_slicing') self.nudge = RelativeInternalParameter(name='Nudge', parent=self) self.multi_sample_mode_param = BoolWrappingParameter(name='Multi Sample', parent=self, property_host=self._live_object, source_property='multi_sample_mode') self.warp = BoolWrappingParameter(name='Warp', parent=self, property_host=self._live_object.sample, source_property='warping') - self.warp_mode_param = EnumWrappingParameter(name='Warp Mode', parent=self, values_property_host=self, index_property_host=self._live_object.sample, values_property='available_warp_modes', index_property='warp_mode', to_index_conversion=lambda i: Live.Clip.WarpMode(SimplerWarpModes.key_by_index(i)), from_index_conversion=lambda i: SimplerWarpModes.index_by_key(i)) - self.voices_param = EnumWrappingParameter(name='Voices', parent=self, values_property_host=self, index_property_host=self, values_property='available_voice_numbers', index_property='voices', to_index_conversion=lambda i: self.available_voice_numbers[i], from_index_conversion=lambda i: self.available_voice_numbers.index(i)) - self.granulation_resolution = EnumWrappingParameter(name='Preserve', parent=self, values_property_host=self, index_property_host=self._live_object.sample, values_property='available_resolutions', index_property='beats_granulation_resolution') - self.transient_loop_mode = EnumWrappingParameter(name='Loop Mode', parent=self, values_property_host=self, index_property_host=self._live_object.sample, values_property='available_transient_loop_modes', index_property='beats_transient_loop_mode') + self.warp_mode_param = EnumWrappingParameter(name='Warp Mode', parent=self, values_host=self, index_property_host=self._live_object.sample, values_property='available_warp_modes', index_property='warp_mode', to_index_conversion=lambda i: Live.Clip.WarpMode(SimplerWarpModes.key_by_index(i)), from_index_conversion=lambda i: SimplerWarpModes.index_by_key(i)) + self.voices_param = EnumWrappingParameter(name='Voices', parent=self, values_host=self, index_property_host=self, values_property='available_voice_numbers', index_property='voices', to_index_conversion=lambda i: self.available_voice_numbers[i], from_index_conversion=lambda i: self.available_voice_numbers.index(i)) + self.granulation_resolution = EnumWrappingParameter(name='Preserve', parent=self, values_host=self, index_property_host=self._live_object.sample, values_property='available_resolutions', index_property='beats_granulation_resolution') + self.transient_loop_mode = EnumWrappingParameter(name='Loop Mode', parent=self, values_host=self, index_property_host=self._live_object.sample, values_property='available_transient_loop_modes', index_property='beats_transient_loop_mode') self.transient_envelope = WrappingParameter(name='Envelope', parent=self, property_host=self._live_object.sample, source_property='beats_transient_envelope', from_property_value=from_user_range(0.0, 100.0), to_property_value=to_user_range(0.0, 100.0)) self.tones_grain_size_param = WrappingParameter(name='Grain Size Tones', parent=self, property_host=self._live_object.sample, source_property='tones_grain_size', from_property_value=from_user_range(12.0, 100.0), to_property_value=to_user_range(12.0, 100.0)) self.texture_grain_size_param = WrappingParameter(name='Grain Size Texture', parent=self, property_host=self._live_object.sample, source_property='texture_grain_size', from_property_value=from_user_range(2.0, 263.0), to_property_value=to_user_range(2.0, 263.0)) self.flux = WrappingParameter(name='Flux', parent=self, property_host=self._live_object.sample, source_property='texture_flux', from_property_value=from_user_range(0.0, 100.0), to_property_value=to_user_range(0.0, 100.0)) self.formants = WrappingParameter(name='Formants', parent=self, property_host=self._live_object.sample, source_property='complex_pro_formants', from_property_value=from_user_range(0.0, 100.0), to_property_value=to_user_range(0.0, 100.0)) self.complex_pro_envelope_param = WrappingParameter(name='Envelope Complex Pro', parent=self, property_host=self._live_object.sample, source_property='complex_pro_envelope', from_property_value=from_user_range(8.0, 256.0), to_property_value=to_user_range(8.0, 256.0)) - self.gain_param = WrappingParameter(name='Gain', parent=self, property_host=self._live_object.sample, source_property='gain', display_value_conversion=lambda _: (self._live_object.sample.gain_display_string() if liveobj_valid(self._live_object) and liveobj_valid(self._live_object.sample) else '')) - self._sample_based_parameters.extend([self.start, + self.gain_param = WrappingParameter(name='Gain', parent=self, property_host=self._live_object.sample, source_property='gain', display_value_conversion=lambda _: if liveobj_valid(self._live_object) and liveobj_valid(self._live_object.sample): +self._live_object.sample.gain_display_string()'') + self.slicing_style_param = EnumWrappingParameter(name='Slice by', parent=self, values_host=self, index_property_host=self._live_object.sample, values_property='available_slice_styles', index_property='slicing_style') + self.slicing_beat_division_param = EnumWrappingParameter(name='Division', parent=self, values_host=self, index_property_host=self._live_object.sample, values_property='available_slicing_beat_divisions', index_property='slicing_beat_division') + self.slicing_region_count_param = WrappingParameter(name='Regions', parent=self, property_host=self._live_object.sample, source_property='slicing_region_count', from_property_value=from_user_range(2, 64), to_property_value=to_user_range_quantized(2, 64)) + self._sample_based_parameters.extend([ + self.start, self.end, self.sensitivity, self.warp, @@ -74,16 +98,20 @@ def setup_parameters(self): self.flux, self.formants, self.complex_pro_envelope_param, - self.gain_param]) - self._additional_parameters.extend([self.mode, + self.gain_param, + self.slicing_region_count_param, + self.warp_mode_param, + self.granulation_resolution, + self.transient_loop_mode, + self.slicing_style_param, + self.slicing_beat_division_param]) + self._additional_parameters.extend([ + self.mode, self.slicing_playback_mode_param, self.pad_slicing_param, self.nudge, self.multi_sample_mode_param, - self.warp_mode_param, - self.voices_param, - self.granulation_resolution, - self.transient_loop_mode]) + self.voices_param]) def _decorated_parameters(self): return tuple(self._sample_based_parameters) + tuple(self._additional_parameters) @@ -94,11 +122,13 @@ def parameters(self): @property def available_playback_modes(self): - return ['Classic', 'One-Shot', 'Slicing'] + return [ + 'Classic', 'One-Shot', 'Slicing'] @property def available_slicing_playback_modes(self): - return ['Mono', 'Poly', 'Thru'] + return [ + 'Mono', 'Poly', 'Thru'] @property def available_voice_numbers(self): @@ -112,6 +142,15 @@ def available_warp_modes(self): def available_resolutions(self): return (u'1 Bar', u'1/2', u'1/4', u'1/8', u'1/16', u'1/32', u'Transients') + @property + def available_slice_styles(self): + return (u'Transient', u'Beat', u'Region', u'Manual') + + @property + def available_slicing_beat_divisions(self): + return (u'1/16', u'1/16T', u'1/8', u'1/8T', u'1/4', u'1/4T', u'1/2', u'1/2T', + u'1 Bar', u'2 Bars', u'4 Bars') + @property def available_transient_loop_modes(self): return ('Off', 'Forward', 'Alternate') @@ -128,15 +167,15 @@ def slices(self): @listens('sample') def __on_sample_changed(self): + self._on_sample_changed() + + def _on_sample_changed(self): self._reconnect_sample_listeners() def _reconnect_sample_listeners(self): for param in self._sample_based_parameters: param.set_property_host(self._live_object.sample) - for param in (self.warp_mode_param, self.granulation_resolution, self.transient_loop_mode): - param.set_index_property_host(self._live_object.sample) - self._reconnect_to_slices() def _reconnect_to_slices(self): @@ -145,6 +184,9 @@ def _reconnect_to_slices(self): @listens('slices') def __on_slices_changed(self): + self._on_slices_changed() + + def _on_slices_changed(self): self.notify_slices() @listens('playback_mode') diff --git a/pushbase/simpler_slice_nudging.py b/pushbase/simpler_slice_nudging.py index 233ba721..42e5a784 100644 --- a/pushbase/simpler_slice_nudging.py +++ b/pushbase/simpler_slice_nudging.py @@ -1,13 +1,18 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/simpler_slice_nudging.py +# uncompyle6 version 2.9.10 +# Python bytecode 2.7 (62211) +# Decompiled from: Python 2.7.13 (default, Dec 17 2016, 23:03:43) +# [GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.42.1)] +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/simpler_slice_nudging.py +# Compiled at: 2016-05-20 03:43:52 from __future__ import absolute_import, print_function from contextlib import contextmanager import Live -from ableton.v2.base import SlotManager, find_if, liveobj_valid, clamp, listens +from ableton.v2.base import EventObject, find_if, liveobj_valid, clamp, listens from .device_chain_utils import is_simpler CENTERED_NUDGE_VALUE = 0.5 MINIMUM_SLICE_DISTANCE = 2 -class SimplerSliceNudging(SlotManager): +class SimplerSliceNudging(EventObject): _simpler = None _nudge_parameter = None @@ -16,6 +21,7 @@ def set_device(self, device): self.__on_selected_slice_changed.subject = self._simpler with self._updating_nudge_parameter(): self._nudge_parameter = find_if(lambda p: p.name == 'Nudge', self._simpler.parameters if liveobj_valid(self._simpler) else []) + return @contextmanager def _updating_nudge_parameter(self): @@ -25,6 +31,7 @@ def _updating_nudge_parameter(self): if self._nudge_parameter: self._nudge_parameter.set_display_value_conversion(self._display_value_conversion) self.__on_nudge_delta.subject = self._nudge_parameter + return def _can_access_slicing_properties(self): return liveobj_valid(self._simpler) and liveobj_valid(self._simpler.sample) and self._simpler.current_playback_mode == Live.SimplerDevice.PlaybackMode.slicing diff --git a/pushbase/skin_default.py b/pushbase/skin_default.py index 317a32b1..9676fe3c 100644 --- a/pushbase/skin_default.py +++ b/pushbase/skin_default.py @@ -1,4 +1,9 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/skin_default.py +# uncompyle6 version 2.9.10 +# Python bytecode 2.7 (62211) +# Decompiled from: Python 2.7.13 (default, Dec 17 2016, 23:03:43) +# [GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.42.1)] +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/skin_default.py +# Compiled at: 2016-11-16 18:13:21 from __future__ import absolute_import, print_function from ableton.v2.control_surface import Skin from .colors import Basic, Rgb, Pulse, Blink, BiLed @@ -21,6 +26,7 @@ class DefaultButton: Off = Basic.HALF Disabled = Basic.OFF Alert = Basic.FULL_BLINK_SLOW + Transparent = Basic.TRANSPARENT class DefaultMatrix: On = Rgb.WHITE @@ -97,6 +103,7 @@ class SlicedSimpler: SliceSelected = Rgb.OCEAN SliceUnselected = Rgb.YELLOW NoSlice = Rgb.YELLOW.shade(2) + NextSlice = Pulse(Rgb.YELLOW.shade(2), Rgb.OCEAN.highlight(), 48) class LoopSelector: Playhead = Rgb.GREEN @@ -106,6 +113,12 @@ class LoopSelector: InsideLoop = Rgb.WHITE OutsideLoop = Rgb.BLACK + class VelocityLevels: + LowLevel = Rgb.DARK_GREY + MidLevel = Rgb.GREY + HighLevel = Rgb.WHITE + SelectedLevel = Rgb.OCEAN + class NoteEditor: class Step: @@ -170,6 +183,8 @@ class Metronome: class FixedLength: On = Basic.FULL Off = Basic.HALF + PhraseAlignedOn = BiLed.AMBER + PhraseAlignedOff = BiLed.YELLOW_HALF class Accent: On = Basic.FULL diff --git a/pushbase/sliced_simpler_component.py b/pushbase/sliced_simpler_component.py index 6c338c4d..ca8d7a47 100644 --- a/pushbase/sliced_simpler_component.py +++ b/pushbase/sliced_simpler_component.py @@ -1,33 +1,51 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/sliced_simpler_component.py +# uncompyle6 version 2.9.10 +# Python bytecode 2.7 (62211) +# Decompiled from: Python 2.7.13 (default, Dec 17 2016, 23:03:43) +# [GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.42.1)] +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/sliced_simpler_component.py +# Compiled at: 2016-09-29 19:13:24 from __future__ import absolute_import, print_function +import Live from ableton.v2.control_surface.components import PlayableComponent from ableton.v2.control_surface.components import Slideable, SlideComponent from ableton.v2.control_surface.control import ButtonControl -from ableton.v2.base import liveobj_valid, listens, listenable_property +from ableton.v2.base import liveobj_valid, listens, listenable_property, NamedTuple, task from .slideable_touch_strip_component import SlideableTouchStripComponent from .matrix_maps import PAD_FEEDBACK_CHANNEL +from .message_box_component import Messenger +from .consts import MessageBoxText, DISTANT_FUTURE BASE_SLICING_NOTE = 36 -class SlicedSimplerComponent(PlayableComponent, SlideableTouchStripComponent, SlideComponent, Slideable): +class NullQuantizer(object): + + def quantize_pitch(self, _pitch, _source=None): + pass + + +class SlicedSimplerComponent(PlayableComponent, SlideableTouchStripComponent, SlideComponent, Slideable, Messenger): delete_button = ButtonControl() + quantize_button = ButtonControl() position_count = 16 page_length = 4 page_offset = 1 - def __init__(self, *a, **k): + def __init__(self, quantizer=None, *a, **k): self._position = 0 super(SlicedSimplerComponent, self).__init__(touch_slideable=self, dragging_enabled=True, *a, **k) self._simpler = None + self._quantizer = quantizer or NullQuantizer() + return def _get_position(self): return self._position def _set_position(self, index): - raise 0 <= index <= 12 or AssertionError + assert 0 <= index <= 12 self._position = index self.notify_position() self._update_led_feedback() self._update_note_translations() + self.notify_selected_target_note() position = property(_get_position, _set_position) @@ -36,8 +54,21 @@ def set_simpler(self, simpler): self.__on_selected_slice_changed.subject = simpler self.__on_file_changed.subject = simpler self.__on_slices_changed.subject = simpler.sample if liveobj_valid(simpler) else None + self.__on_pad_slicing_changed.subject = simpler + self.__on_slicing_style_changed.subject = simpler.sample if liveobj_valid(simpler) else None + self.__on_track_color_changed.subject = self.song.view.selected_track if simpler else None self._update_led_feedback() self.update() + self.notify_selected_target_note() + return + + def set_matrix(self, matrix): + super(SlicedSimplerComponent, self).set_matrix(matrix) + self.notify_selected_target_note() + + @listens('color_index') + def __on_track_color_changed(self): + self._update_led_feedback() @listens('slices') def __on_slices_changed(self): @@ -47,6 +78,19 @@ def __on_slices_changed(self): def __on_selected_slice_changed(self): self._update_led_feedback() self.notify_selected_note() + self.notify_selected_target_note() + + @listens('pad_slicing') + def __on_pad_slicing_changed(self): + self._update_led_feedback() + + @listens('slicing_style') + def __on_slicing_style_changed(self): + + def set_pad_slicing(): + self._simpler.pad_slicing = self._simpler.sample.slicing_style == Live.Sample.SlicingStyle.manual + + self._tasks.add(task.sequence(task.delay(1), task.run(set_pad_slicing))) def _slices(self): if liveobj_valid(self._simpler) and liveobj_valid(self._simpler.sample): @@ -60,8 +104,16 @@ def selected_note(self): index = slices.index(selected_slice) if selected_slice in slices else 0 return BASE_SLICING_NOTE + index + @listenable_property + def selected_target_note(self): + slices = list(self._slices()) + selected_slice = self._selected_slice() + if selected_slice in slices: + return NamedTuple(note=BASE_SLICING_NOTE + slices.index(selected_slice), channel=PAD_FEEDBACK_CHANNEL) + return NamedTuple(note=-1, channel=-1) + def _selected_slice(self): - if liveobj_valid(self._simpler): + if liveobj_valid(self._simpler) and liveobj_valid(self._simpler.sample): return self._simpler.view.selected_slice return -1 @@ -69,6 +121,8 @@ def _selected_slice(self): def __on_file_changed(self): self.__on_slices_changed.subject = self._simpler.sample if liveobj_valid(self._simpler) else None self._update_led_feedback() + self.notify_selected_target_note() + return def _button_coordinate_to_slice_index(self, button): y, x = button.coordinate @@ -80,24 +134,44 @@ def _button_coordinate_to_slice_index(self, button): def _update_button_color(self, button): index = self._button_coordinate_to_slice_index(button) slices = self._slices() - if index < len(slices): + length_of_slices = len(slices) + if index < length_of_slices: button.color = 'SlicedSimpler.SliceSelected' if slices[index] == self._selected_slice() else 'SlicedSimpler.SliceUnselected' + elif self._should_show_next_slice(index, length_of_slices): + button.color = self._next_slice_color() else: button.color = 'SlicedSimpler.NoSlice' def _note_translation_for_button(self, button): identifier = BASE_SLICING_NOTE + self._button_coordinate_to_slice_index(button) - return (identifier, PAD_FEEDBACK_CHANNEL) + return ( + identifier, PAD_FEEDBACK_CHANNEL) + + def _next_slice_color(self): + return 'SlicedSimpler.NextSlice' + + def _should_show_next_slice(self, index, length_of_slices): + return index == length_of_slices and liveobj_valid(self._simpler) and self._simpler.pad_slicing and liveobj_valid(self._simpler.sample) and self._simpler.sample.slicing_style == Live.Sample.SlicingStyle.manual @delete_button.value def delete_button(self, value, button): self._set_control_pads_from_script(bool(value)) + def _try_delete_notes_for_slice(self, index): + clip = self.song.view.detail_clip + pitch = BASE_SLICING_NOTE + index + has_notes = liveobj_valid(clip) and not clip.is_audio_clip and len(clip.get_notes(0, pitch, DISTANT_FUTURE, 1)) > 0 + if has_notes: + clip.remove_notes(0, pitch, DISTANT_FUTURE, 1) + slice_label = 'Slice %d' % (index + 1) + self.show_notification(MessageBoxText.DELETE_NOTES % slice_label) + return has_notes + def _try_delete_slice_at_index(self, index): - if liveobj_valid(self._simpler) and liveobj_valid(self._simpler.sample): - slices = self._slices() - if len(slices) > index: - self._simpler.sample.remove_slice(slices[index]) + slices = self._slices() + if len(slices) > index: + self._simpler.sample.remove_slice(slices[index]) + self.show_notification(MessageBoxText.DELETE_SLICE % str(index + 1)) def set_select_button(self, button): self.select_button.set_control_element(button) @@ -107,10 +181,21 @@ def _try_select_slice_at_index(self, index): if len(slices) > index: self._simpler.view.selected_slice = slices[index] + @quantize_button.value + def quantize_button(self, value, button): + self._set_control_pads_from_script(bool(value)) + + def _try_quantize_notes_for_slice(self, index): + self._quantizer.quantize_pitch(BASE_SLICING_NOTE + index, 'slice') + def _on_matrix_pressed(self, button): - slice_index = self._button_coordinate_to_slice_index(button) - if self.delete_button.is_pressed: - self._try_delete_slice_at_index(slice_index) - elif self.select_button.is_pressed: - self._try_select_slice_at_index(slice_index) + if liveobj_valid(self._simpler) and liveobj_valid(self._simpler.sample): + slice_index = self._button_coordinate_to_slice_index(button) + if self.delete_button.is_pressed: + if not self._try_delete_notes_for_slice(slice_index): + self._try_delete_slice_at_index(slice_index) + elif self.quantize_button.is_pressed: + self._try_quantize_notes_for_slice(slice_index) + elif self.select_button.is_pressed: + self._try_select_slice_at_index(slice_index) super(SlicedSimplerComponent, self)._on_matrix_pressed(button) \ No newline at end of file diff --git a/pushbase/slideable_touch_strip_component.py b/pushbase/slideable_touch_strip_component.py index a3bbfad0..e6357f0b 100644 --- a/pushbase/slideable_touch_strip_component.py +++ b/pushbase/slideable_touch_strip_component.py @@ -1,4 +1,9 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/slideable_touch_strip_component.py +# uncompyle6 version 2.9.10 +# Python bytecode 2.7 (62211) +# Decompiled from: Python 2.7.13 (default, Dec 17 2016, 23:03:43) +# [GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.42.1)] +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/slideable_touch_strip_component.py +# Compiled at: 2016-05-20 03:43:52 """ Component that navigates a series of pages. """ @@ -11,7 +16,7 @@ class SlideableTouchStripComponent(Component): - def __init__(self, touch_slideable = None, dragging_enabled = False, *a, **k): + def __init__(self, touch_slideable=None, dragging_enabled=False, *a, **k): super(SlideableTouchStripComponent, self).__init__(*a, **k) self._behaviour = DraggingBehaviour() if dragging_enabled else SelectingBehaviour() self._touch_strip_array = [] @@ -72,9 +77,11 @@ def _update_touch_strip_state(self, strip): strip_pos = self._scroll_to_touch_strip_position(model_pos) array = list(self._touch_strip_array) led_page_length = self._touch_strip_led_page_length(strip.state_count) - array[led_pos:led_pos + led_page_length] = [TouchStripStates.STATE_FULL] * led_page_length + array[led_pos:led_pos + led_page_length] = [ + TouchStripStates.STATE_FULL] * led_page_length led_size = MAX_PITCHBEND / strip.state_count - self._behaviour.handle = TouchStripHandle(range=(-led_size, led_size * led_page_length), position=strip_pos) + self._behaviour.handle = TouchStripHandle(range=( + -led_size, led_size * led_page_length), position=strip_pos) strip.send_state(array[:strip.state_count]) def _update_touch_strip_array(self, num_leds): @@ -86,7 +93,8 @@ def led_contents(i): pmax = pmin + float(model.position_count) / num_leds return any(imap(model.contents, model.contents_range(pmin, pmax))) - array = [ (TouchStripStates.STATE_HALF if led_contents(i) else TouchStripStates.STATE_OFF) for i in xrange(num_leds) ] + array = [ (TouchStripStates.STATE_HALF if led_contents(i) else TouchStripStates.STATE_OFF) for i in xrange(num_leds) + ] self._touch_strip_array = array @listens('value') diff --git a/pushbase/song_utils.py b/pushbase/song_utils.py new file mode 100644 index 00000000..e2537d96 --- /dev/null +++ b/pushbase/song_utils.py @@ -0,0 +1,20 @@ +# uncompyle6 version 2.9.10 +# Python bytecode 2.7 (62211) +# Decompiled from: Python 2.7.13 (default, Dec 17 2016, 23:03:43) +# [GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.42.1)] +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/song_utils.py +# Compiled at: 2016-05-20 03:43:52 +from __future__ import absolute_import, print_function + +def is_return_track(song, track): + return track in list(song.return_tracks) + + +def delete_track_or_return_track(song, track): + tracks = list(song.tracks) + if track in tracks: + track_index = tracks.index(track) + song.delete_track(track_index) + else: + track_index = list(song.return_tracks).index(track) + song.delete_return_track(track_index) \ No newline at end of file diff --git a/pushbase/special_chan_strip_component.py b/pushbase/special_chan_strip_component.py deleted file mode 100644 index 1650e9ec..00000000 --- a/pushbase/special_chan_strip_component.py +++ /dev/null @@ -1,275 +0,0 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/special_chan_strip_component.py -from __future__ import absolute_import, print_function -import Live -from ableton.v2.base import flatten, listens, listens_group, liveobj_valid, task -from ableton.v2.control_surface import components, ParameterSlot -from ableton.v2.control_surface.elements import DisplayDataSource -from . import consts -from .consts import MessageBoxText -from .message_box_component import Messenger -TRACK_FOLD_DELAY = 0.5 -TRACK_PARAMETER_NAMES = ('Volume', 'Pan', 'Send A', 'Send B', 'Send C', 'Send D', 'Send E', 'Send F', 'Send G', 'Send H', 'Send I', 'Send J', 'Send K', 'Send L') - -def param_value_to_graphic(param, graphic): - if param != None: - param_range = param.max - param.min - graph_range = len(graphic) - 1 - value = int((param.value - param.min) / param_range * graph_range) - graphic_display_string = graphic[value] - else: - graphic_display_string = ' ' - return graphic_display_string - - -def toggle_arm(track_to_arm, song, exclusive = False): - if track_to_arm.can_be_armed: - track_to_arm.arm = not track_to_arm.arm - if exclusive and (track_to_arm.implicit_arm or track_to_arm.arm): - for track in song.tracks: - if track.can_be_armed and track != track_to_arm: - track.arm = False - - -class SpecialChanStripComponent(components.ChannelStripComponent, Messenger): - """ - Channel strip component with press & hold mute solo and stop - buttons - """ - - def __init__(self, *a, **k): - super(SpecialChanStripComponent, self).__init__(*a, **k) - self.empty_color = 'Option.Unused' - self._invert_mute_feedback = True - self._duplicate_button = None - self._selector_button = None - self._delete_handler = None - self._track_parameter_name_sources = [ DisplayDataSource(' ') for _ in xrange(14) ] - self._track_parameter_data_sources = [ DisplayDataSource(' ') for _ in xrange(14) ] - self._track_parameter_graphic_sources = [ DisplayDataSource(' ') for _ in xrange(14) ] - self._on_return_tracks_changed.subject = self.song - self._on_selected_track_changed.subject = self.song.view - self._fold_task = self._tasks.add(task.sequence(task.wait(TRACK_FOLD_DELAY), task.run(self._do_fold_track))).kill() - self._cue_volume_slot = self.register_disconnectable(ParameterSlot()) - - def set_delete_handler(self, delete_handler): - self._delete_handler = delete_handler - - def _update_control_sensitivities(self, control): - if control: - if hasattr(control, 'set_sensitivities'): - control.set_sensitivities(consts.CONTINUOUS_MAPPING_SENSITIVITY, consts.FINE_GRAINED_CONTINUOUS_MAPPING_SENSITIVITY) - else: - control.mapping_sensitivity = consts.CONTINUOUS_MAPPING_SENSITIVITY - - def set_volume_control(self, control): - self._update_control_sensitivities(control) - super(SpecialChanStripComponent, self).set_volume_control(control) - - def set_pan_control(self, control): - self._update_control_sensitivities(control) - super(SpecialChanStripComponent, self).set_pan_control(control) - - def set_send_controls(self, controls): - if controls != None: - for control in controls: - self._update_control_sensitivities(control) - - super(SpecialChanStripComponent, self).set_send_controls(controls) - - def set_cue_volume_control(self, control): - self._update_control_sensitivities(control) - self._cue_volume_slot.control = control - - def set_duplicate_button(self, duplicate_button): - self._duplicate_button = duplicate_button - - def set_selector_button(self, selector_button): - self._selector_button = selector_button - - def track_parameter_data_sources(self, index): - return self._track_parameter_data_sources[index] - - def track_parameter_graphic_sources(self, index): - return self._track_parameter_graphic_sources[index] - - def track_parameter_name_sources(self, index): - return self._track_parameter_name_sources[index] - - def set_track(self, track): - super(SpecialChanStripComponent, self).set_track(track) - self._update_track_listeners() - self._update_parameter_name_sources() - self._update_parameter_values() - arm_subject = track if track and track.can_be_armed else None - self._on_explicit_arm_changed.subject = arm_subject - self._on_implicit_arm_changed.subject = arm_subject - - @listens('arm') - def _on_explicit_arm_changed(self): - if self.is_enabled() and self._track: - self._update_track_button() - - @listens('implicit_arm') - def _on_implicit_arm_changed(self): - if self.is_enabled() and self._track: - self._update_track_button() - - @listens('return_tracks') - def _on_return_tracks_changed(self): - self._update_track_listeners() - self._update_parameter_name_sources() - self._update_parameter_values() - - @listens('selected_track') - def _on_selected_track_changed(self): - self._update_track_listeners() - self._update_track_name_data_source() - self._update_track_button() - - def _update_track_button(self): - if self.is_enabled(): - if self._track == None: - self.select_button.color = self.empty_color - elif self._track.can_be_armed and (self._track.arm or self._track.implicit_arm): - if self._track == self.song.view.selected_track: - self.select_button.color = 'Mixer.ArmSelected' - else: - self.select_button.color = 'Mixer.ArmUnselected' - elif self._track == self.song.view.selected_track: - self.select_button.color = 'Option.Selected' - else: - self.select_button.color = 'Option.Unselected' - - def _update_track_listeners(self): - mixer = self._track.mixer_device if self._track else None - sends = mixer.sends if mixer and self._track != self.song.master_track else () - cue_volume = mixer.cue_volume if self._track == self.song.master_track else None - self._cue_volume_slot.parameter = cue_volume - self._on_volume_value_changed.subject = mixer and mixer.volume - self._on_panning_value_changed.subject = mixer and mixer.panning - self._on_sends_value_changed.replace_subjects(sends) - - def _update_parameter_name_sources(self): - num_params = self._track and len(self._track.mixer_device.sends) + 2 - for index, source in enumerate(self._track_parameter_name_sources): - if index < num_params: - source.set_display_string(TRACK_PARAMETER_NAMES[index]) - else: - source.set_display_string(' ') - - def _update_track_name_data_source(self): - if self._track_name_data_source: - if liveobj_valid(self._track): - selected = self._track == self.song.view.selected_track - prefix = consts.CHAR_SELECT if selected else '' - self._track_name_data_source.set_display_string(prefix + self._track.name) - else: - self._track_name_data_source.set_display_string(' ') - - @property - def _is_deleting(self): - return self._delete_handler and self._delete_handler.is_deleting - - def _on_select_button_pressed(self, button): - if self.is_enabled() and self._track: - if self._duplicate_button and self._duplicate_button.is_pressed(): - self._do_duplicate_track(self._track) - elif self._is_deleting: - self._do_delete_track(self._track) - elif self._shift_pressed: - toggle_arm(self._track, self.song, exclusive=False) - else: - self._select_value_without_modifier(button) - - def _mute_value(self, value): - if self.is_enabled() and liveobj_valid(self._track): - if not self._mute_button.is_momentary() or value != 0: - if self._is_deleting: - self._delete_handler.delete_clip_envelope(self._track.mixer_device.track_activator) - else: - super(SpecialChanStripComponent, self)._mute_value(value) - - def _select_value_without_modifier(self, button): - if self.song.view.selected_track == self._track: - song = self.song - toggle_arm(self._track, song, exclusive=song.exclusive_arm) - else: - super(SpecialChanStripComponent, self)._on_select_button_pressed(button) - if self._track.is_foldable: - self._fold_task.restart() - else: - self._fold_task.kill() - - def _on_select_button_released(self, button): - self._fold_task.kill() - - def _do_delete_track(self, track): - try: - track_index = list(self.song.tracks).index(track) - name = track.name - self.song.delete_track(track_index) - self.show_notification(MessageBoxText.DELETE_TRACK % name) - except RuntimeError: - self.expect_dialog(MessageBoxText.TRACK_DELETE_FAILED) - except ValueError: - pass - - def _do_duplicate_track(self, track): - try: - track_index = list(self.song.tracks).index(track) - self.song.duplicate_track(track_index) - self.show_notification(MessageBoxText.DUPLICATE_TRACK % track.name) - except Live.Base.LimitationError: - self.expect_dialog(MessageBoxText.TRACK_LIMIT_REACHED) - except RuntimeError: - self.expect_dialog(MessageBoxText.TRACK_DUPLICATION_FAILED) - except ValueError: - pass - - def _do_select_track(self, track): - pass - - def _do_fold_track(self): - if self.is_enabled() and liveobj_valid(self._track) and self._track.is_foldable: - self._track.fold_state = not self._track.fold_state - - @listens('value') - def _on_volume_value_changed(self): - if self.is_enabled() and liveobj_valid(self._track): - param = self._track.mixer_device.volume - text = self._track_parameter_data_sources[0] - graph = self._track_parameter_graphic_sources[0] - text.set_display_string(str(param)) - graph.set_display_string(param_value_to_graphic(param, consts.GRAPH_VOL)) - - @listens('value') - def _on_panning_value_changed(self): - if self.is_enabled() and liveobj_valid(self._track): - param = self._track.mixer_device.panning - text = self._track_parameter_data_sources[1] - graph = self._track_parameter_graphic_sources[1] - text.set_display_string(str(param)) - graph.set_display_string(param_value_to_graphic(param, consts.GRAPH_PAN)) - - @listens_group('value') - def _on_sends_value_changed(self, send): - if self.is_enabled() and liveobj_valid(self._track) and self._track != self.song.master_track and send in list(self._track.mixer_device.sends): - index = list(self._track.mixer_device.sends).index(send) + 2 - text = self._track_parameter_data_sources[index] - graph = self._track_parameter_graphic_sources[index] - text.set_display_string(str(send)) - graph.set_display_string(param_value_to_graphic(send, consts.GRAPH_VOL)) - - def _update_parameter_values(self): - for source in flatten(zip(self._track_parameter_data_sources, self._track_parameter_graphic_sources)): - source.set_display_string(' ') - - self._on_volume_value_changed() - self._on_panning_value_changed() - if self._track and self._track != self.song.master_track: - map(self._on_sends_value_changed, self._track.mixer_device.sends) - - def update(self): - super(SpecialChanStripComponent, self).update() - if self.is_enabled(): - self._update_track_button() \ No newline at end of file diff --git a/pushbase/special_mixer_component.py b/pushbase/special_mixer_component.py deleted file mode 100644 index 29fcdfd9..00000000 --- a/pushbase/special_mixer_component.py +++ /dev/null @@ -1,160 +0,0 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/special_mixer_component.py -from __future__ import absolute_import, print_function -from itertools import izip_longest -from ableton.v2.base import listens -from ableton.v2.control_surface import components -from ableton.v2.control_surface.elements import DisplayDataSource -from .special_chan_strip_component import SpecialChanStripComponent - -class SpecialMixerComponent(components.MixerComponent): - """ - Special mixer class that uses return tracks alongside midi and - audio tracks. This provides also a more convenient interface to - set controls for the different modes of Push. - """ - num_label_segments = 4 - - def __init__(self, *a, **k): - super(SpecialMixerComponent, self).__init__(*a, **k) - self._pan_send_index = 0 - self._pan_send_controls = None - self._pan_send_names_display = None - self._pan_send_values_display = None - self._pan_send_graphics_display = None - self._pan_send_toggle_skip = False - self._selected_track_data_sources = map(DisplayDataSource, ('',) * self.num_label_segments) - self._selected_track_data_sources[0].set_display_string('Track Selection:') - self._selected_track_name_data_source = self._selected_track_data_sources[1] - self._on_selected_track_changed.subject = self.song.view - self._on_track_list_changed.subject = self.song - self._update_selected_track_name() - - def _create_strip(self): - return SpecialChanStripComponent() - - def set_pan_send_toggle(self, toggle): - """ - The pan_send_toggle cycles through the different pan, or send - modes changing the bejhaviour of the pan_send display and - controls. - """ - self._pan_send_toggle = toggle - self._on_pan_send_value.subject = toggle - self._pan_send_toggle_skip = True - - def set_selected_track_name_display(self, display): - if display: - display.set_data_sources(self._selected_track_data_sources) - - def set_track_select_buttons(self, buttons): - for strip, button in izip_longest(self._channel_strips, buttons or []): - if button: - button.set_on_off_values('Option.Selected', 'Option.Unselected') - strip.set_select_button(button) - - def set_solo_buttons(self, buttons): - for strip, button in izip_longest(self._channel_strips, buttons or []): - if button: - button.set_on_off_values('Mixer.SoloOn', 'Mixer.SoloOff') - strip.set_solo_button(button) - - def set_mute_buttons(self, buttons): - for strip, button in izip_longest(self._channel_strips, buttons or []): - if button: - button.set_on_off_values('Mixer.MuteOff', 'Mixer.MuteOn') - strip.set_mute_button(button) - - def set_track_names_display(self, display): - if display: - sources = [ strip.track_name_data_source() for strip in self._channel_strips ] - display.set_data_sources(sources) - - def set_volume_names_display(self, display): - self._set_parameter_names_display(display, 0) - - def set_volume_values_display(self, display): - self._set_parameter_values_display(display, 0) - - def set_volume_graphics_display(self, display): - self._set_parameter_graphics_display(display, 0) - - def set_volume_controls(self, controls): - for strip, control in izip_longest(self._channel_strips, controls or []): - strip.set_volume_control(control) - - def set_pan_send_names_display(self, display): - self._normalize_pan_send_index() - self._pan_send_names_display = display - self._set_parameter_names_display(display, self._pan_send_index + 1) - - def set_pan_send_values_display(self, display): - self._normalize_pan_send_index() - self._pan_send_values_display = display - self._set_parameter_values_display(display, self._pan_send_index + 1) - - def set_pan_send_graphics_display(self, display): - self._normalize_pan_send_index() - self._pan_send_graphics_display = display - self._set_parameter_graphics_display(display, self._pan_send_index + 1) - - def set_pan_send_controls(self, controls): - self.set_send_controls(None) - self.set_pan_controls(None) - self._pan_send_controls = controls - self._normalize_pan_send_index() - if self._pan_send_index == 0: - self.set_pan_controls(controls) - else: - sends = self._pan_send_index - 1 - self.set_send_controls(map(lambda ctl: (None,) * sends + (ctl,), controls or [])) - - @listens('visible_tracks') - def _on_track_list_changed(self): - self._update_pan_sends() - - def set_pan_controls(self, controls): - for strip, control in izip_longest(self._channel_strips, controls or []): - strip.set_pan_control(control) - - def set_send_controls(self, controls): - for strip, control in izip_longest(self._channel_strips, controls or []): - strip.set_send_controls(control) - - def _set_parameter_names_display(self, display, parameter): - if display: - sources = [ strip.track_parameter_name_sources(parameter) for strip in self._channel_strips ] - display.set_data_sources(sources) - - def _set_parameter_values_display(self, display, parameter): - if display: - sources = [ strip.track_parameter_data_sources(parameter) for strip in self._channel_strips ] - display.set_data_sources(sources) - - def _set_parameter_graphics_display(self, display, parameter): - if display: - sources = [ strip.track_parameter_graphic_sources(parameter) for strip in self._channel_strips ] - display.set_data_sources(sources) - - @listens('value') - def _on_pan_send_value(self, value): - if not self._pan_send_toggle_skip and self.is_enabled() and (value or not self._pan_send_toggle.is_momentary()): - self._pan_send_index += 1 - self._update_pan_sends() - self._pan_send_toggle_skip = False - - def _update_pan_sends(self): - self.set_pan_send_controls(self._pan_send_controls) - self.set_pan_send_names_display(self._pan_send_names_display) - self.set_pan_send_graphics_display(self._pan_send_graphics_display) - - def _normalize_pan_send_index(self): - if len(self.song.tracks) == 0 or self._pan_send_index > len(self.song.tracks[0].mixer_device.sends): - self._pan_send_index = 0 - - @listens('selected_track.name') - def _on_selected_track_changed(self): - self._update_selected_track_name() - - def _update_selected_track_name(self): - selected = self.song.view.selected_track - self._selected_track_name_data_source.set_display_string(selected.name) \ No newline at end of file diff --git a/pushbase/special_session_component.py b/pushbase/special_session_component.py index 1f568f98..28817de0 100644 --- a/pushbase/special_session_component.py +++ b/pushbase/special_session_component.py @@ -1,22 +1,106 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/special_session_component.py +# uncompyle6 version 2.9.10 +# Python bytecode 2.7 (62211) +# Decompiled from: Python 2.7.13 (default, Dec 17 2016, 23:03:43) +# [GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.42.1)] +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/special_session_component.py +# Compiled at: 2016-06-08 13:13:04 from __future__ import absolute_import, print_function import Live -from ableton.v2.base import forward_property, listens, liveobj_valid +from ableton.v2.base import const, depends, forward_property, inject, listens, liveobj_valid from ableton.v2.control_surface import Component from ableton.v2.control_surface.components import ClipSlotComponent, SceneComponent, SessionComponent from ableton.v2.control_surface.control import ButtonControl from ableton.v2.control_surface.mode import EnablingModesComponent from pushbase.touch_strip_element import TouchStripStates, TouchStripModes +from .actions import clip_name_from_clip_slot from .message_box_component import Messenger from .consts import MessageBoxText +class ClipSlotCopyHandler(Messenger): + + def __init__(self, *a, **k): + super(ClipSlotCopyHandler, self).__init__(*a, **k) + self._is_copying = False + self._source_clip_slot = None + self._last_shown_notification_ref = const(None) + return + + @property + def is_copying(self): + return self._is_copying + + def duplicate(self, clip_slot): + if self._is_copying: + self._finish_copying(clip_slot) + else: + self._start_copying(clip_slot) + + def stop_copying(self): + self._reset_copying_state() + notification_ref = self._last_shown_notification_ref() + if notification_ref is not None: + notification_ref.hide() + return + + def _show_notification(self, notification): + self._last_shown_notification_ref = self.show_notification(notification) + + def _start_copying(self, source_clip_slot): + if not source_clip_slot.is_group_slot: + if liveobj_valid(source_clip_slot.clip): + if not source_clip_slot.clip.is_recording: + self._is_copying = True + self._source_clip_slot = source_clip_slot + clip_name = clip_name_from_clip_slot(source_clip_slot) + self._show_notification((MessageBoxText.COPIED_CLIP, clip_name)) + else: + self._show_notification(MessageBoxText.CANNOT_COPY_RECORDING_CLIP) + else: + self._show_notification(MessageBoxText.CANNOT_COPY_EMPTY_CLIP) + else: + self._show_notification(MessageBoxText.CANNOT_COPY_GROUP_SLOT) + + def _finish_copying(self, target_clip_slot): + if not target_clip_slot.is_group_slot: + source_is_audio = self._source_clip_slot.clip.is_audio_clip + target_track = target_clip_slot.canonical_parent + if source_is_audio: + if target_track.has_audio_input: + self._perform_copy(target_clip_slot) + else: + self._show_notification(MessageBoxText.CANNOT_COPY_AUDIO_CLIP_TO_MIDI_TRACK) + elif not target_track.has_audio_input: + self._perform_copy(target_clip_slot) + else: + self._show_notification(MessageBoxText.CANNOT_COPY_MIDI_CLIP_TO_AUDIO_TRACK) + else: + self._show_notification(MessageBoxText.CANNOT_PASTE_INTO_GROUP_SLOT) + + def _perform_copy(self, target_clip_slot): + self._source_clip_slot.duplicate_clip_to(target_clip_slot) + self._on_duplicated(self._source_clip_slot, target_clip_slot) + self._reset_copying_state() + + def _reset_copying_state(self): + self._source_clip_slot = None + self._is_copying = False + return + + def _on_duplicated(self, source_clip_slot, target_clip_slot): + clip_name = clip_name_from_clip_slot(source_clip_slot) + track_name = target_clip_slot.canonical_parent.name + self._show_notification(( + MessageBoxText.PASTED_CLIP, clip_name, track_name)) + + class DuplicateSceneComponent(Component, Messenger): - def __init__(self, session_ring = None, *a, **k): + def __init__(self, session_ring=None, *a, **k): super(DuplicateSceneComponent, self).__init__(*a, **k) - raise session_ring is not None or AssertionError + assert session_ring is not None self._session_ring = session_ring self._scene_buttons = None + return def set_scene_buttons(self, buttons): self._scene_buttons = buttons @@ -38,6 +122,15 @@ def _on_scene_value(self, value, index, _, is_momentary): class SpecialClipSlotComponent(ClipSlotComponent, Messenger): + @depends(copy_handler=const(None), fixed_length_recording=const(None)) + def __init__(self, copy_handler=None, fixed_length_recording=None, *a, **k): + assert copy_handler is not None + assert fixed_length_recording is not None + super(SpecialClipSlotComponent, self).__init__(*a, **k) + self._copy_handler = copy_handler + self._fixed_length_recording = fixed_length_recording + return + def _do_delete_clip(self): if self._clip_slot and self._clip_slot.has_clip: clip_name = self._clip_slot.clip.name @@ -50,17 +143,25 @@ def _do_select_clip(self, clip_slot): self.song.view.highlighted_clip_slot = self._clip_slot def _do_duplicate_clip(self): - if self._clip_slot and self._clip_slot.has_clip: - try: - slot_name = self._clip_slot.clip.name - track = self._clip_slot.canonical_parent - destination_slot_ix = track.duplicate_clip_slot(list(track.clip_slots).index(self._clip_slot)) - self.show_notification(MessageBoxText.DUPLICATE_CLIP % slot_name) - return destination_slot_ix - except Live.Base.LimitationError: - self.expect_dialog(MessageBoxText.SCENE_LIMIT_REACHED) - except RuntimeError: - self.expect_dialog(MessageBoxText.CLIP_DUPLICATION_FAILED) + self._copy_handler.duplicate(self._clip_slot) + + def _on_clip_duplicated(self, source_clip, destination_clip): + slot_name = source_clip.name + self.show_notification(MessageBoxText.DUPLICATE_CLIP % slot_name) + + def _clip_is_recording(self): + return self.has_clip() and self._clip_slot.clip.is_recording + + def _do_launch_clip(self, fire_state): + should_start_fixed_length_recording = self._fixed_length_recording.should_start_fixed_length_recording(self._clip_slot) + clip_is_recording = self._clip_is_recording() + if fire_state and not should_start_fixed_length_recording and not clip_is_recording or not fire_state: + super(SpecialClipSlotComponent, self)._do_launch_clip(fire_state) + elif should_start_fixed_length_recording: + track = self._clip_slot.canonical_parent + self._fixed_length_recording.start_recording_in_slot(track, list(track.clip_slots).index(self._clip_slot)) + elif clip_is_recording: + self._fixed_length_recording.stop_recording(self._clip_slot.clip) class SpecialSceneComponent(SceneComponent, Messenger): @@ -86,13 +187,17 @@ class SpecialSessionComponent(SessionComponent): scene_component_type = SpecialSceneComponent duplicate_button = ButtonControl() - def __init__(self, *a, **k): - super(SpecialSessionComponent, self).__init__(*a, **k) + def __init__(self, clip_slot_copy_handler=None, fixed_length_recording=None, *a, **k): + self._clip_copy_handler = clip_slot_copy_handler or ClipSlotCopyHandler() + self._fixed_length_recording = fixed_length_recording + with inject(copy_handler=const(self._clip_copy_handler), fixed_length_recording=const(self._fixed_length_recording)).everywhere(): + super(SpecialSessionComponent, self).__init__(*a, **k) self._slot_launch_button = None self._duplicate_button = None self._duplicate = self.register_component(DuplicateSceneComponent(self._session_ring)) self._duplicate_enabler = self.register_component(EnablingModesComponent(component=self._duplicate)) self._end_initialisation() + return duplicate_layer = forward_property('_duplicate')('layer') @@ -103,6 +208,7 @@ def duplicate_button(self, button): @duplicate_button.released def duplicate_button(self, button): self._duplicate_enabler.selected_mode = 'disabled' + self._clip_copy_handler.stop_copying() def set_slot_launch_button(self, button): self._slot_launch_button = button @@ -116,7 +222,8 @@ def set_clip_launch_buttons(self, buttons): def set_touch_strip(self, touch_strip): if touch_strip: touch_strip.set_mode(TouchStripModes.CUSTOM_FREE) - touch_strip.send_state([ TouchStripStates.STATE_OFF for _ in xrange(touch_strip.state_count) ]) + touch_strip.send_state([ TouchStripStates.STATE_OFF for _ in xrange(touch_strip.state_count) + ]) self._on_touch_strip_value.subject = touch_strip @listens('value') diff --git a/pushbase/step_duplicator.py b/pushbase/step_duplicator.py new file mode 100644 index 00000000..dfdb2a20 --- /dev/null +++ b/pushbase/step_duplicator.py @@ -0,0 +1,72 @@ +# uncompyle6 version 2.9.10 +# Python bytecode 2.7 (62211) +# Decompiled from: Python 2.7.13 (default, Dec 17 2016, 23:03:43) +# [GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.42.1)] +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/step_duplicator.py +# Compiled at: 2016-09-29 19:13:24 +from __future__ import absolute_import, print_function +from functools import partial +from ableton.v2.base import liveobj_valid, nop +from ableton.v2.control_surface import Component +from ableton.v2.control_surface.control import ButtonControl +from .consts import MessageBoxText +from .message_box_component import Messenger + +class StepDuplicatorComponent(Component, Messenger): + button = ButtonControl() + + def __init__(self, *a, **k): + super(StepDuplicatorComponent, self).__init__(*a, **k) + self._clip = None + self._source_step = None + self._notification_reference = partial(nop, None) + return + + @property + def is_duplicating(self): + return self.button.is_pressed and liveobj_valid(self._clip) + + def set_clip(self, clip): + self._cancel_duplicate() + self._clip = clip + + def add_step(self, note, step_start, step_end, nudge_offset=0): + if self.is_enabled() and self.is_duplicating: + current_step = ( + note, step_start, step_end - step_start, nudge_offset) + if self._source_step is not None: + self._duplicate_to(current_step) + else: + self._duplicate_from(current_step) + return + + def _duplicate_from(self, source_step): + message = MessageBoxText.CANNOT_COPY_EMPTY_STEP + notes = self._clip.get_notes(source_step[1], source_step[0], source_step[2], 1) + if len(notes) > 0: + message = MessageBoxText.COPIED_STEP + self._source_step = source_step + self._notification_reference = self.show_notification(message) + + def _duplicate_to(self, destination_step): + message = MessageBoxText.CANNOT_PASTE_TO_SOURCE_STEP + if destination_step != self._source_step: + message = MessageBoxText.PASTED_STEP + self._clip.duplicate_region(self._source_step[1], self._source_step[2], destination_step[1] + self._source_step[3], self._source_step[0], destination_step[0] - self._source_step[0]) + self._notification_reference = self.show_notification(message) + self._source_step = None + return + + def _cancel_duplicate(self): + self._source_step = None + if self._notification_reference() is not None: + self._notification_reference().hide() + return + + @button.released + def button(self, _): + self._cancel_duplicate() + + def update(self): + super(StepDuplicatorComponent, self).update() + self._cancel_duplicate() \ No newline at end of file diff --git a/pushbase/step_seq_component.py b/pushbase/step_seq_component.py index f1948b64..dabab4c1 100644 --- a/pushbase/step_seq_component.py +++ b/pushbase/step_seq_component.py @@ -1,45 +1,54 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/step_seq_component.py +# uncompyle6 version 2.9.10 +# Python bytecode 2.7 (62211) +# Decompiled from: Python 2.7.13 (default, Dec 17 2016, 23:03:43) +# [GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.42.1)] +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/step_seq_component.py +# Compiled at: 2016-11-16 18:13:21 from __future__ import absolute_import, print_function from itertools import chain, starmap -from ableton.v2.base import forward_property, listens +from ableton.v2.base import forward_property, listens, liveobj_valid from ableton.v2.control_surface import CompoundComponent from ableton.v2.control_surface.elements import to_midi_value from .loop_selector_component import LoopSelectorComponent from .playhead_component import PlayheadComponent from .note_editor_paginator import NoteEditorPaginator from .matrix_maps import PLAYHEAD_FEEDBACK_CHANNELS +from .step_duplicator import StepDuplicatorComponent class StepSeqComponent(CompoundComponent): - """ Step Sequencer Component """ - - def __init__(self, clip_creator = None, skin = None, grid_resolution = None, note_editor_component = None, instrument_component = None, *a, **k): + """ + This component represents one of the sequencing mechanisms for Push, which has one + NoteEditorComponent associated with a single pitch. The component mostly manages + distributing control elements to sub-components, which then provide the logic for + this layout. + """ + + def __init__(self, clip_creator=None, skin=None, grid_resolution=None, note_editor_component=None, instrument_component=None, *a, **k): super(StepSeqComponent, self).__init__(*a, **k) - raise clip_creator is not None or AssertionError - raise skin is not None or AssertionError - raise instrument_component is not None or AssertionError - raise note_editor_component is not None or AssertionError + assert clip_creator is not None + assert skin is not None + assert instrument_component is not None + assert note_editor_component is not None self._grid_resolution = grid_resolution - self._note_editor, self._loop_selector, self._big_loop_selector = self.register_components(note_editor_component, LoopSelectorComponent(clip_creator=clip_creator), LoopSelectorComponent(clip_creator=clip_creator, measure_length=2.0)) + self._note_editor, self._loop_selector = self.register_components(note_editor_component, LoopSelectorComponent(clip_creator=clip_creator, default_size=16)) self._instrument = instrument_component self._paginator = NoteEditorPaginator([self._note_editor]) - self._big_loop_selector.set_enabled(False) - self._big_loop_selector.set_paginator(self._paginator) + self._step_duplicator = self.register_component(StepDuplicatorComponent()) + self._note_editor.set_step_duplicator(self._step_duplicator) self._loop_selector.set_paginator(self._paginator) - self._note_editor_matrix = None self._on_pressed_pads_changed.subject = self._instrument self._on_selected_note_changed.subject = self._instrument self._on_detail_clip_changed.subject = self.song.view self._detail_clip = None self._playhead = None - self._playhead_component = self.register_component(PlayheadComponent(grid_resolution=grid_resolution, paginator=self._paginator, follower=self._loop_selector, notes=chain(*starmap(range, ((92, 100), - (84, 92), - (76, 84), - (68, 76)))), triplet_notes=chain(*starmap(range, ((92, 98), - (84, 90), - (76, 82), - (68, 74)))), feedback_channels=PLAYHEAD_FEEDBACK_CHANNELS)) + self._playhead_component = self.register_component(PlayheadComponent(grid_resolution=grid_resolution, paginator=self._paginator, follower=self._loop_selector, notes=chain(*starmap(range, ( + (92, 100), (84, 92), + (76, 84), (68, 76)))), triplet_notes=chain(*starmap(range, ( + (92, 98), (84, 90), + (76, 82), (68, 74)))), feedback_channels=PLAYHEAD_FEEDBACK_CHANNELS)) self._skin = skin self._playhead_color = 'NoteEditor.Playhead' + return def set_playhead(self, playhead): self._playhead = playhead @@ -71,15 +80,11 @@ def set_select_button(self, button): def set_mute_button(self, button): self._instrument.set_mute_button(button) - self._note_editor.set_mute_button(button) + self._note_editor.mute_button.set_control_element(button) def set_solo_button(self, button): self._instrument.set_solo_button(button) - def set_shift_button(self, button): - self._big_loop_selector.set_select_button(button) - self._on_shift_value.subject = button - def set_delete_button(self, button): self._instrument.set_delete_button(button) @@ -97,14 +102,12 @@ def set_short_loop_selector_matrix(self, matrix): def set_follow_button(self, button): self._loop_selector.set_follow_button(button) - self._big_loop_selector.set_follow_button(button) def set_duplicate_button(self, button): - self._instrument.duplicate_button.set_control_element(button) + self._step_duplicator.button.set_control_element(button) def set_button_matrix(self, matrix): - self._note_editor_matrix = matrix - self._update_note_editor_matrix() + self._note_editor.set_matrix(matrix) def set_quantization_buttons(self, buttons): self._grid_resolution.set_buttons(buttons) @@ -130,17 +133,12 @@ def update(self): @listens('detail_clip') def _on_detail_clip_changed(self): clip = self.song.view.detail_clip - clip = clip if self.is_enabled() and clip and clip.is_midi_clip else None + clip = clip if liveobj_valid(clip) and clip.is_midi_clip else None self._detail_clip = clip self._note_editor.set_detail_clip(clip) self._loop_selector.set_detail_clip(clip) - self._big_loop_selector.set_detail_clip(clip) self._playhead_component.set_clip(self._detail_clip) - - @listens('value') - def _on_shift_value(self, value): - if self.is_enabled(): - self._update_note_editor_matrix(enable_big_loop_selector=value and not self._loop_selector.is_following) + return @listens('selected_note') def _on_selected_note_changed(self): @@ -150,16 +148,4 @@ def _on_selected_note_changed(self): @listens('pressed_pads') def _on_pressed_pads_changed(self): - self._note_editor.modify_all_notes_enabled = bool(self._instrument.pressed_pads) - - def _update_note_editor_matrix(self, enable_big_loop_selector = False): - if enable_big_loop_selector: - self._note_editor.set_enabled(False) - self._note_editor.set_button_matrix(None) - self._big_loop_selector.set_enabled(True) - self._big_loop_selector.set_loop_selector_matrix(self._note_editor_matrix) - else: - self._big_loop_selector.set_enabled(False) - self._big_loop_selector.set_loop_selector_matrix(None) - self._note_editor.set_enabled(True) - self._note_editor.set_button_matrix(self._note_editor_matrix) \ No newline at end of file + self._note_editor.modify_all_notes_enabled = bool(self._instrument.pressed_pads) \ No newline at end of file diff --git a/pushbase/sysex.py b/pushbase/sysex.py deleted file mode 100644 index 64d99145..00000000 --- a/pushbase/sysex.py +++ /dev/null @@ -1,4 +0,0 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/sysex.py -from __future__ import absolute_import, print_function -USER_MODE = 1 -LIVE_MODE = 0 \ No newline at end of file diff --git a/pushbase/touch_encoder_element.py b/pushbase/touch_encoder_element.py index c87d526a..cba6ff6d 100644 --- a/pushbase/touch_encoder_element.py +++ b/pushbase/touch_encoder_element.py @@ -1,6 +1,10 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/touch_encoder_element.py +# uncompyle6 version 2.9.10 +# Python bytecode 2.7 (62211) +# Decompiled from: Python 2.7.13 (default, Dec 17 2016, 23:03:43) +# [GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.42.1)] +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/touch_encoder_element.py +# Compiled at: 2016-05-20 03:43:52 from __future__ import absolute_import, print_function -from ableton.v2.base import SlotManager from ableton.v2.control_surface.elements import TouchEncoderElement as TouchEncoderElementBase class TouchEncoderObserver(object): @@ -13,21 +17,23 @@ def on_encoder_parameter(self, encoder): pass -class TouchEncoderElement(TouchEncoderElementBase, SlotManager): +class TouchEncoderElement(TouchEncoderElementBase): """ Class representing an encoder that is touch sensitive """ - def __init__(self, undo_step_handler = None, delete_handler = None, *a, **k): + def __init__(self, undo_step_handler=None, delete_handler=None, *a, **k): super(TouchEncoderElement, self).__init__(*a, **k) self._trigger_undo_step = False self._undo_step_open = False self._undo_step_handler = undo_step_handler self._delete_handler = delete_handler self.set_observer(None) + return def set_observer(self, observer): if observer is None: observer = TouchEncoderObserver() self._observer = observer + return def on_nested_control_element_value(self, value, control): self._trigger_undo_step = value @@ -56,6 +62,7 @@ def release_parameter(self): if self.mapped_parameter() != None: super(TouchEncoderElement, self).release_parameter() self._observer.on_encoder_parameter(self) + return def receive_value(self, value): self._begin_undo_step() @@ -64,6 +71,7 @@ def receive_value(self, value): def disconnect(self): super(TouchEncoderElement, self).disconnect() self._undo_step_handler = None + return def _begin_undo_step(self): if self._undo_step_handler and self._trigger_undo_step: diff --git a/pushbase/touch_strip_controller.py b/pushbase/touch_strip_controller.py index 025c9f56..4ea64229 100644 --- a/pushbase/touch_strip_controller.py +++ b/pushbase/touch_strip_controller.py @@ -1,4 +1,9 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/touch_strip_controller.py +# uncompyle6 version 2.9.10 +# Python bytecode 2.7 (62211) +# Decompiled from: Python 2.7.13 (default, Dec 17 2016, 23:03:43) +# [GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.42.1)] +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/touch_strip_controller.py +# Compiled at: 2016-05-20 03:43:52 from __future__ import absolute_import, print_function from ableton.v2.control_surface import Component from ableton.v2.control_surface.control import ToggleButtonControl @@ -13,6 +18,7 @@ def __init__(self, *a, **k): super(TouchStripControllerComponent, self).__init__(*a, **k) self._touch_strip = None self._parameter = None + return def set_parameter(self, parameter): self._parameter = parameter @@ -29,6 +35,7 @@ def _update_strip_state(self): self._touch_strip.connect_to(self._parameter) else: self._touch_strip.release_parameter() + return def _calculate_strip_mode(self): if self._parameter.min == -1 * self._parameter.max: @@ -45,10 +52,12 @@ def __init__(self, strip_controller, touch_button, *a, **k): self._strip_controller = strip_controller self._touch_button = touch_button self._encoder = None + return def disconnect(self): self._set_touched_encoder(None) super(TouchStripEncoderConnection, self).disconnect() + return def on_encoder_touch(self, encoder): self._on_encoder_change(encoder) @@ -59,6 +68,7 @@ def on_encoder_parameter(self, encoder): def _on_encoder_change(self, encoder): if consts.PROTO_TOUCH_ENCODER_TO_STRIP and self._encoder in (encoder, None): self._set_touched_encoder(encoder if self._can_use_touch_encoder(encoder) else None) + return def _can_use_touch_encoder(self, encoder): is_useable = encoder.is_pressed() and encoder.mapped_parameter() != None @@ -71,6 +81,7 @@ def _set_touched_encoder(self, encoder): parameter = encoder.mapped_parameter() if encoder != None else None self._strip_controller.set_parameter(parameter) self._strip_controller.set_enabled(parameter != None) + return class TouchStripPitchModComponent(Component, Messenger): @@ -80,6 +91,7 @@ def __init__(self, *a, **k): super(TouchStripPitchModComponent, self).__init__(*a, **k) self._touch_strip = None self._touch_strip_indication = None + return def set_touch_strip(self, control): self._touch_strip = control @@ -102,7 +114,8 @@ def set_touch_strip_indication(self, control): def _update_touch_strip_indication(self): if self._touch_strip_indication: self._touch_strip_indication.set_mode(TouchStripModes.CUSTOM_FREE) - self._touch_strip_indication.send_state([ (TouchStripStates.STATE_FULL if self.touch_strip_toggle.is_toggled else TouchStripStates.STATE_HALF) for _ in xrange(self._touch_strip_indication.state_count) ]) + self._touch_strip_indication.send_state([ (TouchStripStates.STATE_FULL if self.touch_strip_toggle.is_toggled else TouchStripStates.STATE_HALF) for _ in xrange(self._touch_strip_indication.state_count) + ]) def update(self): super(TouchStripPitchModComponent, self).update() diff --git a/pushbase/touch_strip_element.py b/pushbase/touch_strip_element.py index b9592c21..ff1c99d5 100644 --- a/pushbase/touch_strip_element.py +++ b/pushbase/touch_strip_element.py @@ -1,7 +1,12 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/touch_strip_element.py +# uncompyle6 version 2.9.10 +# Python bytecode 2.7 (62211) +# Decompiled from: Python 2.7.13 (default, Dec 17 2016, 23:03:43) +# [GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.42.1)] +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/touch_strip_element.py +# Compiled at: 2016-05-20 03:43:52 from __future__ import absolute_import, print_function import Live -from ableton.v2.base import SlotManager, in_range, nop, NamedTuple, clamp +from ableton.v2.base import in_range, nop, NamedTuple, clamp from ableton.v2.control_surface import InputControlElement, MIDI_PB_TYPE MAX_PITCHBEND = 16384.0 @@ -28,7 +33,7 @@ class SimpleBehaviour(TouchStripBehaviour): Behaviour with custom mode. """ - def __init__(self, mode = TouchStripModes.PITCHBEND, *a, **k): + def __init__(self, mode=TouchStripModes.PITCHBEND, *a, **k): super(SimpleBehaviour, self).__init__(*a, **k) self._mode = mode @@ -60,8 +65,8 @@ class SelectingBehaviour(TouchStripBehaviour): def handle_value(self, value, notify): range, position = self.handle.range, self.handle.position - if not self._grabbed and range[0] <= value - position < range[1]: - self._offset = value - position + if not self._grabbed: + self._offset = range[0] <= value - position < range[1] and value - position self._grabbed = True else: notify(clamp(value - self._offset, 0, MAX_PITCHBEND)) @@ -88,7 +93,7 @@ def notify_if_dragging(value): DEFAULT_BEHAVIOUR = SimpleBehaviour() MODWHEEL_BEHAVIOUR = SimpleBehaviour(mode=TouchStripModes.MODWHEEL) -class TouchStripElement(InputControlElement, SlotManager): +class TouchStripElement(InputControlElement): """ Represents the Push TouchStrip. """ @@ -104,9 +109,9 @@ class ProxiedInterface(InputControlElement.ProxiedInterface): state_count = 24 - def __init__(self, touch_button = None, mode_element = None, light_element = None, *a, **k): - raise mode_element is not None or AssertionError - raise light_element is not None or AssertionError + def __init__(self, touch_button=None, mode_element=None, light_element=None, *a, **k): + assert mode_element is not None + assert light_element is not None super(TouchStripElement, self).__init__(MIDI_PB_TYPE, 0, 0, *a, **k) self._mode_element = mode_element self._light_element = light_element @@ -114,6 +119,7 @@ def __init__(self, touch_button = None, mode_element = None, light_element = Non self._touch_slot = self.register_slot(touch_button, None, 'value') self._behaviour = None self.behaviour = None + return @property def touch_button(self): @@ -122,6 +128,8 @@ def touch_button(self): def _get_mode(self): if self._behaviour != None: return self._behaviour.mode + else: + return def set_mode(self, mode): if not in_range(mode, 0, TouchStripModes.COUNT): @@ -150,21 +158,22 @@ def is_pressed(self): def reset(self): self.behaviour = None + return def notify_value(self, value): notify = super(TouchStripElement, self).notify_value self._behaviour.handle_value(value, notify) - def turn_on_index(self, index, on_state = TouchStripStates.STATE_FULL, off_state = TouchStripStates.STATE_OFF): - raise in_range(index, 0, self.state_count) or AssertionError + def turn_on_index(self, index, on_state=TouchStripStates.STATE_FULL, off_state=TouchStripStates.STATE_OFF): + assert in_range(index, 0, self.state_count) states = [off_state] * self.state_count states[index] = on_state self.send_state(states) - def turn_off(self, off_state = TouchStripStates.STATE_OFF): + def turn_off(self, off_state=TouchStripStates.STATE_OFF): self.send_state((off_state,) * self.state_count) def send_state(self, state): - if not (self._behaviour.mode == TouchStripModes.CUSTOM_FREE and len(state) == self.state_count): - raise AssertionError + if self._behaviour.mode == TouchStripModes.CUSTOM_FREE: + assert len(state) == self.state_count self._light_element.send_value(state) \ No newline at end of file diff --git a/pushbase/track_frozen_mode.py b/pushbase/track_frozen_mode.py index ae04e0c1..03642200 100644 --- a/pushbase/track_frozen_mode.py +++ b/pushbase/track_frozen_mode.py @@ -1,19 +1,25 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/track_frozen_mode.py +# uncompyle6 version 2.9.10 +# Python bytecode 2.7 (62211) +# Decompiled from: Python 2.7.13 (default, Dec 17 2016, 23:03:43) +# [GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.42.1)] +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/track_frozen_mode.py +# Compiled at: 2016-05-20 03:43:52 from __future__ import absolute_import, print_function from ableton.v2.base import listens from ableton.v2.control_surface.mode import ModesComponent class TrackFrozenModesComponent(ModesComponent): - def __init__(self, default_mode = None, frozen_mode = None, *a, **k): + def __init__(self, default_mode=None, frozen_mode=None, *a, **k): super(TrackFrozenModesComponent, self).__init__(*a, **k) - raise default_mode is not None or AssertionError - if not frozen_mode is not None: - raise AssertionError - self.add_mode('default', default_mode) - self.add_mode('frozen', frozen_mode) - self._on_selected_track_is_frozen_changed.subject = self.song.view - self.is_enabled() and self._update_selected_mode() + assert default_mode is not None + assert frozen_mode is not None + self.add_mode('default', default_mode) + self.add_mode('frozen', frozen_mode) + self._on_selected_track_is_frozen_changed.subject = self.song.view + if self.is_enabled(): + self._update_selected_mode() + return def _update_selected_mode(self): self.selected_mode = 'frozen' if self.song.view.selected_track.is_frozen else 'default' diff --git a/pushbase/transport_component.py b/pushbase/transport_component.py index d060ea5f..fa994b36 100644 --- a/pushbase/transport_component.py +++ b/pushbase/transport_component.py @@ -1,4 +1,9 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/transport_component.py +# uncompyle6 version 2.9.10 +# Python bytecode 2.7 (62211) +# Decompiled from: Python 2.7.13 (default, Dec 17 2016, 23:03:43) +# [GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.42.1)] +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/transport_component.py +# Compiled at: 2016-05-20 03:43:52 from __future__ import absolute_import, print_function from ableton.v2.control_surface import components @@ -6,4 +11,5 @@ class TransportComponent(components.TransportComponent): def __init__(self, *a, **k): super(TransportComponent, self).__init__(*a, **k) - self._metronome_toggle.view_transform = lambda v: ('Metronome.On' if v else 'Metronome.Off') \ No newline at end of file + self._metronome_toggle.view_transform = lambda v: if v: +'Metronome.On''Metronome.Off' \ No newline at end of file diff --git a/pushbase/user_component.py b/pushbase/user_component.py index 9e7a0f23..3f948625 100644 --- a/pushbase/user_component.py +++ b/pushbase/user_component.py @@ -1,4 +1,9 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/user_component.py +# uncompyle6 version 2.9.10 +# Python bytecode 2.7 (62211) +# Decompiled from: Python 2.7.13 (default, Dec 17 2016, 23:03:43) +# [GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.42.1)] +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/user_component.py +# Compiled at: 2016-05-20 03:43:52 from __future__ import absolute_import, print_function from ableton.v2.base import listens, task from ableton.v2.control_surface import Component @@ -8,13 +13,14 @@ class UserComponentBase(Component): __events__ = ('mode', 'before_mode_sent', 'after_mode_sent') defer_sysex_sending = False - def __init__(self, value_control = None, *a, **k): - raise value_control is not None or AssertionError + def __init__(self, value_control=None, *a, **k): + assert value_control is not None super(UserComponentBase, self).__init__(*a, **k) self._value_control = value_control self.__on_value.subject = self._value_control self._selected_mode = sysex.LIVE_MODE self._pending_mode_to_select = None + return def toggle_mode(self): self.mode = sysex.LIVE_MODE if self.mode == sysex.USER_MODE else sysex.USER_MODE @@ -38,6 +44,7 @@ def update(self): if self.is_enabled() and self._pending_mode_to_select: self._apply_mode(self._pending_mode_to_select) self._pending_mode_to_select = None + return def force_send_mode(self): self._do_apply_mode(self._selected_mode) diff --git a/pushbase/value_component.py b/pushbase/value_component.py index f4b4042e..953dc9ef 100644 --- a/pushbase/value_component.py +++ b/pushbase/value_component.py @@ -1,4 +1,9 @@ -#Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/value_component.py +# uncompyle6 version 2.9.10 +# Python bytecode 2.7 (62211) +# Decompiled from: Python 2.7.13 (default, Dec 17 2016, 23:03:43) +# [GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.42.1)] +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/value_component.py +# Compiled at: 2016-05-20 03:43:52 from __future__ import absolute_import, print_function from ableton.v2.base import listenable_property, listens from ableton.v2.control_surface import CompoundComponent, Component, ParameterSlot @@ -19,7 +24,7 @@ def convert_value_to_graphic(value): class ValueDisplayComponentBase(Component): - def __init__(self, display_label = ' ', display_seg_start = 0, *a, **k): + def __init__(self, display_label=' ', display_seg_start=0, *a, **k): super(ValueDisplayComponentBase, self).__init__(*a, **k) self._label_data_source = DisplayDataSource(display_label) self._value_data_source = DisplayDataSource() @@ -48,8 +53,9 @@ def set_graphic_display(self, display): def _set_display(self, display, source): if display: - display.set_data_sources((None,) * NUM_SEGMENTS) + display.set_data_sources((None, ) * NUM_SEGMENTS) display.segment(self._display_seg_start).set_data_source(source) + return None def update(self): super(ValueDisplayComponentBase, self).update() @@ -68,7 +74,7 @@ class ValueComponentBase(CompoundComponent): def create_display_component(self, *a, **k): raise NotImplementedError - def __init__(self, display_label = ' ', display_seg_start = 0, encoder_touch_delay = 0, *a, **k): + def __init__(self, display_label=' ', display_seg_start=0, encoder_touch_delay=0, *a, **k): super(ValueComponentBase, self).__init__(*a, **k) encoder = EncoderControl(touch_event_delay=encoder_touch_delay) encoder.touched = ValueComponentBase.__on_encoder_touched @@ -103,7 +109,7 @@ class ValueDisplayComponent(ValueDisplayComponentBase): Display for values from standard Python properties. """ - def __init__(self, property_name = None, subject = None, display_format = '%f', view_transform = None, graphic_transform = None, *a, **k): + def __init__(self, property_name=None, subject=None, display_format='%f', view_transform=None, graphic_transform=None, *a, **k): super(ValueDisplayComponent, self).__init__(*a, **k) self._subject = subject self._property_name = property_name @@ -114,6 +120,7 @@ def __init__(self, property_name = None, subject = None, display_format = '%f', self.graphic_transform = graphic_transform self.register_slot(subject, self._on_value_changed, property_name) self._on_value_changed() + return def view_transform(self, x): return x @@ -147,7 +154,7 @@ class ValueComponent(ValueComponentBase): def create_display_component(self, *a, **k): return ValueDisplayComponent(property_name=self._property_name, subject=self._subject, display_format=self._display_format, view_transform=(lambda x: self.view_transform(x)), graphic_transform=(lambda x: self.graphic_transform(x)), *a, **k) - def __init__(self, property_name = None, subject = None, display_format = '%f', model_transform = None, view_transform = None, graphic_transform = None, encoder_factor = None, *a, **k): + def __init__(self, property_name=None, subject=None, display_format='%f', model_transform=None, view_transform=None, graphic_transform=None, encoder_factor=None, *a, **k): self._property_name = property_name self._subject = subject self._display_format = display_format @@ -161,6 +168,7 @@ def __init__(self, property_name = None, subject = None, display_format = '%f', if encoder_factor is not None: self.encoder_factor = encoder_factor self._original_encoder_factor = self.encoder_factor + return def model_transform(self, x): """ @@ -202,7 +210,7 @@ class ParameterValueDisplayComponent(ValueDisplayComponentBase): Display for values from device parameters. """ - def __init__(self, device_parameter = None, *a, **k): + def __init__(self, device_parameter=None, *a, **k): super(ParameterValueDisplayComponent, self).__init__(*a, **k) self._on_value_changed.subject = device_parameter self._on_value_changed() @@ -229,7 +237,7 @@ class ParameterValueComponent(ValueComponentBase): def create_display_component(self, *a, **k): return ParameterValueDisplayComponent(device_parameter=self._parameter_slot.parameter, *a, **k) - def __init__(self, device_parameter = None, *a, **k): + def __init__(self, device_parameter=None, *a, **k): self._parameter_slot = ParameterSlot(device_parameter) super(ParameterValueComponent, self).__init__(*a, **k) self.register_disconnectable(self._parameter_slot) diff --git a/pushbase/velocity_levels_component.py b/pushbase/velocity_levels_component.py new file mode 100644 index 00000000..5ba58531 --- /dev/null +++ b/pushbase/velocity_levels_component.py @@ -0,0 +1,145 @@ +# uncompyle6 version 2.9.10 +# Python bytecode 2.7 (62211) +# Decompiled from: Python 2.7.13 (default, Dec 17 2016, 23:03:43) +# [GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.42.1)] +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/velocity_levels_component.py +# Compiled at: 2016-09-29 19:13:24 +from __future__ import absolute_import, print_function +import Live +from ableton.v2.base import listenable_property, listens, liveobj_valid, NamedTuple, EventObject, task +from ableton.v2.control_surface.components import PlayableComponent +from ableton.v2.control_surface.control import ButtonControl, control_matrix +from .matrix_maps import NON_FEEDBACK_CHANNEL +from .pad_control import PadControl +SLICE_MODE = Live.SimplerDevice.PlaybackMode.slicing +BASE_SLICING_NOTE = 36 +INVALID_LEVEL = -1.0 + +class NullTargetNoteProvider(EventObject): + + @listenable_property + def selected_target_note(self): + return NamedTuple(note=-1, channel=-1) + + +class VelocityLevelsComponent(PlayableComponent): + SOURCE_NOTES = list(reversed(range(64, 128))) + DEFAULT_VELOCITY = 100 + matrix = control_matrix(PadControl) + select_button = ButtonControl() + + def __init__(self, velocity_levels=None, target_note_provider=None, skin_base_key=None, *a, **k): + super(VelocityLevelsComponent, self).__init__(*a, **k) + self._target_note_provider = target_note_provider or NullTargetNoteProvider() + self.__on_selected_target_note_changed.subject = self._target_note_provider + self._played_level = INVALID_LEVEL + self.set_skin_base_key(skin_base_key or 'VelocityLevels') + self._notification_task = self._tasks.add(task.run(self._update_velocity)) + self._notification_task.kill() + self.set_velocity_levels(velocity_levels) + + @listenable_property + def velocity(self): + if 0 <= self._played_level < 128: + return self._played_level + return self.DEFAULT_VELOCITY + + def set_velocities_playable(self, playable): + self._notification_task.kill() + self._set_control_pads_from_script(not playable) + + def set_velocity_levels(self, velocity_levels): + self.velocity_levels = velocity_levels + self.__on_last_played_level.subject = velocity_levels + self.update() + + def set_matrix(self, matrix): + super(VelocityLevelsComponent, self).set_matrix(matrix) + self._update_sensitivity_profile() + + def _update_control_from_script(self): + super(VelocityLevelsComponent, self)._update_control_from_script() + self._update_sensitivity_profile() + + def _update_sensitivity_profile(self): + profile = 'default' if self._takeover_pads else 'drums' + for button in self.matrix: + button.sensitivity_profile = profile + + def _update_velocity(self): + self._notification_task.kill() + self.notify_velocity() + + def set_skin_base_key(self, base_key): + self._skin_base_key = base_key + self._update_led_feedback() + + @matrix.pressed + def matrix(self, button): + self._on_matrix_pressed(button) + + @matrix.released + def matrix(self, button): + self._on_matrix_released(button) + + @select_button.pressed + def select_button(self, _value): + self._set_control_pads_from_script(True) + + @select_button.released + def select_button(self, _value): + self._set_control_pads_from_script(False) + + def _on_matrix_pressed(self, button): + has_levels = liveobj_valid(self.velocity_levels) + levels = self.velocity_levels.levels if has_levels else [] + index = self._button_index(button) + self._played_level = levels[index] if index < len(levels) else INVALID_LEVEL + self._update_led_feedback() + self.notify_velocity() + + @listens('selected_target_note') + def __on_selected_target_note_changed(self): + self.update() + + @listens('last_played_level') + def __on_last_played_level(self): + if not self._takeover_pads: + played = self.velocity_levels.last_played_level if liveobj_valid(self.velocity_levels) else INVALID_LEVEL + self._played_level = played + self._update_led_feedback() + self._notification_task.restart() + + def _button_index(self, button): + y, x = button.coordinate + return (self.height - 1 - y) * self.width + x + + def _note_translation_for_button(self, button): + return ( + self.SOURCE_NOTES[self._button_index(button)], NON_FEEDBACK_CHANNEL) + + def _update_button_color(self, button): + index = self._button_index(button) + levels = self.velocity_levels.levels if liveobj_valid(self.velocity_levels) else [] + if index < len(levels) and self._played_level == levels[index]: + color = 'SelectedLevel' + else: + y, _ = button.coordinate + color = 'MidLevel' + if y == 0: + color = 'HighLevel' + elif y == self.height - 1: + color = 'LowLevel' + button.color = self._skin_base_key + '.' + color + + def update(self): + super(VelocityLevelsComponent, self).update() + if liveobj_valid(self.velocity_levels): + self.velocity_levels.enabled = self.is_enabled() + self.velocity_levels.source_channel = NON_FEEDBACK_CHANNEL + self.velocity_levels.notes = self.SOURCE_NOTES[:self.width * self.height] + target_note = self._target_note_provider.selected_target_note + self.velocity_levels.target_note = target_note.note + self.velocity_levels.target_channel = target_note.channel + if not self.is_enabled(): + self._notification_task.kill() \ No newline at end of file diff --git a/pushbase/velocity_levels_element.py b/pushbase/velocity_levels_element.py new file mode 100644 index 00000000..b7d48933 --- /dev/null +++ b/pushbase/velocity_levels_element.py @@ -0,0 +1,34 @@ +# uncompyle6 version 2.9.10 +# Python bytecode 2.7 (62211) +# Decompiled from: Python 2.7.13 (default, Dec 17 2016, 23:03:43) +# [GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.42.1)] +# Embedded file name: /Users/versonator/Jenkins/live/output/mac_64_static/Release/python-bundle/MIDI Remote Scripts/pushbase/velocity_levels_element.py +# Compiled at: 2016-06-13 12:41:02 +from __future__ import absolute_import, print_function +from ableton.v2.base import EventObject, listenable_property +from .proxy_element import ProxyElement + +class NullVelocityLevels(EventObject): + enabled = False + target_note = -1 + target_channel = -1 + source_channel = -1 + notes = [] + + @property + def levels(self): + return [] + + @listenable_property + def last_played_level(self): + return -1.0 + + +class VelocityLevelsElement(ProxyElement): + + def __init__(self, velocity_levels=None, *a, **k): + super(VelocityLevelsElement, self).__init__(proxied_object=velocity_levels, proxied_interface=NullVelocityLevels()) + + def reset(self): + self.notes = [] + self.source_channel = -1 \ No newline at end of file