示例#1
0
class DisplayingSkinableModesComponent(SkinableModesComponent):
    mode_display = TextDisplayControl(segments=(u'', ) * MAX_MODE_NUMBER)
    mode_color_fields = control_list(ColorSysexControl, MAX_MODE_NUMBER)
    mode_selection_fields = control_list(BinaryControl, MAX_MODE_NUMBER)
    selected_mode_color_field = ColorSysexControl()

    def __init__(self, *a, **k):
        super(DisplayingSkinableModesComponent, self).__init__(*a, **k)
        self.__on_selected_mode_changed.subject = self

    def add_mode_button_control(self, mode_name, behaviour):
        super(DisplayingSkinableModesComponent, self).add_mode_button_control(mode_name, behaviour)
        self.mode_display[len(self._mode_list) - 1] = to_camel_case_name(mode_name, separator=' ')
        self.mode_color_fields[(len(self._mode_list) - 1)].color = 'Mode.' + to_camel_case_name(mode_name) + '.On'

    @listens('selected_mode')
    def __on_selected_mode_changed(self, _):
        self._update_selection_fields()
        self._update_selected_mode_color_field()

    def _update_selection_fields(self):
        for field, mode in izip(self.mode_selection_fields, self._mode_list):
            field.is_on = mode == self.selected_mode

    def _update_selected_mode_color_field(self):
        self.selected_mode_color_field.color = ('Mode.{}.On').format(to_camel_case_name(self.selected_mode)) if self.selected_mode else 'DefaultButton.Disabled'
class DeviceParameterComponent(DisplayingDeviceParameterComponent):
    parameter_color_fields = control_list(ColorSysexControl, WIDTH)
    encoder_color_fields = control_list(ColorSysexControl, WIDTH)

    def __init__(self, *a, **k):
        self._parameter_controls = None
        super(DeviceParameterComponent, self).__init__(*a, **k)

    def set_parameter_controls(self, encoders):
        super(DeviceParameterComponent, self).set_parameter_controls(encoders)
        self._parameter_controls = encoders

    def _update_parameter_values(self):
        super(DeviceParameterComponent, self)._update_parameter_values()
        for parameter, control in zip_longest(self.parameters,
                                              self._parameter_controls or []):
            if is_internal_parameter(parameter) and control:
                control.send_value(
                    convert_parameter_value_to_midi_value(parameter))

    def _update_parameters(self):
        super(DeviceParameterComponent, self)._update_parameters()
        self._update_color_fields()

    def _update_color_fields(self):
        for color_field_index, parameter_info in zip_longest(
                range(WIDTH), self._parameter_provider.parameters[:WIDTH]):
            parameter = parameter_info.parameter if parameter_info else None
            color = u'Device.On' if parameter else u'DefaultButton.Disabled'
            self.parameter_color_fields[color_field_index].color = color
            self.encoder_color_fields[color_field_index].color = color
示例#3
0
class DeviceComponent(SimpleDeviceNavigationComponent, SimpleDeviceParameterComponent):
    parameter_name_displays = control_list(DisplayControl, 8)
    parameter_value_displays = control_list(DisplayControl, 8)

    def __init__(self, show_notification = None, *a, **k):
        assert show_notification is not None
        self._show_notification = show_notification
        super(DeviceComponent, self).__init__(*a, **k)
        self._show_on_scroll_task = self._tasks.add(task.sequence(task.wait(0.1), task.run(self.show_device_name_and_bank)))

    def _on_bank_select_button_checked(self, button):
        super(DeviceComponent, self)._on_bank_select_button_checked(button)
        self.show_device_name_and_bank()

    def _on_bank_select_button_released(self):
        self.show_device_name_and_bank()

    def _scroll_device_chain(self, direction):
        super(DeviceComponent, self)._scroll_device_chain(direction)
        self._show_on_scroll_task.restart()

    def show_device_name_and_bank(self):
        device = self._device_provider.device
        if liveobj_valid(device):
            self._show_notification(device.name, parameter_bank_names(device)[self._bank_index] if self.num_banks else u'Best of Parameters')
        else:
            self._show_notification(None, None)

    def _on_device_lock_button_toggled(self):
        super(DeviceComponent, self)._on_device_lock_button_toggled()
        self._show_notification(u'Device', u'Lock {}'.format(u'On' if self._device_provider.is_locked_to_device else u'Off'))

    def update(self):
        super(DeviceComponent, self).update()
        parameters = self.selected_bank if self._parameter_controls else []
        self.__on_parameter_name_changed.replace_subjects(parameters)
        self.__on_parameter_value_changed.replace_subjects(parameters)
        self._update_parameter_name_displays()
        self._update_parameter_value_displays()

    @listens_group(u'name')
    def __on_parameter_name_changed(self, _):
        self._update_parameter_name_displays()

    @listens_group(u'value')
    def __on_parameter_value_changed(self, parameter):
        if self.is_enabled():
            display = self.parameter_value_displays[self.selected_bank.index(parameter)]
            display.message = unicode(parameter)

    def _update_parameter_name_displays(self):
        if self.is_enabled():
            for parameter, display in zip_longest(self.selected_bank, self.parameter_name_displays):
                display.message = parameter.name if liveobj_valid(parameter) else u'-'

    def _update_parameter_value_displays(self):
        if self.is_enabled():
            for parameter, display in zip_longest(self.selected_bank, self.parameter_value_displays):
                display.message = unicode(parameter) if liveobj_valid(parameter) else u'-'
class MixerComponent(MixerComponentBase):
    pot_parameter_name_displays = control_list(DisplayControl, 8)
    fader_parameter_name_displays = control_list(DisplayControl, 8)

    def __init__(self, *a, **k):
        self._pot_parameter_name = None
        self._fader_parameter_name = None
        self.set_send_controls = nop
        (super(MixerComponent, self).__init__)(*a, **k)

    def set_pot_parameter_name(self, name):
        self._pot_parameter_name = name
        self._update_parameter_name_displays()

    def set_fader_parameter_name(self, name):
        self._fader_parameter_name = name
        self._update_parameter_name_displays()

    def _set_send_controls(self, controls, send_index):
        if controls:
            for index, control in enumerate(controls):
                if control:
                    self.channel_strip(index).set_send_control(
                        control, send_index)

        else:
            for strip in self._channel_strips:
                strip.set_send_control(None, send_index)

    def on_num_sends_changed(self):
        super(MixerComponent, self).on_num_sends_changed()
        self._update_parameter_name_displays()

    def _reassign_tracks(self):
        super(MixerComponent, self)._reassign_tracks()
        self._update_parameter_name_displays()

    def _update_parameter_name_displays(self):
        tracks = self._track_assigner.tracks(self._provider)
        pot_param_name = self._get_parameter_name_to_display(
            self._pot_parameter_name)
        fader_param_name = self._get_parameter_name_to_display(
            self._fader_parameter_name)
        for track, pot_dsp, fader_dsp in zip(
                tracks, self.pot_parameter_name_displays,
                self.fader_parameter_name_displays):
            track_is_valid = liveobj_valid(track)
            pot_dsp.message = pot_param_name if track_is_valid else None
            fader_dsp.message = fader_param_name if track_is_valid else None

    def _get_parameter_name_to_display(self, desired_parameter_name):
        if desired_parameter_name:
            if 'Send' in desired_parameter_name:
                send_index = ord(desired_parameter_name[(-1)]) - ASCII_A
                if send_index >= self.num_sends:
                    return
        return desired_parameter_name
class FixedLengthSettingComponent(Component):
    """ UI component for selecting a length for fixed length recording """
    length_option_buttons = control_list(ButtonControl,
                                         color=b'FixedLength.Option',
                                         control_count=NUM_LENGTHS)

    def __init__(self, fixed_length_setting=None, *a, **k):
        super(FixedLengthSettingComponent, self).__init__(*a, **k)
        assert fixed_length_setting is not None
        self._fixed_length_setting = fixed_length_setting
        self._update_length_option_buttons()
        return

    @length_option_buttons.pressed
    def length_option_buttons(self, button):
        self._fixed_length_setting.selected_index = button.index
        self._update_length_option_buttons()

    @length_option_buttons.released
    def length_option_buttons(self, _):
        self._update_length_option_buttons()

    def _update_length_option_buttons(self):
        for index, button in enumerate(self.length_option_buttons):
            if button.is_pressed:
                button.color = b'FixedLength.OptionHeld'
            else:
                button.color = (b'FixedLength.{}').format(
                    b'OptionInRange' if index <= self._fixed_length_setting.
                    selected_index else b'Option')
示例#6
0
class FixedLengthSettingComponent(Component):
    length_option_buttons = control_list(RadioButtonControl,
                                         checked_color='Option.Selected',
                                         unchecked_color='Option.Unselected',
                                         control_count=len(LENGTH_OPTIONS))
    fixed_length_toggle_button = ToggleButtonControl(
        toggled_color='Option.On', untoggled_color='Option.Off')
    label_display_line = TextDisplayControl(LENGTH_LABELS)
    option_display_line = TextDisplayControl(LENGTH_OPTION_NAMES)

    def __init__(self, fixed_length_setting=None, *a, **k):
        raise fixed_length_setting is not None or AssertionError
        super(FixedLengthSettingComponent, self).__init__(*a, **k)
        self._fixed_length_setting = fixed_length_setting
        self.length_option_buttons.connect_property(fixed_length_setting,
                                                    'selected_index')
        self.fixed_length_toggle_button.connect_property(
            fixed_length_setting, 'enabled')
        self.__on_setting_selected_index_changes.subject = fixed_length_setting
        self.__on_setting_selected_index_changes(
            fixed_length_setting.selected_index)

    @listens('selected_index')
    def __on_setting_selected_index_changes(self, index):
        self._update_option_display()

    def _update_option_display(self):
        for index, option_name in enumerate(LENGTH_OPTION_NAMES):
            prefix = consts.CHAR_SELECT if index == self._fixed_length_setting.selected_index else ' '
            self.option_display_line[index] = prefix + option_name
class GridResolution(ControlManager):
    quantization_buttons = control_list(
        RadioButtonControl,
        checked_color='NoteEditor.QuantizationSelected',
        unchecked_color='NoteEditor.QuantizationUnselected',
        control_count=8)
    index = listenable_property.managed(DEFAULT_INDEX)

    def __init__(self, *a, **k):
        (super(GridResolution, self).__init__)(*a, **k)
        self.index = DEFAULT_INDEX
        self.quantization_buttons[self.index].is_checked = True

    @property
    def step_length(self):
        return old_div(QUANTIZATION_LIST[self.index], QUANTIZATION_FACTOR)

    @property
    def clip_grid(self):
        return CLIP_VIEW_GRID_LIST[self.index]

    @property
    def clip_length(self):
        return CLIP_LENGTH_LIST[self.index]

    @quantization_buttons.checked
    def quantization_buttons(self, button):
        self.index = button.index
class ButtonLabelsComponent(Component):
    display_lines = control_list(DisplayControl, control_count=6)

    def show_button_labels_for_mode(self, mode_name):
        if mode_name in BUTTON_LABELS_MAP:
            for display, text in zip(self.display_lines,
                                     BUTTON_LABELS_MAP[mode_name]):
                display.message = text
class MixerComponent(MixerComponentBase):
    send_select_buttons = FixedRadioButtonGroup(control_count=8,
      unchecked_color='Mode.Sends.Bank.Available')
    return_track_color_controls = control_list(SendValueControl, control_count=8)
    stop_fader_control = SendReceiveValueControl()

    def __init__(self, *a, **k):
        (super(MixerComponent, self).__init__)(*a, **k)
        self.on_send_index_changed()
        self._next_send_index = self.send_index

    def update(self):
        super(MixerComponent, self).update()
        if self.is_enabled():
            self._update_send_control_colors()

    @send_select_buttons.checked
    def send_select_buttons(self, button):
        self.stop_fader_control.send_value(SEND_FADER_BANK)
        self._next_send_index = button.index

    @stop_fader_control.value
    def stop_fader_control(self, value, _):
        self.send_index = self._next_send_index

    def on_num_sends_changed(self):
        self.send_select_buttons.active_control_count = clamp(self.num_sends, 0, NUM_SENDS)
        self._update_send_control_colors()
        self._MixerComponent__on_return_track_color_changed.replace_subjects(self.song.return_tracks[:NUM_SENDS])

    def on_send_index_changed(self):
        if self.send_index is None:
            self.send_select_buttons.active_control_count = 0
        elif self.send_index < self.send_select_buttons.active_control_count:
            self.send_select_buttons[self.send_index].is_checked = True
        self._update_send_control_colors()

    def _update_send_control_colors(self):
        self._update_send_select_button_colors()
        self._update_return_track_color_controls()

    def _update_send_select_button_colors(self):
        for select_button, track in zip_longest(self.send_select_buttons, self.song.return_tracks[:NUM_SENDS]):
            if select_button:
                select_button.checked_color = get_midi_color_value_for_track(track)

    def _update_return_track_color_controls(self):
        value = 0
        if self.send_select_buttons.active_control_count:
            value = self.send_select_buttons[self.send_index].checked_color
        for strip, control in zip_longest(self._channel_strips, self.return_track_color_controls):
            control.value = value if liveobj_valid(strip.track) else 0

    @listens_group('color')
    def __on_return_track_color_changed(self, _):
        self._update_send_control_colors()
