class ToggledTaggedSetting(TaggedSettingsComponent, ChannelizedSettingsBase): _set_attribute_tag_model = lambda self, a: str(a) split_toggle = ToggleButtonControl( toggled_color='MonoInstrument.SplitModeOnValue', untoggled_color='DefaultButton.Off') seq_toggle = ToggleButtonControl( toggled_color='MonoInstrument.SequencerModeOnValue', untoggled_color='DefaultButton.Off') def __init__(self, *a, **k): super(ToggledTaggedSetting, self).__init__(value_dict=[ 'none', 'seq', 'split', ], *a, **k) @split_toggle.toggled def split_toggle(self, toggled, button): self.value = 'none' if self.value == 'split' else 'split' self.update() @seq_toggle.toggled def seq_toggle(self, toggled, button): self.value = 'none' if self.value == 'seq' else 'seq' self.update() def _update_controls(self): self.split_toggle.is_toggled = bool(self.value is 'split') self.seq_toggle.is_toggled = bool(self.value is 'seq')
class ViewToggleComponent(Component): detail_view_toggle_button = ToggleButtonControl() main_view_toggle_button = ToggleButtonControl() def __init__(self, *a, **k): super(ViewToggleComponent, self).__init__(*a, **k) self.__on_detail_view_visibility_changed.subject = self.application.view self.__on_main_view_visibility_changed.subject = self.application.view self.__on_detail_view_visibility_changed() self.__on_main_view_visibility_changed() @detail_view_toggle_button.toggled def detail_view_toggle_button(self, is_toggled, _): self._show_or_hide_view(is_toggled, b'Detail') @main_view_toggle_button.toggled def main_view_toggle_button(self, is_toggled, _): self._show_or_hide_view(is_toggled, b'Session') def _show_or_hide_view(self, show_view, view_name): if show_view: self.application.view.show_view(view_name) else: self.application.view.hide_view(view_name) @listens(b'is_view_visible', b'Detail') def __on_detail_view_visibility_changed(self): self.detail_view_toggle_button.is_toggled = self.application.view.is_view_visible( b'Detail') @listens(b'is_view_visible', b'Session') def __on_main_view_visibility_changed(self): self.main_view_toggle_button.is_toggled = self.application.view.is_view_visible( b'Session')
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): 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): self._update_option_display() def _update_option_display(self): for index, option_name in enumerate(LENGTH_OPTION_NAMES): if index == self._fixed_length_setting.selected_index: prefix = consts.CHAR_SELECT if 1 else ' ' self.option_display_line[index] = prefix + option_name
class ViewToggleComponent(Component): detail_view_toggle_button = ToggleButtonControl(untoggled_color=u'View.DetailOff', toggled_color=u'View.DetailOn') main_view_toggle_button = ToggleButtonControl(untoggled_color=u'View.MainOff', toggled_color=u'View.MainOn') clip_view_toggle_button = ToggleButtonControl(untoggled_color=u'View.ClipOff', toggled_color=u'View.ClipOn') browser_view_toggle_button = ToggleButtonControl(untoggled_color=u'View.BrowserOff', toggled_color=u'View.BrowserOn') def __init__(self, *a, **k): super(ViewToggleComponent, self).__init__(*a, **k) self.__on_detail_view_visibility_changed.subject = self.application.view self.__on_main_view_visibility_changed.subject = self.application.view self.__on_clip_view_visibility_changed.subject = self.application.view self.__on_browser_view_visibility_changed.subject = self.application.view self.__on_detail_view_visibility_changed() self.__on_main_view_visibility_changed() self.__on_clip_view_visibility_changed() self.__on_browser_view_visibility_changed() @detail_view_toggle_button.toggled def detail_view_toggle_button(self, is_toggled, _): self._show_or_hide_view(is_toggled, u'Detail') @main_view_toggle_button.toggled def main_view_toggle_button(self, is_toggled, _): self._show_or_hide_view(is_toggled, u'Session') @clip_view_toggle_button.toggled def clip_view_toggle_button(self, is_toggled, _): self._show_or_hide_view(is_toggled, u'Detail/Clip') @browser_view_toggle_button.toggled def browser_view_toggle_button(self, is_toggled, _): self._show_or_hide_view(is_toggled, u'Browser') def _show_or_hide_view(self, show_view, view_name): if show_view: self.application.view.show_view(view_name) else: self.application.view.hide_view(view_name) @listens(u'is_view_visible', u'Detail') def __on_detail_view_visibility_changed(self): self.detail_view_toggle_button.is_toggled = self.application.view.is_view_visible(u'Detail') @listens(u'is_view_visible', u'Session') def __on_main_view_visibility_changed(self): self.main_view_toggle_button.is_toggled = self.application.view.is_view_visible(u'Session') @listens(u'is_view_visible', u'Detail/Clip') def __on_clip_view_visibility_changed(self): self.clip_view_toggle_button.is_toggled = self.application.view.is_view_visible(u'Detail/Clip') @listens(u'is_view_visible', u'Browser') def __on_browser_view_visibility_changed(self): self.browser_view_toggle_button.is_toggled = self.application.view.is_view_visible(u'Browser')
class ProfilingSettingsComponent(Component): show_qml_stats_button = ToggleButtonControl() 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 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 TransportComponent(TransportComponentBase): play_button = ToggleButtonControl(toggled_color='Transport.PlayOn', untoggled_color='Transport.PlayOff') capture_midi_button = ButtonControl() def __init__(self, *a, **k): (super(TransportComponent, self).__init__)(*a, **k) self._metronome_toggle.view_transform = lambda v: 'Transport.MetronomeOn' if v else 'Transport.MetronomeOff' self._TransportComponent__on_can_capture_midi_changed.subject = self.song self._TransportComponent__on_can_capture_midi_changed() @play_button.toggled def _on_play_button_toggled(self, is_toggled, _): self.song.is_playing = is_toggled if is_toggled: self.song.current_song_time = 0.0 @capture_midi_button.pressed def capture_midi_button(self, _): try: if self.song.can_capture_midi: self.song.capture_midi() except RuntimeError: pass @listens('can_capture_midi') def __on_can_capture_midi_changed(self): self.capture_midi_button.color = 'Transport.Capture{}'.format( 'On' if self.song.can_capture_midi else 'Off') def _update_button_states(self): super(TransportComponent, self)._update_button_states() self.continue_playing_button.color = 'Transport.Continue{}'.format( 'Off' if self.song.is_playing else 'On')
class MasterTrackComponent(Component): toggle_button = ToggleButtonControl() def __init__(self, tracks_provider=None, *a, **k): raise tracks_provider is not None or AssertionError 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() @listens('selected_item') def __on_selected_item_changed(self, *a): self._update_button_state() if not self._is_on_master(): self._previous_selection = self._tracks_provider.selected_item def _update_button_state(self): self.toggle_button.is_toggled = self._is_on_master() @toggle_button.toggled def toggle_button(self, toggled, button): if toggled: self._previous_selection = self._tracks_provider.selected_item self._tracks_provider.selected_item = self.song.master_track else: self._tracks_provider.selected_item = self._previous_selection self._update_button_state() def _is_on_master(self): return self._tracks_provider.selected_item == self.song.master_track
class DeviceComponent(DeviceComponentBase): prev_bank_button = ButtonControl(color=u'Action.Off', pressed_color=u'Action.On') next_bank_button = ButtonControl(color=u'Action.Off', pressed_color=u'Action.On') bank_name_display = TextDisplayControl() device_lock_button = ToggleButtonControl() def __init__(self, toggle_lock=None, *a, **k): super(DeviceComponent, self).__init__(*a, **k) assert toggle_lock is not None self._toggle_lock = toggle_lock self.__on_is_locked_to_device_changed.subject = self._device_provider self.__on_is_locked_to_device_changed() @prev_bank_button.pressed def prev_bank_button(self, _): self._scroll_bank(-1) @next_bank_button.pressed def next_bank_button(self, _): self._scroll_bank(1) @device_lock_button.toggled def device_lock_button(self, toggled, _): self._toggle_lock() self._update_device_lock_button() def _create_parameter_info(self, parameter, name): return ParameterInfo(parameter=parameter, name=name, default_encoder_sensitivity=1.0) def _set_bank_index(self, index): super(DeviceComponent, self)._set_bank_index(index) self._update_bank_name_display() def _scroll_bank(self, offset): bank = self._bank if bank: new_index = clamp(bank.index + offset, 0, bank.bank_count() - 1) self._device_bank_registry.set_device_bank(self.device(), new_index) def _update_bank_name_display(self): bank_name = u'' device = self.device() if liveobj_valid(device): device_bank_names = self._banking_info.device_bank_names(device) if device_bank_names: bank_name = device_bank_names[ self._device_bank_registry.get_device_bank(device)] self.bank_name_display[0] = bank_name def _update_device_lock_button(self): self.device_lock_button.is_toggled = self._device_provider.is_locked_to_device @listens(u'is_locked_to_device') def __on_is_locked_to_device_changed(self): self._update_device_lock_button()
class ExperimentalSettingsComponent(Component): new_waveform_navigation_button = ToggleButtonControl() 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')
class ActionsComponent(Component): u""" Simple component that provides undo/redo, record quantization toggle and clip quantization handling. """ undo_button = ButtonControl(**ACTION_BUTTON_COLORS) redo_button = ButtonControl(color=u'Misc.Shift', pressed_color=u'Misc.ShiftOn', disabled_color=u'DefaultButton.Disabled') quantization_on_button = ToggleButtonControl(untoggled_color=u'Misc.Shift', toggled_color=u'Misc.ShiftOn') def __init__(self, *a, **k): self.suppressing_control_notifications = BooleanContext() super(ActionsComponent, self).__init__(*a, **k) self._record_quantization = RecordingQuantization.rec_q_sixtenth self._on_record_quantization_changed_in_live.subject = self.song self._on_record_quantization_changed_in_live() self._metronome_toggle = ToggleComponent(u'metronome', self.song) def control_notifications_enabled(self): return self.is_enabled() and not self.suppressing_control_notifications def quantize_clip(self, clip): raise isinstance(clip, Live.Clip.Clip) or AssertionError clip.quantize(self._record_quantization, 1.0) @undo_button.pressed def undo_button(self, button): if self.song.can_undo: self.song.undo() @redo_button.pressed def redo_button(self, button): if self.song.can_redo: self.song.redo() @quantization_on_button.toggled def quantization_on_button(self, is_toggled, button): self._record_quantization_on = is_toggled self.song.midi_recording_quantization = self._record_quantization if self._record_quantization_on else RecordingQuantization.rec_q_no_q @listens(u'midi_recording_quantization') def _on_record_quantization_changed_in_live(self): quant_value = self.song.midi_recording_quantization quant_on = quant_value != RecordingQuantization.rec_q_no_q if quant_on: self._record_quantization = quant_value self._record_quantization_on = quant_on with self.suppressing_control_notifications(): self.quantization_on_button.is_toggled = quant_on def set_metronome_button(self, button): self._metronome_toggle.set_toggle_button(button) def update(self): super(ActionsComponent, self).update() self._metronome_toggle.update()
class SessionRecordingComponent(SessionRecordingComponentBase): mpc_automation_toggle = ToggleButtonControl(toggled_color=u'Automation.On', untoggled_color=u'Automation.Off') def _on_session_automation_record_changed(self): super(SessionRecordingComponent, self)._on_session_automation_record_changed() self.mpc_automation_toggle.is_toggled = self.song.session_automation_record @mpc_automation_toggle.toggled def mpc_automation_toggle(self, is_toggled, _): self.song.session_automation_record = is_toggled
class TouchStripPitchModComponent(Component, Messenger): touch_strip_toggle = ToggleButtonControl() 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 self._update_touch_strip() def _update_touch_strip(self): if self._touch_strip: self._touch_strip.behaviour = MODWHEEL_BEHAVIOUR if self.touch_strip_toggle.is_toggled else DEFAULT_BEHAVIOUR @touch_strip_toggle.toggled def touch_strip_toggle(self, toggled, button): self._update_touch_strip() self._update_touch_strip_indication() self.show_notification( consts.MessageBoxText.TOUCHSTRIP_MODWHEEL_MODE if toggled else consts.MessageBoxText.TOUCHSTRIP_PITCHBEND_MODE) def set_touch_strip_indication(self, control): self._touch_strip_indication = control self._update_touch_strip_indication() 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 1 else TouchStripStates.STATE_HALF for _ in xrange(self._touch_strip_indication.state_count) if self.touch_strip_toggle.is_toggled ]) def update(self): super(TouchStripPitchModComponent, self).update() if self.is_enabled(): self._update_touch_strip() self._update_touch_strip_indication()
class ToggledChannelizedSettingsComponent(ChannelizedSettingsBase): toggle_button = ToggleButtonControl() def __init__(self, toggled_color = 'DefaultButton.On', untoggled_color = 'DefaultButton.Off', *a, **k): super(ToggledChannelizedSettingsComponent, self).__init__(value_dict = [False, True], *a, **k) self.toggle_button.toggled_color = toggled_color self.toggle_button.untoggled_color = untoggled_color @toggle_button.toggled def toggle_button(self, toggled, button): self.index = int(toggled) self.update() def _update_controls(self): self.toggle_button.is_toggled = bool(self._values[self._channel])
class DisplayDebugSettingsComponent(Component): show_row_spaces_button = ToggleButtonControl() show_row_margins_button = ToggleButtonControl() show_row_middle_button = ToggleButtonControl() show_button_spaces_button = ToggleButtonControl() show_unlit_button_button = ToggleButtonControl() show_lit_button_button = ToggleButtonControl() 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, u'show_row_spaces') self.show_row_margins_button.connect_property(settings, u'show_row_margins') self.show_row_middle_button.connect_property(settings, u'show_row_middle') self.show_button_spaces_button.connect_property(settings, u'show_button_spaces') self.show_unlit_button_button.connect_property(settings, u'show_unlit_button') self.show_lit_button_button.connect_property(settings, u'show_lit_button')
class QuantizationComponent(Component): quantization_toggle_button = ToggleButtonControl( untoggled_color='Quantization.Off', toggled_color='Quantization.On') def __init__(self, *a, **k): super(QuantizationComponent, self).__init__(*a, **k) self._record_quantization = RecordingQuantization.rec_q_sixtenth self.__on_record_quantization_changed.subject = self.song self.__on_record_quantization_changed() def quantize_clip(self, clip): clip.quantize(self._record_quantization, 1.0) @quantization_toggle_button.toggled def quantization_toggle_button(self, is_toggled, _): self.song.midi_recording_quantization = self._record_quantization if is_toggled else RecordingQuantization.rec_q_no_q @listens('midi_recording_quantization') def __on_record_quantization_changed(self): quantization_on = self.song.midi_recording_quantization != RecordingQuantization.rec_q_no_q if quantization_on: self._record_quantization = self.song.midi_recording_quantization self.quantization_toggle_button.is_toggled = quantization_on
class LoopSettingsControllerComponent(LoopSettingsControllerComponentBase): __events__ = ('looping', 'loop_parameters', 'zoom') zoom_encoder = MappedControl() loop_button = ToggleButtonControl(toggled_color='Clip.Option', untoggled_color='Clip.OptionDisabled') def __init__(self, zoom_handler=None, *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') ] 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() @loop_button.toggled def loop_button(self, toggled, button): self._loop_model.looping = toggled @property def looping(self): return self._loop_model.looping if self.clip else False @property def loop_parameters(self): if not liveobj_valid(self.clip): return [] parameters = self._looping_settings if self.looping else self._non_looping_settings return [self.zoom] + parameters if self.zoom else parameters @property def zoom(self): return getattr(self.clip, 'zoom', None) if liveobj_valid( self.clip) else None @listenable_property def processed_zoom_requests(self): return self._processed_zoom_requests @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._looping_settings[1].recording = recording self._non_looping_settings[1].recording = recording @listens('looping') def __on_looping_changed(self): self._update_and_notify() def _update_loop_button(self): self.loop_button.enabled = liveobj_valid(self.clip) if liveobj_valid(self.clip): self.loop_button.is_toggled = self._loop_model.looping def _on_clip_changed(self): 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._connect_encoder() def _update_and_notify(self): self._update_loop_button() self.notify_looping() self.notify_loop_parameters() self.notify_zoom() def _connect_encoder(self): self.zoom_encoder.mapped_parameter = self.zoom def set_zoom_encoder(self, encoder): self.zoom_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 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 SimpleDeviceParameterComponent(Component): bank_select_buttons = FixedRadioButtonGroup(control_count=8, unchecked_color='Mode.Device.Bank.Available', checked_color='Mode.Device.Bank.Selected') device_lock_button = ToggleButtonControl() @depends(device_provider=None) def __init__(self, device_provider=None, device_bank_registry=None, toggle_lock=None, use_parameter_banks=False, *a, **k): (super(SimpleDeviceParameterComponent, self).__init__)(*a, **k) self._toggle_lock = toggle_lock self._use_parameter_banks = use_parameter_banks self._device = None self._banks = [] self._bank_index = 0 self._parameter_controls = None self._empty_control_slots = self.register_disconnectable(EventObject()) self._device_bank_registry = device_bank_registry self._device_provider = device_provider self._SimpleDeviceParameterComponent__on_provided_device_changed.subject = device_provider self._SimpleDeviceParameterComponent__on_provided_device_changed() if toggle_lock: self._SimpleDeviceParameterComponent__on_is_locked_to_device_changed.subject = self._device_provider self._SimpleDeviceParameterComponent__on_is_locked_to_device_changed() @bank_select_buttons.checked def bank_select_buttons(self, button): self._on_bank_select_button_checked(button) def _on_bank_select_button_checked(self, button): self.bank_index = button.index @bank_select_buttons.value def bank_select_buttons(self, value, _): if not value: self._on_bank_select_button_released() def _on_bank_select_button_released(self): pass @device_lock_button.toggled def device_lock_button(self, *_): self._on_device_lock_button_toggled() def _on_device_lock_button_toggled(self): self._toggle_lock() self._update_device_lock_button() @property def bank_index(self): if self._use_parameter_banks: return self._bank_index return 0 @bank_index.setter def bank_index(self, value): self._bank_index = self._clamp_to_bank_size(value) if self._device_bank_registry: self._device_bank_registry.set_device_bank(self._device, self._bank_index) self.update() def _clamp_to_bank_size(self, value): return clamp(value, 0, self.num_banks - 1) @property def selected_bank(self): if self.num_banks: return self._banks[(self._bank_index or 0)] return [] @property def num_banks(self): return len(self._banks) def set_parameter_controls(self, controls): for control in self._parameter_controls or []: release_control(control) self._parameter_controls = controls self.update() @listens('device') def __on_provided_device_changed(self): for control in self._parameter_controls or []: release_control(control) self._device = self._device_provider.device self._SimpleDeviceParameterComponent__on_bank_changed.subject = self._device_bank_registry if self._device_bank_registry: self._bank_index = self._device_bank_registry.get_device_bank(self._device) self.update() else: self.bank_index = 0 @listens('device_bank') def __on_bank_changed(self, device, bank): if device == self._device: self.bank_index = bank @listens('is_locked_to_device') def __on_is_locked_to_device_changed(self): self._update_device_lock_button() def update(self): super(SimpleDeviceParameterComponent, self).update() if self.is_enabled(): self._update_parameter_banks() self._update_bank_select_buttons() self._empty_control_slots.disconnect() if liveobj_valid(self._device): self._connect_parameters() else: self._disconnect_parameters() else: self._disconnect_parameters() def _disconnect_parameters(self): for control in self._parameter_controls or []: release_control(control) self._empty_control_slots.register_slot(control, nop, 'value') def _connect_parameters(self): for control, parameter in zip_longest(self._parameter_controls or [], self.selected_bank): if liveobj_valid(control): if liveobj_valid(parameter): control.connect_to(parameter) else: control.release_parameter() self._empty_control_slots.register_slot(control, nop, 'value') def _update_parameter_banks(self): if liveobj_valid(self._device): if self._use_parameter_banks: self._banks = parameter_banks(self._device) else: self._banks = [ best_of_parameter_bank(self._device)] else: self._banks = [] self._bank_index = self._clamp_to_bank_size(self._bank_index) def _update_bank_select_buttons(self): self.bank_select_buttons.active_control_count = self.num_banks if self.bank_index < self.num_banks: self.bank_select_buttons[self.bank_index].is_checked = True def _update_device_lock_button(self): self.device_lock_button.is_toggled = self._device_provider.is_locked_to_device
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) 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='Scales.OptionOn', unchecked_color='Scales.OptionOff') 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') 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 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.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_root_note_changed(note_layout.root_note) self.__on_scale_changed(note_layout.scale) 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('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('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() @close_button.pressed def close_button(self, button): self.notify_close() @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
class SimpleDeviceParameterComponent(Component): prev_bank_button = ButtonControl() next_bank_button = ButtonControl() device_lock_button = ToggleButtonControl() device_on_off_button = ToggleButtonControl() device_name_display = DisplayControl() @depends(device_provider=None) def __init__(self, device_provider=None, device_bank_registry=None, toggle_lock=None, *a, **k): super(SimpleDeviceParameterComponent, self).__init__(*a, **k) self._toggle_lock = toggle_lock self._device = None self._banks = [] self._bank_index = 0 self._parameter_controls = None self._empty_control_slots = self.register_disconnectable(EventObject()) self._device_bank_registry = device_bank_registry self._device_provider = device_provider self._device_name_slot = self.register_slot( None, self._update_device_name_display, u'name') self.__on_provided_device_changed.subject = device_provider self.__on_provided_device_changed() self._display_bank_name_task = self._tasks.add( task.sequence(task.run(self._display_bank_name), task.wait(BANK_NAME_DISPLAY_DURATION), task.run(self._update_device_name_display))) self._display_bank_name_task.kill() if toggle_lock: self.__on_is_locked_to_device_changed.subject = self._device_provider self.__on_is_locked_to_device_changed() @device_lock_button.toggled def device_lock_button(self, *_): self._on_device_lock_button_toggled() @device_on_off_button.toggled def device_on_off_button(self, is_toggled, _): parameter = self._on_off_parameter() if parameter is not None: parameter.value = float(is_toggled) def _on_device_lock_button_toggled(self): self._toggle_lock() self._update_device_lock_button() @prev_bank_button.pressed def prev_bank_button(self, _): self.bank_index = self._bank_index - 1 @next_bank_button.pressed def next_bank_button(self, _): self.bank_index = self._bank_index + 1 @property def bank_index(self): return self._bank_index @bank_index.setter def bank_index(self, value): prev_bank_index = self._bank_index self._bank_index = self._clamp_to_bank_size(value) if prev_bank_index != self._bank_index: self._display_bank_name_task.restart() if self._device_bank_registry: self._device_bank_registry.set_device_bank(self._device, self._bank_index) self.update() def _clamp_to_bank_size(self, value): return clamp(value, 0, self.num_banks - 1) @property def selected_bank(self): if self.num_banks: return self._banks[self._bank_index or 0] return [] @property def num_banks(self): return len(self._banks) def set_parameter_controls(self, controls): for control in self._parameter_controls or []: release_control(control) self._parameter_controls = controls self.update() @listens(u'device') def __on_provided_device_changed(self): for control in self._parameter_controls or []: release_control(control) self._device = self._device_provider.device self.__on_bank_changed.subject = self._device_bank_registry if self._device_bank_registry: self._bank_index = self._device_bank_registry.get_device_bank( self._device) self.update() else: self.bank_index = 0 self._device_name_slot.subject = self._device self._update_device_name_display() @listens(u'device_bank') def __on_bank_changed(self, device, bank): if device == self._device: self.bank_index = bank @listens(u'is_locked_to_device') def __on_is_locked_to_device_changed(self): self._update_device_lock_button() @listens(u'value') def __on_device_on_off_changed(self): self._update_device_on_off_button() def update(self): super(SimpleDeviceParameterComponent, self).update() if self.is_enabled(): self._update_parameter_banks() self._update_bank_navigation_buttons() self._empty_control_slots.disconnect() self.__on_device_on_off_changed.subject = self._on_off_parameter() self._update_device_on_off_button() if liveobj_valid(self._device): self._connect_parameters() else: self._disconnect_parameters() else: self._disconnect_parameters() def _disconnect_parameters(self): for control in self._parameter_controls or []: release_control(control) self._empty_control_slots.register_slot(control, nop, u'value') def _connect_parameters(self): for control, parameter in izip_longest(self._parameter_controls or [], self.selected_bank): if liveobj_valid(control): if liveobj_valid(parameter): control.connect_to(parameter) else: control.release_parameter() self._empty_control_slots.register_slot( control, nop, u'value') def _on_off_parameter(self): if liveobj_valid(self._device): for p in self._device.parameters: if p.name.startswith(u'Device On') and liveobj_valid( p) and p.is_enabled: return p def _update_parameter_banks(self): if liveobj_valid(self._device): self._banks = parameter_banks(self._device) else: self._banks = [] self._bank_index = self._clamp_to_bank_size(self._bank_index) def _update_bank_navigation_buttons(self): self.prev_bank_button.enabled = self.bank_index > 0 self.next_bank_button.enabled = self.bank_index < self.num_banks - 1 def _update_device_lock_button(self): self.device_lock_button.is_toggled = self._device_provider.is_locked_to_device def _update_device_on_off_button(self): parameter = self._on_off_parameter() self.device_on_off_button.enabled = parameter is not None if parameter is not None: self.device_on_off_button.is_toggled = parameter.value > 0 def _update_device_name_display(self): self.device_name_display.message = self._device.name if liveobj_valid( self._device) else u' - ' def _display_bank_name(self): names = parameter_bank_names(self._device) if self.bank_index < len(names): self.device_name_display.message = names[self.bank_index]
class ActionsComponent(Component): actions_display = TextDisplayControl(segments=ACTION_NAMES) actions_color_fields = control_list(ColorSysexControl, len(ACTION_NAMES)) actions_selection_fields = control_list(BinaryControl, len(ACTION_NAMES)) undo_button = ButtonControl(color='Action.Available') redo_button = ButtonControl(color='Action.Available') capture_midi_button = ButtonControl() metronome_button = ToggleButtonControl( toggled_color='Transport.MetronomeOn', untoggled_color='Transport.MetronomeOff') def __init__(self, *a, **k): super(ActionsComponent, self).__init__(*a, **k) self.__on_can_capture_midi_changed.subject = self.song self.__on_can_capture_midi_changed() self.actions_color_fields[ METRONOME_DISPLAY_INDEX].color = 'Transport.MetronomeOn' self.actions_color_fields[ UNDO_DISPLAY_INDEX].color = 'Action.Available' self.actions_color_fields[ REDO_DISPLAY_INDEX].color = 'Action.Available' self.__on_metronome_changed.subject = self.song self.__on_metronome_changed() @property def capture_midi_display(self): return self.actions_display[CAPTURE_DISPLAY_INDEX] @capture_midi_display.setter def capture_midi_display(self, string): self.actions_display[CAPTURE_DISPLAY_INDEX] = string @property def capture_midi_color_field(self): return self.actions_color_fields[CAPTURE_DISPLAY_INDEX] @property def capture_midi_selection_field(self): return self.actions_selection_fields[CAPTURE_DISPLAY_INDEX] @undo_button.pressed def undo_button(self, _): if self.song.can_undo: self.song.undo() @redo_button.pressed def redo_button(self, _): if self.song.can_redo: self.song.redo() @capture_midi_button.pressed def capture_midi_button(self, _): try: self.song.capture_midi() except RuntimeError: pass @metronome_button.toggled def metronome_button(self, toggled, _): self.song.metronome = toggled @listens('can_capture_midi') def __on_can_capture_midi_changed(self): self._update_capture_midi_controls() @listens('metronome') def __on_metronome_changed(self): self._update_metronome_controls() def _update_capture_midi_controls(self): can_capture_midi = self.song.can_capture_midi self.capture_midi_button.enabled = can_capture_midi self.capture_midi_display = 'capture' if can_capture_midi else '' self.capture_midi_color_field.color = 'DefaultButton.On' if can_capture_midi else 'DefaultButton.Disabled' self.capture_midi_selection_field.is_on = can_capture_midi def _update_metronome_controls(self): metronome = self.song.metronome self.metronome_button.is_toggled = metronome self.actions_selection_fields[ METRONOME_DISPLAY_INDEX].is_on = metronome
class ScrollingChannelizedSettingsComponent(ChannelizedSettingsBase): up_button = ButtonControl(repeat=True) down_button = ButtonControl(repeat=True) bank_up_button = ButtonControl(repeat=True) bank_down_button = ButtonControl(repeat=True) shift_toggle = ToggleButtonControl() def __init__(self, bank_increment = 16, on_color = 'DefaultButton.On', off_color = 'DefaultButton.Off', bank_on_color = 'DefaultButton.On', bank_off_color = 'DefaultButton.Off', *a, **k): super(ScrollingChannelizedSettingsComponent, self).__init__(*a, **k) self._bank_increment = bank_increment self.up_button.color = on_color self.up_button.disabled_color = off_color self.down_button.color = on_color self.down_button.disabled_color = off_color self.shift_toggle.toggled_color = on_color self.shift_toggle.untoggled_color = off_color @up_button.pressed def up_button(self, button): if self.shift_toggle.is_toggled: value = self._values[self._channel] self.index = clamp(value+self._bank_increment, 0, (self._range-1)) else: value = self._values[self._channel] self.index = clamp(value+1, 0, (self._range-1)) @down_button.pressed def down_button(self, button): if self.shift_toggle.is_toggled: value = self._values[self._channel] self.index = clamp(value-self._bank_increment, 0, (self._range-1)) else: value = self._values[self._channel] self.index = clamp(value-1, 0, (self._range-1)) @bank_up_button.pressed def bank_up_button(self, button): value = self._values[self._channel] self.index = clamp(value+self._bank_increment, 0, (self._range-1)) @bank_down_button.pressed def bank_down_button(self, button): value = self._values[self._channel] self.index = clamp(value-self._bank_increment, 0, (self._range-1)) def _update_controls(self): at_beginning = self.index == 0 at_end = self.index == (self._range - 1) self.up_button.enabled = not at_end self.down_button.enabled = not at_beginning self.bank_up_button.enabled = not at_end self.bank_down_button.enabled = not at_beginning def buttons_are_pressed(self): return self.up_button.is_pressed or self.down_button.is_pressed or self.bank_up_button.is_pressed or self.bank_down_button.is_pressed
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
class InstrumentComponent(PlayableComponent, CompoundComponent, Slideable, Messenger): """ Class that sets up the button matrix as a piano, using different selectable layouts for the notes. """ __events__ = ('pattern', ) touch_strip_toggle = ToggleButtonControl() matrix = control_matrix(PadControl, pressed_color='Instrument.NoteAction') def __init__(self, note_layout=None, *a, **k): raise note_layout is not None or AssertionError super(InstrumentComponent, self).__init__(*a, **k) self._note_layout = note_layout self._delete_button = None self._first_note = self.page_length * 3 + self.page_offset self._last_page_length = self.page_length self._delete_button = None self._last_page_offset = self.page_offset self._touch_strip = None self._touch_strip_indication = None self._detail_clip = None self._has_notes = [False] * 128 self._has_notes_pattern = self._get_pattern(0) 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'): self.register_slot(self._note_layout, self._on_note_layout_changed, event) self._update_pattern() def set_detail_clip(self, clip): if clip != self._detail_clip: self._detail_clip = clip self._on_clip_notes_changed.subject = clip self._on_loop_start_changed.subject = clip self._on_loop_end_changed.subject = clip self._on_clip_notes_changed() @listens('notes') def _on_clip_notes_changed(self): if self._detail_clip: 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) for note in notes: self._has_notes[note[0]] = True self.notify_contents() @listens('loop_start') def _on_loop_start_changed(self): self._on_loop_selection_changed() @listens('loop_end') def _on_loop_end_changed(self): self._on_loop_selection_changed() def _on_loop_selection_changed(self): self._on_clip_notes_changed() def contents(self, index): if self._detail_clip: note = self._has_notes_pattern[index].index return self._has_notes[note] if note is not None else False return False @property def page_length(self): return len( self._note_layout.notes) if self._note_layout.is_in_key else 12 @property def position_count(self): if not self._note_layout.is_in_key: return 139 else: offset = self.page_offset octaves = 11 if self._note_layout.notes[0] < 8 else 10 return offset + len(self._note_layout.notes) * octaves 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: return len(self._note_layout.notes) - index_if( lambda n: n >= 12, self._note_layout.notes) @property def page_offset(self): return 0 if self._note_layout.is_fixed else self._first_scale_note_offset( ) def _get_position(self): return self._first_note def _set_position(self, note): self._first_note = note self._update_pattern() self._update_matrix() self.notify_position() position = property(_get_position, _set_position) @property def pattern(self): return self._pattern @matrix.pressed def matrix(self, button): self._on_matrix_pressed(button) def _on_matrix_pressed(self, button): if self._delete_button and self._delete_button.is_pressed(): pitch = self._get_note_info_for_coordinate(button.coordinate).index if pitch and self._detail_clip: self._do_delete_pitch(pitch) def _do_delete_pitch(self, pitch): clip = self._detail_clip if clip: note_name = pitch_index_to_string(pitch) loop_length = clip.loop_end - clip.loop_start clip.remove_notes(clip.loop_start, pitch, loop_length, 1) self.show_notification(consts.MessageBoxText.DELETE_NOTES % note_name) @listens('value') def _on_delete_value(self, value): self._set_control_pads_from_script(bool(value)) @touch_strip_toggle.toggled def touch_strip_toggle(self, toggled, button): self._update_touch_strip() self._update_touch_strip_indication() self.show_notification( consts.MessageBoxText.TOUCHSTRIP_MODWHEEL_MODE if toggled else consts.MessageBoxText.TOUCHSTRIP_PITCHBEND_MODE) def set_touch_strip(self, control): self._touch_strip = control self._update_touch_strip() def _update_touch_strip(self): if self._touch_strip: self._touch_strip.behaviour = MODWHEEL_BEHAVIOUR if self.touch_strip_toggle.is_toggled else DEFAULT_BEHAVIOUR def set_touch_strip_indication(self, control): self._touch_strip_indication = control self._update_touch_strip_indication() 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) ]) def set_note_strip(self, strip): self._touch_slider.set_scroll_strip(strip) def set_octave_strip(self, strip): self._touch_slider.set_page_strip(strip) def set_octave_up_button(self, button): self._slider.set_scroll_page_up_button(button) def set_octave_down_button(self, button): self._slider.set_scroll_page_down_button(button) def set_scale_up_button(self, button): self._slider.set_scroll_up_button(button) def set_scale_down_button(self, button): self._slider.set_scroll_down_button(button) def set_aftertouch_control(self, control): self._aftertouch_control = control self._update_aftertouch() def set_delete_button(self, button): self._delete_button = button self._on_delete_value.subject = button self._set_control_pads_from_script(button and button.is_pressed()) def _align_first_note(self): self._first_note = self.page_offset + ( self._first_note - self._last_page_offset) * float( self.page_length) / float(self._last_page_length) if self._first_note >= self.position_count: self._first_note -= self.page_length self._last_page_length = self.page_length self._last_page_offset = self.page_offset def _on_note_layout_changed(self, _): self._update_scale() def _update_scale(self): self._align_first_note() self._update_pattern() self._update_matrix() self.notify_position_count() self.notify_position() self.notify_contents() def update(self): super(InstrumentComponent, self).update() if self.is_enabled(): self._update_matrix() self._update_aftertouch() self._update_touch_strip() self._update_touch_strip_indication() def _update_pattern(self): self._pattern = self._get_pattern() self._has_notes_pattern = self._get_pattern(0) self.notify_pattern() def _invert_and_swap_coordinates(self, coordinates): return (coordinates[1], self.width - 1 - coordinates[0]) def _get_note_info_for_coordinate(self, coordinate): x, y = self._invert_and_swap_coordinates(coordinate) return self.pattern.note(x, y) def _update_button_color(self, button): note_info = self._get_note_info_for_coordinate(button.coordinate) button.color = 'Instrument.' + note_info.color def _button_should_be_enabled(self, button): return self._get_note_info_for_coordinate( button.coordinate).index != None def _note_translation_for_button(self, button): note_info = self._get_note_info_for_coordinate(button.coordinate) return (note_info.index, note_info.channel) def _set_button_control_properties(self, button): super(InstrumentComponent, self)._set_button_control_properties(button) button.sensitivity_profile = 'default' if self._takeover_pads else 'instrument' def _update_matrix(self): self._update_control_from_script() self._update_led_feedback() self._update_note_translations() 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 octave = first_note / self.page_length offset = first_note % self.page_length - self._first_scale_note_offset( ) if interval == None: interval = 8 elif not self._note_layout.is_in_key: interval = [0, 2, 4, 5, 7, 9, 10, 11][interval] if self._note_layout.is_horizontal: steps = [1, interval] origin = [offset, 0] else: 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) def _update_aftertouch(self): if self.is_enabled() and self._aftertouch_control != None: self._aftertouch_control.send_value('mono')
class BrowserComponent(Component): u""" Component for controlling the Live library browser. It has 4 browsing columns that are controlled by encoders and state buttons. The contents of these lists are provided by a browser model -- see BrowserModel and derivatives. """ __events__ = (u'load_item',) NUM_COLUMNS = 4 COLUMN_SIZE = 4 enter_button = ButtonControl(**consts.SIDE_BUTTON_COLORS) exit_button = ButtonControl(**consts.SIDE_BUTTON_COLORS) shift_button = ButtonControl() prehear_button = ToggleButtonControl(toggled_color=u'Browser.Prehear', untoggled_color=u'Browser.PrehearOff') def __init__(self, browser = None, make_browser_model = None, preferences = dict(), *a, **k): super(BrowserComponent, self).__init__(*a, **k) assert make_browser_model is not None self._browser = browser or self.application.browser self._browser_model = EmptyBrowserModel(browser=self._browser) self._make_browser_model = make_browser_model num_data_sources = self.NUM_COLUMNS * self.COLUMN_SIZE self._data_sources = map(DisplayDataSource, (u'',) * num_data_sources) self._last_loaded_item = None self._default_item_formatter = DefaultItemFormatter() self._list_components = [ ListComponent(parent=self) for _ in xrange(self.NUM_COLUMNS) ] for i, component in enumerate(self._list_components): component.do_trigger_action = lambda item: self._do_load_item(item) component.last_action_item = lambda : self._last_loaded_item component.item_formatter = partial(self._item_formatter, i) self._preferences = preferences self._select_buttons = [] self._state_buttons = [] self._encoder_controls = [] self._on_selected_item.replace_subjects(self._list_components) self._on_list_item_action.replace_subjects(self._list_components) self._on_hotswap_target_changed.subject = self._browser self._on_filter_type_changed.subject = self._browser self._on_browser_full_refresh.subject = self._browser self._scroll_offset = 0 self._max_scroll_offset = 0 self._max_hierarchy = 0 self._last_filter_type = None self._skip_next_preselection = False self._browser_model_dirty = True self._on_content_lists_changed() self.prehear_button.is_toggled = preferences.setdefault(u'browser_prehear', True) self._last_selected_item = None def disconnect(self): self._last_selected_item = None super(BrowserComponent, self).disconnect() def set_display_line1(self, display): self.set_display_line_with_index(display, 0) def set_display_line2(self, display): self.set_display_line_with_index(display, 1) def set_display_line3(self, display): self.set_display_line_with_index(display, 2) def set_display_line4(self, display): self.set_display_line_with_index(display, 3) def set_display_line_with_index(self, display, index): if display: sources = self._data_sources[index::self.COLUMN_SIZE] display.set_data_sources(sources) def set_select_buttons(self, buttons): for button in buttons or []: if button: button.reset() self._on_select_matrix_value.subject = buttons or None self._select_buttons = buttons buttons = buttons or (None, None, None, None, None, None, None, None) for component, button in izip(self._list_components, buttons[1::2]): self._set_button_if_enabled(component, u'action_button', button) for component, button in izip(self._list_components, buttons[::2]): if self.shift_button.is_pressed: self._set_button_if_enabled(component, u'prev_page_button', button) self._set_button_if_enabled(component, u'select_prev_button', None) else: self._set_button_if_enabled(component, u'prev_page_button', None) self._set_button_if_enabled(component, u'select_prev_button', button) def set_state_buttons(self, buttons): for button in buttons or []: if button: button.reset() self._on_state_matrix_value.subject = buttons or None self._state_buttons = buttons buttons = buttons or (None, None, None, None, None, None, None, None) for component, button in izip(self._list_components, buttons[::2]): if self.shift_button.is_pressed: self._set_button_if_enabled(component, u'next_page_button', button) self._set_button_if_enabled(component, u'select_next_button', None) else: self._set_button_if_enabled(component, u'next_page_button', None) self._set_button_if_enabled(component, u'select_next_button', button) for button in buttons[1::2]: if button and self.is_enabled(): button.set_light(u'DefaultButton.Disabled') @shift_button.value def shift_button(self, value, control): self.set_select_buttons(self._select_buttons) self.set_state_buttons(self._state_buttons) def _set_button_if_enabled(self, component, name, button): control = getattr(component, name) if component.is_enabled(explicit=True): control.set_control_element(button) else: control.set_control_element(None) if button and self.is_enabled(): button.set_light(u'DefaultButton.Disabled') def set_encoder_controls(self, encoder_controls): if encoder_controls: num_active_lists = len(self._browser_model.content_lists) - self._scroll_offset num_assignable_lists = min(num_active_lists, len(encoder_controls) / 2) index = 0 for component in self._list_components[:num_assignable_lists - 1]: component.encoders.set_control_element(encoder_controls[index:index + 2]) index += 2 self._list_components[num_assignable_lists - 1].encoders.set_control_element(encoder_controls[index:]) else: for component in self._list_components: component.encoders.set_control_element([]) self._encoder_controls = encoder_controls def update(self): super(BrowserComponent, self).update() if self.is_enabled(): self.set_state_buttons(self._state_buttons) self.set_select_buttons(self._select_buttons) self._update_browser_model() else: self._browser.stop_preview() def reset_load_memory(self): self._update_load_memory(None) def _do_load_item(self, item): self.do_load_item(item) self._update_load_memory(item) self._skip_next_preselection = True def reset_skip_next_preselection(): self._skip_next_preselection = False self._tasks.add(task.run(reset_skip_next_preselection)) def _update_load_memory(self, item): self._last_loaded_item = item for component in self._list_components: component.update() def do_load_item(self, item): item.action() self.notify_load_item(item.content) def back_to_top(self): self._set_scroll_offset(0) def _set_scroll_offset(self, offset): self._scroll_offset = offset self._on_content_lists_changed() scrollable_list = self._list_components[-1].scrollable_list if scrollable_list: scrollable_list.request_notify_item_activated() def _update_navigation_button_state(self): self.exit_button.enabled = self._scroll_offset > 0 self.enter_button.enabled = self._scroll_offset < self._max_scroll_offset def _shorten_item_name(self, shortening_limit, list_index, item_name): u""" Creates the name of an item shortened by removing words from the parents name """ def is_short_enough(item_name): return len(item_name) <= 9 content_lists = self._browser_model.content_lists parent_lists = reversed(content_lists[max(0, list_index - 3):list_index]) for content_list in parent_lists: if is_short_enough(item_name): break parent_name = unicode(content_list.selected_item) stems = split_stem(parent_name) for stem in stems: short_name = make_stem_cleaner(stem)(item_name) short_name = full_strip(short_name) item_name = short_name if len(short_name) > 4 else item_name if is_short_enough(item_name): break if len(item_name) >= shortening_limit and item_name[-1] == consts.CHAR_ELLIPSIS: return item_name[:-1] return item_name def _item_formatter(self, depth, index, item, action_in_progress): display_string = u'' separator_length = len(self._data_sources[self.COLUMN_SIZE * depth].separator) shortening_limit = 16 - separator_length if item: item_name = u'Loading...' if action_in_progress else self._shorten_item_name(shortening_limit, depth + self._scroll_offset, unicode(item)) display_string = consts.CHAR_SELECT if item and item.is_selected else u' ' display_string += item_name if depth == len(self._list_components) - 1 and item.is_selected and self._scroll_offset < self._max_hierarchy: display_string = string.ljust(display_string, consts.DISPLAY_LENGTH / 4 - 1) shortening_limit += 1 display_string = display_string[:shortening_limit] + consts.CHAR_ARROW_RIGHT if depth == 0 and self._scroll_offset > 0: prefix = consts.CHAR_ARROW_LEFT if index == 0 else u' ' display_string = prefix + display_string return display_string[:shortening_limit + 1] @enter_button.pressed def enter_button(self, control): self._set_scroll_offset(min(self._max_scroll_offset, self._scroll_offset + 1)) @exit_button.pressed def exit_button(self, control): self._set_scroll_offset(max(0, self._scroll_offset - 1)) @prehear_button.toggled def prehear_button(self, toggled, button): if not toggled: self._browser.stop_preview() elif self._last_selected_item is not None: self._last_selected_item.preview() self._preferences[u'browser_prehear'] = toggled @listens(u'hotswap_target') def _on_hotswap_target_changed(self): if not self._skip_next_preselection: self._set_scroll_offset(0) self._update_browser_model() @listens(u'filter_type') def _on_filter_type_changed(self): self._update_browser_model() @listens(u'full_refresh') def _on_browser_full_refresh(self): self._browser_model_dirty = True def _update_browser_model(self): if self.is_enabled(): self._do_update_browser_model() def _create_browser_model_of_type(self, filter_type): self._last_filter_type = filter_type new_model = self._make_browser_model(self._browser, filter_type) if self._browser_model and self._browser_model.can_be_exchanged(new_model) and new_model.can_be_exchanged(self._browser_model): self._browser_model.exchange_model(new_model) new_model.disconnect() else: self.disconnect_disconnectable(self._browser_model) self._browser_model = self.register_disconnectable(new_model) self._on_content_lists_changed.subject = self._browser_model self._on_selection_updated.subject = self._browser_model self._browser_model.update_content() def _do_update_browser_model(self): filter_type = filter_type_for_browser(self._browser) if filter_type != self._last_filter_type: self._create_browser_model_of_type(filter_type) elif self._browser_model_dirty: self._browser_model.update_content() elif not self._skip_next_preselection: self._browser_model.update_selection() self._skip_next_preselection = False self._browser_model_dirty = False @listens_group(u'item_action') def _on_list_item_action(self, item, _): self.notify_load_item(item.content) @listens_group(u'selected_item') def _on_selected_item(self, item, _): if item is not None and self.prehear_button.is_toggled: item.preview() self._last_selected_item = item @listens(u'selection_updated') def _on_selection_updated(self, index): more_content_available = len(self._browser_model.content_lists) > self.NUM_COLUMNS + self._scroll_offset required_scroll_offset = index - (self.NUM_COLUMNS - 1) if more_content_available and required_scroll_offset > self._scroll_offset: self._set_scroll_offset(self._scroll_offset + 1) self._browser_model.update_selection() @listens(u'content_lists') def _on_content_lists_changed(self): self._last_selected_item = None components = self._list_components contents = self._browser_model.content_lists[self._scroll_offset:] messages = self._browser_model.empty_list_messages scroll_depth = len(self._browser_model.content_lists) - len(self._list_components) self._max_scroll_offset = max(0, scroll_depth + 2) self._max_hierarchy = max(0, scroll_depth) for component, content, message in izip_longest(components, contents, messages): if component != None: component.scrollable_list = content component.empty_list_message = message active_lists = len(contents) num_head = clamp(active_lists - 1, 0, self.NUM_COLUMNS - 1) head = components[:num_head] last = components[num_head:] def set_data_sources_with_separator(component, sources, separator): for source in sources: source.separator = separator component.set_data_sources(sources) component.set_enabled(True) for idx, component in enumerate(head): offset = idx * self.COLUMN_SIZE sources = self._data_sources[offset:offset + self.COLUMN_SIZE] set_data_sources_with_separator(component, sources, u'|') if last: offset = num_head * self.COLUMN_SIZE scrollable_list = last[0].scrollable_list if scrollable_list and find_if(lambda item: item.content.is_folder, scrollable_list.items): sources = self._data_sources[offset:offset + self.COLUMN_SIZE] map(DisplayDataSource.clear, self._data_sources[offset + self.COLUMN_SIZE:]) else: sources = self._data_sources[offset:] set_data_sources_with_separator(last[0], sources, u'') for component in last[1:]: component.set_enabled(False) self.set_select_buttons(self._select_buttons) self.set_state_buttons(self._state_buttons) self.set_encoder_controls(self._encoder_controls) self._update_navigation_button_state() @listens(u'value') def _on_select_matrix_value(self, value, *_): pass @listens(u'value') def _on_state_matrix_value(self, value, *_): pass @listens(u'value') def _on_encoder_matrix_value(self, value, *_): pass
class ScalesComponent(Component): __events__ = ('close', ) root_note_buttons = control_list(RadioButtonControl, control_count=len(ROOT_NOTES), checked_color='Scales.OptionOn', unchecked_color='Scales.OptionOff') 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') def __init__(self, note_layout=None, *a, **k): raise note_layout is not None or AssertionError 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.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_root_note_changed(note_layout.root_note) self.__on_scale_changed(note_layout.scale) @root_note_buttons.pressed def root_note_buttons(self, button): self._note_layout.root_note = ROOT_NOTES[button.index] @listens('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): index = clamp(self._selected_scale_index + value, 0, len(self._scale_list) - 1) self._note_layout.scale = self._scale_list[index] @property def scale_names(self): return self._scale_name_list @listenable_property def selected_scale_index(self): return self._selected_scale_index @listens('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.notify_selected_scale_index() @close_button.pressed def close_button(self, button): self.notify_close() @property def note_layout(self): return self._note_layout
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 LoopSettingsControllerComponent(LoopSettingsControllerComponentBase): __events__ = (u'looping', u'loop_parameters', u'zoom', u'clip') ZOOM_DEFAULT_SENSITIVITY = MappedSensitivitySettingControl.DEFAULT_SENSITIVITY ZOOM_FINE_SENSITIVITY = MappedSensitivitySettingControl.FINE_SENSITIVITY zoom_encoder = MappedSensitivitySettingControl() zoom_touch_encoder = EncoderControl() loop_button = ToggleButtonControl(toggled_color='Clip.Option', untoggled_color='Clip.OptionDisabled') crop_button = ButtonControl(color='Clip.Action') 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') ] for setting in self._looping_settings + self._non_looping_settings: self.register_disconnectable(setting) self.__on_looping_changed.subject = self._loop_model self.__on_looping_changed() def update(self): super(LoopSettingsControllerComponent, self).update() if self.is_enabled(): self.notify_timeline_navigation() self.notify_clip() @loop_button.toggled def loop_button(self, toggled, button): self._loop_model.looping = toggled @crop_button.pressed def crop_button(self, button): if liveobj_valid(self.clip): self.clip.crop() @property def looping(self): if self.clip: return self._loop_model.looping return False @property def loop_parameters(self): if not liveobj_valid(self.clip): return [] parameters = self._looping_settings if self.looping else self._non_looping_settings if self.zoom: return [self.zoom] + parameters return parameters @property def zoom(self): if liveobj_valid(self.clip): return getattr(self.clip, 'zoom', None) else: return @listenable_property def timeline_navigation(self): if liveobj_valid(self.clip): return getattr(self.clip, 'timeline_navigation', None) else: return @listens('is_recording') def __on_is_recording_changed(self): 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 @listens('looping') def __on_looping_changed(self): self._update_and_notify() def _update_loop_button(self): self.loop_button.enabled = liveobj_valid(self.clip) if liveobj_valid(self.clip): self.loop_button.is_toggled = self._loop_model.looping def _on_clip_changed(self): if self.timeline_navigation is not None: self.timeline_navigation.reset_focus_and_animation() self._update_and_notify() self.__on_is_recording_changed.subject = self._loop_model.clip self.__on_is_overdubbing_changed.subject = self._loop_model.clip self._update_recording_state() self.crop_button.enabled = liveobj_valid( self.clip) and self.clip.is_midi_clip self._connect_encoder() if self.is_enabled(): self.notify_timeline_navigation() return def _on_clip_start_marker_touched(self): if self.timeline_navigation is not None: self.timeline_navigation.touch_object( self.timeline_navigation.start_marker_focus) return def _on_clip_position_touched(self): if self.timeline_navigation is not None: self.timeline_navigation.touch_object( self.timeline_navigation.loop_start_focus) return def _on_clip_end_touched(self): if self.timeline_navigation is not None: self.timeline_navigation.touch_object( self.timeline_navigation.loop_end_focus) return def _on_clip_start_marker_released(self): if self.timeline_navigation is not None: self.timeline_navigation.release_object( self.timeline_navigation.start_marker_focus) return def _on_clip_position_released(self): if self.timeline_navigation is not None: self.timeline_navigation.release_object( self.timeline_navigation.loop_start_focus) return def _on_clip_end_released(self): if self.timeline_navigation is not None: self.timeline_navigation.release_object( self.timeline_navigation.loop_end_focus) return @zoom_touch_encoder.touched def zoom_touch_encoder(self, encoder): if self.timeline_navigation is not None: self.timeline_navigation.touch_object( self.timeline_navigation.zoom_focus) return @zoom_touch_encoder.released def zoom_touch_encoder(self, encoder): if self.timeline_navigation is not None: self.timeline_navigation.release_object( self.timeline_navigation.zoom_focus) return def _update_and_notify(self): self._update_loop_button() self.notify_looping() self.notify_loop_parameters() self.notify_zoom() def _connect_encoder(self): self.zoom_encoder.mapped_parameter = self.zoom self.zoom_encoder.update_sensitivities(self.ZOOM_DEFAULT_SENSITIVITY, self.ZOOM_FINE_SENSITIVITY) def set_zoom_encoder(self, encoder): self.zoom_encoder.set_control_element(encoder) self.zoom_touch_encoder.set_control_element(encoder) self._connect_encoder()
class TransportComponent(TransportComponentBase): tempo_control = InputControl() tempo_display = TextDisplayControl() play_button = ButtonControl() stop_button = ButtonControl() shift_button = ButtonControl() tui_metronome_button = ToggleButtonControl() metronome_color_control = ButtonControl() follow_song_button = ButtonControl() clip_trigger_quantization_control = SendReceiveValueControl() clip_trigger_quantization_button_row = control_list( RadioButtonControl, len(RADIO_BUTTON_GROUP_QUANTIZATION_VALUES)) clip_trigger_quantization_color_controls = control_list( ColorSysexControl, len(RADIO_BUTTON_GROUP_QUANTIZATION_VALUES)) jump_backward_button = ButtonControl() jump_forward_button = ButtonControl() loop_start_display = TextDisplayControl() loop_length_display = TextDisplayControl() arrangement_position_display = TextDisplayControl() arrangement_position_control = EncoderControl() loop_start_control = EncoderControl() loop_length_control = EncoderControl() tui_arrangement_record_button = ToggleButtonControl() def __init__(self, *a, **k): super(TransportComponent, self).__init__(*a, **k) song = self.song self._cached_num_beats_in_bar = num_beats_in_bar(song) self.__on_song_tempo_changed.subject = song self.__on_song_tempo_changed() self.__on_metronome_changed.subject = song self.__on_metronome_changed() self.__on_clip_trigger_quantization_changed.subject = song self.__on_clip_trigger_quantization_changed() self.__on_follow_song_changed.subject = song.view self.__on_follow_song_changed() self.__on_signature_numerator_changed.subject = song self.__on_signature_denominator_changed.subject = song self.__on_loop_start_changed.subject = song self.__on_loop_start_changed() self.__on_loop_length_changed.subject = song self.__on_loop_length_changed() self.__on_arrangement_position_changed.subject = song self.__on_arrangement_position_changed() self.__on_record_mode_changed.subject = song self.__on_record_mode_changed() def set_tempo_control(self, control): self.tempo_control.set_control_element(control) @listens(b'tempo') def __on_song_tempo_changed(self): self.tempo_display[0] = (b'{0:.2f}').format(self.song.tempo) @tempo_control.value def tempo_control(self, value, _): self.song.tempo = clamp(float((b'').join(imap(chr, value[2:]))), TEMPO_MIN, TEMPO_MAX) @play_button.pressed def play_button(self, _): song = self.song song.is_playing = not song.is_playing @stop_button.pressed def stop_button(self, _): self.song.stop_playing() if self.shift_button.is_pressed: self.song.current_song_time = 0.0 @tui_metronome_button.toggled def tui_metronome_button(self, toggled, _): self.song.metronome = toggled @follow_song_button.pressed def follow_song_button(self, _): view = self.song.view view.follow_song = not view.follow_song @clip_trigger_quantization_control.value def clip_trigger_quantization_control(self, value, _): if is_valid_launch_quantize_value(value): self.song.clip_trigger_quantization = value @clip_trigger_quantization_button_row.checked def clip_trigger_quantization_button_row(self, button): self.song.clip_trigger_quantization = RADIO_BUTTON_GROUP_QUANTIZATION_VALUES[ button.index] def _apply_value_to_arrangement_property(self, property_name, value): factor = 0.25 if self.shift_button.is_pressed else 1.0 delta = factor * sign(value) old_value = getattr(self.song, property_name) setattr(self.song, property_name, max(0.0, old_value + delta)) @arrangement_position_control.value def arrangement_position_control(self, value, _): self._apply_value_to_arrangement_property(b'current_song_time', value) @loop_start_control.value def loop_start_control(self, value, _): self._apply_value_to_arrangement_property(b'loop_start', value) @loop_length_control.value def loop_length_control(self, value, _): self._apply_value_to_arrangement_property(b'loop_length', value) @jump_backward_button.pressed def jump_backward_button(self, _): self.song.jump_by(self._cached_num_beats_in_bar * -1) @jump_forward_button.pressed def jump_forward_button(self, _): self.song.jump_by(self._cached_num_beats_in_bar) @tui_arrangement_record_button.toggled def tui_arrangement_record_button(self, toggled, _): self.song.record_mode = toggled def _update_button_states(self): self._update_play_button_color() self._update_continue_playing_button_color() self._update_stop_button_color() def _update_play_button_color(self): raise NotImplementedError def _update_continue_playing_button_color(self): self.continue_playing_button.color = b'Transport.PlayOn' if self.song.is_playing else b'Transport.PlayOff' def _update_stop_button_color(self): self.stop_button.color = b'Transport.StopOff' if self.song.is_playing else b'Transport.StopOn' @listens(b'metronome') def __on_metronome_changed(self): self._update_tui_metronome_button() self._update_metronome_color_control() @listens(b'follow_song') def __on_follow_song_changed(self): self.follow_song_button.color = b'DefaultButton.On' if self.song.view.follow_song else b'DefaultButton.Off' @listens(b'clip_trigger_quantization') def __on_clip_trigger_quantization_changed(self): self._update_clip_trigger_quantization_control() self._update_clip_trigger_quantization_color_controls() @listens(b'signature_numerator') def __on_signature_numerator_changed(self): self._cached_num_beats_in_bar = num_beats_in_bar(self.song) @listens(b'signature_denominator') def __on_signature_denominator_changed(self): self._cached_num_beats_in_bar = num_beats_in_bar(self.song) def _update_clip_trigger_quantization_control(self): self.clip_trigger_quantization_control.value = int( self.song.clip_trigger_quantization) def _update_clip_trigger_quantization_color_controls(self): quantization = self.song.clip_trigger_quantization for index, control in enumerate( self.clip_trigger_quantization_color_controls): control.color = b'DefaultButton.On' if RADIO_BUTTON_GROUP_QUANTIZATION_VALUES[ index] == quantization else b'DefaultButton.Off' def _update_tui_metronome_button(self): self.tui_metronome_button.is_toggled = self.song.metronome def _update_metronome_color_control(self): self.metronome_color_control.color = b'Transport.MetronomeOn' if self.song.metronome else b'Transport.MetronomeOff' def _update_tui_arrangement_record_button(self): self.tui_arrangement_record_button.is_toggled = self.song.record_mode @listens(b'loop_start') def __on_loop_start_changed(self): loop_start_time = self.song.get_beats_loop_start() self.loop_start_display[0] = format_beat_time(loop_start_time) @listens(b'loop_length') def __on_loop_length_changed(self): loop_length_time = self.song.get_beats_loop_length() self.loop_length_display[0] = format_beat_time(loop_length_time) @listens(b'current_song_time') def __on_arrangement_position_changed(self): song_time = self.song.get_current_beats_song_time() self.arrangement_position_display[0] = format_beat_time(song_time) @listens(b'record_mode') def __on_record_mode_changed(self): self._update_tui_arrangement_record_button()
class UserComponent(UserComponentBase): user_mode_toggle_button = ToggleButtonControl() @user_mode_toggle_button.toggled def user_mode_toggle_button(self, toggled, button): self.toggle_mode()