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
Exemplo n.º 2
0
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()
Exemplo n.º 3
0
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
Exemplo n.º 4
0
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()
Exemplo n.º 5
0
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()
Exemplo n.º 9
0
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)]
Exemplo n.º 10
0
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')
Exemplo n.º 11
0
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)))
Exemplo n.º 12
0
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))
Exemplo n.º 13
0
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()
Exemplo n.º 14
0
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)))
Exemplo n.º 16
0
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 []
Exemplo n.º 17
0
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
Exemplo n.º 20
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
Exemplo n.º 22
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
Exemplo n.º 23
0
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
Exemplo n.º 24
0
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
Exemplo n.º 25
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')
Exemplo n.º 26
0
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
Exemplo n.º 27
0
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