示例#10
0
class SetupComponent(ModesComponent):
    category_radio_buttons = control_list(RadioButtonControl, checked_color='Option.Selected', unchecked_color='Option.Unselected')
    make_it_go_boom_button = ButtonControl()
    make_it_go_boom = listenable_property.managed(False)

    def __init__(self, settings=None, pad_curve_sender=None, firmware_switcher=None, *a, **k):
        assert settings is not None
        super(SetupComponent, self).__init__(*a, **k)
        self._settings = settings
        self._pad_curve_sender = pad_curve_sender
        has_option = self.application.has_option
        self.make_it_go_boom_button.enabled = not has_option('_Push2DeveloperMode') and has_option('_MakePush2GoBoom')
        self._general = GeneralSettingsComponent(parent=self, settings=settings.general, hardware_settings=settings.hardware, is_enabled=False)
        self._info = InfoComponent(parent=self, firmware_switcher=firmware_switcher, is_enabled=False)
        self._pad_settings = PadSettingsComponent(parent=self, pad_settings=settings.pad_settings, is_enabled=False)
        self._display_debug = DisplayDebugSettingsComponent(parent=self, settings=settings.display_debug, is_enabled=False)
        self.add_mode('Settings', [self._general, self._pad_settings])
        self.add_mode('Info', [self._info])
        if self.application.has_option('_Push2DeveloperMode'):
            self.add_mode('Display Debug', [self._display_debug])
        self.selected_mode = 'Settings'
        self.category_radio_buttons.control_count = len(self.modes)
        self.category_radio_buttons.checked_index = 0
        return

    @make_it_go_boom_button.pressed
    def make_it_go_boom_button(self, _button):
        self.make_it_go_boom = True

    @property
    def general(self):
        return self._general

    @property
    def info(self):
        return self._info

    @property
    def pad_settings(self):
        return self._pad_settings

    @property
    def display_debug(self):
        return self._display_debug

    @property
    def settings(self):
        return self._settings

    @property
    def velocity_curve(self):
        return self._pad_curve_sender

    @category_radio_buttons.checked
    def category_radio_buttons(self, button):
        self.selected_mode = self.modes[button.index]
class LoopSettingsControllerComponent(Component):
    encoders = control_list(StepEncoderControl, control_count=4)
    shift_button = ButtonControl()

    def __init__(self, *a, **k):
        super(LoopSettingsControllerComponent, self).__init__(*a, **k)
        self._encoder_callbacks_looped = [self._on_clip_position_value,
         self._on_clip_end_value,
         self._on_clip_start_marker_value,
         self._on_clip_looping_value]
        self._encoder_callbacks_unlooped = [self._on_clip_start_marker_value,
         self._on_clip_end_value,
         nop,
         self._on_clip_looping_value]
        self._loop_model = self.register_disconnectable(LoopSettingsModel(self.song))
        self._update_encoder_state()

    def _get_clip(self):
        return self._loop_model.clip

    def _set_clip(self, clip):
        self._loop_model.clip = clip
        self.update()
        self._update_encoder_state()
        self._on_clip_changed()

    clip = property(_get_clip, _set_clip)

    def _on_clip_changed(self):
        pass

    @encoders.value
    def encoders(self, value, encoder):
        callback_set = self._encoder_callbacks_looped if self._loop_model.looping else self._encoder_callbacks_unlooped
        callback_set[encoder.index](value)

    def _update_encoder_state(self):
        enable_encoders = liveobj_valid(self.clip)
        for encoder in self.encoders:
            encoder.enabled = enable_encoders

    def _on_clip_position_value(self, value):
        self._loop_model.move_position(value, self.shift_button.is_pressed)

    def _on_clip_end_value(self, value):
        self._loop_model.move_loop_end(value, self.shift_button.is_pressed)

    def _on_clip_start_marker_value(self, value):
        self._loop_model.move_start_marker(value, self.shift_button.is_pressed)

    def _on_clip_looping_value(self, value):
        if self._loop_model.can_loop:
            currently_looping = self._loop_model.looping
            if value >= 0 and not currently_looping or value < 0 and currently_looping:
                self._loop_model.looping = not currently_looping
示例#12
0
class SetupComponent(ModesComponent):
    category_radio_buttons = control_list(RadioButtonControl, checked_color='Option.Selected', unchecked_color='Option.Unselected')

    def __init__(self, settings = None, pad_curve_sender = None, in_developer_mode = False, *a, **k):
        if not settings is not None:
            raise AssertionError
            super(SetupComponent, self).__init__(*a, **k)
            self._settings = settings
            self._pad_curve_sender = pad_curve_sender
            self._general = self.register_component(GeneralSettingsComponent(settings=settings.general, hardware_settings=settings.hardware, is_enabled=False))
            self._pad_settings = self.register_component(PadSettingsComponent(pad_settings=settings.pad_settings, is_enabled=False))
            self._display_debug = self.register_component(DisplayDebugSettingsComponent(settings=settings.display_debug, is_enabled=False))
            self._profiling = self.register_component(ProfilingSettingsComponent(settings=settings.profiling, is_enabled=False))
            self.add_mode('Settings', [self._general, self._pad_settings])
            self.add_mode('Info', [])
            in_developer_mode and self.add_mode('Display Debug', [self._display_debug])
            self.add_mode('Profiling', [self._profiling])
        self.selected_mode = 'Settings'
        self.category_radio_buttons.control_count = len(self.modes)
        self.category_radio_buttons.checked_index = 0
        return

    @property
    def general(self):
        return self._general

    @property
    def pad_settings(self):
        return self._pad_settings

    @property
    def display_debug(self):
        return self._display_debug

    @property
    def profiling(self):
        return self._profiling

    @property
    def settings(self):
        return self._settings

    @property
    def velocity_curve(self):
        return self._pad_curve_sender

    @category_radio_buttons.checked
    def category_radio_buttons(self, button):
        self.selected_mode = self.modes[button.index]
示例#13
0
class NotificationComponent(Component):
    display_lines = control_list(DisplayControl, control_count=2)

    def __init__(self, *a, **k):
        super(NotificationComponent, self).__init__(*a, **k)
        self._clear_notification_task = self._tasks.add(task.sequence(task.wait(CLEAR_DELAY), task.run(self._clear_notification)))
        self._clear_notification_task.kill()

    def show_notification(self, upper_line, lower_line):
        self._clear_notification_task.kill()
        self._clear_notification_task.restart()
        self.display_lines[0].message = upper_line
        self.display_lines[1].message = lower_line

    def _clear_notification(self):
        self.display_lines[0].message = u' '
        self.display_lines[1].message = u' '
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
示例#15
0
class FixedLengthSettingComponent(Component):
    length_option_buttons = control_list(ButtonControl,
      color='FixedLength.Option', control_count=NUM_LENGTHS)

    def __init__(self, fixed_length_setting=None, *a, **k):
        (super(FixedLengthSettingComponent, self).__init__)(*a, **k)
        self._fixed_length_setting = fixed_length_setting
        self._update_length_option_buttons()

    @length_option_buttons.pressed
    def length_option_buttons(self, button):
        self._fixed_length_setting.selected_index = button.index
        self._update_length_option_buttons()

    @length_option_buttons.released
    def length_option_buttons(self, _):
        self._update_length_option_buttons()

    def _update_length_option_buttons(self):
        for index, button in enumerate(self.length_option_buttons):
            if button.is_pressed:
                button.color = 'FixedLength.OptionHeld'
            else:
                button.color = 'FixedLength.{}'.format('OptionInRange' if index <= self._fixed_length_setting.selected_index else 'Option')
class OptionsComponent(Component):
    __events__ = (u'selected_option', )
    unselected_color = u'Option.Unselected'
    selected_color = u'Option.Selected'
    _selected_option = None
    select_buttons = control_list(ButtonControl, control_count=0)

    def __init__(self,
                 num_options=8,
                 num_labels=4,
                 num_display_segments=None,
                 *a,
                 **k):
        super(OptionsComponent, self).__init__(*a, **k)
        num_display_segments = num_display_segments or num_options
        self._label_data_sources = [
            DisplayDataSource() for _ in xrange(num_labels)
        ]
        self._data_sources = [
            DisplayDataSource() for _ in xrange(num_display_segments)
        ]
        self._option_names = []

    def _get_option_names(self):
        return self._option_names

    def _set_option_names(self, value):
        self._option_names = value
        self.select_buttons.control_count = len(value)
        if self._selected_option:
            currently_selected_option = self.selected_option
            self.selected_option = clamp(self._selected_option, 0,
                                         len(self._option_names) - 1)
            if currently_selected_option != self.selected_option:
                self.notify_selected_option(self.selected_option)
        self._update_select_buttons()
        self._update_data_sources()

    option_names = property(_get_option_names, _set_option_names)

    def _get_selected_option(self):
        return self._selected_option

    def _set_selected_option(self, selected_option):
        assert in_range(selected_option, 0, len(
            self.option_names)) or selected_option is None
        self._selected_option = selected_option
        self._update_select_buttons()
        self._update_data_sources()
        return

    selected_option = property(_get_selected_option, _set_selected_option)

    def set_display_line(self, line):
        if line:
            self._update_data_sources()
            line.set_num_segments(len(self._data_sources))
            for segment in xrange(len(self._data_sources)):
                line.segment(segment).set_data_source(
                    self._data_sources[segment])

    def set_label_display_line(self, line):
        if line:
            line.set_num_segments(len(self._label_data_sources))
            for segment in xrange(len(self._label_data_sources)):
                line.segment(segment).set_data_source(
                    self._label_data_sources[segment])

    def _get_labels(self):
        return map(lambda segment: segment.display_string(),
                   self._label_data_sources)

    def _set_labels(self, labels):
        for segment, label in izip_longest(self._label_data_sources, labels
                                           or []):
            segment.set_display_string(label)

    labels = property(_get_labels, _set_labels)

    def set_blank_display_line1(self, line):
        if line:
            line.reset()

    def set_blank_display_line2(self, line):
        if line:
            line.reset()

    def set_state_buttons(self, buttons):
        if buttons:
            buttons.reset()

    @select_buttons.pressed
    def _on_select_value(self, button):
        index = list(self.select_buttons).index(button)
        if in_range(index, 0, len(self.option_names)):
            self.selected_option = index
            self.notify_selected_option(self.selected_option)

    def _update_select_buttons(self):
        for index, button in enumerate(self.select_buttons):
            button.color = self.selected_color if index == self._selected_option else self.unselected_color

    def _update_data_sources(self):
        for index, (source, name) in enumerate(
                izip_longest(self._data_sources, self.option_names)):
            if name:
                source.set_display_string((consts.CHAR_SELECT if index ==
                                           self._selected_option else u' ') +
                                          name)
            else:
                source.set_display_string(u'')
