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
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')
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()
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
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]
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
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)
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()
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()
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)
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
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
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)
class ListComponent(Component): """ Component that handles a ScrollableList. If an action button is passed, it can handle an ActionList. """ __events__ = ('item_action', 'selected_item') SELECTION_DELAY = 0.5 ENCODER_FACTOR = 10.0 empty_list_message = b'' _current_action_item = None _last_action_item = None action_button = ButtonControl(color=b'Browser.Load') encoders = control_list(EncoderControl) def __init__(self, scrollable_list=None, data_sources=tuple(), *a, **k): super(ListComponent, self).__init__(*a, **k) self._data_sources = data_sources self._activation_task = task.Task() self._action_on_scroll_task = task.Task() self._scrollable_list = None self._scroller = ScrollComponent(parent=self) self._pager = ScrollComponent(parent=self) self.last_action_item = lambda: self._last_action_item self.item_formatter = DefaultItemFormatter() for c in (self._scroller, self._pager): for button in (c.scroll_up_button, c.scroll_down_button): button.color = b'List.ScrollerOn' button.pressed_color = None button.disabled_color = b'List.ScrollerOff' if scrollable_list == None: self.scrollable_list = ActionList( num_visible_items=len(data_sources)) else: self.scrollable_list = scrollable_list self._scrollable_list.num_visible_items = len(data_sources) self._delay_activation = BooleanContext() self._selected_index_float = 0.0 self._in_encoder_selection = BooleanContext(False) self._execute_action_task = self._tasks.add( task.sequence(task.delay(1), task.run(self._execute_action))) self._execute_action_task.kill() return @property def _trigger_action_on_scrolling(self): return self.action_button.is_pressed def _get_scrollable_list(self): return self._scrollable_list def _set_scrollable_list(self, new_list): if new_list != self._scrollable_list: self._scrollable_list = new_list if new_list != None: new_list.num_visible_items = len(self._data_sources) self._scroller.scrollable = new_list self._pager.scrollable = new_list.pager self._on_scroll.subject = new_list self._selected_index_float = new_list.selected_item_index else: self._scroller.scrollable = ScrollComponent.default_scrollable self._scroller.scrollable = ScrollComponent.default_pager self._on_selected_item_changed.subject = new_list self.update() return scrollable_list = property(_get_scrollable_list, _set_scrollable_list) def set_data_sources(self, sources): self._data_sources = sources if self._scrollable_list: self._scrollable_list.num_visible_items = len(sources) self._update_display() select_next_button = forward_property(b'_scroller')(b'scroll_down_button') select_prev_button = forward_property(b'_scroller')(b'scroll_up_button') next_page_button = forward_property(b'_pager')(b'scroll_down_button') prev_page_button = forward_property(b'_pager')(b'scroll_up_button') def on_enabled_changed(self): super(ListComponent, self).on_enabled_changed() if not self.is_enabled(): self._execute_action_task.kill() @listens(b'scroll') def _on_scroll(self): if self._trigger_action_on_scrolling: trigger_selected = partial(self._trigger_action, self.selected_item) self._action_on_scroll_task.kill() self._action_on_scroll_task = self._tasks.add( task.sequence(task.wait(defaults.MOMENTARY_DELAY), task.delay(1), task.run(trigger_selected))) @listens(b'selected_item') def _on_selected_item_changed(self): self._scroller.update() self._pager.update() self._update_display() self._update_action_feedback() self._activation_task.kill() self._action_on_scroll_task.kill() if self.SELECTION_DELAY and self._delay_activation: self._activation_task = self._tasks.add( task.sequence( task.wait(self.SELECTION_DELAY), task.run( self._scrollable_list.request_notify_item_activated))) else: self._scrollable_list.request_notify_item_activated() if not self._in_encoder_selection: self._selected_index_float = float( self._scrollable_list.selected_item_index) self.notify_selected_item(self._scrollable_list.selected_item) @encoders.value def encoders(self, value, encoder): self._add_offset_to_selected_index(value) def _add_offset_to_selected_index(self, offset): if self.is_enabled() and self._scrollable_list: with self._delay_activation(): with self._in_encoder_selection(): self._selected_index_float = clamp( self._selected_index_float + offset * self.ENCODER_FACTOR, 0, len(self._scrollable_list.items)) self._scrollable_list.select_item_index_with_border( int(self._selected_index_float), 1) @action_button.pressed def action_button(self, button): if self._current_action_item == None: self._trigger_action( self.next_item if self._action_target_is_next_item( ) else self.selected_item) return def do_trigger_action(self, item): item.action() self.notify_item_action(item) def _trigger_action(self, item): if self.is_enabled() and self._can_be_used_for_action(item): if self._scrollable_list != None: self._scrollable_list.select_item(item) self._current_action_item = item self.update() self._execute_action_task.restart() return def _execute_action(self): """ Is called by the execute action task and should not be called directly use _trigger_action instead """ if self._current_action_item != None: self.do_trigger_action(self._current_action_item) self._last_action_item = self._current_action_item self._current_action_item = None self.update() return @property def selected_item(self): if self._scrollable_list != None: return self._scrollable_list.selected_item else: return @property def next_item(self): item = None if self._scrollable_list != None: all_items = self._scrollable_list.items next_index = self._scrollable_list.selected_item_index + 1 item = all_items[next_index] if in_range(next_index, 0, len(all_items)) else None return item def _can_be_used_for_action(self, item): return item != None and item.supports_action and item != self.last_action_item( ) def _action_target_is_next_item(self): return self.selected_item == self.last_action_item( ) and self._can_be_used_for_action(self.next_item) def _update_action_feedback(self): color = b'Browser.Loading' if self._current_action_item == None: if self._action_target_is_next_item(): color = b'Browser.LoadNext' elif self._can_be_used_for_action(self.selected_item): color = b'Browser.Load' else: color = b'Browser.LoadNotPossible' self.action_button.color = color return def _update_display(self): visible_items = self._scrollable_list.visible_items if self._scrollable_list else [] for index, data_source in enumerate(self._data_sources): item = visible_items[index] if index < len(visible_items) else None action_in_progress = item and item == self._current_action_item display_string = self.item_formatter(index, item, action_in_progress) data_source.set_display_string(display_string) if not visible_items and self._data_sources and self.empty_list_message: self._data_sources[0].set_display_string(self.empty_list_message) return def update(self): super(ListComponent, self).update() if self.is_enabled(): self._update_action_feedback() self._update_display()
class NoteEditorSettingsComponent(ModesComponent): initial_encoders = control_list(EncoderControl) encoders = control_list(EncoderControl) def __init__(self, note_settings_component=None, automation_component=None, initial_encoder_layer=None, encoder_layer=None, *a, **k): super(NoteEditorSettingsComponent, self).__init__(*a, **k) assert encoder_layer self.settings = self.register_component(note_settings_component) self.settings.set_enabled(False) self._automation = self.register_component(automation_component) self._automation.set_enabled(False) self._mode_selector = self.register_component(ModeSelector(is_enabled=False)) self._visible_detail_view = 'Detail/DeviceChain' self._show_settings_task = self._tasks.add(task.sequence(task.wait(defaults.MOMENTARY_DELAY), task.run(self._show_settings))).kill() self._update_infos_task = self._tasks.add(task.run(self._update_note_infos)).kill() self._settings_modes = self.register_component(ModesComponent()) self._settings_modes.set_enabled(False) self._settings_modes.add_mode('automation', [ self._automation, self._mode_selector, partial(self._set_envelope_view_visible, True), partial(show_clip_view, self.application)]) self._settings_modes.add_mode('note_settings', [ self.settings, self._update_note_infos, self._mode_selector, partial(self._set_envelope_view_visible, False), partial(show_clip_view, self.application)]) self._settings_modes.selected_mode = 'note_settings' self.__on_selected_setting_mode_changed.subject = self._settings_modes self.add_mode('disabled', []) self.add_mode('about_to_show', [ AddLayerMode(self, initial_encoder_layer), ( self._show_settings_task.restart, self._show_settings_task.kill)]) self.add_mode('enabled', [ DetailViewRestorerMode(self.application), AddLayerMode(self, encoder_layer), self._settings_modes]) self.selected_mode = 'disabled' self._editors = [] self._on_detail_clip_changed.subject = self.song.view self._on_selected_track_changed.subject = self.song.view self.__on_full_velocity_changed.subject = self.settings self.__on_setting_changed.subject = self.settings automation_layer = forward_property('_automation')('layer') mode_selector_layer = forward_property('_mode_selector')('layer') selected_setting = forward_property('_settings_modes')('selected_mode') @property def step_settings(self): return self._settings_modes @property def editors(self): return self._editors @listenable_property def is_touched(self): return any(imap(lambda e: e and e.is_touched, ifilter(lambda e: self._can_notify_is_touched(e), self.encoders))) def _is_step_held(self): return len(self._active_note_regions()) > 0 def add_editor(self, editor): assert editor != None self._editors.append(editor) self._on_active_note_regions_changed.add_subject(editor) self._on_notes_changed.replace_subjects(self._editors) self.__on_modify_all_notes_changed.add_subject(editor) return def set_encoders(self, encoders): self.encoders.set_control_element(encoders) self.settings.set_encoder_controls(encoders) self._automation.set_parameter_controls(encoders) @property def parameter_provider(self): self._automation.parameter_provider @parameter_provider.setter def parameter_provider(self, value): self._automation.parameter_provider = value @listens('selected_mode') def __on_selected_setting_mode_changed(self, mode): if mode == 'automation': self._automation.selected_time = self._active_note_regions() def update_view_state_based_on_selected_setting(self, setting): if self.selected_mode == 'enabled' and self.is_touched or setting is None: self._set_settings_view_enabled(False) else: if self._is_step_held(): if self.selected_setting == 'automation' and self._automation.can_automate_parameters or self.selected_setting == 'note_settings': self._show_settings() return @listens('full_velocity') def __on_full_velocity_changed(self): for editor in self._editors: editor.set_full_velocity() @listens('setting_changed') def __on_setting_changed(self, index, value): for editor in self._editors: self._modify_note_property_offset(editor, index, value) def _modify_note_property_offset(self, editor, index, value): if index == 1: editor.set_nudge_offset(value) else: if index == 2: editor.set_length_offset(value) else: if index == 3: editor.set_velocity_offset(value) def _set_envelope_view_visible(self, visible): clip = self.song.view.detail_clip if liveobj_valid(clip): if visible: clip.view.show_envelope() else: clip.view.hide_envelope() def _set_settings_view_enabled(self, should_show_view): really_show_view = should_show_view and self._automation.can_automate_parameters if self.selected_setting == 'automation' else should_show_view if really_show_view: if self.selected_mode == 'disabled': self.selected_mode = 'about_to_show' else: self._hide_settings() def _active_note_regions(self): all_active_regions = imap(lambda e: e.active_note_regions, self._editors) return list(set(chain.from_iterable(all_active_regions))) @listens_group('active_note_regions') def _on_active_note_regions_changed(self, _): if self.is_enabled(): all_steps = self._active_note_regions() self._automation.selected_time = all_steps self._update_note_infos() self._set_settings_view_enabled(len(all_steps) > 0 and self.selected_setting != None or self.is_touched) return @listens_group('modify_all_notes') def __on_modify_all_notes_changed(self, editor): self.selected_mode = 'about_to_show' if editor.modify_all_notes_enabled else 'disabled' @listens_group('notes_changed') def _on_notes_changed(self, editor): self._update_infos_task.restart() @listens('detail_clip') def _on_detail_clip_changed(self): self._automation.clip = self.song.view.detail_clip if self.is_enabled() else None return @listens('selected_track') def _on_selected_track_changed(self): self.selected_mode = 'disabled' @initial_encoders.touched def initial_encoders(self, encoder): if self.selected_mode == 'about_to_show': self._show_settings() @initial_encoders.value def initial_encoders(self, encoder, value): if self.selected_mode == 'about_to_show': self._show_settings() @encoders.touched def encoders(self, encoder): if self._can_notify_is_touched(encoder): self.notify_is_touched() @encoders.released def encoders(self, encoder): if not self.is_touched and not self._is_step_held() and not self._is_edit_all_notes_active(): self._hide_settings() if self._can_notify_is_touched(encoder): self.notify_is_touched() @encoders.value def encoders(self, encoder, value): self._notify_modification() def _can_notify_is_touched(self, encoder): if self.is_enabled(): return self._settings_modes.selected_mode != 'note_settings' or encoder.index >= self.encoders.control_count - self.settings.number_of_settings return False def _is_edit_all_notes_active(self): return find_if(lambda e: e.modify_all_notes_enabled, self._editors) != None def _notify_modification(self): for editor in self._editors: editor.notify_modification() def _update_note_infos(self): if self.settings.is_enabled(): def min_max((l_min, l_max), (r_min, r_max)): return (min(l_min, r_min), max(l_max, r_max)) all_min_max_attributes = filter(None, imap(lambda e: e.get_min_max_note_values(), self._editors)) min_max_values = [(99999, -99999)] * 4 if len(all_min_max_attributes) > 0 else None for min_max_attribute in all_min_max_attributes: for i, attribute in enumerate(min_max_attribute): min_max_values[i] = min_max(min_max_values[i], attribute) for i in xrange(4): self.settings.set_min_max(i, min_max_values[i] if min_max_values else None) self.settings.set_info_message('Tweak to add note' if not self._is_edit_all_notes_active() and not min_max_values else '') return
class 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)