class PadSettingsComponent(Component): sensitivity_encoder = StepEncoderControl(num_steps=PAD_SETTING_STEP_SIZE) 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): assert pad_settings is not None super(PadSettingsComponent, self).__init__(*a, **k) self._pad_settings = pad_settings @sensitivity_encoder.value def sensitivity_encoder(self, value, encoder): self._pad_settings.sensitivity = clamp(self._pad_settings.sensitivity + value, self._pad_settings.min_sensitivity, self._pad_settings.max_sensitivity) @gain_encoder.value def gain_encoder(self, value, encoder): self._pad_settings.gain = clamp(self._pad_settings.gain + value, self._pad_settings.min_gain, self._pad_settings.max_gain) @dynamics_encoder.value def dynamics_encoder(self, value, encoder): self._pad_settings.dynamics = clamp(self._pad_settings.dynamics + value, self._pad_settings.min_dynamics, self._pad_settings.max_dynamics)
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): 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') return @led_brightness_encoder.value def led_brightness_encoder(self, value, encoder): self._hardware_settings.led_brightness = clamp(self._hardware_settings.led_brightness + value, self._hardware_settings.min_led_brightness, self._hardware_settings.max_led_brightness) @display_brightness_encoder.value def display_brightness_encoder(self, value, encoder): self._hardware_settings.display_brightness = clamp(self._hardware_settings.display_brightness + value, self._hardware_settings.min_display_brightness, self._hardware_settings.max_display_brightness)
class AudioClipSettingsControllerComponent(Component): """ Component for managing settings of an audio clip """ warp_mode_encoder = StepEncoderControl() transpose_encoder = EncoderControl() detune_encoder = EncoderControl() gain_encoder = EncoderControl() shift_button = ButtonControl() def __init__(self, *a, **k): super(AudioClipSettingsControllerComponent, self).__init__(*a, **k) self._audio_clip_model = self.register_disconnectable(AudioClipSettingsModel()) def _get_clip(self): return self._audio_clip_model.clip def _set_clip(self, clip): self._audio_clip_model.clip = clip self._update_encoder_enabled_state() self._on_clip_changed() clip = property(_get_clip, _set_clip) def _update_encoder_enabled_state(self): enabled = liveobj_valid(self.clip) self.warp_mode_encoder.enabled = self.transpose_encoder.enabled = self.detune_encoder.enabled = self.gain_encoder.enabled = enabled @warp_mode_encoder.value def warp_mode_encoder(self, value, encoder): self._on_clip_warp_mode_value(value) def _on_clip_warp_mode_value(self, value): self._audio_clip_model.warp_mode = value @transpose_encoder.value def transpose_encoder(self, value, encoder): self._on_transpose_encoder_value(value) def _on_transpose_encoder_value(self, value): self._audio_clip_model.set_clip_pitch_coarse(value, self.shift_button.is_pressed) @detune_encoder.value def detune_encoder(self, value, encoder): self._on_detune_encoder_value(value) def _on_detune_encoder_value(self, value): self._audio_clip_model.set_clip_pitch_fine(value, self.shift_button.is_pressed) @gain_encoder.value def gain_encoder(self, value, encoder): self._audio_clip_model.set_clip_gain(value, self.shift_button.is_pressed)
class ViewControlComponent(ViewControlComponentBase): scene_scroll_encoder = StepEncoderControl(num_steps=64) def __init__(self, *a, **k): (super(ViewControlComponent, self).__init__)(*a, **k) self._scroll_scenes = ScrollComponent((BasicSceneScroller()), parent=self) @scene_scroll_encoder.value def scene_scroll_encoder(self, value, _): if value > 0: self._scroll_scenes.scroll_down() elif value < 0: self._scroll_scenes.scroll_up()
class DrumPadParameterComponent(CompoundComponent, ParameterProvider): choke_encoder = StepEncoderControl(num_steps=10) def __init__(self, view_model=None, *a, **k): raise view_model is not None or AssertionError 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)) return def _get_drum_pad(self): return self._drum_pad def _set_drum_pad(self, pad): if pad != self._drum_pad: self._drum_pad = pad self._rebuild_parameter_list() 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._view_connector.update() @property def parameters(self): return self._parameters @choke_encoder.value def choke_encoder(self, value, encoder): if len(self._parameters) > 0: self._parameters[0].parameter.value += value
class DrumPadParameterComponent(CompoundComponent, ParameterProvider): choke_encoder = StepEncoderControl(num_steps=10) transpose_encoder = StepEncoderControl(num_steps=10) 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.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 def _set_drum_pad(self, pad): if pad != self._drum_pad: self._drum_pad = pad 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._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): return self._parameters @choke_encoder.value def choke_encoder(self, value, encoder): if len(self._parameters) > 0: 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
class ScalesComponent(Component): navigation_colors = dict(color=b'Scales.Navigation', disabled_color=b'Scales.NavigationDisabled') up_button = ButtonControl(repeat=True, **navigation_colors) down_button = ButtonControl(repeat=True, **navigation_colors) right_button = ButtonControl(repeat=True, **navigation_colors) left_button = ButtonControl(repeat=True, **navigation_colors) root_note_buttons = control_list(RadioButtonControl, control_count=len(ROOT_NOTES), checked_color=b'Scales.OptionOn', unchecked_color=b'Scales.OptionOff') in_key_toggle_button = ToggleButtonControl( toggled_color=b'Scales.OptionOn', untoggled_color=b'Scales.OptionOn') fixed_toggle_button = ToggleButtonControl( toggled_color=b'Scales.OptionOn', untoggled_color=b'Scales.OptionOff') scale_encoders = control_list(StepEncoderControl) 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): 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(b'4ths', 3), Layout(b'3rds', 2), Layout(b'Sequential', None)) self._selected_layout_index = 0 self.in_key_toggle_button.connect_property(note_layout, b'is_in_key') self.fixed_toggle_button.connect_property(note_layout, b'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) self._note_layout.scale = self._scale_list[index] @down_button.pressed def down_button(self, button): self._update_horizontal_navigation() self._set_selected_scale_index(self._selected_scale_index + 1) @up_button.pressed def up_button(self, button): self._update_horizontal_navigation() self._set_selected_scale_index(self._selected_scale_index - 1) @left_button.pressed def left_button(self, button): self._update_horizontal_navigation() self._set_selected_scale_index(self._selected_scale_index - self.NUM_DISPLAY_ROWS) @right_button.pressed def right_button(self, button): self._update_horizontal_navigation() self._set_selected_scale_index(self._selected_scale_index + self.NUM_DISPLAY_ROWS) @root_note_buttons.pressed def root_note_buttons(self, button): self._note_layout.root_note = ROOT_NOTES[button.index] @listens(b'root_note') def __on_root_note_changed(self, root_note): self._selected_root_note_index = list(ROOT_NOTES).index(root_note) self.root_note_buttons.checked_index = self._selected_root_note_index self.notify_selected_root_note_index() @property def root_note_names(self): return [NOTE_NAMES[note] for note in ROOT_NOTES] @listenable_property def selected_root_note_index(self): return self._selected_root_note_index @scale_encoders.value def scale_encoders(self, value, encoder): self._update_horizontal_navigation() self._set_selected_scale_index(self._selected_scale_index + value) @property def scale_names(self): return self._scale_name_list @listenable_property def selected_scale_index(self): return self._selected_scale_index @listens(b'scale') def __on_scale_changed(self, scale): index = self._scale_list.index( scale) if scale in self._scale_list else -1 if index != self._selected_scale_index: self._selected_scale_index = index self.up_button.enabled = index > 0 self.left_button.enabled = index > 0 self.down_button.enabled = index < len(self._scale_list) - 1 self.right_button.enabled = index < len(self._scale_list) - 1 self.notify_selected_scale_index() @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): return self._note_layout def _update_horizontal_navigation(self): self.horizontal_navigation = self.right_button.is_pressed or self.left_button.is_pressed @listens(b'interval') def __on_interval_changed(self, interval): index = index_if(lambda layout: layout.interval == interval, self._layouts) self.selected_layout_index = index
class BrowserComponent(Component, Messenger): __events__ = (u'loaded', u'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 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) close_button = ButtonControl() prehear_button = ToggleButtonControl(toggled_color=u'Browser.Option', untoggled_color=u'Browser.OptionDisabled') scroll_encoders = control_list(StepEncoderControl, num_steps=10, control_count=NUM_VISIBLE_BROWSER_LISTS) scroll_focused_encoder = StepEncoderControl(num_steps=10) scrolling = listenable_property.managed(False) horizontal_navigation = listenable_property.managed(False) list_offset = listenable_property.managed(0) can_enter = listenable_property.managed(False) can_exit = listenable_property.managed(False) context_color_index = listenable_property.managed(-1) context_text = listenable_property.managed(u'') @depends(commit_model_changes=None, selection=None) 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 self._current_hotswap_target = self._browser.hotswap_target self._updating_root_items = BooleanContext() self._focused_list_index = 0 self._commit_model_changes = commit_model_changes self._preferences = preferences self._expanded = False self._unexpand_with_scroll_encoder = False self._delay_preview_list = BooleanContext() self._selection = selection self._main_modes_ref = main_modes_ref if main_modes_ref is not None else nop self._load_neighbour_overlay = LoadNeighbourOverlayComponent(parent=self, 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_context() self.prehear_button.is_toggled = preferences.setdefault(u'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, u'focused_list_index') def auto_unexpand(): self.expanded = False self._update_list_offset() self._unexpand_task = self._tasks.add(task.sequence(task.wait(self.EXPAND_LIST_TIME), task.run(auto_unexpand))).kill() @up_button.pressed def up_button(self, button): with self._delay_preview_list(): self.focused_list.select_index_with_offset(-1) self._update_auto_expand() self._update_scrolling() self._update_horizontal_navigation() @up_button.released def up_button(self, button): self._finish_preview_list_task() self._update_scrolling() @down_button.pressed def down_button(self, button): with self._delay_preview_list(): self.focused_list.select_index_with_offset(1) self._update_auto_expand() self._update_scrolling() self._update_horizontal_navigation() @down_button.released def down_button(self, button): self._finish_preview_list_task() self._update_scrolling() @right_button.pressed def right_button(self, button): if self._expanded and self._can_auto_expand() and self._focused_list_index > 0: self.focused_list.select_index_with_offset(self.NUM_ITEMS_PER_COLUMN) self._update_scrolling() self.horizontal_navigation = True elif not self._enter_selected_item(): self._update_auto_expand() @right_button.released def right_button(self, button): self._update_scrolling() @left_button.pressed def left_button(self, button): if self._expanded and self._focused_list_index > 0 and self.focused_list.selected_index >= self.NUM_ITEMS_PER_COLUMN: self.focused_list.select_index_with_offset(-self.NUM_ITEMS_PER_COLUMN) self._update_scrolling() self.horizontal_navigation = True else: self._exit_selected_item() @left_button.released def left_button(self, button): self._update_scrolling() @open_button.pressed def open_button(self, button): self._enter_selected_item() @back_button.pressed def back_button(self, button): self._exit_selected_item() @scroll_encoders.touched def scroll_encoders(self, encoder): list_index = self._get_list_index_for_encoder(encoder) if list_index is not None: try: if self._focus_list_with_index(list_index, crop=False): self._unexpand_with_scroll_encoder = True self._prehear_selected_item() if self.focused_list.selected_item.is_loadable and encoder.index == self.scroll_encoders.control_count - 1: self._update_list_offset() self._on_encoder_touched() except CannotFocusListError: pass @scroll_encoders.released def scroll_encoders(self, encoders): self._on_encoder_released() @scroll_encoders.value def scroll_encoders(self, value, encoder): list_index = self._get_list_index_for_encoder(encoder) if list_index is not None: try: if self._focus_list_with_index(list_index): self._unexpand_with_scroll_encoder = True self._on_encoder_value(value) except CannotFocusListError: pass @scroll_focused_encoder.value def scroll_focused_encoder(self, value, encoder): self._on_encoder_value(value) @scroll_focused_encoder.touched def scroll_focused_encoder(self, encoder): self._on_encoder_touched() @scroll_focused_encoder.released def scroll_focused_encoder(self, encoder): self._on_encoder_released() def _on_encoder_value(self, value): with self._delay_preview_list(): self.focused_list.select_index_with_offset(value) first_visible_list_focused = self.focused_list_index == self.list_offset if self.expanded and first_visible_list_focused: self.expanded = False self._unexpand_with_scroll_encoder = True elif not first_visible_list_focused and not self.expanded and self._can_auto_expand(): self._update_auto_expand() self._unexpand_with_scroll_encoder = True self._update_scrolling() self._update_horizontal_navigation() def _on_encoder_touched(self): self._unexpand_task.kill() self._update_scrolling() self._update_horizontal_navigation() def _on_encoder_released(self): any_encoder_touched = any(map(lambda e: e.is_touched, self.scroll_encoders)) or self.scroll_focused_encoder.is_touched if not any_encoder_touched and self._unexpand_with_scroll_encoder: self._unexpand_task.restart() self._update_scrolling() def _get_list_index_for_encoder(self, encoder): if self.expanded: 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.should_widen_focused_item: index = self.focused_list_index if 0 <= index < len(self._lists): return index else: return None @load_button.pressed def load_button(self, button): self._load_selected_item() @prehear_button.toggled def prehear_button(self, toggled, button): if toggled: self._prehear_selected_item() else: self._browser.stop_preview() self._preferences[u'browser_prehear'] = toggled self.notify_prehear_enabled() @close_button.pressed def close_button(self, button): self.notify_close() @listenable_property def lists(self): return self._lists @listenable_property def focused_list_index(self): return self._focused_list_index @listenable_property def prehear_enabled(self): return self.prehear_button.is_toggled @property def focused_list(self): return self._lists[self._focused_list_index] @listenable_property def focused_item(self): return self.focused_list.selected_item @listenable_property 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 u'custom_button' def disconnect(self): super(BrowserComponent, self).disconnect() self._lists = [] self._commit_model_changes = None @expanded.setter def expanded(self, expanded): if self._expanded != expanded: self._expanded = expanded self._unexpand_with_scroll_encoder = False self._update_navigation_buttons() if len(self._lists) > self._focused_list_index + 1: self._lists[self._focused_list_index + 1].limit = self.num_preview_items self.notify_expanded() @listens(u'selected_track.color_index') def _on_selected_track_color_index_changed(self): if self.is_enabled(): self._update_context() self._update_navigation_buttons() @listens(u'selected_track.name') def _on_selected_track_name_changed(self): if self.is_enabled(): self._update_context() @listens(u'detail_clip.name') def _on_detail_clip_name_changed(self): if self.is_enabled(): self._update_context() @listens(u'hotswap_target') def _on_hotswap_target_changed(self): if self.is_enabled(): if not self._switched_to_empty_pad(): 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(u'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 u'clip' in main_modes.active_modes def _switched_to_empty_pad(self): hotswap_target = self._browser.hotswap_target is_browsing_drumpad = isinstance(hotswap_target, Live.DrumPad.DrumPad) 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): u""" Focus the list with the given index. Raises CannotFocusListError if the operation fails. Returns True if a new list was focused and False if it was already focused. """ if self._focused_list_index != index: if self._finish_preview_list_task(): 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:]: l.selected_index = -1 self._focused_list_index = index self.focused_list.limit = -1 if self.focused_list.selected_index == -1: self.focused_list.selected_index = 0 self.notify_focused_list_index() self._on_focused_selection_changed.subject = self.focused_list if crop: self._crop_browser_lists(self._focused_list_index + 2) if self._focused_list_index == len(self._lists) - 1: self._replace_preview_list() self._load_neighbour_overlay.set_enabled(False) self._update_navigation_buttons() return True return False @listens(u'selected_index') def _on_focused_selection_changed(self): if self._delay_preview_list and not self.focused_item.is_loadable: self._preview_list_task.restart() else: self._replace_preview_list() self._update_navigation_buttons() self._prehear_selected_item() self._load_neighbour_overlay.set_enabled(False) self.notify_focused_item() def _get_actual_item(self, item): contained_item = getattr(item, u'contained_item', None) if contained_item is not None: return contained_item 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(u'load_next') def _on_load_next(self): self.focused_list.selected_index += 1 self._load_selected_item() @listens(u'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 self._update_load_neighbour_overlay_visibility() self._update_navigation_buttons() item = self._get_actual_item(focused_list.selected_item) self._load_item(item) self.notify_loaded() 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) * old_div(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 u'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 else: self._browser.load_item(item) self._content_hotswap_target = self._browser.hotswap_target else: with self._insert_right_of_selected(): self._browser.load_item(item) @contextmanager def _insert_right_of_selected(self): DeviceInsertMode = Live.Track.DeviceInsertMode device_to_select = get_selection_for_new_device(self._selection) if device_to_select: self._selection.selected_object = device_to_select selected_track_view = self.song.view.selected_track.view selected_track_view.device_insert_mode = DeviceInsertMode.selected_right yield selected_track_view.device_insert_mode = DeviceInsertMode.default def _prehear_selected_item(self): if self.prehear_button.is_toggled and not self._updating_root_items: self._browser.stop_preview() item = self._get_actual_item(self.focused_list.selected_item) if item and item.is_loadable and isinstance(item, Live.Browser.BrowserItem): self._browser.preview_item(item) def _stop_prehear(self): if self.prehear_button.is_toggled and not self._updating_root_items: self._browser.stop_preview() 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 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 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 u'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() else: num_columns = int(ceil(old_div(float(len(self.focused_list.items)), self.NUM_ITEMS_PER_COLUMN))) last_column_start_index = (num_columns - 1) * self.NUM_ITEMS_PER_COLUMN self.left_button.enabled = self._focused_list_index > 0 self.right_button.enabled = can_enter or self.focused_list.selected_index < last_column_start_index self.can_enter = can_enter self.can_exit = can_exit def _update_scrolling(self): self.scrolling = self.up_button.is_pressed or self.down_button.is_pressed or self.scroll_focused_encoder.is_touched or any(map(lambda e: e.is_touched, self.scroll_encoders)) or self.right_button.is_pressed and self._expanded or self.left_button.is_pressed and self._expanded def _update_horizontal_navigation(self): self.horizontal_navigation = self.right_button.is_pressed or self.left_button.is_pressed def _update_context(self): selected_track = self.song.view.selected_track clip = self.song.view.detail_clip if self.browse_for_audio_clip and clip: self.context_text = clip.name elif liveobj_valid(self._browser.hotswap_target): self.context_text = self._browser.hotswap_target.name else: 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 def _enter_selected_item(self): item_entered = False self._finish_preview_list_task() new_index = self._focused_list_index + 1 if 0 <= new_index < len(self._lists): self._focus_list_with_index(new_index) self._unexpand_task.kill() self._update_list_offset() self._update_auto_expand() self._prehear_selected_item() item_entered = True return item_entered def _exit_selected_item(self): item_exited = False try: self._focus_list_with_index(self._focused_list_index - 1) self._update_list_offset() self._update_auto_expand() self._stop_prehear() item_exited = True except CannotFocusListError: pass return item_exited def _can_auto_expand(self): return len(self.focused_list.items) > self.NUM_ITEMS_PER_COLUMN * 2 and self.focused_list.selected_item.is_loadable and getattr(self.focused_list.selected_item, u'contained_item', None) == None def _update_auto_expand(self): self.expanded = self._can_auto_expand() self._update_list_offset() def _update_list_offset(self): if self.expanded: self.list_offset = max(0, self.focused_list_index - 1) else: offset = len(self._lists) if self.focused_list.selected_item.is_loadable: offset += 1 self.list_offset = max(0, offset - self.NUM_VISIBLE_BROWSER_LISTS) def _replace_preview_list_by_task(self): self._replace_preview_list() self._update_navigation_buttons() def _finish_preview_list_task(self): if self._preview_list_task.is_running: self._replace_preview_list_by_task() return True return False def _replace_preview_list(self): self._preview_list_task.kill() self._crop_browser_lists(self._focused_list_index + 1) selected_item = self.focused_list.selected_item children_iterator = selected_item.iter_children if len(children_iterator) > 0: enable_wrapping = getattr(selected_item, u'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): 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) self.register_disconnectable(l) self.notify_lists() def _crop_browser_lists(self, length): num_items_to_crop = len(self._lists) - length for _ in range(num_items_to_crop): l = self._lists.pop() self.unregister_disconnectable(l) if num_items_to_crop > 0: self.notify_lists() def _make_root_browser_items(self): filter_type = self._browser.filter_type hotswap_target = self._browser.hotswap_target if liveobj_valid(hotswap_target): filter_type = filter_type_for_hotswap_target(hotswap_target, default=filter_type) return make_root_browser_items(self._browser, filter_type) def _content_cache_is_valid(self): return self._content_filter_type == self._browser.filter_type and not liveobj_changed(self._content_hotswap_target, self._browser.hotswap_target) def _invalidate_content_cache(self): self._content_hotswap_target = None self._content_filter_type = None def _update_content_cache(self): self._content_filter_type = self._browser.filter_type self._content_hotswap_target = self._browser.hotswap_target def _update_root_items(self): if not self._content_cache_is_valid(): self._update_content_cache() with self._updating_root_items(): self._on_focused_selection_changed.subject = None self._crop_browser_lists(0) self._append_browser_list(children_iterator=self._make_root_browser_items()) self._focused_list_index = 0 self.focused_list.selected_index = 0 self._select_hotswap_target() self._on_focused_selection_changed.subject = self.focused_list self._on_focused_selection_changed() def _select_hotswap_target(self, list_index = 0): if list_index < len(self._lists): l = self._lists[list_index] l.access_all = True children = l.items i = index_if(lambda i: i.is_selected, children) if i < len(children): self._focused_list_index = list_index l.selected_index = i self._replace_preview_list() self._select_hotswap_target(list_index + 1) @property def num_preview_items(self): if self._expanded: return self.NUM_ITEMS_PER_COLUMN * self.NUM_COLUMNS_IN_EXPANDED_LIST return 6 def update(self): super(BrowserComponent, self).update() self._invalidate_content_cache() if self.is_enabled(): 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() else: self._stop_prehear() self.list_offset = 0 def _wrap_item(self, item): if item.is_device: return self._wrap_device_item(item) if self._is_hotswap_target_plugin(item): return self._wrap_hotswapped_plugin_item(item) return item def _wrap_device_item(self, item): u""" Create virtual folder around items that can be loaded AND have children, to avoid 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, is_loadable=True, is_device=True, contained_item=item, wrapped_loadable=wrapped_loadable, icon=u'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 def _wrap_hotswapped_plugin_item(self, item): return PluginBrowserItem(name=item.name, vst_device=self._browser.hotswap_target)
class QuantizationSettingsComponent(Component): swing_amount_encoder = EncoderControl() quantize_to_encoder = StepEncoderControl() quantize_amount_encoder = EncoderControl() record_quantization_encoder = StepEncoderControl() record_quantization_toggle_button = ToggleButtonControl( toggled_color='Recording.FixedLengthRecordingOn', untoggled_color='Recording.FixedLengthRecordingOff') quantize_amount = listenable_property.managed(1.0) quantize_to_index = listenable_property.managed(DEFAULT_QUANTIZATION_INDEX) record_quantization_index = listenable_property.managed( DEFAULT_QUANTIZATION_INDEX) def __init__(self, *a, **k): super(QuantizationSettingsComponent, self).__init__(*a, **k) self.__on_swing_amount_changed.subject = self.song self.__on_record_quantization_changed.subject = self.song self.__on_record_quantization_changed() @property def quantize_to(self): return QUANTIZATION_OPTIONS[self.quantize_to_index] @listenable_property def swing_amount(self): return self.song.swing_amount @listenable_property def record_quantization_enabled(self): return self.record_quantization_toggle_button.is_toggled @property def quantization_option_names(self): return QUANTIZATION_NAMES @swing_amount_encoder.value def swing_amount_encoder(self, value, encoder): self.song.swing_amount = clamp(self.song.swing_amount + value * 0.5, 0.0, 0.5) @staticmethod def _clamp_quantization_index(index): return clamp(index, 0, len(QUANTIZATION_OPTIONS) - 1) @quantize_to_encoder.value def quantize_to_encoder(self, value, encoder): self.quantize_to_index = self._clamp_quantization_index( self.quantize_to_index + value) @quantize_amount_encoder.value def quantize_amount_encoder(self, value, encoder): self.quantize_amount = clamp(self.quantize_amount + value, 0.0, 1.0) @record_quantization_encoder.value def record_quantization_encoder(self, value, encoder): self.record_quantization_index = self._clamp_quantization_index( self.record_quantization_index + value) self._update_record_quantization() @record_quantization_toggle_button.toggled def record_quantization_toggle_button(self, value, button): self._update_record_quantization() @listens('swing_amount') def __on_swing_amount_changed(self): self.notify_swing_amount() @listens('midi_recording_quantization') def __on_record_quantization_changed(self): quant_value = self.song.midi_recording_quantization quant_on = quant_value != RecordingQuantization.rec_q_no_q if quant_value in QUANTIZATION_OPTIONS: self.record_quantization_index = QUANTIZATION_OPTIONS.index( quant_value) self.record_quantization_toggle_button.is_toggled = quant_on self.notify_record_quantization_enabled(quant_on) def _update_record_quantization(self): index = QUANTIZATION_OPTIONS[self.record_quantization_index] self.song.midi_recording_quantization = index if self.record_quantization_toggle_button.is_toggled else RecordingQuantization.rec_q_no_q