class ScalesComponent(Component):
    __events__ = ('close',)
    navigation_colors = dict(color='Scales.Navigation', disabled_color='Scales.NavigationDisabled')
    up_button = ButtonControl(repeat=True, **navigation_colors)
    down_button = ButtonControl(repeat=True, **navigation_colors)
    right_button = ButtonControl(repeat=True, **navigation_colors)
    left_button = ButtonControl(repeat=True, **navigation_colors)
    root_note_buttons = control_list(RadioButtonControl, control_count=len(ROOT_NOTES), checked_color='Scales.OptionOn', unchecked_color='Scales.OptionOff')
    in_key_toggle_button = ToggleButtonControl(toggled_color='Scales.OptionOn', untoggled_color='Scales.OptionOn')
    fixed_toggle_button = ToggleButtonControl(toggled_color='Scales.OptionOn', untoggled_color='Scales.OptionOff')
    scale_encoders = control_list(StepEncoderControl)
    close_button = ButtonControl(color='Scales.Close')
    horizontal_navigation = listenable_property.managed(False)
    NUM_DISPLAY_ROWS = 4
    NUM_DISPLAY_COLUMNS = int(ceil(float(len(SCALES)) / NUM_DISPLAY_ROWS))

    def __init__(self, note_layout = None, *a, **k):
        raise note_layout is not None or AssertionError
        super(ScalesComponent, self).__init__(*a, **k)
        self._note_layout = note_layout
        self._scale_list = list(SCALES)
        self._scale_name_list = map(lambda m: m.name, self._scale_list)
        self._selected_scale_index = -1
        self._selected_root_note_index = -1
        self.in_key_toggle_button.connect_property(note_layout, 'is_in_key')
        self.fixed_toggle_button.connect_property(note_layout, 'is_fixed')
        self.__on_root_note_changed.subject = self._note_layout
        self.__on_scale_changed.subject = self._note_layout
        self.__on_root_note_changed(note_layout.root_note)
        self.__on_scale_changed(note_layout.scale)

    def _set_selected_scale_index(self, index):
        index = clamp(index, 0, len(self._scale_list) - 1)
        self._note_layout.scale = self._scale_list[index]

    @down_button.pressed
    def down_button(self, button):
        self._update_horizontal_navigation()
        self._set_selected_scale_index(self._selected_scale_index + 1)

    @up_button.pressed
    def up_button(self, button):
        self._update_horizontal_navigation()
        self._set_selected_scale_index(self._selected_scale_index - 1)

    @left_button.pressed
    def left_button(self, button):
        self._update_horizontal_navigation()
        self._set_selected_scale_index(self._selected_scale_index - self.NUM_DISPLAY_ROWS)

    @right_button.pressed
    def right_button(self, button):
        self._update_horizontal_navigation()
        self._set_selected_scale_index(self._selected_scale_index + self.NUM_DISPLAY_ROWS)

    @root_note_buttons.pressed
    def root_note_buttons(self, button):
        self._note_layout.root_note = ROOT_NOTES[button.index]

    @listens('root_note')
    def __on_root_note_changed(self, root_note):
        self._selected_root_note_index = list(ROOT_NOTES).index(root_note)
        self.root_note_buttons.checked_index = self._selected_root_note_index
        self.notify_selected_root_note_index()

    @property
    def root_note_names(self):
        return [ NOTE_NAMES[note] for note in ROOT_NOTES ]

    @listenable_property
    def selected_root_note_index(self):
        return self._selected_root_note_index

    @scale_encoders.value
    def scale_encoders(self, value, encoder):
        self._update_horizontal_navigation()
        self._set_selected_scale_index(self._selected_scale_index + value)

    @property
    def scale_names(self):
        return self._scale_name_list

    @listenable_property
    def selected_scale_index(self):
        return self._selected_scale_index

    @listens('scale')
    def __on_scale_changed(self, scale):
        index = self._scale_list.index(scale) if scale in self._scale_list else -1
        if index != self._selected_scale_index:
            self._selected_scale_index = index
            self.up_button.enabled = index > 0
            self.left_button.enabled = index > 0
            self.down_button.enabled = index < len(self._scale_list) - 1
            self.right_button.enabled = index < len(self._scale_list) - 1
            self.notify_selected_scale_index()

    @close_button.pressed
    def close_button(self, button):
        self.notify_close()

    @property
    def note_layout(self):
        return self._note_layout

    def _update_horizontal_navigation(self):
        self.horizontal_navigation = self.right_button.is_pressed or self.left_button.is_pressed
