class NoteEditorPaginator(Component, Paginator): def __init__(self, note_editors=None, *a, **k): (super(NoteEditorPaginator, self).__init__)(*a, **k) self._note_editors = note_editors self._last_page_index = -1 self._on_page_length_changed.subject = self._reference_editor self._on_active_steps_changed.replace_subjects(note_editors) @property def _reference_editor(self): return self._note_editors[0] page_index = forward_property('_reference_editor')('page_index') page_length = forward_property('_reference_editor')('page_length') def update(self): super(NoteEditorPaginator, self).update() if self.is_enabled(): self.notify_page_index() self.notify_page() self.notify_page_length() def _update_from_page_index(self): needed_update = self._last_page_index != self.page_index if needed_update: self._last_page_index = self.page_index if self.is_enabled(): self.notify_page_index() return needed_update @listens_group('active_steps') def _on_active_steps_changed(self, editor): if self.is_enabled(): self.notify_page() @listens('page_length') def _on_page_length_changed(self): if self.is_enabled(): self.notify_page() self.notify_page_length() self._update_from_page_index() @property def can_change_page(self): return all([e.can_change_page for e in self._note_editors]) def select_page_in_point(self, value): can_change_page = self.can_change_page if can_change_page: list( map(lambda e: e.set_selected_page_point(value), self._note_editors)) if self._update_from_page_index(): if self.is_enabled(): self.notify_page() return can_change_page
class SpecialSessionComponent(SessionComponent): """ Special session subclass that handles ConfigurableButtons and has a button to fire the selected clip slot. """ _session_component_ends_initialisation = False scene_component_type = SpecialSceneComponent duplicate_button = ButtonControl() def __init__(self, *a, **k): super(SpecialSessionComponent, self).__init__(*a, **k) self._slot_launch_button = None self._duplicate_button = None self._duplicate = self.register_component( DuplicateSceneComponent(self._session_ring)) self._duplicate_enabler = self.register_component( EnablingModesComponent(component=self._duplicate)) self._end_initialisation() duplicate_layer = forward_property('_duplicate')('layer') @duplicate_button.pressed def duplicate_button(self, button): self._duplicate_enabler.selected_mode = 'enabled' @duplicate_button.released def duplicate_button(self, button): self._duplicate_enabler.selected_mode = 'disabled' def set_slot_launch_button(self, button): self._slot_launch_button = button self._on_slot_launch_value.subject = button def set_clip_launch_buttons(self, buttons): if buttons: buttons.reset() super(SpecialSessionComponent, self).set_clip_launch_buttons(buttons) def set_touch_strip(self, touch_strip): if touch_strip: touch_strip.set_mode(TouchStripModes.CUSTOM_FREE) touch_strip.send_state([ TouchStripStates.STATE_OFF for _ in xrange(touch_strip.state_count) ]) self._on_touch_strip_value.subject = touch_strip @listens('value') def _on_touch_strip_value(self, value): pass @listens('value') def _on_slot_launch_value(self, value): if self.is_enabled(): if value != 0 or not self._slot_launch_button.is_momentary(): if liveobj_valid(self.song.view.highlighted_clip_slot): self.song.view.highlighted_clip_slot.fire() self._slot_launch_button.turn_on() else: self._slot_launch_button.turn_off()
class ConstantParameter(InternalParameterBase): forward_from_original = forward_property('_original_parameter') def __init__(self, original_parameter=None, *a, **k): (super(InternalParameterBase, self).__init__)(*a, **k) self._original_parameter = original_parameter add_value_listener = forward_from_original('add_value_listener') remove_value_listener = forward_from_original('remove_value_listener') value_has_listener = forward_from_original('value_has_listener') canonical_parent = forward_from_original('canonical_parent') min = forward_from_original('min') max = forward_from_original('max') name = forward_from_original('name') original_name = forward_from_original('original_name') default_value = forward_from_original('default_value') automation_state = forward_from_original('automation_state') state = forward_from_original('state') _live_ptr = forward_from_original('_live_ptr') @property def display_value(self): return str(self._original_parameter) def _get_value(self): return self._original_parameter.value def _set_value(self, _): pass value = property(_get_value, _set_value) linear_value = property(_get_value, _set_value) def __str__(self): return self.display_value
class SpecialSessionComponent(SessionComponent): u""" Special session subclass that handles ConfigurableButtons and has a button to fire the selected clip slot. """ _session_component_ends_initialisation = False scene_component_type = SpecialSceneComponent duplicate_button = ButtonControl() def __init__(self, clip_slot_copy_handler = None, fixed_length_recording = None, *a, **k): self._clip_copy_handler = clip_slot_copy_handler or ClipSlotCopyHandler() self._fixed_length_recording = fixed_length_recording with inject(copy_handler=const(self._clip_copy_handler), fixed_length_recording=const(self._fixed_length_recording)).everywhere(): super(SpecialSessionComponent, self).__init__(*a, **k) self._slot_launch_button = None self._duplicate_button = None self._duplicate = DuplicateSceneComponent(self._session_ring, parent=self) self._duplicate_enabler = EnablingModesComponent(parent=self, component=self._duplicate) self._end_initialisation() duplicate_layer = forward_property(u'_duplicate')(u'layer') @duplicate_button.pressed def duplicate_button(self, button): self._duplicate_enabler.selected_mode = u'enabled' @duplicate_button.released def duplicate_button(self, button): self._duplicate_enabler.selected_mode = u'disabled' self._clip_copy_handler.stop_copying() def set_slot_launch_button(self, button): self._slot_launch_button = button self._on_slot_launch_value.subject = button def set_clip_launch_buttons(self, buttons): if buttons: buttons.reset() super(SpecialSessionComponent, self).set_clip_launch_buttons(buttons) def set_touch_strip(self, touch_strip): if touch_strip: touch_strip.set_mode(TouchStripModes.CUSTOM_FREE) touch_strip.send_state([ TouchStripStates.STATE_OFF for _ in range(touch_strip.state_count) ]) self._on_touch_strip_value.subject = touch_strip @listens(u'value') def _on_touch_strip_value(self, value): pass @listens(u'value') def _on_slot_launch_value(self, value): if self.is_enabled(): if value != 0 or not self._slot_launch_button.is_momentary(): if liveobj_valid(self.song.view.highlighted_clip_slot): self.song.view.highlighted_clip_slot.fire() self._slot_launch_button.turn_on() else: self._slot_launch_button.turn_off()
class ScrollOverlayComponent(CompoundComponent): def __init__(self, *a, **k): super(ScrollOverlayComponent, self).__init__(*a, **k) self._scroll_left_component, self._scroll_right_component = self.register_components( ScrollComponent(is_enabled=False), ScrollComponent(is_enabled=False)) self.__on_scroll_left.subject = self._scroll_left_component self.__on_scroll_right.subject = self._scroll_right_component scroll_left_layer = forward_property('_scroll_left_component')('layer') scroll_right_layer = forward_property('_scroll_right_component')('layer') def can_scroll_left(self): raise NotImplementedError def can_scroll_right(self): raise NotImplementedError def scroll_left(self): raise NotImplementedError def scroll_right(self): raise NotImplementedError def update_scroll_buttons(self): if self.is_enabled(): self._scroll_left_component.set_enabled(self.can_scroll_left()) self._scroll_right_component.set_enabled(self.can_scroll_right()) @listens('scroll') def __on_scroll_left(self): self.scroll_left() @listens('scroll') def __on_scroll_right(self): self.scroll_right() def update(self): super(ScrollOverlayComponent, self).update() if self.is_enabled(): self.update_scroll_buttons()
class DialogComponent(Component): """' Handles representing modal dialogs from the application. The script can also request dialogs. """ __module__ = __name__ def __init__(self, *a, **k): super(DialogComponent, self).__init__(*a, **k) self._message_box = MessageBoxComponent(parent=self, is_enabled=False) self._next_message = None self._on_open_dialog_count.subject = self.application self._on_message_cancel.subject = self._message_box return message_box_layer = forward_property('_message_box')('layer') def expect_dialog(self, message): u""" Expects a dialog from Live to appear soon. The dialog will be shown on the controller with the given message regardless of wether a dialog actually appears. This dialog can be cancelled. """ self._next_message = message self._update_dialog() @listens('open_dialog_count') def _on_open_dialog_count(self): self._update_dialog(open_dialog_changed=True) self._next_message = None return @listens('cancel') def _on_message_cancel(self): self._next_message = None try: self.application.press_current_dialog_button(0) except RuntimeError: pass self._update_dialog() return def _update_dialog(self, open_dialog_changed=False): message = self._next_message or MessageBoxText.LIVE_DIALOG can_cancel = self._next_message != None self._message_box.text = message self._message_box.can_cancel = can_cancel self._message_box.set_enabled( self.application.open_dialog_count > 0 or not open_dialog_changed and self._next_message) return
class ItemListerComponent(ItemListerComponentBase): select_buttons = control_list( RadioButtonControl, checked_color='ItemNavigation.ItemSelected', unchecked_color='ItemNavigation.ItemNotSelected', unavailable_color='ItemNavigation.NoItem') def __init__(self, *a, **k): super(ItemListerComponent, self).__init__(*a, **k) self._scroll_overlay = self.register_component( ScrollOverlayComponent(is_enabled=True)) self._scroll_overlay.can_scroll_left = self.can_scroll_left self._scroll_overlay.can_scroll_right = self.can_scroll_right self._scroll_overlay.scroll_left = self.scroll_left self._scroll_overlay.scroll_right = self.scroll_right self.__on_items_changed.subject = self self.__on_selection_changed.subject = self._item_provider scroll_left_layer = forward_property('_scroll_overlay')( 'scroll_left_layer') scroll_right_layer = forward_property('_scroll_overlay')( 'scroll_right_layer') @listens('items') def __on_items_changed(self): self.select_buttons.control_count = len(self.items) self._update_button_selection() self._scroll_overlay.update_scroll_buttons() @listens('selected_item') def __on_selection_changed(self): self._update_button_selection() def _update_button_selection(self): selected_item = self._item_provider.selected_item items = self.items selected_index = index_if(lambda item: item == selected_item, items) if selected_index >= len(items): selected_index = -1 self.select_buttons.checked_index = selected_index
class UserComponent(UserComponentBase): action_button = ButtonControl(**consts.SIDE_BUTTON_COLORS) settings_layer = forward_property('_settings')('layer') settings = forward_property('_settings')('settings') def __init__(self, *a, **k): (super(UserComponent, self).__init__)(*a, **k) self._settings = UserSettingsComponent(parent=self) self._settings.set_enabled(False) @action_button.pressed_delayed def action_button(self, button): self._settings.set_enabled(True) @action_button.released_delayed def hide_settings(self, button): self._settings.set_enabled(False) def set_settings_info_text(self, text): self._settings.set_info_text(text) @action_button.released_immediately def post_trigger_action(self, button): self.toggle_mode()
class MixerComponent(MixerComponentBase): track_scroll_encoder = forward_property('_track_scrolling')( 'scroll_encoder') selected_track_arm_button = ButtonControl() def __init__(self, *a, **k): super(MixerComponent, self).__init__(*a, **k) self._track_scrolling = ScrollComponent(parent=self) self._track_scrolling.can_scroll_up = self._can_select_prev_track self._track_scrolling.can_scroll_down = self._can_select_next_track self._track_scrolling.scroll_up = self._select_prev_track self._track_scrolling.scroll_down = self._select_next_track @selected_track_arm_button.pressed def selected_track_arm_button(self, _): selected_track = self.song.view.selected_track if liveobj_valid(selected_track) and selected_track.can_be_armed: new_value = not selected_track.arm for track in self.song.tracks: if track.can_be_armed: if track == selected_track or track.is_part_of_selection and selected_track.is_part_of_selection: track.arm = new_value elif self.song.exclusive_arm and track.arm: track.arm = False def _can_select_prev_track(self): return self.song.view.selected_track != self._provider.tracks_to_use( )[0] def _can_select_next_track(self): return self.song.view.selected_track != self._provider.tracks_to_use()[ (-1)] def _select_prev_track(self): selected_track = self.song.view.selected_track tracks = self._provider.tracks_to_use() assert selected_track in tracks index = list(tracks).index(selected_track) self.song.view.selected_track = tracks[(index - 1)] def _select_next_track(self): selected_track = self.song.view.selected_track tracks = self._provider.tracks_to_use() assert selected_track in tracks index = list(tracks).index(selected_track) self.song.view.selected_track = tracks[(index + 1)]
class ItemSlot(SimpleItemSlot): def __init__(self, item = None, nesting_level = 0, **k): assert item != None super(ItemSlot, self).__init__(item=item, name=item.name, nesting_level=nesting_level, **k) return def __eq__(self, other): return id(self) == id(other) or self._item == other def __ne__(self, other): return not self == other def __hash__(self): return hash(self._item) _live_ptr = forward_property(u'_item')(u'_live_ptr')
class DialogComponent(Component): def __init__(self, *a, **k): (super(DialogComponent, self).__init__)(*a, **k) self._message_box = MessageBoxComponent(parent=self, is_enabled=False) self._next_message = None self._on_open_dialog_count.subject = self.application self._on_message_cancel.subject = self._message_box message_box_layer = forward_property('_message_box')('layer') def expect_dialog(self, message): self._next_message = message self._update_dialog() @listens('open_dialog_count') def _on_open_dialog_count(self): self._update_dialog(open_dialog_changed=True) self._next_message = None @listens('cancel') def _on_message_cancel(self): self._next_message = None try: self.application.press_current_dialog_button(0) except RuntimeError: pass self._update_dialog() def _update_dialog(self, open_dialog_changed=False): message = self._next_message or MessageBoxText.LIVE_DIALOG can_cancel = self._next_message != None self._message_box.text = message self._message_box.can_cancel = can_cancel self._message_box.set_enabled( (self.application.open_dialog_count > 0) or ((not open_dialog_changed) and (self._next_message)))
class InstrumentScalesComponent(CompoundComponent): presets_toggle_button = ButtonControl(color='DefaultButton.Off', pressed_color='DefaultButton.On') def __init__(self, note_layout=None, *a, **k): raise note_layout is not None or AssertionError super(InstrumentScalesComponent, self).__init__(*a, **k) self._note_layout = note_layout self._key_center_buttons = [] self._encoder_touch_button_slots = self.register_slot_manager() self._encoder_touch_buttons = [] self._top_key_center_buttons = None self._bottom_key_center_buttons = None self._absolute_relative_button = None self._diatonic_chromatic_button = None self._info_sources = map(DisplayDataSource, ('Scale selection:', '', '')) self._line_sources = recursive_map(DisplayDataSource, (('', '', '', '', '', '', ''), ('', '', '', '', '', '', ''))) self._scale_sources = map( partial(DisplayDataSource, adjust_string_fn=adjust_string_crop), ('', '', '', '')) self._presets = self.register_component( InstrumentPresetsComponent(self._note_layout, is_enabled=False)) self._presets.selected_mode = 'scale_p4_vertical' self._scale_list = self.register_component( ListComponent(data_sources=self._scale_sources)) self._scale_list.scrollable_list.fixed_offset = 1 self._scale_list.scrollable_list.assign_items(SCALES) self._scale_list.scrollable_list.select_item_index_with_offset( list(SCALES).index(self._note_layout.scale), 1) self._on_selected_scale.subject = self._scale_list.scrollable_list self._update_data_sources() presets_layer = forward_property('_presets')('layer') @property def available_scales(self): return self._note_layout.scale.scale_for_notes(ROOT_NOTES) def set_scale_line1(self, display): self._set_scale_line(display, 0) def set_scale_line2(self, display): self._set_scale_line(display, 1) def set_scale_line3(self, display): self._set_scale_line(display, 2) def set_scale_line4(self, display): self._set_scale_line(display, 3) def _set_scale_line(self, display, index): if display: display.set_data_sources([self._scale_sources[index]]) for segment in display.segments: segment.separator = '' def set_info_line(self, display): if display: display.set_data_sources(self._info_sources) def set_top_display_line(self, display): self._set_display_line(display, 0) def set_bottom_display_line(self, display): self._set_display_line(display, 1) def _set_display_line(self, display, line): if display: display.set_data_sources(self._line_sources[line]) @presets_toggle_button.pressed def presets_toggle_button(self, button): self._presets.set_enabled(True) @presets_toggle_button.released def presets_toggle_button(self, button): self._presets.set_enabled(False) def set_top_buttons(self, buttons): if buttons: buttons.reset() self.set_absolute_relative_button(buttons[7]) self._top_key_center_buttons = buttons[1:7] self.set_scale_up_button(buttons[0]) else: self.set_absolute_relative_button(None) self._top_key_center_buttons = None self.set_scale_up_button(None) if self._top_key_center_buttons and self._bottom_key_center_buttons: self.set_key_center_buttons(self._top_key_center_buttons + self._bottom_key_center_buttons) else: self.set_key_center_buttons(tuple()) def set_bottom_buttons(self, buttons): if buttons: buttons.reset() self.set_diatonic_chromatic_button(buttons[7]) self._bottom_key_center_buttons = buttons[1:7] self.set_scale_down_button(buttons[0]) else: self.set_diatonic_chromatic_button(None) self._bottom_key_center_buttons = None self.set_scale_down_button(None) if self._top_key_center_buttons and self._bottom_key_center_buttons: self.set_key_center_buttons(self._top_key_center_buttons + self._bottom_key_center_buttons) else: self.set_key_center_buttons([]) def set_scale_down_button(self, button): self._scale_list.select_next_button.set_control_element(button) def set_scale_up_button(self, button): self._scale_list.select_prev_button.set_control_element(button) def set_encoder_controls(self, encoders): self._scale_list.encoders.set_control_element( [encoders[0]] if encoders else []) def set_key_center_buttons(self, buttons): raise not buttons or len(buttons) == 12 or AssertionError buttons = buttons or [] self._key_center_buttons = buttons self._on_key_center_button_value.replace_subjects(buttons) self._update_key_center_buttons() def set_absolute_relative_button(self, absolute_relative_button): self._absolute_relative_button = absolute_relative_button self._on_absolute_relative_value.subject = absolute_relative_button self._update_absolute_relative_button() def set_diatonic_chromatic_button(self, diatonic_chromatic_button): self._diatonic_chromatic_button = diatonic_chromatic_button self._on_diatonic_chromatic_value.subject = diatonic_chromatic_button self._update_diatonic_chromatic_button() @listens_group('value') def _on_key_center_button_value(self, value, sender): if self.is_enabled() and (value or not sender.is_momentary()): index = list(self._key_center_buttons).index(sender) self._note_layout.root_note = ROOT_NOTES[index] self._update_key_center_buttons() self._update_data_sources() @listens('value') def _on_absolute_relative_value(self, value): if self.is_enabled(): if value != 0 or not self._absolute_relative_button.is_momentary(): self._note_layout.is_fixed = not self._note_layout.is_fixed self._update_absolute_relative_button() self._update_data_sources() @listens('value') def _on_diatonic_chromatic_value(self, value): if self.is_enabled(): if value != 0 or not self._diatonic_chromatic_button.is_momentary( ): self._note_layout.is_in_key = not self._note_layout.is_in_key self._update_diatonic_chromatic_button() self._update_data_sources() @listens('selected_item') def _on_selected_scale(self): self._note_layout.scale = self._scale_list.scrollable_list.selected_item.content self._update_data_sources() def update(self): super(InstrumentScalesComponent, self).update() if self.is_enabled(): self._update_key_center_buttons() self._update_absolute_relative_button() self._update_diatonic_chromatic_button() else: self._presets.set_enabled(False) def _update_key_center_buttons(self): if self.is_enabled(): for index, button in enumerate(self._key_center_buttons): if button: button.set_on_off_values('Scales.Selected', 'Scales.Unselected') button.set_light( self._note_layout.root_note == ROOT_NOTES[index]) def _update_absolute_relative_button(self): if self.is_enabled() and self._absolute_relative_button != None: self._absolute_relative_button.set_on_off_values( 'Scales.FixedOn', 'Scales.FixedOff') self._absolute_relative_button.set_light( self._note_layout.is_fixed) def _update_diatonic_chromatic_button(self): if self.is_enabled() and self._diatonic_chromatic_button != None: self._diatonic_chromatic_button.set_on_off_values( 'Scales.Diatonic', 'Scales.Chromatic') self._diatonic_chromatic_button.set_light( self._note_layout.is_in_key) def _update_data_sources(self): key_index = list(ROOT_NOTES).index(self._note_layout.root_note) key_sources = self._line_sources[0][:6] + self._line_sources[1][:6] key_names = [scale.name for scale in self.available_scales] for idx, (source, orig) in enumerate(zip(key_sources, key_names)): source.set_display_string(' ' + consts.CHAR_SELECT + orig if idx == key_index else ' ' + orig) self._line_sources[0][6].set_display_string( 'Fixed: Y' if self._note_layout.is_fixed else 'Fixed: N') self._line_sources[1][6].set_display_string( 'In Key' if self._note_layout.is_in_key else 'Chromatc') self._info_sources[1].set_display_string( str(self._scale_list.scrollable_list.selected_item))
class ListComponent(Component): """ Component that handles a ScrollableList. If an action button is passed, it can handle an ActionList. """ __events__ = ('item_action', 'selected_item') SELECTION_DELAY = 0.5 ENCODER_FACTOR = 10.0 empty_list_message = b'' _current_action_item = None _last_action_item = None action_button = ButtonControl(color=b'Browser.Load') encoders = control_list(EncoderControl) def __init__(self, scrollable_list=None, data_sources=tuple(), *a, **k): super(ListComponent, self).__init__(*a, **k) self._data_sources = data_sources self._activation_task = task.Task() self._action_on_scroll_task = task.Task() self._scrollable_list = None self._scroller = ScrollComponent(parent=self) self._pager = ScrollComponent(parent=self) self.last_action_item = lambda: self._last_action_item self.item_formatter = DefaultItemFormatter() for c in (self._scroller, self._pager): for button in (c.scroll_up_button, c.scroll_down_button): button.color = b'List.ScrollerOn' button.pressed_color = None button.disabled_color = b'List.ScrollerOff' if scrollable_list == None: self.scrollable_list = ActionList( num_visible_items=len(data_sources)) else: self.scrollable_list = scrollable_list self._scrollable_list.num_visible_items = len(data_sources) self._delay_activation = BooleanContext() self._selected_index_float = 0.0 self._in_encoder_selection = BooleanContext(False) self._execute_action_task = self._tasks.add( task.sequence(task.delay(1), task.run(self._execute_action))) self._execute_action_task.kill() return @property def _trigger_action_on_scrolling(self): return self.action_button.is_pressed def _get_scrollable_list(self): return self._scrollable_list def _set_scrollable_list(self, new_list): if new_list != self._scrollable_list: self._scrollable_list = new_list if new_list != None: new_list.num_visible_items = len(self._data_sources) self._scroller.scrollable = new_list self._pager.scrollable = new_list.pager self._on_scroll.subject = new_list self._selected_index_float = new_list.selected_item_index else: self._scroller.scrollable = ScrollComponent.default_scrollable self._scroller.scrollable = ScrollComponent.default_pager self._on_selected_item_changed.subject = new_list self.update() return scrollable_list = property(_get_scrollable_list, _set_scrollable_list) def set_data_sources(self, sources): self._data_sources = sources if self._scrollable_list: self._scrollable_list.num_visible_items = len(sources) self._update_display() select_next_button = forward_property(b'_scroller')(b'scroll_down_button') select_prev_button = forward_property(b'_scroller')(b'scroll_up_button') next_page_button = forward_property(b'_pager')(b'scroll_down_button') prev_page_button = forward_property(b'_pager')(b'scroll_up_button') def on_enabled_changed(self): super(ListComponent, self).on_enabled_changed() if not self.is_enabled(): self._execute_action_task.kill() @listens(b'scroll') def _on_scroll(self): if self._trigger_action_on_scrolling: trigger_selected = partial(self._trigger_action, self.selected_item) self._action_on_scroll_task.kill() self._action_on_scroll_task = self._tasks.add( task.sequence(task.wait(defaults.MOMENTARY_DELAY), task.delay(1), task.run(trigger_selected))) @listens(b'selected_item') def _on_selected_item_changed(self): self._scroller.update() self._pager.update() self._update_display() self._update_action_feedback() self._activation_task.kill() self._action_on_scroll_task.kill() if self.SELECTION_DELAY and self._delay_activation: self._activation_task = self._tasks.add( task.sequence( task.wait(self.SELECTION_DELAY), task.run( self._scrollable_list.request_notify_item_activated))) else: self._scrollable_list.request_notify_item_activated() if not self._in_encoder_selection: self._selected_index_float = float( self._scrollable_list.selected_item_index) self.notify_selected_item(self._scrollable_list.selected_item) @encoders.value def encoders(self, value, encoder): self._add_offset_to_selected_index(value) def _add_offset_to_selected_index(self, offset): if self.is_enabled() and self._scrollable_list: with self._delay_activation(): with self._in_encoder_selection(): self._selected_index_float = clamp( self._selected_index_float + offset * self.ENCODER_FACTOR, 0, len(self._scrollable_list.items)) self._scrollable_list.select_item_index_with_border( int(self._selected_index_float), 1) @action_button.pressed def action_button(self, button): if self._current_action_item == None: self._trigger_action( self.next_item if self._action_target_is_next_item( ) else self.selected_item) return def do_trigger_action(self, item): item.action() self.notify_item_action(item) def _trigger_action(self, item): if self.is_enabled() and self._can_be_used_for_action(item): if self._scrollable_list != None: self._scrollable_list.select_item(item) self._current_action_item = item self.update() self._execute_action_task.restart() return def _execute_action(self): """ Is called by the execute action task and should not be called directly use _trigger_action instead """ if self._current_action_item != None: self.do_trigger_action(self._current_action_item) self._last_action_item = self._current_action_item self._current_action_item = None self.update() return @property def selected_item(self): if self._scrollable_list != None: return self._scrollable_list.selected_item else: return @property def next_item(self): item = None if self._scrollable_list != None: all_items = self._scrollable_list.items next_index = self._scrollable_list.selected_item_index + 1 item = all_items[next_index] if in_range(next_index, 0, len(all_items)) else None return item def _can_be_used_for_action(self, item): return item != None and item.supports_action and item != self.last_action_item( ) def _action_target_is_next_item(self): return self.selected_item == self.last_action_item( ) and self._can_be_used_for_action(self.next_item) def _update_action_feedback(self): color = b'Browser.Loading' if self._current_action_item == None: if self._action_target_is_next_item(): color = b'Browser.LoadNext' elif self._can_be_used_for_action(self.selected_item): color = b'Browser.Load' else: color = b'Browser.LoadNotPossible' self.action_button.color = color return def _update_display(self): visible_items = self._scrollable_list.visible_items if self._scrollable_list else [] for index, data_source in enumerate(self._data_sources): item = visible_items[index] if index < len(visible_items) else None action_in_progress = item and item == self._current_action_item display_string = self.item_formatter(index, item, action_in_progress) data_source.set_display_string(display_string) if not visible_items and self._data_sources and self.empty_list_message: self._data_sources[0].set_display_string(self.empty_list_message) return def update(self): super(ListComponent, self).update() if self.is_enabled(): self._update_action_feedback() self._update_display()
class SelectComponent(Component): """ This component handles selection of objects. """ select_button = ButtonControl(**SIDE_BUTTON_COLORS) def __init__(self, *a, **k): super(SelectComponent, self).__init__(*a, **k) self._selected_clip = None self._selection_display = SelectionDisplayComponent(parent=self) self._selection_display.set_enabled(False) return selection_display_layer = forward_property(b'_selection_display')(b'layer') def set_selected_clip(self, clip): self._selected_clip = clip self._on_playing_position_changed.subject = clip def on_select_clip(self, clip_slot): clip_name = select_clip_and_get_name_from_slot(clip_slot, self.song) if liveobj_valid(clip_slot): self.set_selected_clip(clip_slot.clip) self._selection_display.set_display_string(b'Clip Selection:') self._selection_display.set_display_string(clip_name, 1) self._do_show_time_remaining() self._selection_display.set_enabled(True) @listens(b'playing_position') def _on_playing_position_changed(self): self._do_show_time_remaining() def _do_show_time_remaining(self): clip = self._selected_clip if liveobj_valid(clip) and (clip.is_triggered or clip.is_playing): if clip.is_recording: label = b'Record Count:' length = ( clip.playing_position - clip.loop_start ) * clip.signature_denominator / clip.signature_numerator time = convert_beat_length_to_bars_beats_sixteenths( (clip.signature_numerator, clip.signature_denominator), length) else: label = b'Time Remaining:' length = clip.loop_end - clip.playing_position if clip.is_audio_clip and not clip.warping: time = convert_length_to_mins_secs(length) else: time = convert_beats_to_mins_secs(length, self.song.tempo) else: label = b' ' time = b' ' self._selection_display.set_display_string(label, 2) self._selection_display.set_display_string(time, 3) def on_select_scene(self, scene): scene_name = select_scene_and_get_name(scene, self.song) self._selection_display.set_display_string(b'Scene Selection:') self._selection_display.set_display_string(scene_name, 1) self._selection_display.reset_display_right() self._selection_display.set_enabled(True) def on_select_track(self, track): if liveobj_valid(track): track_name = track.name if track.name != b'' else b'[unnamed]' else: track_name = b'[none]' self._selection_display.set_display_string(b'Track Selection:') self._selection_display.set_display_string(track_name, 1) self._selection_display.reset_display_right() self._selection_display.set_enabled(True) def on_select_drum_pad(self, drum_pad): if liveobj_valid(drum_pad): drum_pad_name = drum_pad.name if drum_pad.name != b'' else b'[unnamed]' else: drum_pad_name = b'[none]' self._selection_display.set_display_string(b'Pad Selection:') self._selection_display.set_display_string(drum_pad_name, 1) self._selection_display.reset_display_right() self._selection_display.set_enabled(True) @select_button.released def select_button(self, control): self._selection_display.set_enabled(False) self._selection_display.reset_display() self.set_selected_clip(None) return
class MelodicComponent(ModesComponent, Messenger): def __init__(self, clip_creator=None, parameter_provider=None, grid_resolution=None, note_layout=None, note_editor_settings=None, note_editor_class=NoteEditorComponent, velocity_range_thresholds=None, skin=None, instrument_play_layer=None, instrument_sequence_layer=None, pitch_mod_touch_strip_mode=None, layer=None, *a, **k): super(MelodicComponent, self).__init__(*a, **k) self._matrices = None self._grid_resolution = grid_resolution self._instrument = self.register_component( InstrumentComponent(note_layout=note_layout)) self._note_editors = self.register_components(*[ note_editor_class( clip_creator=clip_creator, grid_resolution=self._grid_resolution, velocity_range_thresholds=velocity_range_thresholds, is_enabled=False) for _ in xrange(NUM_NOTE_EDITORS) ]) for editor in self._note_editors: note_editor_settings.add_editor(editor) self._paginator = NoteEditorPaginator(self._note_editors) self._loop_selector = self.register_component( LoopSelectorComponent(clip_creator=clip_creator, paginator=self._paginator, is_enabled=False)) self._playhead = None self._playhead_component = self.register_component( PlayheadComponent(grid_resolution=grid_resolution, paginator=self._paginator, follower=self._loop_selector, feedback_channels=PLAYHEAD_FEEDBACK_CHANNELS, is_enabled=False)) self.add_mode('play', [ LayerMode(self._instrument, instrument_play_layer), pitch_mod_touch_strip_mode ]) self.add_mode('sequence', [ LayerMode(self._instrument, instrument_sequence_layer), self._loop_selector, note_editor_settings, LayerMode(self, layer), self._playhead_component ] + self._note_editors) self.selected_mode = 'play' self._on_detail_clip_changed.subject = self.song.view self._on_pattern_changed.subject = self._instrument self._on_notes_changed.subject = self._instrument self._on_detail_clip_changed() self._update_note_editors() self._skin = skin self._playhead_color = 'Melodic.Playhead' self._update_playhead_color() def set_playhead(self, playhead): self._playhead = playhead self._playhead_component.set_playhead(playhead) self._update_playhead_color() @forward_property('_loop_selector') def set_loop_selector_matrix(self, matrix): pass @forward_property('_loop_selector') def set_short_loop_selector_matrix(self, matrix): pass next_loop_page_button = forward_property('_loop_selector')( 'next_page_button') prev_loop_page_button = forward_property('_loop_selector')( 'prev_page_button') def set_note_editor_matrices(self, matrices): raise not matrices or len( matrices) <= NUM_NOTE_EDITORS or AssertionError self._matrices = matrices for editor, matrix in izip_longest(self._note_editors, matrices or []): if editor: editor.set_button_matrix(matrix) self._update_matrix_channels_for_playhead() def _get_playhead_color(self): self._playhead_color def _set_playhead_color(self, value): self._playhead_color = 'Melodic.' + value self._update_playhead_color() playhead_color = property(_get_playhead_color, _set_playhead_color) @listens('detail_clip') def _on_detail_clip_changed(self): if self.is_enabled(): clip = self.song.view.detail_clip clip = clip if self.is_enabled( ) and clip and clip.is_midi_clip else None for note_editor in self._note_editors: note_editor.set_detail_clip(clip) self._loop_selector.set_detail_clip(clip) self._playhead_component.set_clip(clip) self._instrument.set_detail_clip(clip) def _set_full_velocity(self, enable): for note_editor in self._note_editors: note_editor.full_velocity = enable def _get_full_velocity(self): self._note_editors[0].full_velocity full_velocity = property(_get_full_velocity, _set_full_velocity) def set_quantization_buttons(self, buttons): self._grid_resolution.set_buttons(buttons) def set_mute_button(self, button): for e in self._note_editors: e.set_mute_button(button) @listens('position') def _on_notes_changed(self, *args): self._update_note_editors() self._show_notes_information() @listens('pattern') def _on_pattern_changed(self): self._update_note_editors() def _update_note_editors(self, *a): for row, note_editor in enumerate(self._note_editors): note_info = self._instrument.pattern[row] note_editor.background_color = 'NoteEditor.' + note_info.color note_editor.editing_note = note_info.index self._update_matrix_channels_for_playhead() def _update_matrix_channels_for_playhead(self): if self.is_enabled() and self._matrices is not None: pattern = self._instrument.pattern for matrix, (y, _) in self._matrices.iterbuttons(): if matrix: for x, button in enumerate(matrix): if button: if pattern[y].index is not None: button.set_identifier(x) button.set_channel( PLAYHEAD_FEEDBACK_CHANNELS[y]) else: button.set_identifier( button._original_identifier) button.set_channel(NON_FEEDBACK_CHANNEL) def _update_playhead_color(self): if self.is_enabled() and self._skin and self._playhead: self._playhead.velocity = to_midi_value( self._skin[self._playhead_color]) def update(self): super(MelodicComponent, self).update() self._on_detail_clip_changed() self._update_playhead_color() def _show_notes_information(self, mode=None): if self.is_enabled(): if mode is None: mode = self.selected_mode if mode == 'sequence': message = u'Sequence %s to %s' first = find_if(lambda editor: editor.editing_note is not None, self._note_editors) last = find_if(lambda editor: editor.editing_note is not None, reversed(self._note_editors)) start_note = first.editing_note if first is not None else None end_note = last.editing_note if last is not None else None else: message = u'Play %s to %s' start_note = self._instrument._pattern.note(0, 0).index end_note = self._instrument._pattern.note(7, 7).index self.show_notification(message % (pitch_index_to_string(start_note), pitch_index_to_string(end_note)))
class AudioClipSettingsModel(EventObject): __events__ = (u'pitch_fine', u'pitch_coarse', u'gain', u'warp_mode', u'warping') def __init__(self, *a, **k): super(AudioClipSettingsModel, self).__init__(*a, **k) self.clip = None return def _get_clip(self): return self._clip def _set_clip(self, clip): self._clip = clip self.__on_pitch_fine_changed.subject = self._clip self.__on_pitch_coarse_changed.subject = self._clip self.__on_gain_changed.subject = self._clip self.__on_warp_mode_changed.subject = self._clip self.__on_warping_changed.subject = self._clip clip = property(_get_clip, _set_clip) pitch_fine = forward_property('clip')('pitch_fine') pitch_coarse = forward_property('clip')('pitch_coarse') gain = forward_property('clip')('gain') warping = forward_property('clip')('warping') def _get_warp_mode(self): return self.clip.warp_mode def _set_warp_mode(self, value): if self.clip.warping: available_warp_modes = self.available_warp_modes warp_mode_index = available_warp_modes.index(self.clip.warp_mode) new_warp_mode_index = clamp(warp_mode_index + value, 0, len(available_warp_modes) - 1) self.clip.warp_mode = available_warp_modes[new_warp_mode_index] warp_mode = property(_get_warp_mode, _set_warp_mode) def set_clip_gain(self, value, fine_grained): self.clip.gain = clamp( self.clip.gain + value * self._encoder_factor(fine_grained), 0.0, 1.0) def set_clip_pitch_coarse(self, value, fine_grained): self.clip.pitch_coarse = int( clamp( self.clip.pitch_coarse + value * self._encoder_factor(fine_grained), -48.0, 48.0)) def set_clip_pitch_fine(self, value, fine_grained): self.clip.pitch_fine = int(self.clip.pitch_fine + value * 100.0 * self._encoder_factor(fine_grained)) def _encoder_factor(self, fine_grained): if fine_grained: return 0.1 return 1.0 @listens('pitch_fine') def __on_pitch_fine_changed(self): self.notify_pitch_fine() @listens('pitch_coarse') def __on_pitch_coarse_changed(self): self.notify_pitch_coarse() @listens('gain') def __on_gain_changed(self): self.notify_gain() @listens('warp_mode') def __on_warp_mode_changed(self): self.notify_warp_mode() @listens('warping') def __on_warping_changed(self): self.notify_warping() @property def available_warp_modes(self): if liveobj_valid(self.clip): return list(self.clip.available_warp_modes) return []
class MelodicComponent(MessengerModesComponent): def __init__(self, clip_creator=None, parameter_provider=None, grid_resolution=None, note_layout=None, note_editor_settings=None, note_editor_class=NoteEditorComponent, velocity_range_thresholds=None, skin=None, instrument_play_layer=None, instrument_sequence_layer=None, pitch_mod_touch_strip_mode=None, play_loop_instrument_layer=None, layer=None, sequence_layer_with_loop=None, *a, **k): super(MelodicComponent, self).__init__(*a, **k) self._matrices = None self._grid_resolution = grid_resolution self.instrument, self._step_duplicator, self._accent_component = self.register_components( InstrumentComponent(note_layout=note_layout), StepDuplicatorComponent(), AccentComponent()) self._note_editors = self.register_components(*[ note_editor_class( clip_creator=clip_creator, grid_resolution=self._grid_resolution, velocity_range_thresholds=velocity_range_thresholds, is_enabled=False) for _ in xrange(NUM_NOTE_EDITORS) ]) for editor in self._note_editors: note_editor_settings.add_editor(editor) editor.set_step_duplicator(self._step_duplicator) self.paginator = self.register_component( NoteEditorPaginator(self._note_editors)) self._loop_selector = self.register_component( LoopSelectorComponent(clip_creator=clip_creator, paginator=self.paginator, is_enabled=False, default_size=8)) self._playhead = None self._playhead_component = self.register_component( PlayheadComponent(grid_resolution=grid_resolution, paginator=self.paginator, follower=self._loop_selector, feedback_channels=PLAYHEAD_FEEDBACK_CHANNELS, is_enabled=False)) self._play_modes = self.register_component( MessengerModesComponent(muted=True, is_enabled=False)) self._play_modes.add_mode('play', [ LayerMode(self.instrument, instrument_play_layer), pitch_mod_touch_strip_mode ], default_mode='play', alternative_mode='play_loop') self._play_modes.add_mode( 'play_loop', [ LayerMode(self.instrument, instrument_play_layer), self._loop_selector, LayerMode(self, play_loop_instrument_layer), self._playhead_component, self.paginator, pitch_mod_touch_strip_mode ], message=consts.MessageBoxText.ALTERNATE_PLAY_LOOP, default_mode='play', alternative_mode='play_loop') self._play_modes.selected_mode = 'play' self.add_mode('play', self._play_modes, message=MessageBoxText.LAYOUT_MELODIC_PLAYING) self._sequence_modes = self.register_component( MessengerModesComponent(muted=True, is_enabled=False)) self._sequence_modes.add_mode( 'sequence', [ LayerMode(self.instrument, instrument_sequence_layer), note_editor_settings, self._loop_selector, LayerMode(self, layer), self._playhead_component, self._update_note_editors, self.paginator, self._accent_component ] + self._note_editors, message=MessageBoxText.LAYOUT_MELODIC_SEQUENCER, default_mode='sequence', alternative_mode='sequence_loop') self._sequence_modes.add_mode( 'sequence_loop', [ LayerMode(self.instrument, instrument_sequence_layer), note_editor_settings, self._loop_selector, LayerMode(self, sequence_layer_with_loop), self._playhead_component, self._update_note_editors, self.paginator, self._accent_component ] + self._note_editors, message=MessageBoxText.ALTERNATE_SEQUENCE_LOOP, default_mode='sequence', alternative_mode='sequence_loop') self._sequence_modes.selected_mode = 'sequence' self.add_mode('sequence', self._sequence_modes, message=MessageBoxText.LAYOUT_MELODIC_SEQUENCER) self.selected_mode = 'play' self._on_detail_clip_changed.subject = self.song.view self._on_pattern_changed.subject = self.instrument self._on_notes_changed.subject = self.instrument self.__on_grid_resolution_changed.subject = self._grid_resolution self._on_page_index_changed.subject = self.paginator self._on_page_length_changed.subject = self.paginator self._on_active_steps_changed.replace_subjects(self._note_editors) self._on_modify_all_notes_changed.replace_subjects(self._note_editors) self.__on_accent_activated_changed.subject = self._accent_component self._on_detail_clip_changed() self._update_note_editors() self._skin = skin self._playhead_color = 'Melodic.Playhead' self._update_playhead_color() self._loop_selector.set_step_duplicator(self._step_duplicator) self._show_notifications = True return @property def play_modes(self): return self._play_modes @property def sequence_modes(self): return self._sequence_modes def set_playhead(self, playhead): self._playhead = playhead self._playhead_component.set_playhead(playhead) self._update_playhead_color() set_loop_selector_matrix = forward_property('_loop_selector')( 'set_loop_selector_matrix') set_short_loop_selector_matrix = forward_property('_loop_selector')( 'set_short_loop_selector_matrix') next_loop_page_button = forward_property('_loop_selector')( 'next_page_button') prev_loop_page_button = forward_property('_loop_selector')( 'prev_page_button') delete_button = forward_property('_loop_selector')('delete_button') def set_duplicate_button(self, button): self._step_duplicator.button.set_control_element(button) def set_note_editor_matrices(self, matrices): assert not matrices or len(matrices) <= NUM_NOTE_EDITORS self._matrices = matrices for editor, matrix in izip_longest(self._note_editors, matrices or []): if editor: editor.set_matrix(matrix) self._update_matrix_channels_for_playhead() def _get_playhead_color(self): self._playhead_color def _set_playhead_color(self, value): self._playhead_color = 'Melodic.' + value self._update_playhead_color() playhead_color = property(_get_playhead_color, _set_playhead_color) @listens('detail_clip') def _on_detail_clip_changed(self): if self.is_enabled(): clip = self.song.view.detail_clip clip = clip if liveobj_valid(clip) and clip.is_midi_clip else None for note_editor in self._note_editors: note_editor.set_detail_clip(clip) self._loop_selector.set_detail_clip(clip) self._playhead_component.set_clip(clip) self.instrument.set_detail_clip(clip) return @listens('activated') def __on_accent_activated_changed(self): self._update_full_velocity_for_editors() def _update_full_velocity_for_editors(self): enabled = self._accent_component.activated for note_editor in self._note_editors: note_editor.full_velocity = enabled def set_full_velocity(self, full_velocity): self._accent_component.set_full_velocity(full_velocity) self._update_full_velocity_for_editors() def set_accent_button(self, accent_button): self._accent_component.accent_button.set_control_element(accent_button) self._update_full_velocity_for_editors() def set_quantization_buttons(self, buttons): self._grid_resolution.quantization_buttons.set_control_element(buttons) def set_mute_button(self, button): for e in self._note_editors: e.mute_button.set_control_element(button) @property def show_notifications(self): return self._show_notifications @show_notifications.setter def show_notifications(self, value): self._show_notifications = value @listenable_property def editable_pitches(self): note_editor_range = self._note_editors if self.sequence_modes.selected_mode == 'sequence' else self._note_editors[ 0:7] return [ editor.editing_notes[0] for editor in note_editor_range if len(editor.editing_notes) > 0 ] @listenable_property def step_length(self): return self._grid_resolution.step_length @listenable_property def editing_note_regions(self): return sum([ note_editor.editing_note_regions for note_editor in self._note_editors ], []) @listenable_property def row_start_times(self): return self._note_editors[0].get_row_start_times() @listens('index') def __on_grid_resolution_changed(self, *a): if self.is_enabled(): self.notify_row_start_times() self.notify_step_length() @listens('page_index') def _on_page_index_changed(self): if self.is_enabled(): self.notify_row_start_times() @listens('page_length') def _on_page_length_changed(self): if self.is_enabled(): self.notify_row_start_times() @listens_group('active_steps') def _on_active_steps_changed(self, _): if self.is_enabled(): self.notify_editing_note_regions() @listens_group('modify_all_notes') def _on_modify_all_notes_changed(self, _): if self.is_enabled(): self.notify_editing_note_regions() @listens('position') def _on_notes_changed(self, *args): self._update_note_editors() self._show_notes_information() @listens('pattern') def _on_pattern_changed(self): self._update_note_editors() def _update_note_editors(self, *a): for row, note_editor in enumerate(self._note_editors): note_info = self.instrument.pattern[row] note_editor.background_color = 'NoteEditor.' + note_info.color if note_info.index != None: note_editor.editing_notes = [note_info.index] if 1 else [] self._update_matrix_channels_for_playhead() self.notify_editable_pitches() self.notify_row_start_times() self.notify_step_length() return def _update_matrix_channels_for_playhead(self): if self.is_enabled() and self._matrices is not None: pattern = self.instrument.pattern for matrix, (y, _) in self._matrices.iterbuttons(): if matrix: for x, button in enumerate(matrix): if button: if pattern[y].index is not None: button.set_identifier(x) button.set_channel( PLAYHEAD_FEEDBACK_CHANNELS[y]) else: button.set_identifier( button._original_identifier) button.set_channel(NON_FEEDBACK_CHANNEL) return def _update_playhead_color(self): if self.is_enabled() and self._skin and self._playhead: self._playhead.velocity = to_midi_value( self._skin[self._playhead_color]) def update(self): super(MelodicComponent, self).update() if self.is_enabled(): self._on_detail_clip_changed() self._update_playhead_color() self._update_note_editors() def _show_notes_information(self, mode=None): if self.is_enabled() and self.show_notifications: if mode is None: mode = self.selected_mode if mode == 'sequence': message = 'Sequence %s to %s' start_note = self._note_editors[0].editing_notes[0] end_editor = find_if( lambda editor: len(editor.editing_notes) > 0, reversed(self._note_editors)) end_note = end_editor.editing_notes[0] self.show_notification(message % (pitch_index_to_string(start_note), pitch_index_to_string(end_note))) else: self.instrument.show_pitch_range_notification() return
class NotificationComponent(Component): _default_align_text_fn = partial(maybe(partial(align_none, DISPLAY_LENGTH))) def __init__(self, default_notification_time=2.5, blinking_time=0.3, display_lines=[], *a, **k): (super(NotificationComponent, self).__init__)(*a, **k) self._display_lines = get_element(display_lines) self._token_control = _TokenControlElement() self._align_text_fn = self._default_align_text_fn self._message_box = MessageBoxComponent(parent=self) self._message_box.set_enabled(False) self._default_notification_time = default_notification_time self._blinking_time = blinking_time self._original_text = None self._blink_text = None self._blink_text_task = self._tasks.add( task.loop( task.sequence( task.run(lambda: self._message_box.__setattr__( 'text', self._original_text)), task.wait(self._blinking_time), task.run(lambda: self._message_box.__setattr__( 'text', self._blink_text)), task.wait(self._blinking_time)))).kill() message_box_layer = forward_property('_message_box')('layer') def show_notification(self, text, blink_text=None, notification_time=None): self._create_tasks(notification_time) text = apply_formatting(text) text = self._align_text_fn(text) blink_text = self._align_text_fn(blink_text) if blink_text is not None: self._original_text = text self._blink_text = blink_text self._blink_text_task.restart() self._message_box.text = text self._message_box.set_enabled(True) self._notification_timeout_task.restart() self._current_notification = Notification(self) return ref(self._current_notification) def hide_notification(self): self._blink_text_task.kill() self._message_box.set_enabled(False) def use_single_line(self, line_index, line_slice=None, align=align_none): display = self._display_lines[line_index] if line_slice is not None: display = display.subdisplay[line_slice] layer = Layer(priority=MESSAGE_BOX_PRIORITY, display_line1=display) return _CallbackControl( self._token_control, partial(self._set_message_box_layout, layer, maybe(partial(align, display.width)))) def use_full_display(self, message_line_index=2): layer = Layer( priority=MESSAGE_BOX_PRIORITY, **dict([ ('display_line1' if i == message_line_index else 'bg%d' % i, line) for i, line in enumerate(self._display_lines) ])) return _CallbackControl(self._token_control, partial(self._set_message_box_layout, layer)) def _set_message_box_layout(self, layer, align_text_fn=None): self._message_box.layer = layer self._align_text_fn = partial(align_text_fn or self._default_align_text_fn) def _create_tasks(self, notification_time): duration = notification_time if notification_time is not None else self._default_notification_time self._notification_timeout_task = self._tasks.add( task.sequence(task.wait(duration), task.run(self.hide_notification) )).kill() if duration != -1 else self._tasks.add( task.Task())
class LoopSettingsModel(Subject, SlotManager): __events__ = ('looping', 'loop_start', 'loop_end', 'loop_length', 'position', 'start_marker') def __init__(self, song, *a, **k): super(LoopSettingsModel, self).__init__(*a, **k) self.clip = None self._song = song def _get_clip(self): return self._clip def _set_clip(self, clip): self._clip = clip self._on_looping_changed.subject = clip self._on_start_marker_changed.subject = clip self._on_loop_start_changed.subject = clip self._on_loop_end_changed.subject = clip self._on_position_changed.subject = clip clip = property(_get_clip, _set_clip) loop_start = forward_property('clip')('loop_start') start_marker = forward_property('clip')('start_marker') loop_end = forward_property('clip')('loop_end') looping = forward_property('clip')('looping') position = forward_property('clip')('position') @listens('looping') def _on_looping_changed(self): self.notify_looping() @listens('start_marker') def _on_start_marker_changed(self): self.notify_start_marker() @listens('loop_start') def _on_loop_start_changed(self): self.notify_loop_start() self.notify_loop_length() @listens('loop_end') def _on_loop_end_changed(self): self.notify_loop_end() self.notify_loop_length() @listens('position') def _on_position_changed(self): self.notify_position() @property def loop_length(self): return self.loop_end - self.loop_start @property def can_loop(self): return self.clip.is_midi_clip or self.clip.is_audio_clip and self.clip.warping def move_start_marker(self, value, fine_grained): marker = self.clip.start_marker if self.looping else self.clip.loop_start new_value = marker + self._adjusted_offset(value, fine_grained) measure_in_beats = one_measure_in_note_values(self.clip) measure_in_sixteenths = one_measure_in_note_values(self.clip, 16.0) additional_offset = measure_in_beats / measure_in_sixteenths * (measure_in_sixteenths - 1) if fine_grained else 0.0 new_value = min(new_value, self.clip.loop_end - measure_in_beats + additional_offset) if self.looping: if new_value >= self.clip.end_marker: self.clip.end_marker = self.clip.loop_end self.clip.start_marker = new_value else: self.clip.loop_start = new_value def move_position(self, value, fine_grained): if not is_new_recording(self.clip): self.clip.position += self._adjusted_offset(value, fine_grained) self.clip.view.show_loop() def move_loop_end(self, value, fine_grained): if not is_new_recording(self.clip): new_end = self.clip.loop_end + self._adjusted_offset(value, fine_grained) if new_end > self.loop_start: self.clip.loop_end = new_end def _adjusted_offset(self, value, fine_grained): return value * self._encoder_factor(fine_grained) * one_measure_in_note_values(self.clip) def _encoder_factor(self, fine_grained): return 1.0 / one_measure_in_note_values(self.clip, 16.0) if fine_grained else 1.0
class MixerComponent(MixerComponentBase): num_sends_control = SendValueControl() master_button = ButtonControl() def __init__(self, *a, **k): super(MixerComponent, self).__init__(*a, **k) self._last_selected_track = None self._last_track_offset = None self.__on_offsets_changed.subject = self._provider self.__on_offsets_changed(self._provider.track_offset, self._provider.scene_offset) return def __getattr__(self, name): if name.startswith(b'set_') and name.endswith(b's'): return partial(self._set_channel_strip_controls, name[4:-1]) raise AttributeError def on_num_sends_changed(self): self.num_sends_control.value = clamp(self.num_sends, 0, MAX_NUM_SENDS) @property def max_track_offset(self): return max( 0, len(self._provider.tracks_to_use()) - self._provider.num_tracks) def _on_selected_track_changed(self): selected_track = self.song.view.selected_track button_color = b'DefaultButton.On' if selected_track != self.song.master_track: self._last_selected_track = selected_track button_color = b'DefaultButton.Off' self.master_button.color = button_color @listens(b'offset') def __on_offsets_changed(self, track_offset, _): max_track_offset = self.max_track_offset if max_track_offset == 0 or track_offset < max_track_offset: self._last_track_offset = track_offset def set_send_controls(self, controls): self._send_controls = controls for strip, row in izip_longest(self._channel_strips, controls.rows() if controls else []): strip.set_send_controls(row) def set_send_value_displays(self, displays): for strip, row in izip_longest(self._channel_strips, displays.rows() if displays else []): strip.set_send_value_displays(row) def set_selected_track_mute_button(self, button): self._selected_strip.mpc_mute_button.set_control_element(button) set_selected_track_arm_button = forward_property(b'_selected_strip')( b'set_arm_button') set_selected_track_solo_button = forward_property(b'_selected_strip')( b'set_solo_button') def set_track_type_controls(self, controls): for strip, control in izip_longest(self._channel_strips, controls or []): strip.track_type_control.set_control_element(control) def _set_channel_strip_controls(self, name, controls): for strip, control in izip_longest(self._channel_strips, controls or []): set_method = getattr(strip, (b'set_{}').format(name), None) if not set_method: set_method = getattr(strip, name, None).set_control_element set_method(control) return def set_solo_mute_buttons(self, buttons): for strip, button in izip_longest(self._channel_strips, buttons or []): strip.solo_mute_button.set_control_element(button) @master_button.pressed def master_button_value(self, _button): master_track = self.song.master_track if self.song.view.selected_track != master_track: self.song.view.selected_track = master_track else: self.song.view.selected_track = self._last_selected_track if liveobj_valid( self._last_selected_track) else self.song.tracks[0] if self._provider.track_offset < self.max_track_offset: self._provider.track_offset = self.max_track_offset else: self._provider.track_offset = self._last_track_offset
class StepSeqComponent(Component): u""" This component represents one of the sequencing mechanisms for Push, which has one NoteEditorComponent associated with a single pitch. The component mostly manages distributing control elements to sub-components, which then provide the logic for this layout. """ def __init__(self, clip_creator=None, skin=None, grid_resolution=None, note_editor_component=None, instrument_component=None, *a, **k): super(StepSeqComponent, self).__init__(*a, **k) assert clip_creator is not None assert skin is not None assert instrument_component is not None assert note_editor_component is not None self._grid_resolution = grid_resolution self._note_editor = note_editor_component self._loop_selector = LoopSelectorComponent(parent=self, clip_creator=clip_creator, default_size=16) self._instrument = instrument_component self.paginator = NoteEditorPaginator([self._note_editor], parent=self) self._step_duplicator = StepDuplicatorComponent(parent=self) self._note_editor.set_step_duplicator(self._step_duplicator) self._loop_selector.set_step_duplicator(self._step_duplicator) self._loop_selector.set_paginator(self.paginator) self._on_pressed_pads_changed.subject = self._instrument self._on_selected_notes_changed.subject = self._instrument.selected_notes_provider self._on_detail_clip_changed.subject = self.song.view self.__on_grid_resolution_changed.subject = self._grid_resolution self._on_page_index_changed.subject = self.paginator self._on_page_length_changed.subject = self.paginator self._on_active_steps_changed.subject = self._note_editor self._on_modify_all_notes_changed.subject = self._note_editor self._detail_clip = None self._playhead = None self._playhead_component = PlayheadComponent( parent=self, grid_resolution=grid_resolution, paginator=self.paginator, follower=self._loop_selector, notes=chain(*starmap(range, ((92, 100), (84, 92), (76, 84), (68, 76)))), triplet_notes=chain(*starmap(range, ((92, 98), (84, 90), (76, 82), (68, 74)))), feedback_channels=PLAYHEAD_FEEDBACK_CHANNELS) self._accent_component = AccentComponent(parent=self) self.__on_accent_mode_changed.subject = self._accent_component self._skin = skin self._playhead_color = 'NoteEditor.Playhead' return next_loop_page_button = forward_property('_loop_selector')( 'next_page_button') prev_loop_page_button = forward_property('_loop_selector')( 'prev_page_button') def set_playhead(self, playhead): self._playhead = playhead self._playhead_component.set_playhead(playhead) self._update_playhead_color() def set_full_velocity(self, full_velocity): self._accent_component.set_full_velocity(full_velocity) self.__on_accent_mode_changed() def set_accent_button(self, accent_button): self._accent_component.accent_button.set_control_element(accent_button) def _get_playhead_color(self): return self._playhead_color def _set_playhead_color(self, value): self._playhead_color = 'NoteEditor.' + value self._update_playhead_color() playhead_color = property(_get_playhead_color, _set_playhead_color) @listenable_property def editing_note_regions(self): return self._note_editor.editing_note_regions @listenable_property def editable_pitches(self): return self._note_editor.editing_notes @listenable_property def step_length(self): return self._grid_resolution.step_length @listenable_property def row_start_times(self): return self._note_editor.get_row_start_times() @listens('index') def __on_grid_resolution_changed(self, *a): if self.is_enabled(): self.notify_row_start_times() self.notify_step_length() @listens('page_index') def _on_page_index_changed(self): if self.is_enabled(): self.notify_row_start_times() @listens('page_length') def _on_page_length_changed(self): if self.is_enabled(): self.notify_row_start_times() @listens('active_steps') def _on_active_steps_changed(self): if self.is_enabled(): self.notify_editing_note_regions() @listens('modify_all_notes') def _on_modify_all_notes_changed(self): if self.is_enabled(): self.notify_editing_note_regions() @listens('activated') def __on_accent_mode_changed(self): self._note_editor.full_velocity = self._accent_component.activated def _is_triplet_quantization(self): return self._grid_resolution.clip_grid[1] def _update_playhead_color(self): if self.is_enabled() and self._skin and self._playhead: self._playhead.velocity = to_midi_value( self._skin[self._playhead_color]) def set_select_button(self, button): self._instrument.select_button.set_control_element(button) self._loop_selector.select_button.set_control_element(button) def set_mute_button(self, button): self._note_editor.mute_button.set_control_element(button) def set_delete_button(self, button): self._instrument.delete_button.set_control_element(button) self._loop_selector.delete_button.set_control_element(button) def set_loop_selector_matrix(self, matrix): self._loop_selector.set_loop_selector_matrix(matrix) def set_short_loop_selector_matrix(self, matrix): self._loop_selector.set_short_loop_selector_matrix(matrix) def set_duplicate_button(self, button): self._step_duplicator.button.set_control_element(button) def set_button_matrix(self, matrix): self._note_editor.set_matrix(matrix) def set_quantization_buttons(self, buttons): self._grid_resolution.quantization_buttons.set_control_element(buttons) def set_velocity_control(self, control): self._note_editor.set_velocity_control(control) def set_length_control(self, control): self._note_editor.set_length_control(control) def set_nudge_control(self, control): self._note_editor.set_nudge_control(control) def update(self): super(StepSeqComponent, self).update() if self.is_enabled(): self._on_selected_notes_changed( self._instrument.selected_notes_provider.selected_notes) self._update_playhead_color() self._on_detail_clip_changed() self.notify_row_start_times() self.notify_step_length() @listens('detail_clip') def _on_detail_clip_changed(self): clip = self.song.view.detail_clip clip = clip if liveobj_valid(clip) and clip.is_midi_clip else None self._detail_clip = clip self._note_editor.set_detail_clip(clip) self._loop_selector.set_detail_clip(clip) self._playhead_component.set_clip(self._detail_clip) return @listens('selected_notes') def _on_selected_notes_changed(self, notes): if self.is_enabled(): self._note_editor.editing_notes = notes self.notify_editable_pitches() @listens('pressed_pads') def _on_pressed_pads_changed(self, _): self._note_editor.modify_all_notes_enabled = len( self._instrument.pressed_pads) > 0
class NotificationComponent(Component): """' Displays notifications to the user for a given amount of time. A notification time of -1 creates an infinite duration notification. To adjust the way notifications are shown in special cases, assign a generated control using use_single_line or use_full_display to a layer. If the layer is on top, it will set the preferred view. This will show the notification on line 1 if my_component is enabled and the priority premise of the layer is met: my_component.layer = Layer( _notification = notification_component.use_single_line(1)) """ __module__ = __name__ _default_align_text_fn = partial(maybe(partial(align_none, DISPLAY_LENGTH))) def __init__(self, default_notification_time=2.5, blinking_time=0.3, display_lines=[], *a, **k): super(NotificationComponent, self).__init__(*a, **k) self._display_lines = get_element(display_lines) self._token_control = _TokenControlElement() self._align_text_fn = self._default_align_text_fn self._message_box = MessageBoxComponent(parent=self) self._message_box.set_enabled(False) self._default_notification_time = default_notification_time self._blinking_time = blinking_time self._original_text = None self._blink_text = None self._blink_text_task = self._tasks.add( task.loop( task.sequence( task.run(lambda: self._message_box.__setattr__( 'text', self._original_text)), task.wait(self._blinking_time), task.run(lambda: self._message_box.__setattr__( 'text', self._blink_text)), task.wait(self._blinking_time)))).kill() return message_box_layer = forward_property('_message_box')('layer') def show_notification(self, text, blink_text=None, notification_time=None): u""" Triggers a notification with the given text. If text is a tuple, it will treat it as a format string + arguments. """ self._create_tasks(notification_time) text = apply_formatting(text) text = self._align_text_fn(text) blink_text = self._align_text_fn(blink_text) if blink_text is not None: self._original_text = text self._blink_text = blink_text self._blink_text_task.restart() self._message_box.text = text self._message_box.set_enabled(True) self._notification_timeout_task.restart() self._current_notification = Notification(self) return ref(self._current_notification) def hide_notification(self): u""" Hides the current notification, if any existing. """ self._blink_text_task.kill() self._message_box.set_enabled(False) def use_single_line(self, line_index, line_slice=None, align=align_none): u""" Returns a control, that will change the notification to a single line view, if it is grabbed. """ assert line_index >= 0 and line_index < len(self._display_lines) display = self._display_lines[line_index] if line_slice is not None: display = display.subdisplay[line_slice] layer = Layer(priority=MESSAGE_BOX_PRIORITY, display_line1=display) return _CallbackControl( self._token_control, partial(self._set_message_box_layout, layer, maybe(partial(align, display.width)))) def use_full_display(self, message_line_index=2): u""" Returns a control, that will change the notification to use the whole display, if it is grabbed. """ layer = Layer( priority=MESSAGE_BOX_PRIORITY, **dict([ ('display_line1' if i == message_line_index else 'bg%d' % i, line) for i, line in enumerate(self._display_lines) ])) return _CallbackControl(self._token_control, partial(self._set_message_box_layout, layer)) def _set_message_box_layout(self, layer, align_text_fn=None): self._message_box.layer = layer self._align_text_fn = partial(align_text_fn or self._default_align_text_fn) def _create_tasks(self, notification_time): duration = notification_time if notification_time is not None else self._default_notification_time self._notification_timeout_task = self._tasks.add( task.sequence(task.wait(duration), task.run(self.hide_notification) )).kill() if duration != -1 else self._tasks.add( task.Task()) return
class NoteEditorSettingsComponent(ModesComponent): def __init__(self, note_settings_component=None, automation_component=None, initial_encoder_layer=None, encoder_layer=None, *a, **k): super(NoteEditorSettingsComponent, self).__init__(*a, **k) raise encoder_layer or AssertionError self._request_hide = False self.settings = self.register_component(note_settings_component) self.settings.set_enabled(False) self._automation = self.register_component(automation_component) self._automation.set_enabled(False) self._mode_selector = self.register_component( OptionsComponent(num_options=2, num_labels=0, num_display_segments=8)) self._mode_selector.set_enabled(False) self._on_selected_option.subject = self._mode_selector self._update_available_modes() self._mode_selector.selected_option = 0 self._visible_detail_view = 'Detail/DeviceChain' self._show_settings_task = self._tasks.add( task.sequence(task.wait(defaults.MOMENTARY_DELAY), task.run(self._show_settings))).kill() self._update_infos_task = self._tasks.add( task.run(self._update_note_infos)).kill() self._settings_modes = self.register_component(ModesComponent()) self._settings_modes.set_enabled(False) self._settings_modes.add_mode('automation', [ self._automation, self._mode_selector, partial(self._set_envelope_view_visible, True), self._show_clip_view ]) self._settings_modes.add_mode('note_settings', [ self.settings, self._update_note_infos, self._mode_selector, partial(self._set_envelope_view_visible, False), self._show_clip_view ]) self._encoders = None self._initial_encoders = None self.add_mode('disabled', []) self.add_mode('about_to_show', [ AddLayerMode(self, initial_encoder_layer), (self._show_settings_task.restart, self._show_settings_task.kill) ]) self.add_mode('enabled', [ DetailViewRestorerMode(self.application), AddLayerMode(self, encoder_layer), self._update_available_modes, self._settings_modes ]) self.selected_mode = 'disabled' self._editors = [] self._on_detail_clip_changed.subject = self.song.view self._on_selected_track_changed.subject = self.song.view self.__on_full_velocity_changed.subject = self.settings self.__on_setting_changed.subject = self.settings return automation_layer = forward_property('_automation')('layer') mode_selector_layer = forward_property('_mode_selector')('layer') selected_setting = forward_property('_settings_modes')('selected_mode') @property def step_settings(self): return self._settings_modes @property def editors(self): return self._editors def add_editor(self, editor): raise editor != None or AssertionError self._editors.append(editor) self._on_active_note_regions_changed.add_subject(editor) self._on_notes_changed.replace_subjects(self._editors) self.__on_modify_all_notes_changed.add_subject(editor) return def set_display_line(self, line): self._mode_selector.set_display_line(line) def set_initial_encoders(self, encoders): self._initial_encoders = encoders self._on_init_encoder_touch.replace_subjects(encoders or []) self._on_init_encoder_value.replace_subjects(encoders or []) def set_encoders(self, encoders): self._encoders = encoders self._on_encoder_touch.replace_subjects(encoders or []) self._on_encoder_value.replace_subjects(encoders or []) self.settings.set_encoder_controls(encoders) self._automation.set_parameter_controls(encoders) def _get_parameter_provider(self): self._automation.parameter_provider def _set_parameter_provider(self, value): self._automation.parameter_provider = value if self.selected_mode != 'disabled': self._update_available_modes() parameter_provider = property(_get_parameter_provider, _set_parameter_provider) @listens('full_velocity') def __on_full_velocity_changed(self): for editor in self._editors: editor.set_full_velocity() @listens('setting_changed') def __on_setting_changed(self, index, value): for editor in self._editors: self._modify_note_property_offset(editor, index, value) def _modify_note_property_offset(self, editor, index, value): if index == 1: editor.set_nudge_offset(value) elif index == 2: editor.set_length_offset(value) elif index == 3: editor.set_velocity_offset(value) def _update_available_modes(self): available_modes = ['Notes'] if self._automation.can_automate_parameters: available_modes.append('Automat') self._mode_selector.option_names = available_modes def _show_clip_view(self): try: view = self.application.view if view.is_view_visible( 'Detail/DeviceChain', False) and not view.is_view_visible('Detail/Clip', False): self.application.view.show_view('Detail/Clip') except RuntimeError: pass def _set_envelope_view_visible(self, visible): clip = self.song.view.detail_clip if clip: if visible: clip.view.show_envelope() else: clip.view.hide_envelope() def _try_immediate_show_settings(self): if self.selected_mode == 'about_to_show' and any( imap(lambda e: e and e.is_pressed(), self._initial_encoders or [])): self._show_settings() @listens_group('active_note_regions') def _on_active_note_regions_changed(self, _): if self.is_enabled(): all_steps = list( set( chain.from_iterable( imap(lambda e: e.active_note_regions, self._editors)))) self._automation.selected_time = all_steps self._update_note_infos() if len(all_steps) > 0: self._request_hide = False if self.selected_mode == 'disabled': self.selected_mode = 'about_to_show' self._try_immediate_show_settings() else: self._request_hide = True self._try_hide_settings() @listens_group('modify_all_notes') def __on_modify_all_notes_changed(self, editor): self.selected_mode = 'about_to_show' if editor.modify_all_notes_enabled else 'disabled' @listens_group('notes_changed') def _on_notes_changed(self, editor): self._update_infos_task.restart() @listens('detail_clip') def _on_detail_clip_changed(self): clip = self.song.view.detail_clip if self.is_enabled() else None self._automation.clip = clip return @listens('selected_track') def _on_selected_track_changed(self): self.selected_mode = 'disabled' @listens('selected_option') def _on_selected_option(self, option): self._update_selected_setting(option) @listens_group('touch_value') def _on_init_encoder_touch(self, value, encoder): self._show_settings() @listens_group('value') def _on_init_encoder_value(self, value, encoder): self._show_settings() @listens_group('touch_value') def _on_encoder_touch(self, value, encoder): if not value: self._try_hide_settings() @listens_group('value') def _on_encoder_value(self, value, encoder): self._notify_modification() def _notify_modification(self): for editor in self._editors: editor.notify_modification() def _update_note_infos(self): if self.settings.is_enabled(): def min_max((l_min, l_max), (r_min, r_max)): return (min(l_min, r_min), max(l_max, r_max)) all_min_max_attributes = filter( None, imap(lambda e: e.get_min_max_note_values(), self._editors)) min_max_values = [ (99999, -99999) ] * 4 if len(all_min_max_attributes) > 0 else None for min_max_attribute in all_min_max_attributes: for i, attribute in enumerate(min_max_attribute): min_max_values[i] = min_max(min_max_values[i], attribute) for i in xrange(4): self.settings.set_min_max( i, min_max_values[i] if min_max_values else None) edit_all_notes_active = find_if( lambda e: e.modify_all_notes_enabled, self._editors) != None self.settings.set_info_message( 'Tweak to add note' if not edit_all_notes_active and not min_max_values else '') return
class LoopSettingsModel(EventObject): __events__ = (u'looping', u'loop_start', u'loop_end', u'loop_length', u'position', u'start_marker') def __init__(self, song, *a, **k): super(LoopSettingsModel, self).__init__(*a, **k) self.clip = None self._song = song return @listenable_property def clip(self): return self._clip @clip.setter def clip(self, clip): self._clip = clip self._loop_length = self._get_loop_length() self._on_looping_changed.subject = clip self._on_start_marker_changed.subject = clip self._on_loop_start_changed.subject = clip self._on_loop_end_changed.subject = clip self._on_position_changed.subject = clip self.notify_clip() loop_start = forward_property('clip')('loop_start') start_marker = forward_property('clip')('start_marker') loop_end = forward_property('clip')('loop_end') looping = forward_property('clip')('looping') position = forward_property('clip')('position') @listens('looping') def _on_looping_changed(self): self.notify_looping() @listens('start_marker') def _on_start_marker_changed(self): self.notify_start_marker() @listens('loop_start') def _on_loop_start_changed(self): self._update_loop_length() self.notify_loop_start() @listens('loop_end') def _on_loop_end_changed(self): self._update_loop_length() self.notify_loop_end() @listens('position') def _on_position_changed(self): self.notify_position() @property def loop_length(self): return self._loop_length def _get_loop_length(self): if liveobj_valid(self._clip): return self.loop_end - self.loop_start return 0 def _update_loop_length(self): loop_length = self._get_loop_length() if self._loop_length != loop_length: self._loop_length = loop_length self.notify_loop_length() @property def can_loop(self): return self.clip.is_midi_clip or self.clip.is_audio_clip and self.clip.warping def move_start_marker(self, value, fine_grained): marker = self.looping and self.clip.start_marker if 1 else self.clip.loop_start new_value = marker + self._adjusted_offset(value, fine_grained) signature = (self.clip.signature_numerator, self.clip.signature_denominator) measure_in_beats = one_bar_in_note_values(signature) measure_in_sixteenths = one_bar_in_note_values(signature, 16.0) additional_offset = measure_in_beats / measure_in_sixteenths * ( measure_in_sixteenths - 1) if fine_grained else 0.0 new_value = min( new_value, self.clip.loop_end - measure_in_beats + additional_offset) if self.looping: if new_value >= self.clip.end_marker: self.clip.end_marker = self.clip.loop_end self.clip.start_marker = new_value else: self.clip.loop_start = new_value def move_position(self, value, fine_grained): if not is_new_recording(self.clip): new_value = self.clip.position + self._adjusted_offset( value, fine_grained) should_update_start_marker = self.clip.position == self.clip.start_marker self.clip.position = new_value if should_update_start_marker: self.clip.start_marker = new_value self.clip.view.show_loop() def move_loop_end(self, value, fine_grained): if not is_new_recording(self.clip): new_end = self.clip.loop_end + self._adjusted_offset( value, fine_grained) if new_end > self.loop_start: self.clip.loop_end = new_end def _adjusted_offset(self, value, fine_grained): return value * self._encoder_factor( fine_grained) * one_bar_in_note_values( (self.clip.signature_numerator, self.clip.signature_denominator)) def _encoder_factor(self, fine_grained): if fine_grained: return 1.0 / one_bar_in_note_values( (self.clip.signature_numerator, self.clip.signature_denominator), 16.0) return 1.0
class FixedLengthSessionRecordingComponent(SessionRecordingComponent): _length_buttons = [] def __init__(self, clip_creator, length_values = LENGTH_VALUES, *a, **k): super(FixedLengthSessionRecordingComponent, self).__init__(*a, **k) self._clip_creator = clip_creator self._length_value = 1 self._length_values = length_values self._fixed_length = self.register_component(ToggleWithOptionsComponent()) self._length_selector = self._fixed_length.options self._length_selector.option_names = LENGTH_OPTION_NAMES self._length_selector.selected_option = 3 self._length_selector.labels = LENGTH_LABELS self._on_selected_fixed_length_option_changed.subject = self._length_selector length, _ = self._get_selected_length() self._clip_creator.fixed_length = length length_layer = forward_property('_length_selector')('layer') def _length_should_be_fixed(self): return self._fixed_length.is_active def _original_get_selected_length(self): song = self.song length = 2.0 ** self._length_selector.selected_option quant = LAUNCH_QUANTIZATION[self._length_selector.selected_option] if self._length_selector.selected_option > 1: length = length * song.signature_numerator / song.signature_denominator return (length, quant) def _get_selected_length(self): song = self.song length = 2.0 ** (self._length_values[self._length_value]) quant = LAUNCH_QUANTIZATION[(self._length_values[self._length_value])] length = length * song.signature_numerator / song.signature_denominator return (length, quant) def set_length_button(self, button): self._fixed_length.action_button.set_control_element(button) self._on_length_value.subject = button self._length_press_state = None def set_length_buttons(self, buttons): self._on_length_buttons_value.subject = buttons self.update_length_buttons() @listens('value') def _on_length_buttons_value(self, value, x, y, *a, **k): if value > 0: self._length_value = x self.update_length_buttons() def _start_recording(self): song = self.song song.overdub = True selected_scene = song.view.selected_scene scene_index = list(song.scenes).index(selected_scene) track = self.song.view.selected_track if track.can_be_armed and (track.arm or track.implicit_arm): self._record_in_slot(track, track.clip_slots[scene_index]) self._ensure_slot_is_visible(track, scene_index) if not song.is_playing: song.is_playing = True def _record_in_slot(self, track, clip_slot): if self._length_should_be_fixed() and not clip_slot.has_clip: length, quant = self._get_selected_length() if track_can_overdub(track): self._clip_creator.create(clip_slot, length) else: clip_slot.fire(record_length=length, launch_quantization=quant) elif not clip_slot.is_playing: if clip_slot.has_clip: clip_slot.fire(force_legato=True, launch_quantization=_Q.q_no_q) else: clip_slot.fire() def _ensure_slot_is_visible(self, track, scene_index): song = self.song if song.view.selected_track == track: song.view.selected_scene = song.scenes[scene_index] self._view_selected_clip_detail() @listens('selected_option') def _on_selected_fixed_length_option_changed(self, _): length, _ = self._get_selected_length() self._clip_creator.fixed_length = length @listens('value') def _on_length_value(self, value): if value: self._on_length_press() else: self._on_length_release() def _on_length_press(self): song = self.song slot = song_selected_slot(song) if slot == None: return clip = slot.clip if slot.is_recording and not clip.is_overdubbing: self._length_press_state = (slot, clip.playing_position) def _on_length_release(self): song = self.song slot = song_selected_slot(song) if slot == None: return clip = slot.clip if self._length_press_state is not None: press_slot, press_position = self._length_press_state if press_slot == slot and self._length_should_be_fixed() and slot.is_recording and not clip.is_overdubbing: length, _ = self._get_selected_length() one_bar = 4.0 * song.signature_numerator / song.signature_denominator loop_end = int(press_position / one_bar) * one_bar loop_start = loop_end - length if loop_start >= 0.0: clip.loop_end = loop_end clip.end_marker = loop_end clip.loop_start = loop_start clip.start_marker = loop_start self._tasks.add(Task.sequence(Task.delay(0), Task.run(partial(slot.fire, force_legato=True, launch_quantization=_Q.q_no_q)))) self.song.overdub = False self._fixed_length.is_active = False self._length_press_state = None def _handle_limitation_error_on_scene_creation(self): pass def update(self, *a, **k): super(FixedLengthSessionRecordingComponent, self).update(*a, **k) if self.is_enabled(): self.update_length_buttons() def update_length_buttons(self): buttons = self._on_length_buttons_value.subject if buttons: for button, (x, y) in buttons.iterbuttons(): if button: if x == self._length_value: button.set_light('Recorder.FixedAssigned') else: button.set_light('Recorder.FixedNotAssigned') def _update_new_button(self): self._update_generic_new_button(self._new_button) def _update_generic_new_button(self, new_button): if new_button and self.is_enabled(): song = self.song selected_track = song.view.selected_track clip_slot = song.view.highlighted_clip_slot can_new = liveobj_valid(clip_slot) and clip_slot.clip or selected_track.can_be_armed and selected_track.playing_slot_index >= 0 new_button.set_light('Recorder.NewOn' if can_new else 'Recorder.NewOff')
class NoteEditorSettingsComponent(ModesComponent): initial_encoders = control_list(EncoderControl) encoders = control_list(EncoderControl) def __init__(self, note_settings_component=None, automation_component=None, initial_encoder_layer=None, encoder_layer=None, *a, **k): super(NoteEditorSettingsComponent, self).__init__(*a, **k) assert encoder_layer self.settings = self.register_component(note_settings_component) self.settings.set_enabled(False) self._automation = self.register_component(automation_component) self._automation.set_enabled(False) self._mode_selector = self.register_component(ModeSelector(is_enabled=False)) self._visible_detail_view = 'Detail/DeviceChain' self._show_settings_task = self._tasks.add(task.sequence(task.wait(defaults.MOMENTARY_DELAY), task.run(self._show_settings))).kill() self._update_infos_task = self._tasks.add(task.run(self._update_note_infos)).kill() self._settings_modes = self.register_component(ModesComponent()) self._settings_modes.set_enabled(False) self._settings_modes.add_mode('automation', [ self._automation, self._mode_selector, partial(self._set_envelope_view_visible, True), partial(show_clip_view, self.application)]) self._settings_modes.add_mode('note_settings', [ self.settings, self._update_note_infos, self._mode_selector, partial(self._set_envelope_view_visible, False), partial(show_clip_view, self.application)]) self._settings_modes.selected_mode = 'note_settings' self.__on_selected_setting_mode_changed.subject = self._settings_modes self.add_mode('disabled', []) self.add_mode('about_to_show', [ AddLayerMode(self, initial_encoder_layer), ( self._show_settings_task.restart, self._show_settings_task.kill)]) self.add_mode('enabled', [ DetailViewRestorerMode(self.application), AddLayerMode(self, encoder_layer), self._settings_modes]) self.selected_mode = 'disabled' self._editors = [] self._on_detail_clip_changed.subject = self.song.view self._on_selected_track_changed.subject = self.song.view self.__on_full_velocity_changed.subject = self.settings self.__on_setting_changed.subject = self.settings automation_layer = forward_property('_automation')('layer') mode_selector_layer = forward_property('_mode_selector')('layer') selected_setting = forward_property('_settings_modes')('selected_mode') @property def step_settings(self): return self._settings_modes @property def editors(self): return self._editors @listenable_property def is_touched(self): return any(imap(lambda e: e and e.is_touched, ifilter(lambda e: self._can_notify_is_touched(e), self.encoders))) def _is_step_held(self): return len(self._active_note_regions()) > 0 def add_editor(self, editor): assert editor != None self._editors.append(editor) self._on_active_note_regions_changed.add_subject(editor) self._on_notes_changed.replace_subjects(self._editors) self.__on_modify_all_notes_changed.add_subject(editor) return def set_encoders(self, encoders): self.encoders.set_control_element(encoders) self.settings.set_encoder_controls(encoders) self._automation.set_parameter_controls(encoders) @property def parameter_provider(self): self._automation.parameter_provider @parameter_provider.setter def parameter_provider(self, value): self._automation.parameter_provider = value @listens('selected_mode') def __on_selected_setting_mode_changed(self, mode): if mode == 'automation': self._automation.selected_time = self._active_note_regions() def update_view_state_based_on_selected_setting(self, setting): if self.selected_mode == 'enabled' and self.is_touched or setting is None: self._set_settings_view_enabled(False) else: if self._is_step_held(): if self.selected_setting == 'automation' and self._automation.can_automate_parameters or self.selected_setting == 'note_settings': self._show_settings() return @listens('full_velocity') def __on_full_velocity_changed(self): for editor in self._editors: editor.set_full_velocity() @listens('setting_changed') def __on_setting_changed(self, index, value): for editor in self._editors: self._modify_note_property_offset(editor, index, value) def _modify_note_property_offset(self, editor, index, value): if index == 1: editor.set_nudge_offset(value) else: if index == 2: editor.set_length_offset(value) else: if index == 3: editor.set_velocity_offset(value) def _set_envelope_view_visible(self, visible): clip = self.song.view.detail_clip if liveobj_valid(clip): if visible: clip.view.show_envelope() else: clip.view.hide_envelope() def _set_settings_view_enabled(self, should_show_view): really_show_view = should_show_view and self._automation.can_automate_parameters if self.selected_setting == 'automation' else should_show_view if really_show_view: if self.selected_mode == 'disabled': self.selected_mode = 'about_to_show' else: self._hide_settings() def _active_note_regions(self): all_active_regions = imap(lambda e: e.active_note_regions, self._editors) return list(set(chain.from_iterable(all_active_regions))) @listens_group('active_note_regions') def _on_active_note_regions_changed(self, _): if self.is_enabled(): all_steps = self._active_note_regions() self._automation.selected_time = all_steps self._update_note_infos() self._set_settings_view_enabled(len(all_steps) > 0 and self.selected_setting != None or self.is_touched) return @listens_group('modify_all_notes') def __on_modify_all_notes_changed(self, editor): self.selected_mode = 'about_to_show' if editor.modify_all_notes_enabled else 'disabled' @listens_group('notes_changed') def _on_notes_changed(self, editor): self._update_infos_task.restart() @listens('detail_clip') def _on_detail_clip_changed(self): self._automation.clip = self.song.view.detail_clip if self.is_enabled() else None return @listens('selected_track') def _on_selected_track_changed(self): self.selected_mode = 'disabled' @initial_encoders.touched def initial_encoders(self, encoder): if self.selected_mode == 'about_to_show': self._show_settings() @initial_encoders.value def initial_encoders(self, encoder, value): if self.selected_mode == 'about_to_show': self._show_settings() @encoders.touched def encoders(self, encoder): if self._can_notify_is_touched(encoder): self.notify_is_touched() @encoders.released def encoders(self, encoder): if not self.is_touched and not self._is_step_held() and not self._is_edit_all_notes_active(): self._hide_settings() if self._can_notify_is_touched(encoder): self.notify_is_touched() @encoders.value def encoders(self, encoder, value): self._notify_modification() def _can_notify_is_touched(self, encoder): if self.is_enabled(): return self._settings_modes.selected_mode != 'note_settings' or encoder.index >= self.encoders.control_count - self.settings.number_of_settings return False def _is_edit_all_notes_active(self): return find_if(lambda e: e.modify_all_notes_enabled, self._editors) != None def _notify_modification(self): for editor in self._editors: editor.notify_modification() def _update_note_infos(self): if self.settings.is_enabled(): def min_max((l_min, l_max), (r_min, r_max)): return (min(l_min, r_min), max(l_max, r_max)) all_min_max_attributes = filter(None, imap(lambda e: e.get_min_max_note_values(), self._editors)) min_max_values = [(99999, -99999)] * 4 if len(all_min_max_attributes) > 0 else None for min_max_attribute in all_min_max_attributes: for i, attribute in enumerate(min_max_attribute): min_max_values[i] = min_max(min_max_values[i], attribute) for i in xrange(4): self.settings.set_min_max(i, min_max_values[i] if min_max_values else None) self.settings.set_info_message('Tweak to add note' if not self._is_edit_all_notes_active() and not min_max_values else '') return
class ItemListerComponent(ItemListerComponentBase): color_class_name = 'ItemNavigation' select_buttons = control_list(ButtonControl, unavailable_color=color_class_name + '.NoItem') def __init__(self, *a, **k): super(ItemListerComponent, self).__init__(*a, **k) self._scroll_overlay = self.register_component( ScrollOverlayComponent(is_enabled=True)) self._scroll_overlay.can_scroll_left = self.can_scroll_left self._scroll_overlay.can_scroll_right = self.can_scroll_right self._scroll_overlay.scroll_left = self.scroll_left self._scroll_overlay.scroll_right = self.scroll_right self.__on_items_changed.subject = self self.__on_selection_changed.subject = self._item_provider scroll_left_layer = forward_property('_scroll_overlay')( 'scroll_left_layer') scroll_right_layer = forward_property('_scroll_overlay')( 'scroll_right_layer') @listens('items') def __on_items_changed(self): self.select_buttons.control_count = len(self.items) self._update_button_colors() self._scroll_overlay.update_scroll_buttons() @listens('selected_item') def __on_selection_changed(self): self._update_button_colors() def _items_equal(self, item, selected_item): return item == selected_item def _update_button_colors(self): selected_item = self._item_provider.selected_item for button, item in izip(self.select_buttons, self.items): button.color = self._color_for_button( button.index, self._items_equal(item, selected_item)) def _color_for_button(self, button_index, is_selected): if is_selected: return self.color_class_name + '.ItemSelected' return self.color_class_name + '.ItemNotSelected' @select_buttons.pressed def select_buttons(self, button): self._on_select_button_pressed(button) @select_buttons.pressed_delayed def select_buttons(self, button): self._on_select_button_pressed_delayed(button) @select_buttons.released def select_buttons(self, button): self._on_select_button_released(button) @select_buttons.released_immediately def select_buttons(self, button): self._on_select_button_released_immediately(button) def _on_select_button_pressed(self, button): pass def _on_select_button_pressed_delayed(self, button): pass def _on_select_button_released(self, button): pass def _on_select_button_released_immediately(self, button): pass