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 StepDuplicatorComponent(Component, Messenger): button = ButtonControl() def __init__(self, *a, **k): super(StepDuplicatorComponent, self).__init__(*a, **k) self._clip = None self._source_step = None self._notification_reference = partial(nop, None) return @property def is_duplicating(self): return self.button.is_pressed and liveobj_valid(self._clip) def set_clip(self, clip): self._cancel_duplicate() self._clip = clip def add_step_with_pitch(self, note, step_start, step_end, nudge_offset=0, is_page=False): if self.is_enabled() and self.is_duplicating: current_step = (note, step_start, step_end - step_start, nudge_offset, is_page) if self._source_step is not None: self._duplicate_to(current_step) else: self._duplicate_from(current_step) return def add_step(self, step_start, step_end, nudge_offset=0, is_page=False): self.add_step_with_pitch(ALL_NOTES, step_start, step_end, nudge_offset, is_page) def _duplicate_from(self, source_step): message = MessageBoxText.CANNOT_COPY_EMPTY_PAGE if source_step[4] else MessageBoxText.CANNOT_COPY_EMPTY_STEP from_pitch = source_step[0] pitch_span = 1 if from_pitch == ALL_NOTES: from_pitch = 0 pitch_span = 127 notes = self._clip.get_notes(source_step[1], from_pitch, source_step[2], pitch_span) if len(notes) > 0: message = MessageBoxText.COPIED_PAGE if source_step[4] else MessageBoxText.COPIED_STEP self._source_step = source_step self._notification_reference = self.show_notification(message) def _duplicate_to(self, destination_step): if self._source_step[4] == destination_step[4]: message = MessageBoxText.CANNOT_PASTE_TO_SOURCE_PAGE if destination_step[4] else MessageBoxText.CANNOT_PASTE_TO_SOURCE_STEP if destination_step != self._source_step: message = MessageBoxText.PASTED_PAGE if destination_step[4] else MessageBoxText.PASTED_STEP self._clip.duplicate_region(self._source_step[1], self._source_step[2], destination_step[1] + self._source_step[3], self._source_step[0], get_transposition_amount(self._source_step, destination_step)) else: message = MessageBoxText.CANNOT_PASTE_FROM_STEP_TO_PAGE if destination_step[4] else MessageBoxText.CANNOT_PASTE_FROM_PAGE_TO_STEP loop_start = destination_step[1] loop_end = loop_start + self._source_step[2] if destination_step[4] and not (loop_start >= self._clip.loop_start and loop_end <= self._clip.loop_end): set_loop(self._clip, loop_start, loop_end) self._notification_reference = self.show_notification(message) self._source_step = None return def _cancel_duplicate(self): self._source_step = None if self._notification_reference() is not None: self._notification_reference().hide() return @button.released def button(self, _): self._cancel_duplicate() def update(self): super(StepDuplicatorComponent, self).update() self._cancel_duplicate()
class LoopSelectorComponent(Component, Messenger): """ Component that uses a button matrix to display the timeline of a clip. It allows you to select the loop of the clip and a page within it of a given Paginator object. """ next_page_button = ButtonControl() prev_page_button = ButtonControl() delete_button = ButtonControl() select_button = ButtonControl() loop_selector_matrix = control_matrix(PadControl, sensitivity_profile=b'loop', mode=PlayableControl.Mode.listenable) short_loop_selector_matrix = control_matrix(ButtonControl) is_following = listenable_property.managed(False) def __init__(self, clip_creator=None, measure_length=4.0, follow_detail_clip=False, paginator=None, default_size=None, *a, **k): super(LoopSelectorComponent, self).__init__(*a, **k) assert default_size is not None self._clip_creator = clip_creator self._sequencer_clip = None self._paginator = Paginator() self._loop_start = 0 self._loop_end = 0 self._loop_length = 0 self._default_size = default_size self._pressed_pages = [] self._page_colors = [] self._measure_length = measure_length self._last_playhead_page = -1 def set_is_following_true(): self.is_following = True self._follow_task = self._tasks.add( task.sequence(task.wait(defaults.MOMENTARY_DELAY), task.run(set_is_following_true))) self._follow_task.kill() self.set_step_duplicator(None) self._notification_reference = partial(nop, None) self.is_deleting = False if follow_detail_clip: self._on_detail_clip_changed.subject = self.song.view self._on_detail_clip_changed() self._on_session_record_changed.subject = self.song self._on_song_playback_status_changed.subject = self.song if paginator is not None: self.set_paginator(paginator) return def set_paginator(self, paginator): self._paginator = paginator or Paginator() self._on_page_index_changed.subject = paginator self._on_page_length_changed.subject = paginator self._update_page_colors() @listens(b'page_index') def _on_page_index_changed(self): self._update_page_colors() @listens(b'page_length') def _on_page_length_changed(self): self._update_page_colors() self._select_start_page() def set_step_duplicator(self, duplicator): self._step_duplicator = duplicator or NullStepDuplicator() self._step_duplicator.set_clip(self._sequencer_clip) @listens(b'detail_clip') def _on_detail_clip_changed(self): self.set_detail_clip(self.song.view.detail_clip) def set_detail_clip(self, clip): if liveobj_changed(clip, self._sequencer_clip): self.is_following = liveobj_valid(clip) and ( self.is_following or clip_is_new_recording(clip)) self._on_playing_position_changed.subject = clip self._on_playing_status_changed.subject = clip self._on_loop_start_changed.subject = clip self._on_loop_end_changed.subject = clip self._on_is_recording_changed.subject = clip self._sequencer_clip = clip self._step_duplicator.set_clip(clip) self._on_loop_changed() def _select_start_page(self): if liveobj_valid(self._sequencer_clip): page_start = self._paginator.page_index * self._paginator.page_length to_select = page_start if page_start <= self._sequencer_clip.loop_start: to_select = self._sequencer_clip.loop_start elif page_start >= self._sequencer_clip.loop_end: to_select = max( self._sequencer_clip.loop_end - self._paginator.page_length, self._sequencer_clip.loop_start) self._paginator.select_page_in_point(to_select) @listens(b'loop_start') def _on_loop_start_changed(self): self._on_loop_changed() @listens(b'loop_end') def _on_loop_end_changed(self): self._on_loop_changed() def _on_loop_changed(self): if liveobj_valid(self._sequencer_clip): self._loop_start = self._sequencer_clip.loop_start self._loop_end = self._sequencer_clip.loop_end self._loop_length = self._loop_end - self._loop_start else: self._loop_start = 0 self._loop_end = 0 self._loop_length = 0 self._select_start_page() self._update_page_colors() def set_loop_selector_matrix(self, matrix): self.loop_selector_matrix.set_control_element(matrix) self._update_page_colors() def set_short_loop_selector_matrix(self, matrix): self.short_loop_selector_matrix.set_control_element(matrix) self._update_page_colors() def update(self): super(LoopSelectorComponent, self).update() self._update_page_and_playhead_leds() @listens(b'is_recording') def _on_is_recording_changed(self): self.is_following = self.is_following or clip_is_new_recording( self._sequencer_clip) @listens(b'playing_position') def _on_playing_position_changed(self): self._update_page_and_playhead_leds() self._update_page_selection() @listens(b'playing_status') def _on_playing_status_changed(self): self._update_page_and_playhead_leds() @listens(b'session_record') def _on_session_record_changed(self): self._update_page_and_playhead_leds() @listens(b'is_playing') def _on_song_playback_status_changed(self): self._update_page_and_playhead_leds() def _has_running_clip(self): return liveobj_valid( self._sequencer_clip) and (self._sequencer_clip.is_playing or self._sequencer_clip.is_recording) def _update_page_selection(self): if self.is_enabled() and self.is_following and self._has_running_clip( ): position = self._sequencer_clip.playing_position self._paginator.select_page_in_point(position) def _update_page_and_playhead_leds(self): @contextmanager def save_page_color(page_colors, page): old_page_value = page_colors[page] yield page_colors[page] = old_page_value @contextmanager def replace_and_restore_tail_colors(page_colors, page): if clip_is_new_recording(self._sequencer_clip): old_tail_values = page_colors[page + 1:] page_colors[( page + 1):] = [b'LoopSelector.OutsideLoop'] * len(old_tail_values) yield if clip_is_new_recording(self._sequencer_clip): page_colors[(page + 1):] = old_tail_values if self.is_enabled() and self._has_running_clip(): position = self._sequencer_clip.playing_position visible_page = int( position / self._page_length_in_beats) - self.page_offset page_colors = self._page_colors if 0 <= visible_page < len(page_colors): with save_page_color(page_colors, visible_page): if self.song.is_playing: page_colors[ visible_page] = b'LoopSelector.PlayheadRecord' if self.song.session_record else b'LoopSelector.Playhead' with replace_and_restore_tail_colors( page_colors, visible_page): self._update_page_leds() else: self._update_page_leds() self._last_playhead_page = visible_page else: self._update_page_leds() def _get_size(self): return max(self.loop_selector_matrix.control_count, self.short_loop_selector_matrix.control_count, self._default_size) def _get_loop_in_pages(self): page_length = self._page_length_in_beats loop_start = int(self._loop_start / page_length) loop_end = int(self._loop_end / page_length) loop_length = loop_end - loop_start + int( self._loop_end % page_length != 0) return (loop_start, loop_length) def _selected_pages_range(self): size = self._get_size() page_length = self._page_length_in_beats seq_page_length = max(self._paginator.page_length / page_length, 1) seq_page_start = int(self._paginator.page_index * self._paginator.page_length / page_length) seq_page_end = int( min(seq_page_start + seq_page_length, self.page_offset + size)) return (seq_page_start, seq_page_end) def _update_page_colors(self): """ Update the offline array mapping the timeline of the clip to buttons. """ page_length = self._page_length_in_beats size = self._get_size() def calculate_page_colors(): l_start, l_length = self._get_loop_in_pages() page_offset = self.page_offset pages_per_measure = int(self._one_measure_in_beats / page_length) def color_for_page(absolute_page): if l_start <= absolute_page < l_start + l_length: if absolute_page % pages_per_measure == 0: return b'LoopSelector.InsideLoopStartBar' return b'LoopSelector.InsideLoop' return b'LoopSelector.OutsideLoop' return map(color_for_page, xrange(page_offset, page_offset + size)) def mark_selected_pages(page_colors): for page_index in xrange(*self._selected_pages_range()): button_index = page_index - self.page_offset if page_colors[button_index].startswith( b'LoopSelector.InsideLoop'): page_colors[button_index] = b'LoopSelector.SelectedPage' page_colors = calculate_page_colors() mark_selected_pages(page_colors) self._page_colors = page_colors self._update_page_and_playhead_leds() def _update_page_leds(self): self._update_page_leds_in_matrix(self.loop_selector_matrix) self._update_page_leds_in_matrix(self.short_loop_selector_matrix) def _update_page_leds_in_matrix(self, matrix): """ update hardware leds to match precomputed map """ if self.is_enabled() and matrix: for button, color in izip(matrix, self._page_colors): button.color = color def _jump_to_page(self, next_page): start, length = self._get_loop_in_pages() if next_page >= start + length: next_page = start elif next_page < start: next_page = start + length - 1 self._paginator.select_page_in_point(next_page * self._page_length_in_beats) @next_page_button.pressed def next_page_button(self, button): if self.is_following: self.is_following = False else: _, end = self._selected_pages_range() self._jump_to_page(end) self._follow_task.restart() @next_page_button.released def next_page_button(self, button): self._follow_task.kill() @prev_page_button.pressed def prev_page_button(self, button): if self.is_following: self.is_following = False else: begin, end = self._selected_pages_range() self._jump_to_page(begin - (end - begin)) self._follow_task.restart() @prev_page_button.released def prev_page_button(self, button): self._follow_task.kill() @short_loop_selector_matrix.pressed def short_loop_selector_matrix(self, button): if self.is_enabled(): page = self._get_corresponding_page( button, self.short_loop_selector_matrix) self._pressed_pages = [page] self._try_set_loop() self._pressed_pages = [] @loop_selector_matrix.pressed def loop_selector_matrix(self, button): if self.is_enabled(): page = self._get_corresponding_page(button, self.loop_selector_matrix) if page not in self._pressed_pages: self._on_press_loop_selector_matrix(page) @loop_selector_matrix.released def loop_selector_matrix(self, button): page = self._get_corresponding_page(button, self.loop_selector_matrix) if page in self._pressed_pages: self._pressed_pages.remove(page) def _get_corresponding_page(self, button, matrix): y, x = button.coordinate return x + y * matrix.width def _quantize_page_index(self, page_index, quant): page_length = self._page_length_in_beats return quant * float(int(page_length * page_index / quant)) def _clear_page(self, page): page_start, page_end = self._selected_pages_time_range(page) notes = self._sequencer_clip.get_notes(page_start, 0, page_end, 128) if len(notes) > 0: self._sequencer_clip.remove_notes(page_start, 0, page_end - page_start, 128) self._notification_reference = self.show_notification( MessageBoxText.PAGE_CLEARED) else: self._notification_reference = self.show_notification( MessageBoxText.CANNOT_CLEAR_EMPTY_PAGE) def _selected_pages_time_range(self, page): page_start = 0 page_end = 0 page_length = self._page_length_in_beats if self._loop_length > page_length: range_start, range_end = self._selected_pages_range() page_start = range_start * page_length page_end = range_end * page_length else: page_start = page * page_length page_end = page_start + page_length return (page_start, page_end) def _add_page_to_duplicator(self, page): page_start, page_end = self._selected_pages_time_range(page) self._step_duplicator.add_step(page_start, page_end, nudge_offset=0, is_page=True) def _on_press_loop_selector_matrix(self, page): def create_clip(pages): measure = self._one_measure_in_beats length = self._quantize_page_index(pages, measure) + measure create_clip_in_selected_slot(self._clip_creator, self.song, length) def handle_page_press_on_clip(page): l_start, l_length = self._get_loop_in_pages() page_in_loop = l_start <= page < l_start + l_length buttons_pressed = len(self._pressed_pages) if buttons_pressed == 1 and page_in_loop: self._try_select_page(page) elif buttons_pressed > 1 or not page_in_loop: self._try_set_loop() if self._step_duplicator.is_duplicating: self._add_page_to_duplicator(page) if self.delete_button.is_pressed: self._clear_page(page) self._pressed_pages.append(page) absolute_page = page + self.page_offset if not self.select_button.is_pressed: if not liveobj_valid( self._sequencer_clip ) and not self.song.view.highlighted_clip_slot.has_clip: create_clip(absolute_page) elif liveobj_valid(self._sequencer_clip): handle_page_press_on_clip(absolute_page) elif not self.is_following: self._try_select_page(absolute_page) def _try_select_page(self, page): step_time = page * self._page_length_in_beats if self._paginator.select_page_in_point(step_time): self.is_following = False return True return False def _try_set_loop(self): did_set_loop = False if liveobj_valid(self._sequencer_clip): if not clip_is_new_recording(self._sequencer_clip): lowest_page = min(self._pressed_pages) + self.page_offset if self._try_select_page(lowest_page): self._set_loop_in_live() did_set_loop = True if did_set_loop: self.is_following = True return did_set_loop def _set_loop_in_live(self): quant = self._page_length_in_beats start_page = min(self._pressed_pages) + self.page_offset end_page = max(self._pressed_pages) + self.page_offset loop_start = self._quantize_page_index(start_page, quant) loop_end = self._quantize_page_index(end_page, quant) + quant set_loop(self._sequencer_clip, loop_start, loop_end) self._sequencer_clip.view.show_loop() @property def _page_length_in_beats(self): return clamp(self._paginator.page_length, 0.25, self._one_measure_in_beats) @property def _one_measure_in_beats(self): return self._measure_length * self.song.signature_numerator / self.song.signature_denominator @property def page_offset(self): def zero_if_none(n): if n is None: return 0 else: return n width = zero_if_none(self.loop_selector_matrix.width) height = zero_if_none(self.loop_selector_matrix.height) size = max(width * height, 1) page_index = self._paginator.page_index page_length = self._paginator.page_length selected_page_index = int(page_index * page_length / self._page_length_in_beats) return size * int(selected_page_index / size)
class SessionRecordingComponent(SessionRecordingComponentBase): record_stop_button = ButtonControl() @record_stop_button.pressed def record_stop_button(self, _): self.song.session_record = False
class TrackMixerControlComponent(Component): __events__ = (u'parameters', u'scroll_offset', u'items') BUTTON_SKIN = dict(color='TrackControlView.ButtonOff', pressed_color='TrackControlView.ButtonOn', disabled_color='TrackControlView.ButtonDisabled') controls = control_list(MappedSensitivitySettingControl) scroll_right_button = ButtonControl(**BUTTON_SKIN) scroll_left_button = ButtonControl(**BUTTON_SKIN) @depends(tracks_provider=None, real_time_mapper=None, register_real_time_data=None) def __init__(self, real_time_mapper=None, register_real_time_data=None, tracks_provider=None, *a, **k): assert liveobj_valid(real_time_mapper) assert tracks_provider is not None super(TrackMixerControlComponent, self).__init__(*a, **k) self._tracks_provider = tracks_provider self._on_return_tracks_changed.subject = self.song self.real_time_meter_channel = RealTimeDataComponent( parent=self, real_time_mapper=real_time_mapper, register_real_time_data=register_real_time_data, channel_type='meter') self._scroll_offset = 0 self._items = [] self._number_return_tracks = self._number_sends() self._update_scroll_buttons() self.__on_selected_item_changed.subject = self._tracks_provider self.__on_selected_item_changed() return def set_controls(self, controls): self.controls.set_control_element(controls) self._update_controls() @listens('panning_mode') def __on_pan_mode_changed(self): self._update_controls() self._update_scroll_offset() @listens('selected_item') def __on_selected_item_changed(self): self._update_scroll_offset() self._update_real_time_channel_id() mixer = self._tracks_provider.selected_item.mixer_device self.__on_pan_mode_changed.subject = mixer if has_pan_mode( mixer) else None self.__on_pan_mode_changed() return def update(self): super(TrackMixerControlComponent, self).update() if self.is_enabled(): self._update_controls() self._update_scroll_buttons() self._update_real_time_channel_id() def _update_real_time_channel_id(self): self.real_time_meter_channel.set_data( self._tracks_provider.selected_item.mixer_device) def _update_controls(self): if self.is_enabled(): assign_parameters(self.controls, self.parameters[self.scroll_offset:]) self.notify_parameters() @property def parameters(self): return self._get_track_mixer_parameters() @property def scroll_offset(self): return self._scroll_offset @listens('return_tracks') def _on_return_tracks_changed(self): self._update_controls() self._update_scroll_offset() def _number_sends(self): mixable = self._tracks_provider.selected_item if mixable != self.song.master_track: return len(mixable.mixer_device.sends) return 0 def _max_return_tracks(self): mixer = self._tracks_provider.selected_item.mixer_device if is_set_to_split_stereo(mixer): return 5 return 6 def _update_scroll_offset(self): new_number_return_tracks = self._number_sends() max_return_tracks = self._max_return_tracks() if max_return_tracks <= new_number_return_tracks < self._number_return_tracks and max_return_tracks + self._scroll_offset > new_number_return_tracks: delta = min(new_number_return_tracks - self._number_return_tracks, 0) self._scroll_controls(delta) elif new_number_return_tracks < max_return_tracks or self._tracks_provider.selected_item == self.song.master_track: self._scroll_offset = 0 self._update_controls() self._update_scroll_buttons() self._number_return_tracks = new_number_return_tracks def _get_track_mixer_parameters(self): mixer_params = [] if self._tracks_provider.selected_item: mixer = self._tracks_provider.selected_item.mixer_device mixer_params = [mixer.volume] if is_set_to_split_stereo(mixer): mixer_params += [ mixer.left_split_stereo, mixer.right_split_stereo ] else: mixer_params += [mixer.panning] mixer_params += list(mixer.sends) return mixer_params @scroll_right_button.pressed def scroll_right_button(self, button): self._scroll_controls(1) @scroll_left_button.pressed def scroll_left_button(self, button): self._scroll_controls(-1) def _update_scroll_buttons(self): if self.is_enabled(): num_return_tracks = self._number_sends() self.scroll_right_button.enabled = num_return_tracks > self._max_return_tracks( ) + self._scroll_offset self.scroll_left_button.enabled = self._scroll_offset > 0 self._update_view_slots() @property def items(self): return self._items def _update_view_slots(self): self._items = [IconItemSlot() for _ in xrange(6)] self._items.append( IconItemSlot(icon='page_left.svg' if self.scroll_left_button. enabled else '')) self._items.append( IconItemSlot(icon='page_right.svg' if self.scroll_right_button. enabled else '')) self.notify_items() def _scroll_controls(self, delta): num_return_tracks = self._number_sends() self._scroll_offset = clamp( self._scroll_offset + delta, 0, num_return_tracks if num_return_tracks > self._max_return_tracks() else 0) self.notify_scroll_offset() self._update_controls() self._update_scroll_buttons()
class NoteSettingsComponentBase(Component): __events__ = (u'setting_changed', u'full_velocity') full_velocity_button = ButtonControl() def __init__(self, grid_resolution=None, *a, **k): super(NoteSettingsComponentBase, self).__init__(*a, **k) self._settings = [] self._encoders = [] self._create_settings(grid_resolution) def _create_settings(self, grid_resolution): self._add_setting(NoteNudgeSetting(grid_resolution=grid_resolution)) self._add_setting( NoteLengthCoarseSetting(grid_resolution=grid_resolution)) self._add_setting( NoteLengthFineSetting(grid_resolution=grid_resolution)) self._add_setting(NoteVelocitySetting(grid_resolution=grid_resolution)) def _add_setting(self, setting): assert len(self._settings) < 8, 'Cannot show more than 8 settings' self._settings.append(setting) self._update_encoders() self.register_disconnectable(setting) self.register_slot(setting, self.notify_setting_changed, 'setting_changed') @property def number_of_settings(self): return len(self._settings) def set_info_message(self, message): pass def set_encoder_controls(self, encoders): self._encoders = encoders or [] self._update_encoders() def set_min_max(self, index, min_max_value): setting_for_index = [ i for i in self._settings if i.attribute_index == index ] for setting in setting_for_index: setting.set_min_max(min_max_value) @full_velocity_button.pressed def full_velocity_button(self, button): if self.is_enabled(): self.notify_full_velocity() def _update_encoders(self): if self.is_enabled() and self._encoders: for encoder, setting in izip_longest( self._encoders[-len(self._settings):], self._settings): setting.encoder.set_control_element(encoder) else: map(lambda setting: setting.encoder.set_control_element(None), self._settings) def update(self): super(NoteSettingsComponentBase, self).update() self._update_encoders()
class ActionsComponent(Component): actions_display = TextDisplayControl(segments=ACTION_NAMES) actions_color_fields = control_list(ColorSysexControl, len(ACTION_NAMES)) actions_selection_fields = control_list(BinaryControl, len(ACTION_NAMES)) undo_button = ButtonControl(color='Action.Available') redo_button = ButtonControl(color='Action.Available') capture_midi_button = ButtonControl() metronome_button = ToggleButtonControl( toggled_color='Transport.MetronomeOn', untoggled_color='Transport.MetronomeOff') def __init__(self, *a, **k): super(ActionsComponent, self).__init__(*a, **k) self.__on_can_capture_midi_changed.subject = self.song self.__on_can_capture_midi_changed() self.actions_color_fields[ METRONOME_DISPLAY_INDEX].color = 'Transport.MetronomeOn' self.actions_color_fields[ UNDO_DISPLAY_INDEX].color = 'Action.Available' self.actions_color_fields[ REDO_DISPLAY_INDEX].color = 'Action.Available' self.__on_metronome_changed.subject = self.song self.__on_metronome_changed() @property def capture_midi_display(self): return self.actions_display[CAPTURE_DISPLAY_INDEX] @capture_midi_display.setter def capture_midi_display(self, string): self.actions_display[CAPTURE_DISPLAY_INDEX] = string @property def capture_midi_color_field(self): return self.actions_color_fields[CAPTURE_DISPLAY_INDEX] @property def capture_midi_selection_field(self): return self.actions_selection_fields[CAPTURE_DISPLAY_INDEX] @undo_button.pressed def undo_button(self, _): if self.song.can_undo: self.song.undo() @redo_button.pressed def redo_button(self, _): if self.song.can_redo: self.song.redo() @capture_midi_button.pressed def capture_midi_button(self, _): try: self.song.capture_midi() except RuntimeError: pass @metronome_button.toggled def metronome_button(self, toggled, _): self.song.metronome = toggled @listens('can_capture_midi') def __on_can_capture_midi_changed(self): self._update_capture_midi_controls() @listens('metronome') def __on_metronome_changed(self): self._update_metronome_controls() def _update_capture_midi_controls(self): can_capture_midi = self.song.can_capture_midi self.capture_midi_button.enabled = can_capture_midi self.capture_midi_display = 'capture' if can_capture_midi else '' self.capture_midi_color_field.color = 'DefaultButton.On' if can_capture_midi else 'DefaultButton.Disabled' self.capture_midi_selection_field.is_on = can_capture_midi def _update_metronome_controls(self): metronome = self.song.metronome self.metronome_button.is_toggled = metronome self.actions_selection_fields[ METRONOME_DISPLAY_INDEX].is_on = metronome
class ChannelStripComponent(ChannelStripComponentBase, Messenger): empty_color = 'DefaultButton.Disabled' monitoring_state_button = ButtonControl() pan_encoder_color_field = ColorSysexControl() track_color_field = ColorSysexControl() volume_led = ColorSysexControl() track_selection_field = BinaryControl() def __init__(self, *a, **k): (super(ChannelStripComponent, self).__init__)(*a, **k) self._pan_value_display_data_source = DisplayDataSource() self._ChannelStripComponent__on_selected_track_changed.subject = self.song.view @property def pan_value_display_data_source(self): return self._pan_value_display_data_source def set_track(self, track): super(ChannelStripComponent, self).set_track(track) self._update_pan_encoder_color_field() self._update_track_selection_field() self._update_listeners() def set_volume_control(self, control): super(ChannelStripComponent, self).set_volume_control(control) self._ChannelStripComponent__on_volume_control_value_received.subject = control def set_pan_control(self, control): if control != None: control.mapping_sensitivity = CONTINUOUS_MAPPING_SENSITIVITY super(ChannelStripComponent, self).set_pan_control(control) def set_send_controls(self, controls): for control in controls or []: if control != None: control.mapping_sensitivity = CONTINUOUS_MAPPING_SENSITIVITY super(ChannelStripComponent, self).set_send_controls(controls) @monitoring_state_button.pressed def monitoring_state_button(self, _): if has_monitoring_state(self.track): self.track.current_monitoring_state = ( self.track.current_monitoring_state + 1) % len( monitoring_states.values) self._message_monitoring_state() def _message_monitoring_state(self): track = self.track self.message( track.name, 'Monitor {}'.format( MONITORING_STATES_TO_STR[track.current_monitoring_state])) def _update_monitoring_state_button(self): color = self.empty_color if liveobj_valid(self.track): if has_monitoring_state(self.track): color = 'Monitor.{}'.format(MONITORING_STATES_TO_STR[ self.track.current_monitoring_state]) else: color = 'Monitor.Disabled' self.monitoring_state_button.color = color def _update_volume_led(self): track = self.track value = (0, 0, 0) if liveobj_valid(track): value = tuple([ clamp( int( old_round( old_div( channel * normalized_parameter_value( track.mixer_device.volume), 2))), 0, 127) for channel in hex_to_channels(track.color) ]) self.volume_led.color = SysexRGBColor(value) def _message_volume_value(self): track = self.track if liveobj_valid(track): self.message(track.name, str(track.mixer_device.volume)) def _update_pan_encoder_color_field(self): self.pan_encoder_color_field.color = 'Mixer.Pan' if liveobj_valid( self.track) else 'DefaultButton.Disabled' def _update_pan_value_display(self): track = self.track self.pan_value_display_data_source.set_display_string( str(track.mixer_device.panning) if liveobj_valid(track) else '') def _update_track_color_field(self): self.track_color_field.color = color_for_track(self.track) def _update_select_button(self): track = self.track color = 'DefaultButton.Disabled' if liveobj_valid(track): if track == self.song.view.selected_track: color = color_for_track(track) else: color = 'Mixer.TrackSelect' self.select_button.color = color def _update_track_selection_field(self): self.track_selection_field.is_on = self.track == self.song.view.selected_track @listens('current_monitoring_state') def __on_track_monitoring_state_changed(self): self._update_monitoring_state_button() @listens('value') def __on_volume_changed(self): self._update_volume_led() @listens('value') def __on_pan_changed(self): self._update_pan_value_display() @listens('color') def __on_track_color_changed(self): self._update_track_color_field() self._update_select_button() self._update_volume_led() @listens('selected_track') def __on_selected_track_changed(self): self._update_select_button() self._update_track_selection_field() @listens('value') def __on_volume_control_value_received(self, _): self._message_volume_value() def _update_listeners(self): track = self.track self._ChannelStripComponent__on_track_monitoring_state_changed.subject = track self._ChannelStripComponent__on_track_monitoring_state_changed() self._ChannelStripComponent__on_volume_changed.subject = track.mixer_device.volume if liveobj_valid( track) else None self._ChannelStripComponent__on_volume_changed() self._ChannelStripComponent__on_pan_changed.subject = track.mixer_device.panning if liveobj_valid( track) else None self._ChannelStripComponent__on_pan_changed() self._ChannelStripComponent__on_track_color_changed.subject = track if liveobj_valid( track) else None self._ChannelStripComponent__on_track_color_changed()
class DeviceSelectorComponent(Component): assign_button = ButtonControl(color = 'DeviceSelector.AssignOff', pressed_color = 'DeviceSelector.AssignOn') def __init__(self, script, prefix = '@d', *a, **k): super(DeviceSelectorComponent, self).__init__(*a, **k) self.log_message = script.log_message self._script = script self._prefix = prefix self._offset = 0 self._buttons = [] self._device_registry = [] self._watched_device = None self._device_colors = DEVICE_COLORS self._selected_colorshift = SELECTED_COLORSHIFT self._device_listener.subject = self.song self._device_listener() self._off_value = 0 def disconnect(self, *a, **k): super(DeviceSelectorComponent, self).disconnect() def set_offset(self, offset): self._offset = offset self.update() def set_matrix(self, matrix): buttons = [] if not matrix is None: for button, address in matrix.iterbuttons(): #self._script.log_message('button is: ' + str(button)) if not button is None: button.use_default_message() if hasattr(button, 'set_enabled'): button.set_enabled(True) elif hasattr(button, 'suppress_script_forwarding'): button.suppress_script_forwarding = False buttons.append(button) self.set_buttons(buttons) def set_buttons(self, buttons): for button in self._buttons: button and button.reset() self._buttons = buttons or [] self._on_button_value.replace_subjects(self._buttons) self.update() def set_assign_button(self, button): debug('set assign button:', button) self.assign_button.set_control_element(button) @listens_group('value') def _on_button_value(self, value, sender): if self.is_enabled(): if value: if self.assign_button.is_pressed: self.assign_device(self._buttons.index(sender)) else: self.select_device(self._buttons.index(sender)) def assign_device(self, index): device = self.song.appointed_device if not device is None and hasattr(device, 'name'): prefix = str(self._prefix)+'_' offset = self._offset key = prefix + str(index + 1 + offset) name = device.name.split(' ') if key in name: name.remove(key) else: old_entry = self._device_registry[index] if old_entry and hasattr(old_entry, 'name'): old_name = old_entry.name.split(' ') if key in old_name: old_name.remove(key) old_entry.name = ' '.join(old_name) for sub in name: sub.startswith(prefix) and name.remove(sub) name.insert(0, key) device.name = ' '.join(name) self.scan_all() self.update() def select_device(self, index): if self.is_enabled(): preset = None if index < len(self._device_registry): preset = self._device_registry[index] if not preset is None and isinstance(preset, Live.Device.Device): self.song.view.select_device(preset) self._script._device_provider.device = preset #self._script.set_appointed_device(preset) try: self._script.monomodular.is_mod(preset) and self._script.modhandler.select_mod(self._script.monomodular.is_mod(preset)) except: pass self.update() def scan_all(self): #debug('scan all--------------------------------') self._device_registry = [None for index in range(len(self._buttons))] prefix = str(self._prefix)+':' prefix2 = str(self._prefix)+'_' offset = self._offset preset = None tracks = self.song.tracks + self.song.return_tracks + tuple([self.song.master_track]) for track in tracks: for device in enumerate_track_device(track): for index, entry in enumerate(self._device_registry): key = str(prefix + str(index + 1 + offset)) key2 = str(prefix2 + str(index + 1 + offset)) if device.name.startswith(key+' ') or device.name == key: self._device_registry[index] = device elif device.name.startswith(key2+' ') or device.name == key2: self._device_registry[index] = device elif (device.name.startswith('*' +key+' ') or device.name == ('*' +key)) and device.can_have_chains and len(device.chains) and len(device.chains[0].devices): self._device_registry[index] = device.chains[0].devices[0] self.update() #debug('device registry: ' + str(self._device_registry)) @listens('appointed_device') def _device_listener(self, *a, **k): #debug('device_listener') self._on_name_changed.subject = self.song.appointed_device self._watched_device = self.song.appointed_device if self.is_enabled(): self.update() @listens('name') def _on_name_changed(self): #debug('on name changed') if self._watched_device == self.song.appointed_device: self.scan_all() def on_enabled_changed(self): if self.is_enabled(): self.update() def update(self): if self.is_enabled(): if len(self._device_registry) != len(self._buttons): self.scan_all() name = 'None' dev = self.song.appointed_device offset = self._offset if self._buttons: for index in range(len(self._buttons)): preset = self._device_registry[index] button = self._buttons[index] if isinstance(button, ButtonElement): if isinstance(preset, Live.Device.Device) and hasattr(preset, 'name'): name = preset.name dev_type = preset.type dev_class = preset.class_name val = (dev_class in self._device_colors and self._device_colors[dev_class]) or (dev_type in self._device_colors and self._device_colors[dev_type]) or 7 selected_shift = (dev == preset)*self._selected_colorshift button.send_value(val + selected_shift) else: button.send_value(self._off_value)
class ScalesComponent(Component): __events__ = ('close', ) root_note_buttons = control_list(RadioButtonControl, control_count=len(ROOT_NOTES), checked_color='Scales.OptionOn', unchecked_color='Scales.OptionOff') in_key_toggle_button = ToggleButtonControl( toggled_color='Scales.OptionOn', untoggled_color='Scales.OptionOn') fixed_toggle_button = ToggleButtonControl( toggled_color='Scales.OptionOn', untoggled_color='Scales.OptionOff') scale_encoders = control_list(StepEncoderControl) close_button = ButtonControl(color='Scales.Close') def __init__(self, note_layout=None, *a, **k): raise note_layout is not None or AssertionError super(ScalesComponent, self).__init__(*a, **k) self._note_layout = note_layout self._scale_list = list(SCALES) self._scale_name_list = map(lambda m: m.name, self._scale_list) self._selected_scale_index = -1 self._selected_root_note_index = -1 self.in_key_toggle_button.connect_property(note_layout, 'is_in_key') self.fixed_toggle_button.connect_property(note_layout, 'is_fixed') self.__on_root_note_changed.subject = self._note_layout self.__on_scale_changed.subject = self._note_layout self.__on_root_note_changed(note_layout.root_note) self.__on_scale_changed(note_layout.scale) @root_note_buttons.pressed def root_note_buttons(self, button): self._note_layout.root_note = ROOT_NOTES[button.index] @listens('root_note') def __on_root_note_changed(self, root_note): self._selected_root_note_index = list(ROOT_NOTES).index(root_note) self.root_note_buttons.checked_index = self._selected_root_note_index self.notify_selected_root_note_index() @property def root_note_names(self): return [NOTE_NAMES[note] for note in ROOT_NOTES] @listenable_property def selected_root_note_index(self): return self._selected_root_note_index @scale_encoders.value def scale_encoders(self, value, encoder): index = clamp(self._selected_scale_index + value, 0, len(self._scale_list) - 1) self._note_layout.scale = self._scale_list[index] @property def scale_names(self): return self._scale_name_list @listenable_property def selected_scale_index(self): return self._selected_scale_index @listens('scale') def __on_scale_changed(self, scale): index = self._scale_list.index( scale) if scale in self._scale_list else -1 if index != self._selected_scale_index: self._selected_scale_index = index self.notify_selected_scale_index() @close_button.pressed def close_button(self, button): self.notify_close() @property def note_layout(self): return self._note_layout
class MixerComponent(MixerComponentBase): num_sends_control = SendValueControl() master_button = ButtonControl() def __init__(self, *a, **k): super(MixerComponent, self).__init__(*a, **k) self._last_selected_track = None self._last_track_offset = None self.__on_offsets_changed.subject = self._provider self.__on_offsets_changed(self._provider.track_offset, self._provider.scene_offset) return def __getattr__(self, name): if name.startswith(b'set_') and name.endswith(b's'): return partial(self._set_channel_strip_controls, name[4:-1]) raise AttributeError def on_num_sends_changed(self): self.num_sends_control.value = clamp(self.num_sends, 0, MAX_NUM_SENDS) @property def max_track_offset(self): return max( 0, len(self._provider.tracks_to_use()) - self._provider.num_tracks) def _on_selected_track_changed(self): selected_track = self.song.view.selected_track button_color = b'DefaultButton.On' if selected_track != self.song.master_track: self._last_selected_track = selected_track button_color = b'DefaultButton.Off' self.master_button.color = button_color @listens(b'offset') def __on_offsets_changed(self, track_offset, _): max_track_offset = self.max_track_offset if max_track_offset == 0 or track_offset < max_track_offset: self._last_track_offset = track_offset def set_send_controls(self, controls): self._send_controls = controls for strip, row in izip_longest(self._channel_strips, controls.rows() if controls else []): strip.set_send_controls(row) def set_send_value_displays(self, displays): for strip, row in izip_longest(self._channel_strips, displays.rows() if displays else []): strip.set_send_value_displays(row) def set_selected_track_mute_button(self, button): self._selected_strip.mpc_mute_button.set_control_element(button) set_selected_track_arm_button = forward_property(b'_selected_strip')( b'set_arm_button') set_selected_track_solo_button = forward_property(b'_selected_strip')( b'set_solo_button') def set_track_type_controls(self, controls): for strip, control in izip_longest(self._channel_strips, controls or []): strip.track_type_control.set_control_element(control) def _set_channel_strip_controls(self, name, controls): for strip, control in izip_longest(self._channel_strips, controls or []): set_method = getattr(strip, (b'set_{}').format(name), None) if not set_method: set_method = getattr(strip, name, None).set_control_element set_method(control) return def set_solo_mute_buttons(self, buttons): for strip, button in izip_longest(self._channel_strips, buttons or []): strip.solo_mute_button.set_control_element(button) @master_button.pressed def master_button_value(self, _button): master_track = self.song.master_track if self.song.view.selected_track != master_track: self.song.view.selected_track = master_track else: self.song.view.selected_track = self._last_selected_track if liveobj_valid( self._last_selected_track) else self.song.tracks[0] if self._provider.track_offset < self.max_track_offset: self._provider.track_offset = self.max_track_offset else: self._provider.track_offset = self._last_track_offset
class 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='Browser.Option', untoggled_color='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('') @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('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, '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() return @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 return @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 return @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(imap(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 else: 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 return return @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['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 'custom_button' def disconnect(self): super(BrowserComponent, self).disconnect() self._lists = [] self._commit_model_changes = None return @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('selected_track.color_index') def _on_selected_track_color_index_changed(self): if self.is_enabled(): self._update_context() self._update_navigation_buttons() @listens('selected_track.name') def _on_selected_track_name_changed(self): if self.is_enabled(): self._update_context() @listens('detail_clip.name') def _on_detail_clip_name_changed(self): if self.is_enabled(): self._update_context() @listens('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('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 else: has_midi_support = self.song.view.selected_track.has_midi_input return not has_midi_support and '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 else: return False @listens('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, 'contained_item', None) if contained_item is not None: return contained_item else: 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('load_next') def _on_load_next(self): self.focused_list.selected_index += 1 self._load_selected_item() @listens('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) * 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 '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 '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(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(imap(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 return 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, '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, '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 xrange(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 return 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() return 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='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 NoteEditorComponent(Component): __events__ = ('page_length', 'active_note_regions', 'active_steps', 'notes_changed', 'modify_all_notes') matrix = control_matrix(PadControl, channel=PLAYHEAD_FEEDBACK_CHANNELS[0], sensitivity_profile=b'loop', mode=PlayableControl.Mode.listenable) mute_button = ButtonControl(color=b'DefaultButton.Transparent') def __init__(self, clip_creator=None, grid_resolution=None, skin_base_key=b'NoteEditor', velocity_range_thresholds=None, velocity_provider=None, get_notes_handler=get_single_note, remove_notes_handler=remove_single_note, duplicate_all_notes=False, *a, **k): assert skin_base_key is not None super(NoteEditorComponent, self).__init__(*a, **k) self._duplicate_all_notes = duplicate_all_notes self._get_notes_handler = get_notes_handler self._remove_notes_handler = remove_notes_handler self._skin_base_key = skin_base_key self.full_velocity = False self._provided_velocity = None self._selected_page_point = 0 self._page_index = 0 self._clip_creator = clip_creator self._sequencer_clip = None self._step_colors = [] self._pressed_steps = [] self._modified_steps = [] self._pressed_step_callback = None self._modify_task = self._tasks.add(task.run(self._do_modification)) self._modify_task.kill() self._modify_all_notes_enabled = False self._step_tap_tasks = {} self._clip_notes = [] self._pitches = [DEFAULT_START_NOTE] self._grid_resolution = grid_resolution self.__on_resolution_changed.subject = self._grid_resolution self.set_step_duplicator(None) self._nudge_offset = 0 self._length_offset = 0 self._velocity_offset = 0 self._triplet_factor = 1.0 self._update_from_grid() self.background_color = self._skin_base_key + b'.StepEmpty' self._velocity_range_thresholds = velocity_range_thresholds or DEFAULT_VELOCITY_RANGE_THRESHOLDS self._velocity_provider = velocity_provider or NullVelocityProvider() self.__on_provided_velocity_changed.subject = self._velocity_provider return @property def page_index(self): return self._page_index @property def page_length(self): return self._get_step_count() * self._get_step_length( ) * self._triplet_factor @property def can_change_page(self): return not self._pressed_steps and not self._modified_steps @property def notes_in_selected_step(self): time_steps = [] if self._modify_all_notes_enabled: time_steps = [TimeStep(0.0, ONE_YEAR_AT_120BPM_IN_BEATS)] else: time_steps = [ self._time_step(self.get_step_start_time(start_time)) for start_time in chain(self._pressed_steps, self._modified_steps) ] time_steps_with_notes = [ time_step.filter_notes(self._clip_notes) for time_step in time_steps ] notes_in_step = [] for time_step in time_steps_with_notes: for note in time_step: notes_in_step.append(note_pitch(note)) return notes_in_step def set_selected_page_point(self, point): assert self.can_change_page self._selected_page_point = point index = int(point / self.page_length) if self.page_length != 0 else 0 if index != self._page_index: self._page_index = index self._on_clip_notes_changed() def _get_modify_all_notes_enabled(self): return self._modify_all_notes_enabled def _set_modify_all_notes_enabled(self, enabled): if enabled != self._modify_all_notes_enabled: self._modify_all_notes_enabled = enabled self._on_clip_notes_changed() self.notify_modify_all_notes() modify_all_notes_enabled = property(_get_modify_all_notes_enabled, _set_modify_all_notes_enabled) def set_detail_clip(self, clip): if liveobj_changed(clip, self._sequencer_clip): self._sequencer_clip = clip self._step_duplicator.set_clip(clip) if self.can_change_page: self.set_selected_page_point(0) self._on_clip_notes_changed.subject = clip self._on_clip_notes_changed() def _can_edit(self): return len(self._pitches) != 0 def _get_editing_notes(self): return self._pitches def _set_editing_notes(self, pitches): self._pitches = pitches enabled = self._can_edit() for button in self.matrix: button.enabled = enabled if enabled: self._on_clip_notes_changed() editing_notes = property(_get_editing_notes, _set_editing_notes) def _get_width(self): if self.matrix.width: return self.matrix.width return 4 def _get_height(self): if self.matrix.height: return self.matrix.height return 4 def set_matrix(self, matrix): last_page_length = self.page_length self.matrix.set_control_element(matrix) for t in self._step_tap_tasks.itervalues(): t.kill() def trigger_modification_task(x, y): trigger = partial(self._trigger_modification, (x, y), done=True) return self._tasks.add( task.sequence(task.wait(defaults.MOMENTARY_DELAY), task.run(trigger))).kill() self._step_tap_tasks = dict([ ((x, y), trigger_modification_task(x, y)) for x, y in product( xrange(self._get_width()), xrange(self._get_height())) ]) if matrix and last_page_length != self.page_length: self._on_clip_notes_changed() self.notify_page_length() else: self._update_editor_matrix() def set_step_duplicator(self, duplicator): self._step_duplicator = duplicator or NullStepDuplicator() self._step_duplicator.set_clip(self._sequencer_clip) def update(self): super(NoteEditorComponent, self).update() self._update_editor_matrix_leds() def _get_clip_notes_time_range(self): if self._modify_all_notes_enabled: time_length = self._get_step_count() * 4.0 time_start = 0 else: time_length = self.page_length time_start = self._page_index * time_length return (time_start - self._time_step(0).offset, time_length) @listens(b'notes') def _on_clip_notes_changed(self): """ get notes from clip for offline array """ if liveobj_valid(self._sequencer_clip) and self._can_edit(): time_start, time_length = self._get_clip_notes_time_range() self._clip_notes = self._get_notes_handler(self._sequencer_clip, time_start, self._pitches, time_length) else: self._clip_notes = [] self._update_editor_matrix() self.notify_notes_changed() def _update_editor_matrix(self): """ update offline array of button LED values, based on note velocity and mute states """ step_colors = [self._skin_base_key + b'.StepDisabled' ] * self._get_step_count() def coords_to_index(coord): return coord[0] + coord[1] * self._get_width() editing_indices = set(map(coords_to_index, self._modified_steps)) selected_indices = set(map(coords_to_index, self._pressed_steps)) last_editing_notes = [] for time_step, index in self._visible_steps(): notes = time_step.filter_notes(self._clip_notes) if len(notes) > 0: last_editing_notes = [] if index in selected_indices: color = self._skin_base_key + b'.StepSelected' elif index in editing_indices: note_color = self._determine_color(notes) color = self._skin_base_key + b'.StepEditing.' + note_color last_editing_notes = notes else: note_color = self._determine_color(notes) color = self._skin_base_key + b'.Step.' + note_color elif any(imap(time_step.overlaps_note, last_editing_notes)): color = self._skin_base_key + b'.StepEditing.' + note_color elif index in editing_indices or index in selected_indices: color = self._skin_base_key + b'.StepSelected' last_editing_notes = [] else: color = self.background_color last_editing_notes = [] step_colors[index] = color self._step_colors = step_colors self._update_editor_matrix_leds() def _determine_color(self, notes): return color_for_note( most_significant_note(notes), velocity_range_thresholds=self._velocity_range_thresholds) def _visible_steps(self): first_time = self.page_length * self._page_index steps_per_page = self._get_step_count() step_length = self._get_step_length() indices = range(steps_per_page) if is_triplet_quantization(self._triplet_factor): indices = filter(lambda k: k % 8 not in (6, 7), indices) return [(self._time_step(first_time + k * step_length), index) for k, index in enumerate(indices)] def _update_editor_matrix_leds(self): if self.is_enabled(): for control in self.matrix: control.color = self._step_colors[control.index] def _get_step_count(self): return self._get_width() * self._get_height() def get_step_start_time(self, step): x, y = step assert in_range(x, 0, self._get_width()) assert in_range(y, 0, self._get_height()) page_time = self._page_index * self._get_step_count( ) * self._triplet_factor step_time = x + y * self._get_width() * self._triplet_factor return (page_time + step_time) * self._get_step_length() def _get_step_length(self): return self._grid_resolution.step_length def get_row_start_times(self): return [ self.get_step_start_time((0, row)) for row in xrange(self._get_height()) ] def _update_from_grid(self): quantization, is_triplet = self._grid_resolution.clip_grid self._triplet_factor = 1.0 if not is_triplet else 0.75 if self._clip_creator: self._clip_creator.grid_quantization = quantization self._clip_creator.is_grid_triplet = is_triplet if liveobj_valid(self._sequencer_clip): self._sequencer_clip.view.grid_quantization = quantization self._sequencer_clip.view.grid_is_triplet = is_triplet @mute_button.pressed def mute_button(self, button): self._trigger_modification(immediate=True) @listens(b'index') def __on_resolution_changed(self, *a): self._release_active_steps() self._update_from_grid() self.set_selected_page_point(self._selected_page_point) self.notify_page_length() self._on_clip_notes_changed() @matrix.pressed def matrix(self, button): self._on_pad_pressed(button.coordinate) @matrix.released def matrix(self, button): self._on_pad_released(button.coordinate) def _on_pad_pressed(self, coordinate): y, x = coordinate if self.is_enabled(): if not liveobj_valid(self._sequencer_clip): self.set_detail_clip( create_clip_in_selected_slot(self._clip_creator, self.song)) if self._can_press_or_release_step(x, y): self._on_press_step((x, y)) self._update_editor_matrix() def _on_pad_released(self, coordinate): y, x = coordinate if self.is_enabled() and self._can_press_or_release_step(x, y): self._on_release_step((x, y)) self._update_editor_matrix() def _can_press_or_release_step(self, x, y): width = self._get_width( ) * self._triplet_factor if is_triplet_quantization( self._triplet_factor) else self._get_width() return x < width and y < self._get_height() @listens(b'velocity') def __on_provided_velocity_changed(self): if len(self._pressed_steps) + len(self._modified_steps) > 0: self._provided_velocity = self._velocity_provider.velocity self._trigger_modification(immediate=True) self._provided_velocity = None return def _get_step_time_range(self, step): time = self.get_step_start_time(step) return (time, time + self._get_step_length()) @property def editing_note_regions(self): active_time_spans = self.active_time_spans editing_notes = set(self.notes_in_selected_step) if len(editing_notes) > 1: editing_notes = [-1] return list(product(editing_notes, list(active_time_spans))) @property def active_time_spans(self): if self._modify_all_notes_enabled: return [(0.0, ONE_YEAR_AT_120BPM_IN_BEATS)] else: def time_span(step): time_step = self._time_step(self.get_step_start_time(step)) return (time_step.left_boundary(), time_step.right_boundary()) return imap(time_span, chain(self._pressed_steps, self._modified_steps)) @property def active_steps(self): return imap(self._get_step_time_range, chain(self._pressed_steps, self._modified_steps)) @property def active_note_regions(self): return imap(self._get_time_range, chain(self._pressed_steps, self._modified_steps)) def _get_time_range(self, step): def note_compare(left, right): return note_start_time(left) - note_start_time(right) time = self.get_step_start_time(step) notes = self._time_step(time).filter_notes(self._clip_notes) if notes: beginning_note = first(sorted(notes, key=cmp_to_key(note_compare))) start = note_start_time(beginning_note) end = start + note_length(beginning_note) if len(notes) > 1: end_note = notes[(-1)] end = note_start_time(end_note) + note_length(end_note) return (start, end) else: return (time, time + self._get_step_length()) def _release_active_steps(self): for step in self._pressed_steps + self._modified_steps: self._on_release_step(step, do_delete_notes=False) def _on_release_step(self, step, do_delete_notes=True): self._step_tap_tasks[step].kill() if step in self._pressed_steps: if do_delete_notes: self._delete_notes_in_step(step) self._pressed_steps.remove(step) for pitch in self._pitches: self._add_or_modify_note_in_step(step, pitch) if step in self._modified_steps: self._modified_steps.remove(step) if len(self._modified_steps) + len(self._pressed_steps) == 0: self._velocity_provider.set_velocities_playable(True) self.notify_active_steps() self.notify_active_note_regions() def _find_continued_step(self, step): def steps_to_note_start(steps): return map(lambda step: self.get_step_start_time(step), steps) time = self.get_step_start_time(step) all_steps_with_notes = [ time_step for time_step, index in self._visible_steps() if time_step.filter_notes(self._clip_notes) ] all_time_step_starts = map(lambda ts: ts.start, all_steps_with_notes) if all_time_step_starts: insert_point = bisect(all_time_step_starts, time) if insert_point > 0: prev_filled_step = all_steps_with_notes[(insert_point - 1)] notes_in_modified_steps = steps_to_note_start( self._modified_steps) if prev_filled_step.start in notes_in_modified_steps: return prev_filled_step return def _add_step_to_duplicator(self, step): nudge_offset = 0 time = self.get_step_start_time(step) notes = self._time_step(time).filter_notes(self._clip_notes) step_start, step_end = self._get_time_range(step) if notes: nudge_offset = min(imap(note_start_time, notes)) - time if self._duplicate_all_notes: self._step_duplicator.add_step(step_start, step_end, nudge_offset) else: self._step_duplicator.add_step_with_pitch(self.editing_notes[0], step_start, step_end, nudge_offset) def _on_press_step(self, step): if liveobj_valid( self._sequencer_clip ) and step not in self._pressed_steps and step not in self._modified_steps: if self._step_duplicator.is_duplicating: self._add_step_to_duplicator(step) else: self._step_tap_tasks[step].restart() continued_step = self._find_continued_step(step) if continued_step: self._modify_length_of_notes_within_existing_step( continued_step, step) else: self._pressed_steps.append(step) self._velocity_provider.set_velocities_playable(False) self.notify_active_steps() self.notify_active_note_regions() def _modify_length_of_notes_within_existing_step(self, existing_time_step, new_step): notes_in_step = existing_time_step.filter_notes(self._clip_notes) new_end = float( self.get_step_start_time(new_step) + self._get_step_length()) step_mute = all(map(lambda note: note_muted(note), notes_in_step)) new_notes = [] for note in self._clip_notes: length_offset = new_end - ( note_length(note) + note_start_time(note)) if note in notes_in_step else 0 new_notes.append( self._modify_single_note(step_mute, existing_time_step, length_offset, note)) self._replace_notes(tuple(new_notes)) def _time_step(self, time): return TimeStep(time, self._get_step_length()) def _get_notes_info_from_step(self, step): time = self.get_step_start_time(step) notes = self._time_step(time).filter_notes(self._clip_notes) pitches = [note_pitch(note) for note in notes] return (time, notes, pitches) def toggle_pitch_for_all_modified_steps(self, pitch): assert liveobj_valid(self._sequencer_clip) for step in set(chain(self._pressed_steps, self._modified_steps)): time, notes, pitches = self._get_notes_info_from_step(step) if pitch not in pitches: self._add_new_note_in_step(step, pitch, time) else: time_step = self._time_step(self.get_step_start_time(step)) for time, length in time_step.connected_time_ranges(): remove_single_note(self._sequencer_clip, time, (pitch, ), length) def _add_new_note_in_step(self, step, pitch, time): mute = self.mute_button.is_pressed velocity = 127 if self.full_velocity else self._velocity_provider.velocity note = (pitch, time, self._get_step_length(), velocity, mute) self._sequencer_clip.set_notes((note, )) self._sequencer_clip.deselect_all_notes() self._trigger_modification(step, done=True) def _add_or_modify_note_in_step(self, step, pitch, modify_existing=True): """ Add note in given step if there are none in there, otherwise select the step for potential deletion or modification """ if liveobj_valid(self._sequencer_clip): time, notes, pitches = self._get_notes_info_from_step(step) if notes: if pitch in pitches and modify_existing: most_significant_velocity = most_significant_note(notes)[3] if self.mute_button.is_pressed or most_significant_velocity != 127 and self.full_velocity: self._trigger_modification(step, immediate=True) else: self._add_new_note_in_step(step, pitch, time) return True return False def _delete_notes_in_step(self, step): """ Delete all notes in the given step """ if liveobj_valid(self._sequencer_clip) and self._can_edit(): time_step = self._time_step(self.get_step_start_time(step)) for time, length in time_step.connected_time_ranges(): self._remove_notes_handler(self._sequencer_clip, time, self._pitches, length) def set_nudge_offset(self, value): self._modify_note_property(b'_nudge_offset', value) def set_length_offset(self, value): self._modify_note_property(b'_length_offset', value) def set_velocity_offset(self, value): self._modify_note_property(b'_velocity_offset', value) def _modify_note_property(self, note_property, value): if self.is_enabled(): with self._full_velocity_context(False): setattr(self, note_property, getattr(self, note_property) + value) self._trigger_modification(immediate=True) @contextmanager def _full_velocity_context(self, desired_full_velocity_state): saved_velocity = self.full_velocity self.full_velocity = desired_full_velocity_state yield self.full_velocity = saved_velocity def set_full_velocity(self): if self.is_enabled(): with self._full_velocity_context(True): self._trigger_modification() def notify_modification(self): """ Tell the note editor about changes to pressed steps, so further modifications by the note editor are ignored. """ self._trigger_modification(done=True) def _trigger_modification(self, step=None, done=False, immediate=False): """ Because the modification of notes is slow, we accumulate modification events and perform all of them alltogether in a task. Call this function whenever a given set of steps (or potentially all steps) are to be modified. If done=True, we just notify that the given steps have been modified without actually executing the task. """ needs_update = False if step is None: needs_update = bool(self._pressed_steps) self._modified_steps += self._pressed_steps self._pressed_steps = [] else: if step not in self._modified_steps: self._modified_steps.append(step) if step in self._pressed_steps: self._pressed_steps.remove(step) needs_update = True if not done: if immediate: self._do_modification() self._modify_task.kill() elif self._modify_task.is_killed: self._do_modification() self._modify_task.restart() if needs_update: self._update_editor_matrix() return def _reset_modifications(self): self._velocity_offset = 0 self._length_offset = 0 self._nudge_offset = 0 def _do_modification(self): if self._modify_all_notes_enabled: new_notes = self._modify_all_notes() self._replace_notes(new_notes) elif self._modified_steps: notes_added = map( lambda step_and_pitch: self._add_or_modify_note_in_step( modify_existing=False, *step_and_pitch), product(self._modified_steps, self._pitches)) if any(notes_added): self._modify_task.restart() else: new_notes = self._modify_step_notes(self._modified_steps) self._replace_notes(new_notes) self._reset_modifications() def _replace_notes(self, new_notes): if new_notes != self._clip_notes and self._can_edit(): time_start, time_length = self._get_clip_notes_time_range() self._remove_notes_handler(self._sequencer_clip, time_start, self._pitches, time_length) self._sequencer_clip.set_notes(tuple(new_notes)) self._sequencer_clip.deselect_all_notes() def _modify_all_notes(self): """ modify all notes in the current pitch """ return self._modify_notes_in_time(TimeStep(0.0, MAX_CLIP_LENGTH), self._clip_notes, self._length_offset) def _limited_nudge_offset(self, steps, notes, nudge_offset): limited_nudge_offset = MAX_CLIP_LENGTH for step in steps: time_step = self._time_step(self.get_step_start_time(step)) for note in time_step.filter_notes(notes): start_time = note_start_time(note) time_after_nudge = time_step.clamp(start_time, nudge_offset) limited_nudge_offset = min(limited_nudge_offset, abs(start_time - time_after_nudge)) return sign(nudge_offset) * limited_nudge_offset def _modify_step_notes(self, steps): """ Return a new list with all notes within steps modified. """ notes = self._clip_notes self._nudge_offset = self._limited_nudge_offset( steps, notes, self._nudge_offset) for step in steps: time_step = self._time_step(self.get_step_start_time(step)) notes = self._modify_notes_in_time(time_step, notes, self._length_offset) return notes def _modify_notes_in_time(self, time_step, notes, length_offset): step_notes = time_step.filter_notes(self._clip_notes) step_mute = all(map(lambda note: note_muted(note), step_notes)) return map( partial(self._modify_single_note, step_mute, time_step, length_offset), notes) def _modify_single_note(self, step_mute, time_step, length_offset, note): """ Return a modified version of the passed in note taking into account current modifiers. If the note is not within the given step, returns the note as-is. If the time_step is inside a loop, the last part of the loop is considered as being the same as the part just before the loop, so the resulting note may, in this case, jump between the beginning and the end. """ pitch, time, length, velocity, mute = note if time_step.includes_time(time): time = time_step.clamp(time, self._nudge_offset) if length_offset <= -time_step.length and length + length_offset < time_step.length: if length > time_step.length: length = time_step.length else: length = max(0, length + length_offset) if self._provided_velocity: velocity = self._provided_velocity elif self.full_velocity: velocity = 127 else: velocity = clamp(velocity + self._velocity_offset, 1, 127) mute = not step_mute if self.mute_button.is_pressed else mute return (pitch, time, length, velocity, mute) def get_min_max_note_values(self): if self._modify_all_notes_enabled and len(self._clip_notes) > 0: return min_max_for_notes(self._clip_notes, 0.0) else: if len(self._pressed_steps) + len(self._modified_steps) > 0: min_max_values = None for step in chain(self._modified_steps, self._pressed_steps): start_time = self.get_step_start_time(step) min_max_values = min_max_for_notes( self._time_step(start_time).filter_notes( self._clip_notes), start_time, min_max_values) return min_max_values return
class ConvertEnabler(Component): convert_toggle_button = ButtonControl(color='DefaultButton.On') def __init__(self, enter_dialog_mode=None, exit_dialog_mode=None, *a, **k): assert enter_dialog_mode is not None assert exit_dialog_mode is not None super(ConvertEnabler, self).__init__(*a, **k) self._enter_dialog_mode = partial(enter_dialog_mode, 'convert') self._exit_dialog_mode = partial(exit_dialog_mode, 'convert') self._selected_item = self.register_disconnectable( SelectedMixerTrackProvider(song=self.song)) self.__on_selected_item_changed.subject = self._selected_item self.__on_selected_item_changed(None) song = self.song self.__on_devices_changed.subject = song.view self.__on_selected_scene_changed.subject = song.view self.__on_detail_clip_updated.subject = song.view self._update_clip_slot_listener() self._update_drum_pad_listeners() return @listens('selected_mixer_track') def __on_selected_item_changed(self, _): self._update_clip_slot_listener() self._disable_and_check_enabled_state() @convert_toggle_button.pressed def convert_toggle_button(self, button): self._enter_dialog_mode() def _can_enable_mode(self): category = possible_conversions( self._selected_item.selected_mixer_track) category.disconnect() return len(category.actions) > 0 def _disable_and_check_enabled_state(self): self._exit_dialog_mode() self.convert_toggle_button.enabled = self._can_enable_mode() @listens('detail_clip') def __on_detail_clip_updated(self): self._disable_and_check_enabled_state() @listens('selected_track.devices') def __on_devices_changed(self): self._disable_and_check_enabled_state() self._update_drum_pad_listeners() def _update_drum_pad_listeners(self): drum_rack = find_drum_rack_instrument( self._selected_item.selected_mixer_track) drum_rack_view_or_none = drum_rack.view if liveobj_valid( drum_rack) else None self.__on_selected_drum_pad_changed.subject = drum_rack_view_or_none self.__on_drum_pad_chains_changed.subject = drum_rack_view_or_none return @listens('selected_drum_pad') def __on_selected_drum_pad_changed(self): self._disable_and_check_enabled_state() drum_rack_view = self.__on_selected_drum_pad_changed.subject if liveobj_valid(drum_rack_view): selected_drum_pad = drum_rack_view.selected_drum_pad first_chain_or_none = None if liveobj_valid(selected_drum_pad): first_chain_or_none = selected_drum_pad.chains[0] if len( selected_drum_pad.chains) > 0 else None self.__on_drum_pad_chain_devices_changed.subject = first_chain_or_none return @listens('selected_drum_pad.chains') def __on_drum_pad_chains_changed(self): self._disable_and_check_enabled_state() @listens('devices') def __on_drum_pad_chain_devices_changed(self): self._disable_and_check_enabled_state() @listens('selected_scene') def __on_selected_scene_changed(self): self._update_clip_slot_listener() self._disable_and_check_enabled_state() def _update_clip_slot_listener(self): clip_slot = self.song.view.highlighted_clip_slot self.__on_clip_slot_has_clip_changed.subject = clip_slot @listens('has_clip') def __on_clip_slot_has_clip_changed(self): self._disable_and_check_enabled_state() clip_slot = self.__on_clip_slot_has_clip_changed.subject self._update_clip_listeners(clip_slot) def _update_clip_listeners(self, clip_slot): self.__on_clip_playing_status_changed.subject = clip_slot.clip self.__on_clip_recording_status_changed.subject = clip_slot.clip @listens('is_recording') def __on_clip_recording_status_changed(self): self._disable_and_check_enabled_state() @listens('playing_status') def __on_clip_playing_status_changed(self): self._disable_and_check_enabled_state()
class GlobalMixerActionComponent(Component): action_button = ButtonControl(delay_time=GLOBAL_ACTION_LOCK_MODE_DELAY) def __init__(self, track_list_component=None, mode=None, immediate_action=None, default_color_indicator=None, mode_locked_color=None, mode_active_color=None, *a, **k): assert track_list_component is not None assert mode is not None assert mode in track_list_component.modes super(GlobalMixerActionComponent, self).__init__(*a, **k) self._mode = mode self._immediate_action = immediate_action self._mode_locked = False self._default_color_indicator = None self._locked_color = mode_locked_color self._active_color = mode_active_color if mode_active_color is not None else 'DefaultButton.On' self._allow_released_immediately_action = True self._track_list_component = track_list_component if default_color_indicator is not None: self._default_color_indicator = self.register_disconnectable( default_color_indicator) self.__on_default_color_changed.subject = default_color_indicator self._update_default_color() return @listenable_property def mode_locked(self): return self._mode_locked @property def mode(self): return self._mode def cancel_release_action(self): self._allow_released_immediately_action = False def cancel_locked_mode(self): self._track_list_component.pop_mode(self._mode) self._mode_locked = False self._update_default_color() @listens('color') def __on_default_color_changed(self, _): self._update_default_color() @action_button.released_immediately def action_button(self, button): if self._allow_released_immediately_action: self._immediate_action() @action_button.pressed def action_button(self, button): if self._mode_locked: self._allow_released_immediately_action = False self._unlock_mode() else: self._allow_released_immediately_action = True self._track_list_component.push_mode(self._mode) self.action_button.color = self._active_color @action_button.pressed_delayed def action_button(self, button): if self._allow_released_immediately_action: self._lock_mode() @action_button.released def action_button(self, button): if not self._mode_locked: self._track_list_component.pop_mode(self._mode) self._update_default_color() def _lock_mode(self): self._mode_locked = True self.action_button.color = self._locked_color self.notify_mode_locked(self._mode_locked) def _unlock_mode(self): self.cancel_locked_mode() self.notify_mode_locked(self._mode_locked) def _update_default_color(self): if self._mode not in self._track_list_component.active_modes: self.action_button.color = self._default_color_indicator.color if self._default_color_indicator else 'DefaultButton.On'
class InstrumentScalesComponent(Component): presets_toggle_button = ButtonControl(color='DefaultButton.Off', pressed_color='DefaultButton.On') def __init__(self, note_layout=None, *a, **k): assert note_layout is not None super(InstrumentScalesComponent, self).__init__(*a, **k) self._note_layout = note_layout self._key_center_buttons = [] self._encoder_touch_button_slots = self.register_disconnectable( EventObject()) self._encoder_touch_buttons = [] self._top_key_center_buttons = None self._bottom_key_center_buttons = None self._absolute_relative_button = None self._diatonic_chromatic_button = None self._info_sources = map(DisplayDataSource, (u'Scale selection:', u'', u'')) self._line_sources = recursive_map( DisplayDataSource, ((u'', u'', u'', u'', u'', u'', u''), (u'', u'', u'', u'', u'', u'', u''))) self._scale_sources = map( partial(DisplayDataSource, adjust_string_fn=adjust_string_crop), (u'', u'', u'', u'')) self._presets = InstrumentPresetsComponent(self._note_layout, is_enabled=False, parent=self) self._presets.selected_mode = 'scale_p4_vertical' self._scale_list = ListComponent(parent=self, data_sources=self._scale_sources) self._scale_list.scrollable_list.fixed_offset = 1 self._scale_list.scrollable_list.assign_items(SCALES) self._scale_list.scrollable_list.select_item_index_with_offset( list(SCALES).index(self._note_layout.scale), 1) self._on_selected_scale.subject = self._scale_list.scrollable_list self._update_data_sources() return presets_layer = forward_property('_presets')('layer') @property def available_scales(self): return self._note_layout.scale.scale_for_notes(ROOT_NOTES) def set_scale_line1(self, display): self._set_scale_line(display, 0) def set_scale_line2(self, display): self._set_scale_line(display, 1) def set_scale_line3(self, display): self._set_scale_line(display, 2) def set_scale_line4(self, display): self._set_scale_line(display, 3) def _set_scale_line(self, display, index): if display: display.set_data_sources([self._scale_sources[index]]) for segment in display.segments: segment.separator = '' def set_info_line(self, display): if display: display.set_data_sources(self._info_sources) def set_top_display_line(self, display): self._set_display_line(display, 0) def set_bottom_display_line(self, display): self._set_display_line(display, 1) def _set_display_line(self, display, line): if display: display.set_data_sources(self._line_sources[line]) @presets_toggle_button.pressed def presets_toggle_button(self, button): self._presets.set_enabled(True) @presets_toggle_button.released def presets_toggle_button(self, button): self._presets.set_enabled(False) def set_top_buttons(self, buttons): if buttons: buttons.reset() self.set_absolute_relative_button(buttons[7]) self._top_key_center_buttons = buttons[1:7] self.set_scale_up_button(buttons[0]) else: self.set_absolute_relative_button(None) self._top_key_center_buttons = None self.set_scale_up_button(None) if self._top_key_center_buttons and self._bottom_key_center_buttons: self.set_key_center_buttons(self._top_key_center_buttons + self._bottom_key_center_buttons) else: self.set_key_center_buttons(tuple()) return def set_bottom_buttons(self, buttons): if buttons: buttons.reset() self.set_diatonic_chromatic_button(buttons[7]) self._bottom_key_center_buttons = buttons[1:7] self.set_scale_down_button(buttons[0]) else: self.set_diatonic_chromatic_button(None) self._bottom_key_center_buttons = None self.set_scale_down_button(None) if self._top_key_center_buttons and self._bottom_key_center_buttons: self.set_key_center_buttons(self._top_key_center_buttons + self._bottom_key_center_buttons) else: self.set_key_center_buttons([]) return def set_scale_down_button(self, button): self._scale_list.select_next_button.set_control_element(button) def set_scale_up_button(self, button): self._scale_list.select_prev_button.set_control_element(button) def set_encoder_controls(self, encoders): self._scale_list.encoders.set_control_element( [encoders[0]] if encoders else []) def set_key_center_buttons(self, buttons): assert not buttons or len(buttons) == 12 buttons = buttons or [] self._key_center_buttons = buttons self._on_key_center_button_value.replace_subjects(buttons) self._update_key_center_buttons() def set_absolute_relative_button(self, absolute_relative_button): self._absolute_relative_button = absolute_relative_button self._on_absolute_relative_value.subject = absolute_relative_button self._update_absolute_relative_button() def set_diatonic_chromatic_button(self, diatonic_chromatic_button): self._diatonic_chromatic_button = diatonic_chromatic_button self._on_diatonic_chromatic_value.subject = diatonic_chromatic_button self._update_diatonic_chromatic_button() @listens_group('value') def _on_key_center_button_value(self, value, sender): if self.is_enabled() and (value or not sender.is_momentary()): index = list(self._key_center_buttons).index(sender) self._note_layout.root_note = ROOT_NOTES[index] self._update_key_center_buttons() self._update_data_sources() @listens('value') def _on_absolute_relative_value(self, value): if self.is_enabled(): if value != 0 or not self._absolute_relative_button.is_momentary(): self._note_layout.is_fixed = not self._note_layout.is_fixed self._update_absolute_relative_button() self._update_data_sources() @listens('value') def _on_diatonic_chromatic_value(self, value): if self.is_enabled(): if value != 0 or not self._diatonic_chromatic_button.is_momentary( ): self._note_layout.is_in_key = not self._note_layout.is_in_key self._update_diatonic_chromatic_button() self._update_data_sources() @listens('selected_item') def _on_selected_scale(self): self._note_layout.scale = self._scale_list.scrollable_list.selected_item.content self._update_data_sources() def update(self): super(InstrumentScalesComponent, self).update() if self.is_enabled(): self._update_key_center_buttons() self._update_absolute_relative_button() self._update_diatonic_chromatic_button() else: self._presets.set_enabled(False) def _update_key_center_buttons(self): if self.is_enabled(): for index, button in enumerate(self._key_center_buttons): if button: color = 'Scales.Selected' if self._note_layout.root_note == ROOT_NOTES[ index] else 'Scales.Unselected' button.set_light(color) def _update_absolute_relative_button(self): if self.is_enabled() and self._absolute_relative_button != None: color = 'Scales.FixedOn' if self._note_layout.is_fixed else 'Scales.FixedOff' self._absolute_relative_button.set_light(color) return def _update_diatonic_chromatic_button(self): if self.is_enabled() and self._diatonic_chromatic_button != None: color = 'Scales.Diatonic' if self._note_layout.is_in_key else 'Scales.Chromatic' self._diatonic_chromatic_button.set_light(color) return def _update_data_sources(self): key_index = list(ROOT_NOTES).index(self._note_layout.root_note) key_sources = self._line_sources[0][:6] + self._line_sources[1][:6] key_names = [scale.name for scale in self.available_scales] for idx, (source, orig) in enumerate(zip(key_sources, key_names)): source.set_display_string(' ' + consts.CHAR_SELECT + orig if idx == key_index else ' ' + orig) self._line_sources[0][6].set_display_string( 'Fixed: Y' if self._note_layout.is_fixed else 'Fixed: N') self._line_sources[1][6].set_display_string( 'In Key' if self._note_layout.is_in_key else 'Chromatc') self._info_sources[1].set_display_string( str(self._scale_list.scrollable_list.selected_item))
class LoopSettingsControllerComponent(LoopSettingsControllerComponentBase): __events__ = (u'looping', u'loop_parameters', u'zoom', u'clip') ZOOM_DEFAULT_SENSITIVITY = MappedSensitivitySettingControl.DEFAULT_SENSITIVITY ZOOM_FINE_SENSITIVITY = MappedSensitivitySettingControl.FINE_SENSITIVITY zoom_encoder = MappedSensitivitySettingControl() zoom_touch_encoder = EncoderControl() loop_button = ToggleButtonControl(toggled_color='Clip.Option', untoggled_color='Clip.OptionDisabled') crop_button = ButtonControl(color='Clip.Action') def __init__(self, *a, **k): super(LoopSettingsControllerComponent, self).__init__(*a, **k) self._looping_settings = [ LoopSetting(name=PARAMETERS_LOOPED[0], parent=self._loop_model, source_property='position'), LoopSetting(name=PARAMETERS_LOOPED[1], parent=self._loop_model, use_length_conversion=True, source_property='loop_length'), LoopSetting(name=PARAMETERS_LOOPED[2], parent=self._loop_model, source_property='start_marker')] self._non_looping_settings = [ LoopSetting(name=PARAMETERS_NOT_LOOPED[0], parent=self._loop_model, source_property='loop_start'), LoopSetting(name=PARAMETERS_NOT_LOOPED[1], parent=self._loop_model, source_property='loop_end')] for setting in self._looping_settings + self._non_looping_settings: self.register_disconnectable(setting) self.__on_looping_changed.subject = self._loop_model self.__on_looping_changed() def update(self): super(LoopSettingsControllerComponent, self).update() if self.is_enabled(): self.notify_timeline_navigation() self.notify_clip() @loop_button.toggled def loop_button(self, toggled, button): self._loop_model.looping = toggled @crop_button.pressed def crop_button(self, button): if liveobj_valid(self.clip): self.clip.crop() @property def looping(self): if self.clip: return self._loop_model.looping return False @property def loop_parameters(self): if not liveobj_valid(self.clip): return [] parameters = self._looping_settings if self.looping else self._non_looping_settings if self.zoom: return [self.zoom] + parameters return parameters @property def zoom(self): if liveobj_valid(self.clip): return getattr(self.clip, 'zoom', None) return @listenable_property def timeline_navigation(self): if liveobj_valid(self.clip): return getattr(self.clip, 'timeline_navigation', None) return @listens('is_recording') def __on_is_recording_changed(self): self._update_recording_state() @listens('is_overdubbing') def __on_is_overdubbing_changed(self): self._update_recording_state() def _update_recording_state(self): clip = self._loop_model.clip if liveobj_valid(clip): recording = clip.is_recording and not clip.is_overdubbing self._looping_settings[1].recording = recording self._non_looping_settings[1].recording = recording @listens('looping') def __on_looping_changed(self): self._update_and_notify() def _update_loop_button(self): self.loop_button.enabled = liveobj_valid(self.clip) if liveobj_valid(self.clip): self.loop_button.is_toggled = self._loop_model.looping def _on_clip_changed(self): if self.timeline_navigation is not None: self.timeline_navigation.reset_focus_and_animation() self._update_and_notify() self.__on_is_recording_changed.subject = self._loop_model.clip self.__on_is_overdubbing_changed.subject = self._loop_model.clip self._update_recording_state() self.crop_button.enabled = liveobj_valid(self.clip) and self.clip.is_midi_clip self._connect_encoder() if self.is_enabled(): self.notify_timeline_navigation() return def _on_clip_start_marker_touched(self): if self.timeline_navigation is not None: self.timeline_navigation.touch_object(self.timeline_navigation.start_marker_focus) return def _on_clip_position_touched(self): if self.timeline_navigation is not None: self.timeline_navigation.touch_object(self.timeline_navigation.loop_start_focus) return def _on_clip_end_touched(self): if self.timeline_navigation is not None: self.timeline_navigation.touch_object(self.timeline_navigation.loop_end_focus) return def _on_clip_start_marker_released(self): if self.timeline_navigation is not None: self.timeline_navigation.release_object(self.timeline_navigation.start_marker_focus) return def _on_clip_position_released(self): if self.timeline_navigation is not None: self.timeline_navigation.release_object(self.timeline_navigation.loop_start_focus) return def _on_clip_end_released(self): if self.timeline_navigation is not None: self.timeline_navigation.release_object(self.timeline_navigation.loop_end_focus) return @zoom_touch_encoder.touched def zoom_touch_encoder(self, encoder): if self.timeline_navigation is not None: self.timeline_navigation.touch_object(self.timeline_navigation.zoom_focus) return @zoom_touch_encoder.released def zoom_touch_encoder(self, encoder): if self.timeline_navigation is not None: self.timeline_navigation.release_object(self.timeline_navigation.zoom_focus) return def _update_and_notify(self): self._update_loop_button() self.notify_looping() self.notify_loop_parameters() self.notify_zoom() def _connect_encoder(self): self.zoom_encoder.mapped_parameter = self.zoom self.zoom_encoder.update_sensitivities(self.ZOOM_DEFAULT_SENSITIVITY, self.ZOOM_FINE_SENSITIVITY) def set_zoom_encoder(self, encoder): self.zoom_encoder.set_control_element(encoder) self.zoom_touch_encoder.set_control_element(encoder) self._connect_encoder()
class MixerControlComponent(ModesComponent): __events__ = (u'items', u'selected_item') controls = control_list(MappedControl) cycle_sends_button = ButtonControl(color=u'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=u'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 = u'volume' self._selected_item = u'' 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(u'volume', view_model.volumeControlListView, lambda mixer: mixer.volume, additional_mode_contents=self.real_time_meter_handlers) self._add_mode( u'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 = u'MixerControlView.SectionSelected' mode_button.mode_unselected_color = u'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 = u'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(u'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 == u'panning' else MIXER_SECTIONS[0] self.notify_selected_item() @listens(u'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(u'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(u'is_frozen') def __on_track_frozen_state_changed(self, identifier): self._update_controls(self._parameter_getter, self._selected_view) @listens_group(u'panning_mode') def __on_panning_mode_changed(self, identifier): self._update_controls(self._parameter_getter, self._selected_view) @listens(u'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 = u'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 = [ SimpleItemSlot(name=name) for name in mixer_section_names ] if self.number_sends > SEND_LIST_LENGTH: self._mixer_sections.extend([SimpleItemSlot()] * (8 - len(self._mixer_sections))) self._mixer_sections[7] = SimpleItemSlot( icon=u'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 = u'MixerControlView.SectionSelected' @cycle_sends_button.released def cycle_sends_button(self, button): button.color = u'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 SetupComponent(ModesComponent): category_radio_buttons = control_list(RadioButtonControl, checked_color=u'Option.Selected', unchecked_color=u'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( u'_Push2DeveloperMode') and has_option(u'_MakePush2GoBoom') self._general = self.register_component( GeneralSettingsComponent(settings=settings.general, hardware_settings=settings.hardware, is_enabled=False)) self._info = self.register_component( InfoComponent(firmware_switcher=firmware_switcher, 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(u'Settings', [self._general, self._pad_settings]) self.add_mode(u'Info', [self._info]) if self.application.has_option(u'_Push2DeveloperMode'): self.add_mode(u'Display Debug', [self._display_debug]) self.add_mode(u'Profiling', [self._profiling]) self.selected_mode = u'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 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 ListComponent(CompoundComponent): u""" Component that handles a ScrollableList. If an action button is passed, it can handle an ActionList. """ __events__ = (u'item_action', u'selected_item') SELECTION_DELAY = 0.5 ENCODER_FACTOR = 10.0 empty_list_message = '' _current_action_item = None _last_action_item = None action_button = ButtonControl(color='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 = self.register_component(ScrollComponent()) self._pager = self.register_component(ScrollComponent()) 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 = 'List.ScrollerOn' button.pressed_color = None button.disabled_color = '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_all() 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('_scroller')('scroll_down_button') select_prev_button = forward_property('_scroller')('scroll_up_button') next_page_button = forward_property('_pager')('scroll_down_button') prev_page_button = forward_property('_pager')('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('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('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): u""" 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 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 = 'Browser.Loading' if self._current_action_item == None: if self._action_target_is_next_item(): color = 'Browser.LoadNext' elif self._can_be_used_for_action(self.selected_item): color = 'Browser.Load' else: color = '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): if index < len(visible_items): item = visible_items[index] if 1 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 InstrumentComponent(PlayableComponent, Slideable, Messenger): u""" Class that sets up the button matrix as a piano, using different selectable layouts for the notes. """ __events__ = (u'pattern', ) matrix = control_matrix(PadControl) delete_button = ButtonControl() def __init__(self, note_layout=None, *a, **k): assert note_layout is not None super(InstrumentComponent, self).__init__(*a, **k) self._note_layout = note_layout self._first_note = self.page_length * 3 + self.page_offset self._last_page_length = self.page_length self._last_page_offset = self.page_offset self._detail_clip = None self._has_notes = [False] * 128 self._has_notes_pattern = self._get_pattern(0) self._aftertouch_control = None self._show_notifications = True self.__on_detail_clip_changed.subject = self.song.view self.__on_detail_clip_changed() self._slider = SlideComponent(slideable=self, parent=self) self._touch_slider = SlideableTouchStripComponent(touch_slideable=self, parent=self) for event in (u'scale', u'root_note', u'is_in_key', u'is_fixed', u'is_horizontal', u'interval'): self.register_slot(self._note_layout, self._on_note_layout_changed, event) self._update_pattern() return @listens('detail_clip') def __on_detail_clip_changed(self): clip = self.song.view.detail_clip self.set_detail_clip(clip if liveobj_valid(clip) and clip.is_midi_clip else None) return def set_detail_clip(self, clip): if clip != self._detail_clip: self._detail_clip = clip self._on_clip_notes_changed.subject = clip self._on_loop_start_changed.subject = clip self._on_loop_end_changed.subject = clip self._on_clip_notes_changed() @listens('notes') def _on_clip_notes_changed(self): if self._detail_clip: self._has_notes = [ False] * 128 loop_start = self._detail_clip.loop_start loop_length = self._detail_clip.loop_end - loop_start notes = self._detail_clip.get_notes(loop_start, 0, loop_length, 128) for note in notes: self._has_notes[note[0]] = True self.notify_contents() @listens('loop_start') def _on_loop_start_changed(self): self._on_loop_selection_changed() @listens('loop_end') def _on_loop_end_changed(self): self._on_loop_selection_changed() def _on_loop_selection_changed(self): self._on_clip_notes_changed() def contents(self, index): if self._detail_clip: note = self._has_notes_pattern[index].index if note is not None: return self._has_notes[note] return False return False @property def show_notifications(self): return self._show_notifications @show_notifications.setter def show_notifications(self, value): self._show_notifications = value @property def page_length(self): if self._note_layout.is_in_key: return len(self._note_layout.notes) return 12 @property def position_count(self): if not self._note_layout.is_in_key: return 139 else: offset = self.page_offset octaves = 11 if self._note_layout.notes[0] < 8 else 10 return offset + len(self._note_layout.notes) * octaves def _first_scale_note_offset(self): if not self._note_layout.is_in_key: return self._note_layout.notes[0] else: if self._note_layout.notes[0] == 0: return 0 return len(self._note_layout.notes) - index_if(lambda n: n >= 12, self._note_layout.notes) @property def page_offset(self): if self._note_layout.is_fixed: return 0 return self._first_scale_note_offset() def _get_position(self): return self._first_note def _set_position(self, note): self._first_note = note self._update_pattern() self._update_matrix() self.notify_position() position = property(_get_position, _set_position) @property def min_pitch(self): return self.pattern[0].index @property def max_pitch(self): identifiers = [ control.identifier for control in self.matrix ] if len(identifiers) > 0: return max(identifiers) return 127 @property def pattern(self): return self._pattern @matrix.pressed def matrix(self, button): self._on_matrix_pressed(button) def _on_matrix_pressed(self, button): if self.delete_button.is_pressed: pitch = self._get_note_info_for_coordinate(button.coordinate).index if pitch and self._detail_clip: self._do_delete_pitch(pitch) @matrix.released def matrix(self, button): self._on_matrix_released(button) def _on_matrix_released(self, button): pass def _do_delete_pitch(self, pitch): clip = self._detail_clip if clip: note_name = pitch_index_to_string(pitch) loop_length = clip.loop_end - clip.loop_start clip.remove_notes(clip.loop_start, pitch, loop_length, 1) self.show_notification(consts.MessageBoxText.DELETE_NOTES % note_name) @delete_button.pressed def delete_button(self, value): self._set_control_pads_from_script(True) @delete_button.released def delete_button(self, value): self._set_control_pads_from_script(False) def set_note_strip(self, strip): self._touch_slider.set_scroll_strip(strip) def set_octave_strip(self, strip): self._touch_slider.set_page_strip(strip) def set_octave_up_button(self, button): self._slider.set_scroll_page_up_button(button) def set_octave_down_button(self, button): self._slider.set_scroll_page_down_button(button) def set_scale_up_button(self, button): self._slider.set_scroll_up_button(button) def set_scale_down_button(self, button): self._slider.set_scroll_down_button(button) def set_aftertouch_control(self, control): self._aftertouch_control = control self._update_aftertouch() def _align_first_note(self): self._first_note = self.page_offset + (self._first_note - self._last_page_offset) * float(self.page_length) / float(self._last_page_length) if self._first_note >= self.position_count: self._first_note -= self.page_length self._last_page_length = self.page_length self._last_page_offset = self.page_offset def _on_note_layout_changed(self, _): self._update_scale() def show_pitch_range_notification(self): if self.is_enabled() and self.show_notifications: self.show_notification(('Play {start_note} to {end_note}').format(start_note=pitch_index_to_string(self.pattern.note(0, 0).index), end_note=pitch_index_to_string(self.pattern.note(self.width - 1, self.height - 1).index))) def _update_scale(self): self._align_first_note() self._update_pattern() self._update_matrix() self.notify_position_count() self.notify_position() self.notify_contents() def update(self): super(InstrumentComponent, self).update() if self.is_enabled(): self._update_matrix() self._update_aftertouch() def _update_pattern(self): self._pattern = self._get_pattern() self._has_notes_pattern = self._get_pattern(0) self.notify_pattern() def _invert_and_swap_coordinates(self, coordinates): return ( coordinates[1], self.height - 1 - coordinates[0]) def _get_note_info_for_coordinate(self, coordinate): x, y = self._invert_and_swap_coordinates(coordinate) return self.pattern.note(x, y) def _update_button_color(self, button): note_info = self._get_note_info_for_coordinate(button.coordinate) button.color = 'Instrument.' + note_info.color def _button_should_be_enabled(self, button): return self._get_note_info_for_coordinate(button.coordinate).index != None def _note_translation_for_button(self, button): note_info = self._get_note_info_for_coordinate(button.coordinate) return ( note_info.index, note_info.channel) def _set_button_control_properties(self, button): super(InstrumentComponent, self)._set_button_control_properties(button) button.sensitivity_profile = 'default' if self._takeover_pads else 'instrument' def _update_matrix(self): self._update_control_from_script() self._update_note_translations() self._update_led_feedback() def _get_pattern(self, first_note=None): if first_note is None: first_note = int(round(self._first_note)) interval = self._note_layout.interval notes = self._note_layout.notes width = None height = None octave = first_note / self.page_length offset = first_note % self.page_length - self._first_scale_note_offset() if interval == None: if self._note_layout.is_in_key: interval = len(self._note_layout.notes) if self._note_layout.is_horizontal: width = interval + 1 else: height = interval + 1 else: interval = 8 elif not self._note_layout.is_in_key: interval = [ 0, 2, 4, 5, 7, 9, 10, 11][interval] if self._note_layout.is_horizontal: steps = [ 1, interval] origin = [offset, 0] else: steps = [ interval, 1] origin = [0, offset] return MelodicPattern(steps=steps, scale=notes, origin=origin, root_note=octave * 12, chromatic_mode=not self._note_layout.is_in_key, width=width, height=height) def _update_aftertouch(self): if self.is_enabled() and self._aftertouch_control != None: self._aftertouch_control.send_value('mono') return
class FixedLengthComponent(Component, Messenger): fixed_length_toggle_button = ButtonControl() def __init__(self, fixed_length_setting=None, *a, **k): assert fixed_length_setting is not None super(FixedLengthComponent, self).__init__(*a, **k) self._fixed_length_setting = fixed_length_setting self.settings_component = FixedLengthSettingComponent( fixed_length_setting=self._fixed_length_setting, parent=self, is_enabled=False) self._length_press_state = None self.__on_setting_enabled_changes.subject = fixed_length_setting self.__on_setting_enabled_changes(fixed_length_setting.enabled) return @fixed_length_toggle_button.released_immediately def fixed_length_toggle_button(self, button): loop_set = self._set_loop() if not loop_set: enabled = not self._fixed_length_setting.enabled self._fixed_length_setting.enabled = enabled self.show_notification(consts.MessageBoxText.FIXED_LENGTH % (b'On' if enabled else b'Off')) @fixed_length_toggle_button.pressed_delayed def fixed_length_toggle_button(self, button): self.settings_component.set_enabled(True) @fixed_length_toggle_button.released_delayed def fixed_length_toggle_button(self, button): self.settings_component.set_enabled(False) self._set_loop() @fixed_length_toggle_button.pressed def fixed_length_toggle_button(self, button): song = self.song slot = song.view.highlighted_clip_slot if slot is None: return else: clip = slot.clip if slot.is_recording and not clip.is_overdubbing: self._length_press_state = (slot, clip.playing_position) return def _set_loop(self): song = self.song slot = song.view.highlighted_clip_slot if slot is None: return else: clip = slot.clip loop_set = False if self._length_press_state is not None: press_slot, press_position = self._length_press_state if press_slot == slot and slot.is_recording and not clip.is_overdubbing: length, _ = self._fixed_length_setting.get_selected_length( song) one_bar = 4.0 * song.signature_numerator / song.signature_denominator loop_end = int(press_position / one_bar) * one_bar loop_start = loop_end - length if loop_start >= 0.0: clip.loop_end = loop_end clip.end_marker = loop_end clip.loop_start = loop_start clip.start_marker = loop_start self._tasks.add( task.sequence( task.delay(0), task.run( partial(slot.fire, force_legato=True, launch_quantization=Quantization. q_no_q)))) self.song.overdub = False loop_set = True self._length_press_state = None return loop_set @listens(b'enabled') def __on_setting_enabled_changes(self, enabled): self.fixed_length_toggle_button.color = b'FixedLength.On' if enabled else b'FixedLength.Off'
class SelectPlayingClipComponent(MessengerModesComponent): action_button = ButtonControl(color='DefaultButton.Alert') def __init__(self, playing_clip_above_layer=None, playing_clip_below_layer=None, *a, **k): super(SelectPlayingClipComponent, self).__init__(*a, **k) self._update_mode_task = self._tasks.add( task.sequence(task.delay(1), task.run(self._update_mode))) self._update_mode_task.kill() self.add_mode('default', None) self.add_mode('above', [AddLayerMode(self, playing_clip_above_layer)], message=MessageBoxText.PLAYING_CLIP_ABOVE_SELECTED_CLIP) self.add_mode('below', [AddLayerMode(self, playing_clip_below_layer)], message=MessageBoxText.PLAYING_CLIP_BELOW_SELECTED_CLIP) self.selected_mode = 'default' self.notify_when_enabled = True self._on_detail_clip_changed.subject = self.song.view self._on_playing_slot_index_changed.subject = self.song.view.selected_track self._notification_reference = partial(nop, None) return @action_button.pressed def action_button(self, button): self._go_to_playing_clip() @listens('detail_clip') def _on_detail_clip_changed(self): self._update_mode_task.restart() @listens('playing_slot_index') def _on_playing_slot_index_changed(self): self._update_mode_task.restart() def _go_to_playing_clip(self): song_view = self.song.view playing_clip_slot = self._playing_clip_slot() if playing_clip_slot: song_view.highlighted_clip_slot = playing_clip_slot song_view.detail_clip = playing_clip_slot.clip self._hide_notification() def _hide_notification(self): if self._notification_reference() is not None: self._notification_reference().hide() return def show_notification(self, display_text): self._notification_reference = super( SelectPlayingClipComponent, self).show_notification( display_text, blink_text=MessageBoxText.SELECTED_CLIP_BLINK, notification_time=-1) def _selected_track_clip_is_playing(self): playing_clip_slot = self._playing_clip_slot() return not (playing_clip_slot and playing_clip_slot.clip != self.song.view.detail_clip) def _playing_clip_slot(self): track = self.song.view.selected_track try: playing_slot_index = track.playing_slot_index slot = track.clip_slots[ playing_slot_index] if 0 <= playing_slot_index < len( track.clip_slots) else None return slot except RuntimeError: pass return def _selected_track_clip_is_above_playing_clip(self): song_view = self.song.view track = song_view.selected_track playing_slot_index = track.playing_slot_index selected_index = index_if( lambda slot: slot == song_view.highlighted_clip_slot, track.clip_slots) return playing_slot_index <= selected_index def _update_mode(self): if not self._selected_track_clip_is_playing(): if self._selected_track_clip_is_above_playing_clip(): self.selected_mode = 'above' else: self.selected_mode = 'below' else: self.selected_mode = 'default' self._hide_notification()
class DrumGroupComponent(SlideableTouchStripComponent, DrumGroupComponent, Messenger): """' Class representing a drum group pads in a matrix. """ __module__ = __name__ matrix = control_matrix(PadControl) duplicate_button = ButtonControl() def __init__(self, quantizer=None, *a, **k): super(DrumGroupComponent, self).__init__(touch_slideable=self, translation_channel=PAD_FEEDBACK_CHANNEL, dragging_enabled=True, *a, **k) self._copy_handler = self._make_copy_handler() self._notification_reference = partial(nop, None) self._quantizer = quantizer self.selected_notes_provider = self.register_disconnectable( SelectedNotesProvider()) self._update_selected_drum_pad() return position_count = 32 page_length = 4 page_offset = 1 def update(self): super(DrumGroupComponent, self).update() if self._copy_handler: self._copy_handler.stop_copying() def set_drum_group_device(self, drum_group_device): super(DrumGroupComponent, self).set_drum_group_device(drum_group_device) self._on_chains_changed.subject = self._drum_group_device self.notify_contents() def quantize_pitch(self, note): self._quantizer.quantize_pitch(note, 'pad') def _update_selected_drum_pad(self): super(DrumGroupComponent, self)._update_selected_drum_pad() if liveobj_valid(self._selected_drum_pad): self.selected_notes_provider.selected_notes = [ self._selected_drum_pad.note ] self.notify_selected_target_note() def _update_assigned_drum_pads(self): super(DrumGroupComponent, self)._update_assigned_drum_pads() self.notify_selected_target_note() def _make_copy_handler(self): return DrumPadCopyHandler(self.show_notification) @matrix.pressed def matrix(self, pad): self._on_matrix_pressed(pad) @matrix.released def matrix(self, pad): self._on_matrix_released(pad) def _on_matrix_pressed(self, pad): super(DrumGroupComponent, self)._on_matrix_pressed(pad) if self.duplicate_button.is_pressed: self._duplicate_pad(pad) def set_select_button(self, button): self.select_button.set_control_element(button) def set_mute_button(self, button): self.mute_button.set_control_element(button) def set_solo_button(self, button): self.solo_button.set_control_element(button) def set_quantize_button(self, button): self.quantize_button.set_control_element(button) @duplicate_button.pressed def duplicate_button(self, button): self._set_control_pads_from_script(True) @duplicate_button.released def duplicate_button(self, button): self._set_control_pads_from_script(False) if self._copy_handler: self._copy_handler.stop_copying() if self._notification_reference() is not None: self._notification_reference().hide() return @listens('chains') def _on_chains_changed(self): self.notify_contents() def delete_pitch(self, drum_pad): clip = self.song.view.detail_clip if clip and any(clip.get_notes(0, drum_pad.note, DISTANT_FUTURE, 1)): clip.remove_notes(0, drum_pad.note, DISTANT_FUTURE, 1) self.show_notification(MessageBoxText.DELETE_NOTES % drum_pad.name) else: self.show_notification(MessageBoxText.DELETE_DRUM_RACK_PAD % drum_pad.name) self.delete_drum_pad_content(drum_pad) def delete_drum_pad_content(self, drum_pad): drum_pad.delete_all_chains() def _duplicate_pad(self, pad): if self._copy_handler and self._drum_group_device: drum_pad = self._pad_for_button(pad) self._notification_reference = self._copy_handler.duplicate_pad( self._drum_group_device, drum_pad) def set_matrix(self, matrix): super(DrumGroupComponent, self).set_matrix(matrix) self._update_sensitivity_profile() def _update_control_from_script(self): super(DrumGroupComponent, self)._update_control_from_script() self._update_sensitivity_profile() def _update_sensitivity_profile(self): profile = 'default' if self._takeover_pads or self.pressed_pads else 'drums' for button in self.matrix: button.sensitivity_profile = profile @listenable_property def selected_target_note(self): note_and_channel = (-1, -1) if liveobj_valid(self._drum_group_device) and liveobj_valid( self._selected_drum_pad): if self._selected_drum_pad in self.assigned_drum_pads: predicate = lambda b: self._pad_for_button( b) == self._selected_drum_pad button = find_if(predicate, self.matrix) if button != None and None not in (button.identifier, button.channel): note_and_channel = (button.identifier, button.channel) else: note_and_channel = (self._selected_drum_pad.note, NON_FEEDBACK_CHANNEL) return NamedTuple(note=note_and_channel[0], channel=note_and_channel[1])
class BrowserComponent(Component): """' Component for controlling the Live library browser. It has 4 browsing columns that are controlled by encoders and state buttons. The contents of these lists are provided by a browser model -- see BrowserModel and derivatives. """ __module__ = __name__ __events__ = (u'load_item', ) NUM_COLUMNS = 4 COLUMN_SIZE = 4 enter_button = ButtonControl(**consts.SIDE_BUTTON_COLORS) exit_button = ButtonControl(**consts.SIDE_BUTTON_COLORS) shift_button = ButtonControl() prehear_button = ToggleButtonControl(toggled_color='Browser.Prehear', untoggled_color='Browser.PrehearOff') def __init__(self, browser=None, make_browser_model=None, preferences=dict(), *a, **k): super(BrowserComponent, self).__init__(*a, **k) assert make_browser_model is not None self._browser = browser or self.application.browser self._browser_model = EmptyBrowserModel(browser=self._browser) self._make_browser_model = make_browser_model num_data_sources = self.NUM_COLUMNS * self.COLUMN_SIZE self._data_sources = map(DisplayDataSource, (u'', ) * num_data_sources) self._last_loaded_item = None self._default_item_formatter = DefaultItemFormatter() self._list_components = [ ListComponent(parent=self) for _ in xrange(self.NUM_COLUMNS) ] for i, component in enumerate(self._list_components): component.do_trigger_action = lambda item: self._do_load_item(item) component.last_action_item = lambda: self._last_loaded_item component.item_formatter = partial(self._item_formatter, i) self._preferences = preferences self._select_buttons = [] self._state_buttons = [] self._encoder_controls = [] self._on_selected_item.replace_subjects(self._list_components) self._on_list_item_action.replace_subjects(self._list_components) self._on_hotswap_target_changed.subject = self._browser self._on_filter_type_changed.subject = self._browser self._on_browser_full_refresh.subject = self._browser self._scroll_offset = 0 self._max_scroll_offset = 0 self._max_hierarchy = 0 self._last_filter_type = None self._skip_next_preselection = False self._browser_model_dirty = True self._on_content_lists_changed() self.prehear_button.is_toggled = preferences.setdefault( 'browser_prehear', True) self._last_selected_item = None return def disconnect(self): self._last_selected_item = None super(BrowserComponent, self).disconnect() return def set_display_line1(self, display): self.set_display_line_with_index(display, 0) def set_display_line2(self, display): self.set_display_line_with_index(display, 1) def set_display_line3(self, display): self.set_display_line_with_index(display, 2) def set_display_line4(self, display): self.set_display_line_with_index(display, 3) def set_display_line_with_index(self, display, index): if display: sources = self._data_sources[index::self.COLUMN_SIZE] display.set_data_sources(sources) def set_select_buttons(self, buttons): for button in buttons or []: if button: button.reset() self._on_select_matrix_value.subject = buttons or None self._select_buttons = buttons buttons = buttons or (None, None, None, None, None, None, None, None) for component, button in izip(self._list_components, buttons[1::2]): self._set_button_if_enabled(component, 'action_button', button) for component, button in izip(self._list_components, buttons[::2]): if self.shift_button.is_pressed: self._set_button_if_enabled(component, 'prev_page_button', button) self._set_button_if_enabled(component, 'select_prev_button', None) else: self._set_button_if_enabled(component, 'prev_page_button', None) self._set_button_if_enabled(component, 'select_prev_button', button) return def set_state_buttons(self, buttons): for button in buttons or []: if button: button.reset() self._on_state_matrix_value.subject = buttons or None self._state_buttons = buttons buttons = buttons or (None, None, None, None, None, None, None, None) for component, button in izip(self._list_components, buttons[::2]): if self.shift_button.is_pressed: self._set_button_if_enabled(component, 'next_page_button', button) self._set_button_if_enabled(component, 'select_next_button', None) else: self._set_button_if_enabled(component, 'next_page_button', None) self._set_button_if_enabled(component, 'select_next_button', button) for button in buttons[1::2]: if button and self.is_enabled(): button.set_light('DefaultButton.Disabled') return @shift_button.value def shift_button(self, value, control): self.set_select_buttons(self._select_buttons) self.set_state_buttons(self._state_buttons) def _set_button_if_enabled(self, component, name, button): control = getattr(component, name) if component.is_enabled(explicit=True): control.set_control_element(button) else: control.set_control_element(None) if button and self.is_enabled(): button.set_light('DefaultButton.Disabled') return def set_encoder_controls(self, encoder_controls): if encoder_controls: num_active_lists = len( self._browser_model.content_lists) - self._scroll_offset num_assignable_lists = min(num_active_lists, len(encoder_controls) / 2) index = 0 for component in self._list_components[:num_assignable_lists - 1]: component.encoders.set_control_element( encoder_controls[index:index + 2]) index += 2 self._list_components[(num_assignable_lists - 1)].encoders.set_control_element( encoder_controls[index:]) else: for component in self._list_components: component.encoders.set_control_element([]) self._encoder_controls = encoder_controls def update(self): super(BrowserComponent, self).update() if self.is_enabled(): self.set_state_buttons(self._state_buttons) self.set_select_buttons(self._select_buttons) self._update_browser_model() else: self._browser.stop_preview() def reset_load_memory(self): self._update_load_memory(None) return def _do_load_item(self, item): self.do_load_item(item) self._update_load_memory(item) self._skip_next_preselection = True def reset_skip_next_preselection(): self._skip_next_preselection = False self._tasks.add(task.run(reset_skip_next_preselection)) def _update_load_memory(self, item): self._last_loaded_item = item for component in self._list_components: component.update() def do_load_item(self, item): item.action() self.notify_load_item(item.content) def back_to_top(self): self._set_scroll_offset(0) def _set_scroll_offset(self, offset): self._scroll_offset = offset self._on_content_lists_changed() scrollable_list = self._list_components[(-1)].scrollable_list if scrollable_list: scrollable_list.request_notify_item_activated() def _update_navigation_button_state(self): self.exit_button.enabled = self._scroll_offset > 0 self.enter_button.enabled = self._scroll_offset < self._max_scroll_offset def _shorten_item_name(self, shortening_limit, list_index, item_name): u""" Creates the name of an item shortened by removing words from the parents name """ def is_short_enough(item_name): return len(item_name) <= 9 content_lists = self._browser_model.content_lists parent_lists = reversed(content_lists[max(0, list_index - 3):list_index]) for content_list in parent_lists: if is_short_enough(item_name): break parent_name = unicode(content_list.selected_item) stems = split_stem(parent_name) for stem in stems: short_name = make_stem_cleaner(stem)(item_name) short_name = full_strip(short_name) item_name = short_name if len(short_name) > 4 else item_name if is_short_enough(item_name): break if len(item_name) >= shortening_limit and item_name[( -1)] == consts.CHAR_ELLIPSIS: return item_name[:-1] return item_name def _item_formatter(self, depth, index, item, action_in_progress): display_string = '' separator_length = len(self._data_sources[(self.COLUMN_SIZE * depth)].separator) shortening_limit = 16 - separator_length if item: item_name = 'Loading...' if action_in_progress else self._shorten_item_name( shortening_limit, depth + self._scroll_offset, unicode(item)) display_string = consts.CHAR_SELECT if item and item.is_selected else ' ' display_string += item_name if depth == len( self._list_components ) - 1 and item.is_selected and self._scroll_offset < self._max_hierarchy: display_string = string.ljust(display_string, consts.DISPLAY_LENGTH / 4 - 1) shortening_limit += 1 display_string = display_string[:shortening_limit] + consts.CHAR_ARROW_RIGHT if depth == 0 and self._scroll_offset > 0: prefix = consts.CHAR_ARROW_LEFT if index == 0 else ' ' display_string = prefix + display_string return display_string[:shortening_limit + 1] @enter_button.pressed def enter_button(self, control): self._set_scroll_offset( min(self._max_scroll_offset, self._scroll_offset + 1)) @exit_button.pressed def exit_button(self, control): self._set_scroll_offset(max(0, self._scroll_offset - 1)) @prehear_button.toggled def prehear_button(self, toggled, button): if not toggled: self._browser.stop_preview() elif self._last_selected_item is not None: self._last_selected_item.preview() self._preferences['browser_prehear'] = toggled return @listens('hotswap_target') def _on_hotswap_target_changed(self): if not self._skip_next_preselection: self._set_scroll_offset(0) self._update_browser_model() @listens('filter_type') def _on_filter_type_changed(self): self._update_browser_model() @listens('full_refresh') def _on_browser_full_refresh(self): self._browser_model_dirty = True def _update_browser_model(self): if self.is_enabled(): self._do_update_browser_model() def _create_browser_model_of_type(self, filter_type): self._last_filter_type = filter_type new_model = self._make_browser_model(self._browser, filter_type) if self._browser_model and self._browser_model.can_be_exchanged( new_model) and new_model.can_be_exchanged(self._browser_model): self._browser_model.exchange_model(new_model) new_model.disconnect() else: self.disconnect_disconnectable(self._browser_model) self._browser_model = self.register_disconnectable(new_model) self._on_content_lists_changed.subject = self._browser_model self._on_selection_updated.subject = self._browser_model self._browser_model.update_content() def _do_update_browser_model(self): filter_type = filter_type_for_browser(self._browser) if filter_type != self._last_filter_type: self._create_browser_model_of_type(filter_type) elif self._browser_model_dirty: self._browser_model.update_content() elif not self._skip_next_preselection: self._browser_model.update_selection() self._skip_next_preselection = False self._browser_model_dirty = False @listens_group('item_action') def _on_list_item_action(self, item, _): self.notify_load_item(item.content) @listens_group('selected_item') def _on_selected_item(self, item, _): if item is not None and self.prehear_button.is_toggled: item.preview() self._last_selected_item = item return @listens('selection_updated') def _on_selection_updated(self, index): more_content_available = len(self._browser_model.content_lists ) > self.NUM_COLUMNS + self._scroll_offset required_scroll_offset = index - (self.NUM_COLUMNS - 1) if more_content_available and required_scroll_offset > self._scroll_offset: self._set_scroll_offset(self._scroll_offset + 1) self._browser_model.update_selection() @listens('content_lists') def _on_content_lists_changed(self): self._last_selected_item = None components = self._list_components contents = self._browser_model.content_lists[self._scroll_offset:] messages = self._browser_model.empty_list_messages scroll_depth = len(self._browser_model.content_lists) - len( self._list_components) self._max_scroll_offset = max(0, scroll_depth + 2) self._max_hierarchy = max(0, scroll_depth) for component, content, message in izip_longest( components, contents, messages): if component != None: component.scrollable_list = content component.empty_list_message = message active_lists = len(contents) num_head = clamp(active_lists - 1, 0, self.NUM_COLUMNS - 1) head = components[:num_head] last = components[num_head:] def set_data_sources_with_separator(component, sources, separator): for source in sources: source.separator = separator component.set_data_sources(sources) component.set_enabled(True) for idx, component in enumerate(head): offset = idx * self.COLUMN_SIZE sources = self._data_sources[offset:offset + self.COLUMN_SIZE] set_data_sources_with_separator(component, sources, '|') if last: offset = num_head * self.COLUMN_SIZE scrollable_list = last[0].scrollable_list if scrollable_list and find_if(lambda item: item.content.is_folder, scrollable_list.items): sources = self._data_sources[offset:offset + self.COLUMN_SIZE] map(DisplayDataSource.clear, self._data_sources[offset + self.COLUMN_SIZE:]) else: sources = self._data_sources[offset:] set_data_sources_with_separator(last[0], sources, '') for component in last[1:]: component.set_enabled(False) self.set_select_buttons(self._select_buttons) self.set_state_buttons(self._state_buttons) self.set_encoder_controls(self._encoder_controls) self._update_navigation_button_state() return @listens('value') def _on_select_matrix_value(self, value, *_): pass @listens('value') def _on_state_matrix_value(self, value, *_): pass @listens('value') def _on_encoder_matrix_value(self, value, *_): pass
class GenericDeviceComponent(DeviceComponentBase): parameter_touch_buttons = control_list(ButtonControl, control_count=8) shift_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 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 SpecialSessionComponent(SessionComponent): """ Special session subclass that handles ConfigurableButtons and has a button to fire the selected clip slot. """ _session_component_ends_initialisation = False scene_component_type = SpecialSceneComponent duplicate_button = ButtonControl() def __init__(self, clip_slot_copy_handler=None, fixed_length_recording=None, *a, **k): self._clip_copy_handler = clip_slot_copy_handler or ClipSlotCopyHandler( ) self._fixed_length_recording = fixed_length_recording with inject(copy_handler=const(self._clip_copy_handler), fixed_length_recording=const( self._fixed_length_recording)).everywhere(): super(SpecialSessionComponent, self).__init__(*a, **k) self._slot_launch_button = None self._duplicate_button = None self._duplicate = DuplicateSceneComponent(self._session_ring, parent=self) self._duplicate_enabler = EnablingModesComponent( parent=self, component=self._duplicate) self._end_initialisation() return duplicate_layer = forward_property(b'_duplicate')(b'layer') @duplicate_button.pressed def duplicate_button(self, button): self._duplicate_enabler.selected_mode = b'enabled' @duplicate_button.released def duplicate_button(self, button): self._duplicate_enabler.selected_mode = b'disabled' self._clip_copy_handler.stop_copying() def set_slot_launch_button(self, button): self._slot_launch_button = button self._on_slot_launch_value.subject = button def set_clip_launch_buttons(self, buttons): if buttons: buttons.reset() super(SpecialSessionComponent, self).set_clip_launch_buttons(buttons) def set_touch_strip(self, touch_strip): if touch_strip: touch_strip.set_mode(TouchStripModes.CUSTOM_FREE) touch_strip.send_state([ TouchStripStates.STATE_OFF for _ in xrange(touch_strip.state_count) ]) self._on_touch_strip_value.subject = touch_strip @listens(b'value') def _on_touch_strip_value(self, value): pass @listens(b'value') def _on_slot_launch_value(self, value): if self.is_enabled(): if value != 0 or not self._slot_launch_button.is_momentary(): if liveobj_valid(self.song.view.highlighted_clip_slot): self.song.view.highlighted_clip_slot.fire() self._slot_launch_button.turn_on() else: self._slot_launch_button.turn_off()
class MuteSoloStopClipComponent(CompoundComponent, Messenger): MESSAGE_FOR_MODE = { 'mute': 'Mute: %s', 'solo': 'Solo: %s', 'stop': 'Stop Clips: %s' } stop_all_clips_button = ButtonControl() def __init__(self, item_provider=None, solo_track_button=None, mute_track_button=None, stop_clips_button=None, track_list_component=None, cancellation_action_performers=[], *a, **k): super(MuteSoloStopClipComponent, self).__init__(*a, **k) self._currently_locked_button_handler = None self._track_list = track_list_component self._solo_button_handler = self.register_component( GlobalMixerActionComponent( track_list_component=track_list_component, mode='solo', mode_active_color='Mixer.SoloOn', mode_locked_color='Mixer.LockedSoloMode', default_color_indicator=TrackStateColorIndicator( item_provider=item_provider, track_property='solo', property_active_color='Mixer.SoloOn', song=self.song), immediate_action=lambda: toggle_mixable_solo( item_provider.selected_item, self.song))) self._solo_button_handler.layer = Layer( action_button=solo_track_button) self._mute_button_handler = self.register_component( GlobalMixerActionComponent( track_list_component=track_list_component, mode='mute', mode_active_color='Mixer.MuteOff', mode_locked_color='Mixer.LockedMuteMode', default_color_indicator=TrackStateColorIndicator( item_provider=item_provider, track_property='mute', property_active_color='Mixer.MuteOff', song=self.song), immediate_action=lambda: toggle_mixable_mute( item_provider.selected_item, self.song))) self._mute_button_handler.layer = Layer( action_button=mute_track_button) self._stop_button_handler = self.register_component( GlobalMixerActionComponent( track_list_component=track_list_component, mode='stop', immediate_action=partial(stop_clip_in_selected_track, self.song), mode_locked_color='StopClips.LockedStopMode')) self._stop_button_handler.layer = Layer( action_button=stop_clips_button) self.__on_mute_solo_stop_cancel_action_performed.replace_subjects( [track_list_component] + cancellation_action_performers) button_handlers = (self._mute_button_handler, self._solo_button_handler, self._stop_button_handler) self.__on_mode_locked_changed.replace_subjects(button_handlers, button_handlers) self.__on_selected_item_changed.subject = item_provider return @stop_all_clips_button.pressed def stop_all_clips_button(self, button): self.song.stop_all_clips() @listens_group('mute_solo_stop_cancel_action_performed') def __on_mute_solo_stop_cancel_action_performed(self, _): self.cancel_release_actions() @listens_group('mode_locked') def __on_mode_locked_changed(self, is_locked, button_handler): if is_locked: if self._currently_locked_button_handler: self._currently_locked_button_handler.cancel_locked_mode() self._currently_locked_button_handler = button_handler else: self._currently_locked_button_handler = None self._track_list.locked_mode = self._currently_locked_button_handler.mode if is_locked else None self._show_mode_lock_change_notification(is_locked, button_handler.mode) return @listens('selected_item') def __on_selected_item_changed(self): self.cancel_release_actions() def _show_mode_lock_change_notification(self, is_locked, mode): message_template = self.MESSAGE_FOR_MODE[mode] message_part = 'Locked' if is_locked else 'Unlocked' self.show_notification(message_template % message_part) def cancel_release_actions(self): self._solo_button_handler.cancel_release_action() self._mute_button_handler.cancel_release_action() self._stop_button_handler.cancel_release_action()
class FixedLengthSessionRecordingComponent(SessionRecordingComponent, Messenger): foot_switch_button = ButtonControl() arrangement_record_button = ButtonControl() capture_midi_button = ButtonControl() def __init__(self, clip_creator=None, fixed_length_setting=None, *a, **k): (super(FixedLengthSessionRecordingComponent, self).__init__)(*a, **k) self._fixed_length_recording = self.register_disconnectable( FixedLengthRecording(song=(self.song), clip_creator=clip_creator, fixed_length_setting=fixed_length_setting)) self.footswitch_toggles_arrangement_recording = False self._FixedLengthSessionRecordingComponent__on_record_mode_changed.subject = self.song self._FixedLengthSessionRecordingComponent__on_record_mode_changed() self.set_trigger_recording_on_release(not any(( self.record_button.is_pressed, self.arrangement_record_button.is_pressed))) def set_trigger_recording_on_release(self, trigger_recording): self._should_trigger_recording = trigger_recording @foot_switch_button.pressed def foot_switch_button(self, button): if self.footswitch_toggles_arrangement_recording: self._toggle_arrangement_recording() else: self._trigger_recording() @capture_midi_button.pressed def capture_midi_button(self, button): try: self.song.capture_midi() self.set_trigger_recording_on_release(not any(( self.record_button.is_pressed, self.arrangement_record_button.is_pressed))) except RuntimeError: pass @arrangement_record_button.pressed def arrangement_record_button(self, _): self._on_arrangement_record_button_pressed() @arrangement_record_button.released def arrangement_record_button(self, _): self._on_arrangement_record_button_released() def _toggle_arrangement_recording(self): self.song.record_mode = not self.song.record_mode def _on_record_button_pressed(self): pass def _on_record_button_released(self): self._trigger_recording_action(self._trigger_recording) def _on_arrangement_record_button_pressed(self): pass def _on_arrangement_record_button_released(self): self._trigger_recording_action(self._toggle_arrangement_recording) def _trigger_recording_action(self, recording_action): if self._should_trigger_recording: recording_action() self._should_trigger_recording = True def _clip_slot_index_to_record_into(self): song = self.song selected_scene = song.view.selected_scene return list(song.scenes).index(selected_scene) def _update_record_button(self): if self.is_enabled(): song = self.song clip_slot = get_clip_slot_from_index( song, song.view.selected_track, self._clip_slot_index_to_record_into()) if liveobj_valid(clip_slot): if clip_slot.is_triggered: if song.overdub and not clip_slot.is_recording: self.record_button.color = 'Recording.Transition' elif song.record_mode: self.record_button.color = 'Recording.ArrangementRecordingOn' else: super(FixedLengthSessionRecordingComponent, self)._update_record_button() self.arrangement_record_button.color = self.record_button.color @listens('record_mode') def __on_record_mode_changed(self): self._update_record_button() def _start_recording(self): track = self.song.view.selected_track clip_slot_index = self._clip_slot_index_to_record_into() self._fixed_length_recording.start_recording_in_slot( track, clip_slot_index) if track_can_record(track): self._ensure_slot_is_visible(self.song.view.selected_track, clip_slot_index) def _ensure_slot_is_visible(self, track, scene_index): song = self.song if song.view.selected_track == track: song.view.selected_scene = song.scenes[scene_index] self._view_selected_clip_detail() def _handle_limitation_error_on_scene_creation(self): self.expect_dialog(MessageBoxText.SCENE_LIMIT_REACHED)
class SimpleDeviceParameterComponent(Component): prev_bank_button = ButtonControl() next_bank_button = ButtonControl() device_lock_button = ToggleButtonControl() device_on_off_button = ToggleButtonControl() device_name_display = DisplayControl() @depends(device_provider=None) def __init__(self, device_provider = None, device_bank_registry = None, toggle_lock = None, *a, **k): super(SimpleDeviceParameterComponent, self).__init__(*a, **k) self._toggle_lock = toggle_lock self._device = None self._banks = [] self._bank_index = 0 self._parameter_controls = None self._empty_control_slots = self.register_disconnectable(EventObject()) self._device_bank_registry = device_bank_registry self._device_provider = device_provider self._device_name_slot = self.register_slot(None, self._update_device_name_display, u'name') self.__on_provided_device_changed.subject = device_provider self.__on_provided_device_changed() self._display_bank_name_task = self._tasks.add(task.sequence(task.run(self._display_bank_name), task.wait(BANK_NAME_DISPLAY_DURATION), task.run(self._update_device_name_display))) self._display_bank_name_task.kill() if toggle_lock: self.__on_is_locked_to_device_changed.subject = self._device_provider self.__on_is_locked_to_device_changed() @device_lock_button.toggled def device_lock_button(self, *_): self._on_device_lock_button_toggled() @device_on_off_button.toggled def device_on_off_button(self, is_toggled, _): parameter = self._on_off_parameter() if parameter is not None: parameter.value = float(is_toggled) def _on_device_lock_button_toggled(self): self._toggle_lock() self._update_device_lock_button() @prev_bank_button.pressed def prev_bank_button(self, _): self.bank_index = self._bank_index - 1 @next_bank_button.pressed def next_bank_button(self, _): self.bank_index = self._bank_index + 1 @property def bank_index(self): return self._bank_index @bank_index.setter def bank_index(self, value): prev_bank_index = self._bank_index self._bank_index = self._clamp_to_bank_size(value) if prev_bank_index != self._bank_index: self._display_bank_name_task.restart() if self._device_bank_registry: self._device_bank_registry.set_device_bank(self._device, self._bank_index) self.update() def _clamp_to_bank_size(self, value): return clamp(value, 0, self.num_banks - 1) @property def selected_bank(self): if self.num_banks: return self._banks[self._bank_index or 0] return [] @property def num_banks(self): return len(self._banks) def set_parameter_controls(self, controls): for control in self._parameter_controls or []: release_control(control) self._parameter_controls = controls self.update() @listens(u'device') def __on_provided_device_changed(self): for control in self._parameter_controls or []: release_control(control) self._device = self._device_provider.device self.__on_bank_changed.subject = self._device_bank_registry if self._device_bank_registry: self._bank_index = self._device_bank_registry.get_device_bank(self._device) self.update() else: self.bank_index = 0 self._device_name_slot.subject = self._device self._update_device_name_display() @listens(u'device_bank') def __on_bank_changed(self, device, bank): if device == self._device: self.bank_index = bank @listens(u'is_locked_to_device') def __on_is_locked_to_device_changed(self): self._update_device_lock_button() @listens(u'value') def __on_device_on_off_changed(self): self._update_device_on_off_button() def update(self): super(SimpleDeviceParameterComponent, self).update() if self.is_enabled(): self._update_parameter_banks() self._update_bank_navigation_buttons() self._empty_control_slots.disconnect() self.__on_device_on_off_changed.subject = self._on_off_parameter() self._update_device_on_off_button() if liveobj_valid(self._device): self._connect_parameters() else: self._disconnect_parameters() else: self._disconnect_parameters() def _disconnect_parameters(self): for control in self._parameter_controls or []: release_control(control) self._empty_control_slots.register_slot(control, nop, u'value') def _connect_parameters(self): for control, parameter in zip_longest(self._parameter_controls or [], self.selected_bank): if liveobj_valid(control): if liveobj_valid(parameter): control.connect_to(parameter) else: control.release_parameter() self._empty_control_slots.register_slot(control, nop, u'value') def _on_off_parameter(self): if liveobj_valid(self._device): for p in self._device.parameters: if p.name.startswith(u'Device On') and liveobj_valid(p) and p.is_enabled: return p def _update_parameter_banks(self): if liveobj_valid(self._device): self._banks = parameter_banks(self._device) else: self._banks = [] self._bank_index = self._clamp_to_bank_size(self._bank_index) def _update_bank_navigation_buttons(self): self.prev_bank_button.enabled = self.bank_index > 0 self.next_bank_button.enabled = self.bank_index < self.num_banks - 1 def _update_device_lock_button(self): self.device_lock_button.is_toggled = self._device_provider.is_locked_to_device def _update_device_on_off_button(self): parameter = self._on_off_parameter() self.device_on_off_button.enabled = parameter is not None if parameter is not None: self.device_on_off_button.is_toggled = parameter.value > 0 def _update_device_name_display(self): self.device_name_display.message = self._device.name if liveobj_valid(self._device) else u' - ' def _display_bank_name(self): names = parameter_bank_names(self._device) if self.bank_index < len(names): self.device_name_display.message = names[self.bank_index]