class BrowserComponent(Component, Messenger):
    __events__ = (u'loaded', u'close')
    NUM_ITEMS_PER_COLUMN = 6
    NUM_VISIBLE_BROWSER_LISTS = 7
    NUM_COLUMNS_IN_EXPANDED_LIST = 3
    EXPAND_LIST_TIME = 1.5
    REVEAL_PREVIEW_LIST_TIME = 0.2
    MIN_TIME = 0.6
    MAX_TIME = 1.4
    MIN_TIME_TEXT_LENGTH = 30
    MAX_TIME_TEXT_LENGTH = 70
    up_button = ButtonControl(repeat=True)
    down_button = ButtonControl(repeat=True)
    right_button = ButtonControl(repeat=True, **NAVIGATION_COLORS)
    left_button = ButtonControl(repeat=True, **NAVIGATION_COLORS)
    back_button = ButtonControl(**NAVIGATION_COLORS)
    open_button = ButtonControl(**NAVIGATION_COLORS)
    load_button = ButtonControl(**NAVIGATION_COLORS)
    close_button = ButtonControl()
    prehear_button = ToggleButtonControl(toggled_color=u'Browser.Option', untoggled_color=u'Browser.OptionDisabled')
    scroll_encoders = control_list(StepEncoderControl, num_steps=10, control_count=NUM_VISIBLE_BROWSER_LISTS)
    scroll_focused_encoder = StepEncoderControl(num_steps=10)
    scrolling = listenable_property.managed(False)
    horizontal_navigation = listenable_property.managed(False)
    list_offset = listenable_property.managed(0)
    can_enter = listenable_property.managed(False)
    can_exit = listenable_property.managed(False)
    context_color_index = listenable_property.managed(-1)
    context_text = listenable_property.managed(u'')

    @depends(commit_model_changes=None, selection=None)
    def __init__(self, preferences = dict(), commit_model_changes = None, selection = None, main_modes_ref = None, *a, **k):
        assert commit_model_changes is not None
        super(BrowserComponent, self).__init__(*a, **k)
        self._lists = []
        self._browser = Live.Application.get_application().browser
        self._current_hotswap_target = self._browser.hotswap_target
        self._updating_root_items = BooleanContext()
        self._focused_list_index = 0
        self._commit_model_changes = commit_model_changes
        self._preferences = preferences
        self._expanded = False
        self._unexpand_with_scroll_encoder = False
        self._delay_preview_list = BooleanContext()
        self._selection = selection
        self._main_modes_ref = main_modes_ref if main_modes_ref is not None else nop
        self._load_neighbour_overlay = LoadNeighbourOverlayComponent(parent=self, is_enabled=False)
        self._content_filter_type = None
        self._content_hotswap_target = None
        self._preview_list_task = self._tasks.add(task.sequence(task.wait(self.REVEAL_PREVIEW_LIST_TIME), task.run(self._replace_preview_list_by_task))).kill()
        self._update_root_items()
        self._update_navigation_buttons()
        self._update_context()
        self.prehear_button.is_toggled = preferences.setdefault(u'browser_prehear', True)
        self._on_selected_track_color_index_changed.subject = self.song.view
        self._on_selected_track_name_changed.subject = self.song.view
        self._on_detail_clip_name_changed.subject = self.song.view
        self._on_hotswap_target_changed.subject = self._browser
        self._on_load_next.subject = self._load_neighbour_overlay
        self._on_load_previous.subject = self._load_neighbour_overlay
        self._on_focused_item_changed.subject = self
        self.register_slot(self, self.notify_focused_item, u'focused_list_index')

        def auto_unexpand():
            self.expanded = False
            self._update_list_offset()

        self._unexpand_task = self._tasks.add(task.sequence(task.wait(self.EXPAND_LIST_TIME), task.run(auto_unexpand))).kill()

    @up_button.pressed
    def up_button(self, button):
        with self._delay_preview_list():
            self.focused_list.select_index_with_offset(-1)
        self._update_auto_expand()
        self._update_scrolling()
        self._update_horizontal_navigation()

    @up_button.released
    def up_button(self, button):
        self._finish_preview_list_task()
        self._update_scrolling()

    @down_button.pressed
    def down_button(self, button):
        with self._delay_preview_list():
            self.focused_list.select_index_with_offset(1)
        self._update_auto_expand()
        self._update_scrolling()
        self._update_horizontal_navigation()

    @down_button.released
    def down_button(self, button):
        self._finish_preview_list_task()
        self._update_scrolling()

    @right_button.pressed
    def right_button(self, button):
        if self._expanded and self._can_auto_expand() and self._focused_list_index > 0:
            self.focused_list.select_index_with_offset(self.NUM_ITEMS_PER_COLUMN)
            self._update_scrolling()
            self.horizontal_navigation = True
        elif not self._enter_selected_item():
            self._update_auto_expand()

    @right_button.released
    def right_button(self, button):
        self._update_scrolling()

    @left_button.pressed
    def left_button(self, button):
        if self._expanded and self._focused_list_index > 0 and self.focused_list.selected_index >= self.NUM_ITEMS_PER_COLUMN:
            self.focused_list.select_index_with_offset(-self.NUM_ITEMS_PER_COLUMN)
            self._update_scrolling()
            self.horizontal_navigation = True
        else:
            self._exit_selected_item()

    @left_button.released
    def left_button(self, button):
        self._update_scrolling()

    @open_button.pressed
    def open_button(self, button):
        self._enter_selected_item()

    @back_button.pressed
    def back_button(self, button):
        self._exit_selected_item()

    @scroll_encoders.touched
    def scroll_encoders(self, encoder):
        list_index = self._get_list_index_for_encoder(encoder)
        if list_index is not None:
            try:
                if self._focus_list_with_index(list_index, crop=False):
                    self._unexpand_with_scroll_encoder = True
                    self._prehear_selected_item()
                if self.focused_list.selected_item.is_loadable and encoder.index == self.scroll_encoders.control_count - 1:
                    self._update_list_offset()
                self._on_encoder_touched()
            except CannotFocusListError:
                pass

    @scroll_encoders.released
    def scroll_encoders(self, encoders):
        self._on_encoder_released()

    @scroll_encoders.value
    def scroll_encoders(self, value, encoder):
        list_index = self._get_list_index_for_encoder(encoder)
        if list_index is not None:
            try:
                if self._focus_list_with_index(list_index):
                    self._unexpand_with_scroll_encoder = True
                self._on_encoder_value(value)
            except CannotFocusListError:
                pass

    @scroll_focused_encoder.value
    def scroll_focused_encoder(self, value, encoder):
        self._on_encoder_value(value)

    @scroll_focused_encoder.touched
    def scroll_focused_encoder(self, encoder):
        self._on_encoder_touched()

    @scroll_focused_encoder.released
    def scroll_focused_encoder(self, encoder):
        self._on_encoder_released()

    def _on_encoder_value(self, value):
        with self._delay_preview_list():
            self.focused_list.select_index_with_offset(value)
        first_visible_list_focused = self.focused_list_index == self.list_offset
        if self.expanded and first_visible_list_focused:
            self.expanded = False
            self._unexpand_with_scroll_encoder = True
        elif not first_visible_list_focused and not self.expanded and self._can_auto_expand():
            self._update_auto_expand()
            self._unexpand_with_scroll_encoder = True
        self._update_scrolling()
        self._update_horizontal_navigation()

    def _on_encoder_touched(self):
        self._unexpand_task.kill()
        self._update_scrolling()
        self._update_horizontal_navigation()

    def _on_encoder_released(self):
        any_encoder_touched = any(map(lambda e: e.is_touched, self.scroll_encoders)) or self.scroll_focused_encoder.is_touched
        if not any_encoder_touched and self._unexpand_with_scroll_encoder:
            self._unexpand_task.restart()
        self._update_scrolling()

    def _get_list_index_for_encoder(self, encoder):
        if self.expanded:
            if encoder.index == 0:
                return self.list_offset
            return self.list_offset + 1
        index = self.list_offset + encoder.index
        if self.focused_list_index + 1 == index and self.should_widen_focused_item:
            index = self.focused_list_index
        if 0 <= index < len(self._lists):
            return index
        else:
            return None

    @load_button.pressed
    def load_button(self, button):
        self._load_selected_item()

    @prehear_button.toggled
    def prehear_button(self, toggled, button):
        if toggled:
            self._prehear_selected_item()
        else:
            self._browser.stop_preview()
        self._preferences[u'browser_prehear'] = toggled
        self.notify_prehear_enabled()

    @close_button.pressed
    def close_button(self, button):
        self.notify_close()

    @listenable_property
    def lists(self):
        return self._lists

    @listenable_property
    def focused_list_index(self):
        return self._focused_list_index

    @listenable_property
    def prehear_enabled(self):
        return self.prehear_button.is_toggled

    @property
    def focused_list(self):
        return self._lists[self._focused_list_index]

    @listenable_property
    def focused_item(self):
        return self.focused_list.selected_item

    @listenable_property
    def expanded(self):
        return self._expanded

    @property
    def load_neighbour_overlay(self):
        return self._load_neighbour_overlay

    @listenable_property
    def should_widen_focused_item(self):
        return self.focused_item.is_loadable and not self.focused_item.is_device

    @property
    def context_display_type(self):
        return u'custom_button'

    def disconnect(self):
        super(BrowserComponent, self).disconnect()
        self._lists = []
        self._commit_model_changes = None

    @expanded.setter
    def expanded(self, expanded):
        if self._expanded != expanded:
            self._expanded = expanded
            self._unexpand_with_scroll_encoder = False
            self._update_navigation_buttons()
            if len(self._lists) > self._focused_list_index + 1:
                self._lists[self._focused_list_index + 1].limit = self.num_preview_items
            self.notify_expanded()

    @listens(u'selected_track.color_index')
    def _on_selected_track_color_index_changed(self):
        if self.is_enabled():
            self._update_context()
            self._update_navigation_buttons()

    @listens(u'selected_track.name')
    def _on_selected_track_name_changed(self):
        if self.is_enabled():
            self._update_context()

    @listens(u'detail_clip.name')
    def _on_detail_clip_name_changed(self):
        if self.is_enabled():
            self._update_context()

    @listens(u'hotswap_target')
    def _on_hotswap_target_changed(self):
        if self.is_enabled():
            if not self._switched_to_empty_pad():
                self._update_root_items()
                self._update_context()
                self._update_list_offset()
                self._update_load_neighbour_overlay_visibility()
            else:
                self._load_neighbour_overlay.set_enabled(False)
        self._current_hotswap_target = self._browser.hotswap_target

    @listens(u'focused_item')
    def _on_focused_item_changed(self):
        self.notify_should_widen_focused_item()

    @property
    def browse_for_audio_clip(self):
        main_modes = self._main_modes_ref()
        if main_modes is None:
            return False
        has_midi_support = self.song.view.selected_track.has_midi_input
        return not has_midi_support and u'clip' in main_modes.active_modes

    def _switched_to_empty_pad(self):
        hotswap_target = self._browser.hotswap_target
        is_browsing_drumpad = isinstance(hotswap_target, Live.DrumPad.DrumPad)
        was_browsing_pad = isinstance(self._current_hotswap_target, Live.DrumPad.DrumPad)
        return is_browsing_drumpad and was_browsing_pad and len(hotswap_target.chains) == 0

    def _focus_list_with_index(self, index, crop = True):
        u"""
        Focus the list with the given index.
        Raises CannotFocusListError if the operation fails.
        Returns True if a new list was focused and False if it was already focused.
        """
        if self._focused_list_index != index:
            if self._finish_preview_list_task():
                if index >= len(self._lists):
                    raise CannotFocusListError()
            assert 0 <= index < len(self._lists)
            self._on_focused_selection_changed.subject = None
            if self._focused_list_index > index and crop:
                for l in self._lists[self._focused_list_index:]:
                    l.selected_index = -1

            self._focused_list_index = index
            self.focused_list.limit = -1
            if self.focused_list.selected_index == -1:
                self.focused_list.selected_index = 0
            self.notify_focused_list_index()
            self._on_focused_selection_changed.subject = self.focused_list
            if crop:
                self._crop_browser_lists(self._focused_list_index + 2)
            if self._focused_list_index == len(self._lists) - 1:
                self._replace_preview_list()
            self._load_neighbour_overlay.set_enabled(False)
            self._update_navigation_buttons()
            return True
        return False

    @listens(u'selected_index')
    def _on_focused_selection_changed(self):
        if self._delay_preview_list and not self.focused_item.is_loadable:
            self._preview_list_task.restart()
        else:
            self._replace_preview_list()
        self._update_navigation_buttons()
        self._prehear_selected_item()
        self._load_neighbour_overlay.set_enabled(False)
        self.notify_focused_item()

    def _get_actual_item(self, item):
        contained_item = getattr(item, u'contained_item', None)
        if contained_item is not None:
            return contained_item
        return item

    def _previous_can_be_loaded(self):
        return self.focused_list.selected_index > 0 and self.focused_list.items[self.focused_list.selected_index - 1].is_loadable

    def _next_can_be_loaded(self):
        items = self.focused_list.items
        return self.focused_list.selected_index < len(items) - 1 and items[self.focused_list.selected_index + 1].is_loadable

    @listens(u'load_next')
    def _on_load_next(self):
        self.focused_list.selected_index += 1
        self._load_selected_item()

    @listens(u'load_previous')
    def _on_load_previous(self):
        self.focused_list.selected_index -= 1
        self._load_selected_item()

    def _update_load_neighbour_overlay_visibility(self):
        self._load_neighbour_overlay.set_enabled(liveobj_valid(self._browser.hotswap_target) and (self._next_can_be_loaded() or self._previous_can_be_loaded()) and not self.focused_list.selected_item.is_device)

    def _load_selected_item(self):
        focused_list = self.focused_list
        self._update_load_neighbour_overlay_visibility()
        self._update_navigation_buttons()
        item = self._get_actual_item(focused_list.selected_item)
        self._load_item(item)
        self.notify_loaded()

    def _show_load_notification(self, item):
        notification_text = self._make_notification_text(item)
        text_length = len(notification_text)
        notification_time = self.MIN_TIME
        if text_length > self.MIN_TIME_TEXT_LENGTH:
            if text_length > self.MAX_TIME_TEXT_LENGTH:
                notification_time = self.MAX_TIME
            else:
                notification_time = self.MIN_TIME + (self.MAX_TIME - self.MIN_TIME) * old_div(float(text_length - self.MIN_TIME_TEXT_LENGTH), self.MAX_TIME_TEXT_LENGTH - self.MIN_TIME_TEXT_LENGTH)
        self.show_notification(notification_text, notification_time=notification_time)
        self._commit_model_changes()

    def _make_notification_text(self, browser_item):
        return u'Loading %s' % browser_item.name

    def _load_item(self, item):
        self._show_load_notification(item)
        if liveobj_valid(self._browser.hotswap_target):
            if isinstance(item, PluginPresetBrowserItem):
                self._browser.hotswap_target.selected_preset_index = item.preset_index
            else:
                self._browser.load_item(item)
                self._content_hotswap_target = self._browser.hotswap_target
        else:
            with self._insert_right_of_selected():
                self._browser.load_item(item)

    @contextmanager
    def _insert_right_of_selected(self):
        DeviceInsertMode = Live.Track.DeviceInsertMode
        device_to_select = get_selection_for_new_device(self._selection)
        if device_to_select:
            self._selection.selected_object = device_to_select
        selected_track_view = self.song.view.selected_track.view
        selected_track_view.device_insert_mode = DeviceInsertMode.selected_right
        yield
        selected_track_view.device_insert_mode = DeviceInsertMode.default

    def _prehear_selected_item(self):
        if self.prehear_button.is_toggled and not self._updating_root_items:
            self._browser.stop_preview()
            item = self._get_actual_item(self.focused_list.selected_item)
            if item and item.is_loadable and isinstance(item, Live.Browser.BrowserItem):
                self._browser.preview_item(item)

    def _stop_prehear(self):
        if self.prehear_button.is_toggled and not self._updating_root_items:
            self._browser.stop_preview()

    def _update_navigation_buttons(self):
        focused_list = self.focused_list
        self.up_button.enabled = focused_list.selected_index > 0
        self.down_button.enabled = focused_list.selected_index < len(focused_list.items) - 1
        selected_item_loadable = self.focused_list.selected_item.is_loadable
        can_exit = self._focused_list_index > 0
        assume_can_enter = self._preview_list_task.is_running and not selected_item_loadable
        can_enter = self._focused_list_index < len(self._lists) - 1 or assume_can_enter
        self.back_button.enabled = can_exit
        self.open_button.enabled = can_enter
        self.load_button.enabled = selected_item_loadable
        self._load_neighbour_overlay.can_load_previous = self._previous_can_be_loaded()
        self._load_neighbour_overlay.can_load_next = self._next_can_be_loaded()
        context_button_color = IndexedColor.from_live_index(self.context_color_index, DISPLAY_BUTTON_SHADE_LEVEL) if self.context_color_index > -1 else u'Browser.Navigation'
        self.load_button.color = context_button_color
        self.close_button.color = context_button_color
        self._load_neighbour_overlay.load_next_button.color = context_button_color
        self._load_neighbour_overlay.load_previous_button.color = context_button_color
        if not self._expanded:
            self.left_button.enabled = self.back_button.enabled
            self.right_button.enabled = can_enter or self._can_auto_expand()
        else:
            num_columns = int(ceil(old_div(float(len(self.focused_list.items)), self.NUM_ITEMS_PER_COLUMN)))
            last_column_start_index = (num_columns - 1) * self.NUM_ITEMS_PER_COLUMN
            self.left_button.enabled = self._focused_list_index > 0
            self.right_button.enabled = can_enter or self.focused_list.selected_index < last_column_start_index
        self.can_enter = can_enter
        self.can_exit = can_exit

    def _update_scrolling(self):
        self.scrolling = self.up_button.is_pressed or self.down_button.is_pressed or self.scroll_focused_encoder.is_touched or any(map(lambda e: e.is_touched, self.scroll_encoders)) or self.right_button.is_pressed and self._expanded or self.left_button.is_pressed and self._expanded

    def _update_horizontal_navigation(self):
        self.horizontal_navigation = self.right_button.is_pressed or self.left_button.is_pressed

    def _update_context(self):
        selected_track = self.song.view.selected_track
        clip = self.song.view.detail_clip
        if self.browse_for_audio_clip and clip:
            self.context_text = clip.name
        elif liveobj_valid(self._browser.hotswap_target):
            self.context_text = self._browser.hotswap_target.name
        else:
            self.context_text = selected_track.name
        selected_track_color_index = selected_track.color_index
        self.context_color_index = selected_track_color_index if selected_track_color_index is not None else -1

    def _enter_selected_item(self):
        item_entered = False
        self._finish_preview_list_task()
        new_index = self._focused_list_index + 1
        if 0 <= new_index < len(self._lists):
            self._focus_list_with_index(new_index)
            self._unexpand_task.kill()
            self._update_list_offset()
            self._update_auto_expand()
            self._prehear_selected_item()
            item_entered = True
        return item_entered

    def _exit_selected_item(self):
        item_exited = False
        try:
            self._focus_list_with_index(self._focused_list_index - 1)
            self._update_list_offset()
            self._update_auto_expand()
            self._stop_prehear()
            item_exited = True
        except CannotFocusListError:
            pass

        return item_exited

    def _can_auto_expand(self):
        return len(self.focused_list.items) > self.NUM_ITEMS_PER_COLUMN * 2 and self.focused_list.selected_item.is_loadable and getattr(self.focused_list.selected_item, u'contained_item', None) == None

    def _update_auto_expand(self):
        self.expanded = self._can_auto_expand()
        self._update_list_offset()

    def _update_list_offset(self):
        if self.expanded:
            self.list_offset = max(0, self.focused_list_index - 1)
        else:
            offset = len(self._lists)
            if self.focused_list.selected_item.is_loadable:
                offset += 1
            self.list_offset = max(0, offset - self.NUM_VISIBLE_BROWSER_LISTS)

    def _replace_preview_list_by_task(self):
        self._replace_preview_list()
        self._update_navigation_buttons()

    def _finish_preview_list_task(self):
        if self._preview_list_task.is_running:
            self._replace_preview_list_by_task()
            return True
        return False

    def _replace_preview_list(self):
        self._preview_list_task.kill()
        self._crop_browser_lists(self._focused_list_index + 1)
        selected_item = self.focused_list.selected_item
        children_iterator = selected_item.iter_children
        if len(children_iterator) > 0:
            enable_wrapping = getattr(selected_item, u'enable_wrapping', True) and self.focused_list.items_wrapped
            self._append_browser_list(children_iterator=children_iterator, limit=self.num_preview_items, enable_wrapping=enable_wrapping)

    def _append_browser_list(self, children_iterator, limit = -1, enable_wrapping = True):
        l = BrowserList(item_iterator=children_iterator, item_wrapper=self._wrap_item if enable_wrapping else nop, limit=limit)
        l.items_wrapped = enable_wrapping
        self._lists.append(l)
        self.register_disconnectable(l)
        self.notify_lists()

    def _crop_browser_lists(self, length):
        num_items_to_crop = len(self._lists) - length
        for _ in range(num_items_to_crop):
            l = self._lists.pop()
            self.unregister_disconnectable(l)

        if num_items_to_crop > 0:
            self.notify_lists()

    def _make_root_browser_items(self):
        filter_type = self._browser.filter_type
        hotswap_target = self._browser.hotswap_target
        if liveobj_valid(hotswap_target):
            filter_type = filter_type_for_hotswap_target(hotswap_target, default=filter_type)
        return make_root_browser_items(self._browser, filter_type)

    def _content_cache_is_valid(self):
        return self._content_filter_type == self._browser.filter_type and not liveobj_changed(self._content_hotswap_target, self._browser.hotswap_target)

    def _invalidate_content_cache(self):
        self._content_hotswap_target = None
        self._content_filter_type = None

    def _update_content_cache(self):
        self._content_filter_type = self._browser.filter_type
        self._content_hotswap_target = self._browser.hotswap_target

    def _update_root_items(self):
        if not self._content_cache_is_valid():
            self._update_content_cache()
            with self._updating_root_items():
                self._on_focused_selection_changed.subject = None
                self._crop_browser_lists(0)
                self._append_browser_list(children_iterator=self._make_root_browser_items())
                self._focused_list_index = 0
                self.focused_list.selected_index = 0
                self._select_hotswap_target()
                self._on_focused_selection_changed.subject = self.focused_list
                self._on_focused_selection_changed()

    def _select_hotswap_target(self, list_index = 0):
        if list_index < len(self._lists):
            l = self._lists[list_index]
            l.access_all = True
            children = l.items
            i = index_if(lambda i: i.is_selected, children)
            if i < len(children):
                self._focused_list_index = list_index
                l.selected_index = i
                self._replace_preview_list()
                self._select_hotswap_target(list_index + 1)

    @property
    def num_preview_items(self):
        if self._expanded:
            return self.NUM_ITEMS_PER_COLUMN * self.NUM_COLUMNS_IN_EXPANDED_LIST
        return 6

    def update(self):
        super(BrowserComponent, self).update()
        self._invalidate_content_cache()
        if self.is_enabled():
            self._update_root_items()
            self._update_context()
            self._update_list_offset()
            self._update_load_neighbour_overlay_visibility()
            self._update_navigation_buttons()
            self.expanded = False
            self._update_list_offset()
        else:
            self._stop_prehear()
            self.list_offset = 0

    def _wrap_item(self, item):
        if item.is_device:
            return self._wrap_device_item(item)
        if self._is_hotswap_target_plugin(item):
            return self._wrap_hotswapped_plugin_item(item)
        return item

    def _wrap_device_item(self, item):
        u"""
        Create virtual folder around items that can be loaded AND have children, to avoid
        having two actions on an item (open and load).
        """
        wrapped_loadable = WrappedLoadableBrowserItem(name=item.name, is_loadable=True, contained_item=item)
        return FolderBrowserItem(name=item.name, is_loadable=True, is_device=True, contained_item=item, wrapped_loadable=wrapped_loadable, icon=u'browser_arrowcontent.svg')

    def _is_hotswap_target_plugin(self, item):
        return isinstance(self._browser.hotswap_target, Live.PluginDevice.PluginDevice) and isinstance(item, Live.Browser.BrowserItem) and self._browser.relation_to_hotswap_target(item) == Live.Browser.Relation.equal

    def _wrap_hotswapped_plugin_item(self, item):
        return PluginBrowserItem(name=item.name, vst_device=self._browser.hotswap_target)
示例#19
0
class MixerComponent(MixerComponentBase):
    send_up_button = ButtonControl(color='Mixer.Send')
    send_down_button = ButtonControl(color='Mixer.Send')
    pan_value_display = ConfigurableTextDisplayControl(segments=(u'', ) *
                                                       SESSION_WIDTH)
    send_value_display = ConfigurableTextDisplayControl(segments=(u'', ) *
                                                        SESSION_WIDTH)
    mixer_display = TextDisplayControl(segments=(u'Mixer', ))
    pan_display = TextDisplayControl(segments=(u'Pan', ))
    send_index_display = ConfigurableTextDisplayControl(segments=(u'', ))
    send_encoder_color_fields = control_list(ColorSysexControl, SESSION_WIDTH)
    selected_track_color_field = ColorSysexControl()

    def __init__(self, *a, **k):
        super(MixerComponent, self).__init__(
            channel_strip_component_type=ChannelStripComponent, *a, **k)
        self.__on_selected_track_changed.subject = self.song.view
        self.__on_selected_track_changed()
        self.pan_value_display.set_data_sources([
            strip.pan_value_display_data_source
            for strip in self._channel_strips
        ])
        self.on_send_index_changed()

    @property
    def controlled_tracks(self):
        return self._track_assigner.tracks(self._provider)

    @property
    def controlled_sends(self):
        tracks = self.controlled_tracks
        controlled_sends = [None] * len(tracks)
        send_index = self.send_index
        for index, track in enumerate(tracks):
            if liveobj_valid(track):
                sends = track.mixer_device.sends
                if send_index != None and send_index < len(sends):
                    controlled_sends[index] = sends[send_index]

        return controlled_sends

    @send_up_button.pressed
    def send_up_button(self, _):
        self.send_index -= 1

    @send_down_button.pressed
    def send_down_button(self, _):
        self.send_index += 1

    def set_track_names_display(self, display):
        if display:
            display.set_data_sources([
                strip.track_name_data_source()
                for strip in self._channel_strips
            ])

    def set_selected_track_name_display(self, display):
        if display:
            display.set_data_sources(
                [self._selected_strip.track_name_data_source()])

    def set_pan_encoder_color_fields(self, controls):
        for strip, control in izip_longest(self._channel_strips, controls
                                           or []):
            strip.pan_encoder_color_field.set_control_element(control)

    def set_track_color_fields(self, controls):
        for strip, control in izip_longest(self._channel_strips, controls
                                           or []):
            strip.track_color_field.set_control_element(control)

    def set_track_selection_fields(self, controls):
        for strip, control in izip_longest(self._channel_strips, controls
                                           or []):
            strip.track_selection_field.set_control_element(control)

    def set_volume_leds(self, controls):
        for strip, control in izip_longest(self._channel_strips, controls
                                           or []):
            strip.volume_led.set_control_element(control)

    def set_monitoring_state_buttons(self, controls):
        for strip, control in izip_longest(self._channel_strips, controls
                                           or []):
            strip.monitoring_state_button.set_control_element(control)

    def on_send_index_changed(self):
        self._update_send_value_subjects()
        self._update_send_navigation_buttons()
        self._update_send_index_display()
        self._update_send_value_display()

    def on_num_sends_changed(self):
        self._update_send_navigation_buttons()
        self._update_send_value_display()

    def _update_send_navigation_buttons(self):
        send_index = self.send_index
        self.send_up_button.enabled = send_index != None and send_index > 0
        self.send_down_button.enabled = send_index != None and send_index < self.num_sends - 1
        return

    def _update_send_index_display(self):
        send_index = self.send_index
        self.send_index_display[0] = 'Send ' + chr(
            send_index + 65) if send_index != None else ''
        return

    def _update_send_value_display(self):
        for index, send in enumerate(self.controlled_sends):
            self.send_value_display[index] = str(send) if send else ''

    def _update_send_encoder_color_fields(self):
        for index, send in enumerate(self.controlled_sends):
            self.send_encoder_color_fields[
                index].color = 'Mixer.Send' if send else 'DefaultButton.Disabled'

    def _update_selected_track_color_field(self):
        self.selected_track_color_field.color = color_for_track(
            self.song.view.selected_track)

    def _update_send_value_subjects(self):
        self.__on_send_value_changed.replace_subjects(self.controlled_sends)

    @listens('selected_track')
    def __on_selected_track_changed(self):
        self._update_selected_strip()
        self._update_selected_track_color_field()
        self.__on_selected_track_color_changed.subject = self.song.view.selected_track

    @listens('color')
    def __on_selected_track_color_changed(self):
        self._update_selected_track_color_field()

    @listens_group('value')
    def __on_send_value_changed(self, _):
        self._update_send_value_display()

    def _reassign_tracks(self):
        super(MixerComponent, self)._reassign_tracks()
        self._update_send_value_subjects()
        self._update_send_value_display()
        self._update_send_encoder_color_fields()
示例#20
0
文件: convert.py 项目: xnamahx/Push2x
class ConvertComponent(Component):
    __events__ = (u'cancel', u'success')
    action_buttons = control_list(ButtonControl, color='Option.Unselected', pressed_color='Option.Selected')
    cancel_button = ButtonControl(color='Option.Unselected', pressed_color='Option.Selected')
    source_color_index = listenable_property.managed(UNCOLORED_INDEX)
    source_name = listenable_property.managed(unicode(''))

    def __init__(self, tracks_provider=None, conversions_provider=possible_conversions, decorator_factory=None, *a, **k):
        assert tracks_provider is not None
        assert callable(conversions_provider)
        super(ConvertComponent, self).__init__(*a, **k)
        self._tracks_provider = tracks_provider
        self._conversions_provider = conversions_provider
        self._decorator_factory = decorator_factory
        self._category = NullConvertCategory()
        self._update_possible_conversions()
        return

    @listenable_property
    def available_conversions(self):
        return map(lambda x: x.label, self._category.actions)

    def on_enabled_changed(self):
        super(ConvertComponent, self).on_enabled_changed()
        self._update_possible_conversions()

    def _update_possible_conversions(self):
        self.disconnect_disconnectable(self._category)
        track = self._tracks_provider.selected_item
        self._category = self.register_disconnectable(self._conversions_provider(track, self._decorator_factory))
        self.__on_action_invalidated.subject = self._category
        self.__on_action_source_color_index_changed.subject = self._category.color_source
        self.__on_action_source_name_changed.subject = self._category.name_source
        self.__on_action_source_color_index_changed()
        self.__on_action_source_name_changed()
        self.action_buttons.control_count = len(self._category.actions)
        self.notify_available_conversions()

    @listens('color_index')
    def __on_action_source_color_index_changed(self):
        color_source = self.__on_action_source_color_index_changed.subject
        self.source_color_index = color_source.color_index if color_source and color_source.color_index is not None else UNCOLORED_INDEX
        return

    @listens('name')
    def __on_action_source_name_changed(self):
        name_source = self.__on_action_source_name_changed.subject
        self.source_name = name_source.name if name_source else unicode()

    @action_buttons.released
    def action_buttons(self, button):
        if self._do_conversion(button.index):
            self.notify_cancel()

    def _do_conversion(self, action_index):
        self._update_possible_conversions()
        if action_index < len(self._category.actions):
            action = self._category.actions[action_index]
            if action.needs_deferred_invocation:
                self._tasks.add(task.sequence(task.delay(1), task.run(lambda : self._do_conversion_deferred(action))))
                return False
            self._invoke_conversion(action)
        return True

    def _do_conversion_deferred(self, action):
        self._invoke_conversion(action)
        self.notify_cancel()

    def _invoke_conversion(self, action):
        self._category.convert(self.song, action)
        self.notify_success(self._category.internal_name)

    @cancel_button.released
    def cancel_button(self, button):
        self.notify_cancel()

    @listens('action_invalidated')
    def __on_action_invalidated(self):
        self.notify_cancel()
示例#21
0
class DisplayingDeviceNavigationComponent(DeviceNavigationComponent):
    device_name_display_1 = TextDisplayControl(segments=('', ) *
                                               NUM_DISPLAY_SEGMENTS)
    device_name_display_2 = TextDisplayControl(segments=('', ) *
                                               NUM_DISPLAY_SEGMENTS)
    device_bank_name_display_1 = TextDisplayControl(segments=('', ) *
                                                    NUM_DISPLAY_SEGMENTS)
    device_bank_name_display_2 = TextDisplayControl(segments=('', ) *
                                                    NUM_DISPLAY_SEGMENTS)
    selected_device_name_display = TextDisplayControl(segments=('', ))
    selected_device_bank_name_display = TextDisplayControl(segments=('', ))
    device_color_fields = control_list(ColorSysexControl, NUM_VISIBLE_ITEMS)
    device_selection_fields = control_list(BinaryControl, NUM_VISIBLE_ITEMS)

    def __init__(self, banking_info=None, device_bank_registry=None, *a, **k):
        super(DisplayingDeviceNavigationComponent, self).__init__(*a, **k)
        self.select_buttons.control_count = NUM_VISIBLE_ITEMS
        assert banking_info is not None
        assert device_bank_registry is not None
        self._banking_info = banking_info
        self._device_bank_registry = device_bank_registry
        self.__on_items_changed.subject = self
        self.__on_items_changed()
        self.__on_selection_changed.subject = self._item_provider
        self.__on_selection_changed()
        self.__on_device_bank_changed.subject = self._device_bank_registry
        return

    @listens(b'items')
    def __on_items_changed(self):
        for index, control in enumerate(self.select_buttons):
            control.enabled = index < len(self.items)

        self._update_button_colors()
        self._update_device_color_fields()
        self._update_device_names()
        self._update_device_bank_names()
        self._update_selection_display()
        self._scroll_overlay.update_scroll_buttons()
        self.__on_item_names_changed.replace_subjects(self.items)

    @listens(b'selected_item')
    def __on_selection_changed(self):
        self._update_button_colors()
        self._update_selection_display()
        self._update_selected_device_name_display()
        self._update_selected_device_bank_name_display()
        self.__on_selected_item_name_changed.subject = self._item_provider.selected_item

    @listens_group(b'name')
    def __on_item_names_changed(self, something):
        self._update_device_names()

    @listens(b'device_bank')
    def __on_device_bank_changed(self, *a):
        self._update_device_bank_names()
        self._update_selected_device_bank_name_display()

    @listens(b'name')
    def __on_selected_item_name_changed(self):
        self._update_selected_device_name_display()

    def _update_device_color_fields(self):
        for color_field, item in izip_longest(self.device_color_fields,
                                              self.items):
            color_field.color = b'Device.On' if item else b'DefaultButton.Disabled'

    def _update_selection_display(self):
        selected_item = self._item_provider.selected_item
        for selection_field, item in izip_longest(self.device_selection_fields,
                                                  self.items):
            selection_field.is_on = bool(item) and self._items_equal(
                item, selected_item)

    def _update_device_names(self):
        for index, item in izip_longest(xrange(NUM_VISIBLE_ITEMS), self.items):
            display = getattr(self,
                              (b'device_name_display_{}'
                               ).format(index / NUM_DISPLAY_SEGMENTS + 1))
            display[index %
                    NUM_DISPLAY_SEGMENTS] = item.item.name if item else b''

    def _update_device_bank_names(self):
        for index, item in izip_longest(xrange(NUM_VISIBLE_ITEMS), self.items):
            display = getattr(self,
                              (b'device_bank_name_display_{}'
                               ).format(index / NUM_DISPLAY_SEGMENTS + 1))
            display[index % NUM_DISPLAY_SEGMENTS] = self._bank_name_for_item(
                item.item if item else None)

        return

    def _update_selected_device_name_display(self):
        item = self._item_provider.selected_item
        self.selected_device_name_display[
            0] = item.name if item else b'No Device'

    def _update_selected_device_bank_name_display(self):
        self.selected_device_bank_name_display[0] = self._bank_name_for_item(
            self._item_provider.selected_item)

    def _bank_name_for_item(self, item):
        bank_name = b''
        if item:
            item_bank_names = self._banking_info.device_bank_names(item)
            if item_bank_names:
                bank = self._device_bank_registry.get_device_bank(item)
                bank_name = item_bank_names[bank]
        return bank_name
class MixerControlComponent(ModesComponent):
    __events__ = (u'items', u'selected_item')
    controls = control_list(MappedSensitivitySettingControl)
    cycle_sends_button = ButtonControl(color='DefaultButton.Off')

    @staticmethod
    def get_tracks(items):
        return filter(
            lambda item: item is not None and isinstance(
                item.proxied_object, Live.Track.Track), items)

    @depends(tracks_provider=None,
             real_time_mapper=None,
             register_real_time_data=None)
    def __init__(self,
                 view_model=None,
                 tracks_provider=None,
                 real_time_mapper=None,
                 register_real_time_data=None,
                 *a,
                 **k):
        assert liveobj_valid(real_time_mapper)
        assert view_model is not None
        assert tracks_provider is not None
        super(MixerControlComponent, self).__init__(*a, **k)
        self._send_offset = 0
        self.real_time_meter_handlers = [
            RealTimeDataComponent(
                channel_type='meter',
                real_time_mapper=real_time_mapper,
                register_real_time_data=register_real_time_data,
                is_enabled=False) for _ in xrange(8)
        ]
        self._track_provider = tracks_provider
        self._on_return_tracks_changed.subject = self.song
        self._on_mode_changed.subject = self
        self._mixer_section_view = None
        self._mixer_sections = []
        self._selected_view = view_model.volumeControlListView
        self._parameter_getter = lambda x: None
        self._setup_modes(view_model)
        self.selected_mode = 'volume'
        self._selected_item = ''
        self._items = []
        self._on_return_tracks_changed()
        self._update_mixer_sections()
        self._on_items_changed.subject = self._track_provider
        self._on_selected_item_changed.subject = self._track_provider
        tracks = self.get_tracks(self._track_provider.items)
        self.__on_track_frozen_state_changed.replace_subjects(tracks)
        self.__on_panning_mode_changed.replace_subjects(
            filter(has_pan_mode, [t.mixer_device for t in tracks]))
        return

    def _setup_modes(self, view_model):
        self._add_mode('volume',
                       view_model.volumeControlListView,
                       lambda mixer: mixer.volume,
                       additional_mode_contents=self.real_time_meter_handlers)
        self._add_mode(
            'panning', view_model.panControlListView,
            lambda mixer: ConstantParameter(original_parameter=mixer.panning)
            if is_set_to_split_stereo(mixer) else mixer.panning)

        def add_send_mode(index):
            self._add_mode(
                SEND_MODE_NAMES[index], view_model.sendControlListView,
                lambda mixer: mixer.sends[(self._send_offset + index)]
                if len(mixer.sends) > self._send_offset + index else None)

        for i in xrange(SEND_LIST_LENGTH):
            add_send_mode(i)

    def _add_mode(self,
                  mode,
                  view,
                  parameter_getter,
                  additional_mode_contents=[]):
        description = MixerSectionDescription(
            view=view, parameter_getter=parameter_getter)
        self.add_mode(
            mode,
            additional_mode_contents + [partial(self._set_mode, description)])
        mode_button = self.get_mode_button(mode)
        mode_button.mode_selected_color = 'MixerControlView.SectionSelected'
        mode_button.mode_unselected_color = 'MixerControlView.SectionUnSelected'

    def on_enabled_changed(self):
        super(MixerControlComponent, self).on_enabled_changed()
        self._selected_view.visible = self.is_enabled()
        self._update_mixer_sections()
        if not self.is_enabled():
            self._update_realtime_ids()

    def set_mixer_section(self, mixer_section):
        self._mixer_section_view = mixer_section
        if self._mixer_section_view:
            self._mixer_section_view.model.mode = 'Global'
            self._update_mixer_sections()

    @property
    def number_sends(self):
        return len(self._track_provider.selected_item.mixer_device.sends)

    def _set_mode(self, description):
        self._selected_view.visible = False
        self._selected_view = description.view
        self._parameter_getter = description.parameter_getter
        self._update_controls(self._parameter_getter, self._selected_view)
        self._selected_view.visible = True

    @listens('selected_mode')
    def _on_mode_changed(self, selected_mode):
        if selected_mode in SEND_MODE_NAMES:
            index = SEND_MODE_NAMES.index(selected_mode)
            self._selected_item = SEND_SECTIONS[clamp(
                index + self._send_offset, 0, self.number_sends - 1)]
        else:
            self._selected_item = MIXER_SECTIONS[
                1] if selected_mode == 'panning' else MIXER_SECTIONS[0]
        self.notify_selected_item()

    @listens('return_tracks')
    def _on_return_tracks_changed(self):
        with self._updating_send_offset_mode_selection():
            self._update_mode_selection()
            new_send_offset = max(
                0,
                int(
                    ceil(
                        float(self.number_sends) / float(SEND_LIST_LENGTH) - 1)
                    * SEND_LIST_LENGTH))
            if new_send_offset < self._send_offset:
                self._send_offset = new_send_offset

    @listens('items')
    def _on_items_changed(self):
        tracks = self.get_tracks(self._track_provider.items)
        self.__on_panning_mode_changed.replace_subjects(
            filter(has_pan_mode, [t.mixer_device for t in tracks]))
        self._update_controls(self._parameter_getter, self._selected_view)

    @listens_group('is_frozen')
    def __on_track_frozen_state_changed(self, identifier):
        self._update_controls(self._parameter_getter, self._selected_view)

    @listens_group('panning_mode')
    def __on_panning_mode_changed(self, identifier):
        self._update_controls(self._parameter_getter, self._selected_view)

    @listens('selected_item')
    def _on_selected_item_changed(self):
        if self.number_sends <= SEND_LIST_LENGTH:
            self._send_offset = 0
        self._update_mode_selection()
        self._update_mixer_sections()
        self._update_buttons(self.selected_mode)

    def _update_mode_selection(self):
        number_sends = self.number_sends
        if self.selected_mode in SEND_MODE_NAMES:
            index = SEND_MODE_NAMES.index(self.selected_mode)
            if index + self._send_offset >= number_sends and number_sends > 0:
                self.selected_mode = SEND_MODE_NAMES[(
                    number_sends % SEND_LIST_LENGTH - 1)]
            elif index == 0 and number_sends == 0:
                self.selected_mode = 'panning'

    def _update_mixer_sections(self):
        if self.is_enabled():
            position = max(self._send_offset, 0)
            pos_range = min(self.number_sends - position, SEND_LIST_LENGTH)
            mixer_section_names = list(
                MIXER_SECTIONS) + SEND_SECTIONS[position:position + pos_range]
            self._mixer_sections = [
                IconItemSlot(name=name) for name in mixer_section_names
            ]
            if self.number_sends > SEND_LIST_LENGTH:
                self._mixer_sections.extend([IconItemSlot()] *
                                            (8 - len(self._mixer_sections)))
                self._mixer_sections[7] = IconItemSlot(icon='page_right.svg')
            self.notify_items()
            if self.selected_mode in SEND_MODE_NAMES:
                index = SEND_MODE_NAMES.index(self.selected_mode)
                self._selected_item = SEND_SECTIONS[(index +
                                                     self._send_offset)]
                self.notify_selected_item()

    @property
    def items(self):
        return self._mixer_sections

    @property
    def selected_item(self):
        return self._selected_item

    def _update_controls(self, parameter_getter, control_view):
        if self.is_enabled():
            parameters = self._get_parameter_for_tracks(parameter_getter)
            control_view.parameters = parameters
            self._update_realtime_ids()
            assign_parameters(self.controls, parameters)

    def _update_realtime_ids(self):
        mixables = self._track_provider.items
        for handler, mixable in izip(self.real_time_meter_handlers, mixables):
            handler.set_data(
                mixable.mixer_device if liveobj_valid(mixable) else None)

        return

    def _get_parameter_for_tracks(self, parameter_getter):
        tracks = self._track_provider.items
        self.controls.control_count = len(tracks)
        return map(lambda t: parameter_getter(t.mixer_device)
                   if t else None, tracks)

    def mode_can_be_used(self, mode):
        return mode not in SEND_MODE_NAMES or SEND_MODE_NAMES.index(
            mode) + self._send_offset < self.number_sends

    def _update_buttons(self, selected_mode):
        for name in self._mode_map.iterkeys():
            self.get_mode_button(name).enabled = self.mode_can_be_used(name)

        self.cycle_sends_button.enabled = self.number_sends > SEND_LIST_LENGTH

    @cycle_sends_button.pressed
    def cycle_sends_button(self, button):
        button.color = 'MixerControlView.SectionSelected'

    @cycle_sends_button.released
    def cycle_sends_button(self, button):
        button.color = 'MixerControlView.SectionUnSelected'
        self._cycle_send_offset()

    def _cycle_send_offset(self):
        with self._updating_send_offset_mode_selection():
            new_offset = self._send_offset + SEND_LIST_LENGTH
            self._send_offset = new_offset if new_offset < self.number_sends else 0
            if self.selected_mode in SEND_MODE_NAMES:
                self.selected_mode = SEND_MODE_NAMES[0]

    @contextmanager
    def _updating_send_offset_mode_selection(self):
        yield
        self._update_mixer_sections()
        self._update_buttons(self.selected_mode)
        self._update_controls(self._parameter_getter, self._selected_view)
示例#23
0
class GenericDeviceComponent(DeviceComponentBase):
    parameter_touch_buttons = control_list(ButtonControl, control_count=8)
    shift_button = ButtonControl()
    lock_button = ButtonControl()

    def __init__(self, visualisation_real_time_data=None, delete_button=None, *a, **k):
        super(GenericDeviceComponent, self).__init__(*a, **k)
        self._visualisation_real_time_data = visualisation_real_time_data
        self._delete_button = delete_button
        self.default_sensitivity = partial(self._sensitivity, DEFAULT_SENSITIVITY_KEY)
        self.fine_sensitivity = partial(self._sensitivity, FINE_GRAINED_SENSITIVITY_KEY)

    def set_device(self, device):
        self._set_device(device)

    def _set_device(self, device):
        super(GenericDeviceComponent, self)._set_device(device)
        self.notify_options()

    def _on_device_changed(self, device):
        u"""
        Override the base class behaviour, which calls _set_device when a device in
        the device provider changes. It's already donne by the DeviceComponentProvider.
        """
        pass

    @parameter_touch_buttons.pressed
    def parameter_touch_buttons(self, button):
        if button.index < len(self._provided_parameters):
            parameter = self._provided_parameters[button.index].parameter
            self._parameter_touched(parameter)

    @parameter_touch_buttons.released
    def parameter_touch_buttons(self, button):
        if button.index < len(self._provided_parameters):
            parameter = self._provided_parameters[button.index].parameter
            self._parameter_released(parameter)

    def parameters_changed(self):
        pass

    def _parameter_touched(self, parameter):
        pass

    def _parameter_released(self, parameter):
        pass

    @shift_button.pressed
    def shift_button(self, button):
        self._shift_button_pressed(button)

    @shift_button.released
    def shift_button(self, button):
        self._shift_button_released(button)

    def _shift_button_pressed(self, button):
        pass

    def _shift_button_released(self, button):
        pass

    @lock_button.pressed
    def lock_button(self, button):
        self._lock_button_pressed(button)

    @lock_button.released
    def lock_button(self, button):
        self._lock_button_released(button)

    def _lock_button_pressed(self, button):
        if self.parent._locked_to_device:
            self.parent._locked_to_device = False
        else:
            self.parent._locked_to_device = True

    def _lock_button_released(self, button):
        pass

    def initialize_visualisation_view_data(self):
        view_data = self._initial_visualisation_view_data()
        if view_data:
            self._update_visualisation_view_data(view_data)

    def _update_visualisation_view_data(self, view_data):
        visualisation = self._visualisation_real_time_data.device_visualisation()
        if liveobj_valid(visualisation):
            visualisation_view_data = visualisation.get_view_data()
            for key, value in view_data.iteritems():
                visualisation_view_data[key] = value

            visualisation.set_view_data(visualisation_view_data)

    def _initial_visualisation_view_data(self):
        return {}

    def _is_parameter_available(self, parameter):
        return True

    def _create_parameter_info(self, parameter, name):
        return ParameterInfo(parameter=parameter if self._is_parameter_available(parameter) else None, name=name, default_encoder_sensitivity=self.default_sensitivity(parameter), fine_grain_encoder_sensitivity=self.fine_sensitivity(parameter))

    def _sensitivity(self, sensitivity_key, parameter):
        device = self.device()
        sensitivity = parameter_sensitivities(device.class_name, parameter)[sensitivity_key]
        if liveobj_valid(parameter):
            sensitivity = self._adjust_parameter_sensitivity(parameter, sensitivity)
        return sensitivity

    def _adjust_parameter_sensitivity(self, parameter, sensitivity):
        return sensitivity

    @listenable_property
    def options(self):
        return getattr(self._bank, 'options', [None] * OPTIONS_PER_BANK)

    @property
    def bank_view_description(self):
        return getattr(self._bank, 'bank_view_description', '')

    @listenable_property
    def visualisation_visible(self):
        return self._visualisation_visible

    @property
    def _visualisation_visible(self):
        return False

    @listenable_property
    def shrink_parameters(self):
        return self._shrink_parameters

    @property
    def _shrink_parameters(self):
        return [
         False] * 8

    @listens('options')
    def __on_options_changed(self):
        self.notify_options()

    def _setup_bank(self, device):
        super(GenericDeviceComponent, self)._setup_bank(device, bank_factory=create_device_bank_with_options)
        try:
            self.__on_options_changed.subject = self._bank
        except EventError:
            pass
示例#24
0
class DeviceParameterComponent(DeviceParameterComponentBase):
    controls = control_list(SendingMappedSensitivitySettingControl, NUM_PARAM_CONTROLS)
    absolute_controls = control_list(SendingMappedAbsoluteControl, NUM_PARAM_CONTROLS)
    parameter_enable_controls = control_list(SendValueControl, NUM_PARAM_CONTROLS)
    display_style_controls = control_list(SendValueControl, NUM_PARAM_CONTROLS)
    touch_controls = control_list(ButtonControl, NUM_PARAM_CONTROLS)
    parameter_name_or_value_displays = control_list(ConfigurableTextDisplayControl, NUM_PARAM_CONTROLS)
    device_enable_button = ButtonControl()

    def __init__(self, *a, **k):
        self._physical_display_parameter_name_data_sources = map(DisplayDataSource, (u'',) * NUM_PARAM_CONTROLS)
        self._physical_display_parameter_value_data_sources = map(DisplayDataSource, (u'',) * NUM_PARAM_CONTROLS)
        super(DeviceParameterComponent, self).__init__(*a, **k)
        self._update_parameter_name_or_value_displays()

    @property
    def device_on_off_parameter(self):
        device = self.parameter_provider.device()
        if liveobj_valid(device):
            return device.parameters[0]

    def set_name_display_line(self, line):
        super(DeviceParameterComponent, self).set_name_display_line(line)
        self._set_display_line(line, self._physical_display_parameter_name_data_sources)

    def set_value_display_line(self, line):
        super(DeviceParameterComponent, self).set_value_display_line(line)
        self._set_display_line(line, self._physical_display_parameter_value_data_sources)

    def set_absolute_parameter_controls(self, controls):
        self.absolute_controls.set_control_element(controls)
        self._connect_parameters()

    @touch_controls.pressed
    def touch_controls(self, _):
        self._update_parameter_name_or_value_displays()

    @touch_controls.released
    def touch_controls(self, _):
        self._update_parameter_name_or_value_displays()

    @device_enable_button.pressed
    def device_enable_button(self, _):
        self._toggle_device_enabled_status()

    def _toggle_device_enabled_status(self):
        on_off = self.device_on_off_parameter
        if liveobj_valid(on_off) and on_off.is_enabled:
            on_off.value = not on_off.value

    def _clear_display(self):
        super(DeviceParameterComponent, self)._clear_display()
        for source in chain(self._physical_parameter_name_data_sources, self._physical_parameter_value_data_sources):
            source.set_display_string(u'')

    def _update_parameter_names(self):
        super(DeviceParameterComponent, self)._update_parameter_names()
        for info, name_data_source in izip_longest(self.parameter_provider.parameters, self._physical_display_parameter_name_data_sources):
            name_data_source.set_display_string(self.info_to_name(info))

    def _update_parameter_values(self):
        super(DeviceParameterComponent, self)._update_parameter_values()
        for parameter, data_source, control, absolute_control in izip_longest(self.parameters, self._physical_display_parameter_value_data_sources, self.controls, self.absolute_controls):
            data_source.set_display_string(self.parameter_to_string(parameter))
            if is_internal_parameter(parameter):
                value_to_send = convert_parameter_value_to_midi_value(parameter)
                control.value = value_to_send
                absolute_control.value = value_to_send

    def _connect_parameters(self):
        for control, absolute_control, display_style_control, parameter_info in izip_longest(self.controls, self.absolute_controls, self.display_style_controls, self._parameter_provider.parameters[:NUM_PARAM_CONTROLS]):
            parameter = parameter_info.parameter if parameter_info else None
            control.mapped_parameter = parameter
            absolute_control.mapped_parameter = parameter
            display_style_control.value = display_style_for_parameter(parameter)
            if parameter:
                control.update_sensitivities(parameter_info.default_encoder_sensitivity, parameter_info.fine_grain_encoder_sensitivity)

        self._update_parameter_enable_controls()

    def _on_parameter_provider_changed(self, provider):
        self.__on_device_changed.subject = provider
        self.__on_device_changed()

    @listens(u'device')
    def __on_device_changed(self):
        self.__on_device_enabled_changed.subject = self.device_on_off_parameter
        self.__on_device_enabled_changed()

    @listens(u'value')
    def __on_device_enabled_changed(self):
        self._update_device_enable_button()

    def _update_device_enable_button(self):
        on_off = self.device_on_off_parameter
        self.device_enable_button.color = u'DefaultButton.On' if liveobj_valid(on_off) and on_off.value else u'DefaultButton.Off'

    def _update_parameter_enable_controls(self):
        for control, parameter_info in izip_longest(self.parameter_enable_controls, self._parameter_provider.parameters[:self.controls.control_count]):
            control.value = ON_VALUE if parameter_info and parameter_info.parameter else OFF_VALUE

    def _update_parameter_name_or_value_displays(self):
        for display, control, name_data_source, value_data_source in izip(self.parameter_name_or_value_displays, self.touch_controls, self._physical_display_parameter_name_data_sources, self._physical_display_parameter_value_data_sources):
            display.set_data_sources([value_data_source if control.is_pressed else name_data_source])
class ScalesComponent(Component):
    navigation_colors = dict(color=b'Scales.Navigation',
                             disabled_color=b'Scales.NavigationDisabled')
    up_button = ButtonControl(repeat=True, **navigation_colors)
    down_button = ButtonControl(repeat=True, **navigation_colors)
    right_button = ButtonControl(repeat=True, **navigation_colors)
    left_button = ButtonControl(repeat=True, **navigation_colors)
    root_note_buttons = control_list(RadioButtonControl,
                                     control_count=len(ROOT_NOTES),
                                     checked_color=b'Scales.OptionOn',
                                     unchecked_color=b'Scales.OptionOff')
    in_key_toggle_button = ToggleButtonControl(
        toggled_color=b'Scales.OptionOn', untoggled_color=b'Scales.OptionOn')
    fixed_toggle_button = ToggleButtonControl(
        toggled_color=b'Scales.OptionOn', untoggled_color=b'Scales.OptionOff')
    scale_encoders = control_list(StepEncoderControl)
    layout_encoder = StepEncoderControl()
    direction_encoder = StepEncoderControl()
    horizontal_navigation = listenable_property.managed(False)
    NUM_DISPLAY_ROWS = 4
    NUM_DISPLAY_COLUMNS = int(ceil(float(len(SCALES)) / NUM_DISPLAY_ROWS))

    def __init__(self, note_layout=None, *a, **k):
        assert note_layout is not None
        super(ScalesComponent, self).__init__(*a, **k)
        self._note_layout = note_layout
        self._scale_list = list(SCALES)
        self._scale_name_list = map(lambda m: m.name, self._scale_list)
        self._selected_scale_index = -1
        self._selected_root_note_index = -1
        self._layouts = (Layout(b'4ths',
                                3), Layout(b'3rds',
                                           2), Layout(b'Sequential', None))
        self._selected_layout_index = 0
        self.in_key_toggle_button.connect_property(note_layout, b'is_in_key')
        self.fixed_toggle_button.connect_property(note_layout, b'is_fixed')
        self.__on_root_note_changed.subject = self._note_layout
        self.__on_scale_changed.subject = self._note_layout
        self.__on_interval_changed.subject = self._note_layout
        self.__on_root_note_changed(note_layout.root_note)
        self.__on_scale_changed(note_layout.scale)
        self.__on_interval_changed(self._note_layout.interval)
        return

    def _set_selected_scale_index(self, index):
        index = clamp(index, 0, len(self._scale_list) - 1)
        self._note_layout.scale = self._scale_list[index]

    @down_button.pressed
    def down_button(self, button):
        self._update_horizontal_navigation()
        self._set_selected_scale_index(self._selected_scale_index + 1)

    @up_button.pressed
    def up_button(self, button):
        self._update_horizontal_navigation()
        self._set_selected_scale_index(self._selected_scale_index - 1)

    @left_button.pressed
    def left_button(self, button):
        self._update_horizontal_navigation()
        self._set_selected_scale_index(self._selected_scale_index -
                                       self.NUM_DISPLAY_ROWS)

    @right_button.pressed
    def right_button(self, button):
        self._update_horizontal_navigation()
        self._set_selected_scale_index(self._selected_scale_index +
                                       self.NUM_DISPLAY_ROWS)

    @root_note_buttons.pressed
    def root_note_buttons(self, button):
        self._note_layout.root_note = ROOT_NOTES[button.index]

    @listens(b'root_note')
    def __on_root_note_changed(self, root_note):
        self._selected_root_note_index = list(ROOT_NOTES).index(root_note)
        self.root_note_buttons.checked_index = self._selected_root_note_index
        self.notify_selected_root_note_index()

    @property
    def root_note_names(self):
        return [NOTE_NAMES[note] for note in ROOT_NOTES]

    @listenable_property
    def selected_root_note_index(self):
        return self._selected_root_note_index

    @scale_encoders.value
    def scale_encoders(self, value, encoder):
        self._update_horizontal_navigation()
        self._set_selected_scale_index(self._selected_scale_index + value)

    @property
    def scale_names(self):
        return self._scale_name_list

    @listenable_property
    def selected_scale_index(self):
        return self._selected_scale_index

    @listens(b'scale')
    def __on_scale_changed(self, scale):
        index = self._scale_list.index(
            scale) if scale in self._scale_list else -1
        if index != self._selected_scale_index:
            self._selected_scale_index = index
            self.up_button.enabled = index > 0
            self.left_button.enabled = index > 0
            self.down_button.enabled = index < len(self._scale_list) - 1
            self.right_button.enabled = index < len(self._scale_list) - 1
            self.notify_selected_scale_index()

    @layout_encoder.value
    def layout_encoder(self, value, encoder):
        index = clamp(self._selected_layout_index + value, 0,
                      len(self._layouts) - 1)
        self.selected_layout_index = index

    @property
    def layout_names(self):
        return [layout.name for layout in self._layouts]

    @listenable_property
    def selected_layout_index(self):
        return self._selected_layout_index

    @selected_layout_index.setter
    def selected_layout_index(self, index):
        if index != self._selected_layout_index:
            self._selected_layout_index = index
            interval = self._layouts[index].interval
            self._note_layout.interval = interval
            self.notify_selected_layout_index()

    @direction_encoder.value
    def direction_encoder(self, value, encoder):
        self._note_layout.is_horizontal = value < 0

    @property
    def note_layout(self):
        return self._note_layout

    def _update_horizontal_navigation(self):
        self.horizontal_navigation = self.right_button.is_pressed or self.left_button.is_pressed

    @listens(b'interval')
    def __on_interval_changed(self, interval):
        index = index_if(lambda layout: layout.interval == interval,
                         self._layouts)
        self.selected_layout_index = index
示例#26
0
class ModeSelector(Component):
    select_buttons = control_list(ButtonControl, color='DefaultButton.Disabled')
    state_buttons = control_list(ButtonControl, color='DefaultButton.Disabled')
class MixerTrackListComponent(Component, Messenger):
    select_buttons = control_list(ButtonControl, control_count=8)
    delete_buttons = control_list(ButtonControl, control_count=8)
    duplicate_buttons = control_list(ButtonControl, control_count=8)
    arm_buttons = control_list(ButtonControl, control_count=8)

    def __init__(self,
                 tracks_provider=None,
                 trigger_recording_on_release_callback=nop,
                 *a,
                 **k):
        raise tracks_provider is not None or AssertionError
        super(MixerTrackListComponent, self).__init__(*a, **k)
        self._can_trigger_recording_callback = trigger_recording_on_release_callback
        self._track_provider = tracks_provider
        self._selected_track = self.register_disconnectable(
            SelectedMixerTrackProvider())
        self.__on_items_changed.subject = self._track_provider
        self.__on_selected_item_changed.subject = self._track_provider
        self.__on_tracks_changed.subject = self.song
        self.__on_selected_track_changed.subject = self.song.view
        self._update_track_and_chain_listeners()
        self._update_button_enabled_state()
        self._update_all_button_colors()
        return

    @listenable_property
    def tracks(self):
        return self._track_provider.items

    @listenable_property
    def selected_track(self):
        return self._track_provider.selected_item

    @listenable_property
    def absolute_selected_track_index(self):
        song = self.song
        tracks = song.tracks + song.return_tracks + (song.master_track, )
        selected_track = song.view.selected_track
        return list(tracks).index(selected_track)

    @listens('tracks')
    def __on_tracks_changed(self):
        self._update_track_and_chain_listeners()
        self._update_button_enabled_state()

    @listens_group('mute')
    def __on_track_mute_state_changed(self, track):
        self._update_all_button_colors()

    @listens_group('solo')
    def __on_track_solo_state_changed(self, track):
        self._update_all_button_colors()

    @listens_group('fired_slot_index')
    def __on_track_fired_slot_changed(self, track):
        self._update_all_button_colors()

    @listens('items')
    def __on_items_changed(self):
        self._update_track_and_chain_listeners()
        self._update_button_enabled_state()

    def _update_track_and_chain_listeners(self):
        self.notify_tracks()
        self.__on_track_color_index_changed.replace_subjects(self.tracks)
        tracks_without_chains = ifilter(can_play_clips, self.tracks)
        self.__on_track_fired_slot_changed.replace_subjects(
            tracks_without_chains)
        all_tracks = [_ for _ in chain(self.song.tracks, self.tracks)]
        self.__on_track_mute_state_changed.replace_subjects(all_tracks)
        self.__on_track_solo_state_changed.replace_subjects(all_tracks)
        self.__on_track_muted_via_solo_changed.replace_subjects(all_tracks)
        self._update_button_enabled_state()
        self._update_all_button_colors()

    def _update_button_enabled_state(self):
        tracks = self.tracks
        for track, control in chain(izip(tracks, self.select_buttons),
                                    izip(tracks, self.delete_buttons),
                                    izip(tracks, self.arm_buttons),
                                    izip(tracks, self.duplicate_buttons)):
            control.enabled = liveobj_valid(track)

    @listens_group('color_index')
    def __on_track_color_index_changed(self, _):
        self._update_all_button_colors()

    @listens('selected_item')
    def __on_selected_item_changed(self):
        self.notify_selected_track()
        self._update_all_button_colors()

    @listens('selected_track')
    def __on_selected_track_changed(self):
        self.notify_absolute_selected_track_index()

    @listens_group('muted_via_solo')
    def __on_track_muted_via_solo_changed(self, mixable):
        self._update_all_button_colors()

    def _update_all_button_colors(self):
        for index, mixer_track in enumerate(self.tracks):
            color = mixable_button_color(mixer_track, self.song,
                                         self.selected_track)
            self.select_buttons[index].color = color
            self.duplicate_buttons[index].color = color
            self.delete_buttons[index].color = color
            self.arm_buttons[index].color = color

    @select_buttons.pressed
    def select_buttons(self, button):
        track = self.tracks[button.index]
        if track:
            if self._track_provider.selected_item != track:
                self._track_provider.selected_item = track
            else:
                if hasattr(track, 'is_foldable') and track.is_foldable:
                    track.fold_state = not track.fold_state
                if hasattr(track,
                           'is_showing_chains') and track.can_show_chains:
                    track.is_showing_chains = not track.is_showing_chains

    @staticmethod
    def can_duplicate_or_delete(track_or_chain, return_tracks):
        unwrapped = track_or_chain.proxied_object
        return isinstance(
            unwrapped,
            Live.Track.Track) and unwrapped not in list(return_tracks)

    @delete_buttons.pressed
    def delete_buttons(self, button):
        track_or_chain = self.tracks[button.index]
        if self.can_duplicate_or_delete(track_or_chain,
                                        self.song.return_tracks):
            try:
                track_index = list(self.song.tracks).index(track_or_chain)
                name = track_or_chain.name
                self.song.delete_track(track_index)
                self.show_notification(MessageBoxText.DELETE_TRACK % name)
            except RuntimeError:
                self.show_notification(MessageBoxText.TRACK_DELETE_FAILED)

    @duplicate_buttons.pressed
    def duplicate_buttons(self, button):
        track_or_chain = self.tracks[button.index]
        if self.can_duplicate_or_delete(track_or_chain,
                                        self.song.return_tracks):
            try:
                track_index = list(self.song.tracks).index(track_or_chain)
                self.song.duplicate_track(track_index)
                self.show_notification(MessageBoxText.DUPLICATE_TRACK %
                                       track_or_chain.name)
                self._update_all_button_colors()
            except Live.Base.LimitationError:
                self.show_notification(MessageBoxText.TRACK_LIMIT_REACHED)
            except RuntimeError:
                self.show_notification(MessageBoxText.TRACK_DUPLICATION_FAILED)

    @arm_buttons.pressed
    def arm_buttons(self, button):
        track_or_chain = self.tracks[button.index]
        if not is_chain(track_or_chain) and track_or_chain.can_be_armed:
            song = self.song
            toggle_arm(track_or_chain, song, exclusive=song.exclusive_arm)
        self._can_trigger_recording_callback(False)
示例#28
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()
示例#29
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
示例#30
0
class SessionComponent(SessionComponentBase):
    scene_component_type = SceneComponent
    stop_clip_color_controls = control_list(ButtonControl, NUM_TRACK_CONTROLS)
    playing_position_controls = control_list(SendValueControl,
                                             NUM_TRACK_CONTROLS)
    insert_scene_button = ButtonControl()

    def __init__(self, *a, **k):
        self._playing_position_subjects = [None] * NUM_TRACK_CONTROLS
        (super(SessionComponent, self).__init__)(*a, **k)
        self._update_playing_position_subjects()

    @insert_scene_button.pressed
    def insert_scene_button(self, _):
        try:
            song = self.song
            scenes = song.scenes
            song.create_scene(
                clamp(
                    index_if(lambda s: s == song.view.selected_scene, scenes) +
                    1, 0, len(scenes)))
        except Live.Base.LimitationError:
            pass

    def set_clip_color_controls(self, controls):
        self._set_clip_controls('clip_color_control', controls)

    def set_clip_name_displays(self, displays):
        self._set_clip_controls('clip_name_display', displays)

    def set_scene_name_displays(self, displays):
        self._set_scene_controls('scene_name_display', displays)

    def set_scene_color_controls(self, controls):
        self._set_scene_controls('scene_color_control', controls)

    def set_scene_selection_controls(self, controls):
        self._set_scene_controls('scene_selection_control', controls)

    def set_force_scene_launch_buttons(self, buttons):
        self._set_scene_controls('force_launch_button', buttons)

    def set_select_button(self, button):
        for scene_index, slot_index in product(
                range(self._session_ring.num_scenes),
                range(self._session_ring.num_tracks)):
            self.scene(scene_index).clip_slot(slot_index).set_select_button(
                button)

    def _reassign_tracks(self):
        super(SessionComponent, self)._reassign_tracks()
        self._update_playing_position_subjects()

    def _on_fired_slot_index_changed(self, track_index):
        super(SessionComponent, self)._on_fired_slot_index_changed(track_index)
        self._update_playing_position_subject(track_index)

    def _on_playing_slot_index_changed(self, track_index):
        super(SessionComponent,
              self)._on_playing_slot_index_changed(track_index)
        self._update_playing_position_subject(track_index)

    def _update_playing_position_subject(self, track_index):
        session_ring = self._session_ring
        track_offset = session_ring.track_offset
        if in_range(track_index, track_offset,
                    track_offset + session_ring.num_tracks):
            self._do_update_playing_position_subject(track_index -
                                                     track_offset)

    def _update_playing_position_subjects(self):
        for index in range(NUM_TRACK_CONTROLS):
            self._do_update_playing_position_subject(index)

    def _do_update_playing_position_subject(self, index):
        session_ring = self._session_ring
        tracks = session_ring.tracks_to_use()
        track_index = session_ring.track_offset + index
        new_subject = None
        if track_index < len(tracks):
            track = tracks[track_index]
            if self._can_have_playing_slots(track):
                if liveobj_valid(track.group_track):
                    group_track_index = index_if(
                        lambda t: t == track.group_track, tracks)
                    if group_track_index < len(tracks):
                        subject_index = index - track_index + group_track_index
                        if in_range(subject_index, 0, session_ring.num_tracks):
                            self._do_update_playing_position_subject(
                                subject_index)
                if track.is_foldable:
                    track = find_first_playing_grouped_track(
                        track, self.song.tracks)
                if liveobj_valid(track):
                    playing_slot_index = track.playing_slot_index
                    if playing_slot_index >= 0:
                        clip_slot = track.clip_slots[playing_slot_index]
                        if clip_slot.has_clip:
                            new_subject = clip_slot.clip
        self._playing_position_subjects[index] = new_subject
        self._SessionComponent__on_playing_position_changed.replace_subjects(
            (self._playing_position_subjects), identifiers=(count()))
        self._SessionComponent__on_playing_position_changed(index)

    @listens_group('playing_position')
    def __on_playing_position_changed(self, index):
        clip = self._playing_position_subjects[index]
        normalized_value = 0.0
        if liveobj_valid(clip):
            playing_position = clip.playing_position
            loop_start = clip.loop_start
            start_marker = clip.start_marker
            if not clip.looping:
                normalized_value = old_div(
                    playing_position - clip.start_marker, clip.length)
            elif start_marker < loop_start and playing_position < loop_start:
                normalized_value = old_div(
                    playing_position - clip.start_marker,
                    loop_start - start_marker)
            else:
                length = clip.length
                position_in_loop = playing_position - loop_start - max(
                    0, clip.start_marker - loop_start)
                if position_in_loop < 0:
                    position_in_loop += length
                normalized_value = old_div(position_in_loop, length)
        self.playing_position_controls[index].value = clamp(
            int(normalized_value * 127), 0, 127)

    def _update_stop_clips_led(self, index):
        super(SessionComponent, self)._update_stop_clips_led(index)
        if index < self.stop_clip_color_controls.control_count:
            color = 'DefaultButton.Off'
            tracks_to_use = self._session_ring.tracks_to_use()
            track_index = index + self._session_ring.track_offset
            if track_index < len(tracks_to_use):
                if tracks_to_use[track_index].clip_slots:
                    color = 'Session.StopClip'
            self.stop_clip_color_controls[index].color = color

    def _set_clip_controls(self, name, controls):
        for x, y in product(range(self._session_ring.num_tracks),
                            range(self._session_ring.num_scenes)):
            scene = self.scene(y)
            slot = scene.clip_slot(x)
            _set_method(slot,
                        name)(controls.get_button(x, y) if controls else None)

    def _set_scene_controls(self, name, controls):
        for x in range(self._session_ring.num_scenes):
            scene = self.scene(x)

        for scene, control in zip_longest(self._scenes, controls or []):
            _set_method(scene, name)(control)

    def _can_have_playing_slots(self, track):
        return liveobj_valid(track) and not (track == self.song.master_track or
                                             track in self.song.return_tracks)