class UserComponent(ActionWithSettingsComponent): __subject_events__ = (SubjectEvent(name='mode', doc=' Called when the mode changes '), SubjectEvent(name='before_mode_sent', doc=' Called before the mode is sent'), SubjectEvent(name='after_mode_sent', doc=' Called after the mode is sent ')) settings_layer = forward_property('_settings')('layer') settings = forward_property('_settings')('settings') def __init__(self, value_control=None, *a, **k): super(UserComponent, self).__init__(*a, **k) raise value_control != None or AssertionError self._settings = self.register_component(UserSettingsComponent()) self._settings.set_enabled(False) self._value_control = value_control self._on_value.subject = self._value_control self._selected_mode = Sysex.LIVE_MODE def show_settings(self): self._settings.set_enabled(True) return True def hide_settings(self): self._settings.set_enabled(False) def set_settings_info_text(self, text): self._settings.set_info_text(text) def post_trigger_action(self): self.mode = Sysex.LIVE_MODE if self.mode == Sysex.USER_MODE else Sysex.USER_MODE @subject_slot('value') def _on_value(self, value): mode = value[0] self._selected_mode = mode self.notify_mode(mode) def _get_mode(self): return self._selected_mode def _set_mode(self, mode): if mode != self._selected_mode: self._selected_mode = mode if self.is_enabled(): self._apply_mode(self._selected_mode) mode = property(_get_mode, _set_mode) def update(self): super(UserComponent, self).update() if self.is_enabled(): self._apply_mode(self._selected_mode) def _apply_mode(self, mode): self.notify_before_mode_sent(mode) self._value_control.send_value((mode, )) self.notify_after_mode_sent(mode)
class QuantizationComponent(ActionWithSettingsComponent, Messenger): settings_layer = forward_property('_settings')('layer') def __init__(self, *a, **k): super(QuantizationComponent, self).__init__(*a, **k) self._settings = self.register_component(QuantizationSettingsComponent()) self._settings.set_enabled(False) self._cancel_quantize = False def quantize_pitch(self, note): clip = self.song().view.detail_clip if clip: clip.quantize_pitch(note, self._settings._quantize_to, self._settings._quantize_amount) self.show_notification(MessageBoxText.QUANTIZE_CLIP_PITCH % dict(amount=self._settings._quantize_amount_display.display_string(), to=self._settings._quantize_to_display.display_string())) self._cancel_quantize = True def show_settings(self): self._settings.set_enabled(True) return True def hide_settings(self): self._settings.set_enabled(False) self._cancel_quantize = False def post_trigger_action(self): clip = self.song().view.detail_clip if clip and not self._cancel_quantize: clip.quantize(self._settings._quantize_to, self._settings._quantize_amount) self.show_notification(MessageBoxText.QUANTIZE_CLIP % dict(amount=self._settings._quantize_amount_display.display_string(), to=self._settings._quantize_to_display.display_string())) self._cancel_quantize = False
class ValueComponentBase(CompoundComponent): """ Component to control one continuous property with a infinite touch-sensitive encoder. You can optionally give it a display and a button such that the value will be displayed while its pressed. """ def create_display_component(self, *a, **k): raise NotImplementedError def __init__(self, display_label=' ', display_seg_start=0, *a, **k): super(ValueComponentBase, self).__init__(*a, **k) self._button = None self._display = self.register_component( self.create_display_component(display_label=display_label, display_seg_start=display_seg_start)) self._display.set_enabled(False) display_layer = forward_property('_display')('layer') def set_encoder(self, encoder): raise NotImplementedError def set_button(self, button): self._button = button self._on_button_value.subject = button self._display.set_enabled(button and button.is_pressed()) @subject_slot('value') def _on_button_value(self, value): button = self._on_button_value.subject self._display.set_enabled(button.is_pressed()) def update(self): button = self._on_button_value.subject self._display.set_enabled(button and button.is_pressed())
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 def __init__(self, *a, **k): super(SpecialSessionComponent, self).__init__(*a, **k) self._slot_launch_button = None self._duplicate_button = None self._duplicate = self.register_component( DuplicateSceneComponent(self)) self._duplicate_enabler = self.register_component( EnablingModesComponent(component=self._duplicate)) self._duplicate_enabler.momentary_toggle = True self._end_initialisation() duplicate_layer = forward_property('_duplicate')('layer') def set_duplicate_button(self, button): self._duplicate_enabler.set_toggle_button(button) 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([ TouchStripElement.STATE_OFF for _ in xrange(TouchStripElement.STATE_COUNT) ]) self._on_touch_strip_value.subject = touch_strip @subject_slot('value') def _on_touch_strip_value(self, value): pass @subject_slot('value') def _on_slot_launch_value(self, value): if self.is_enabled(): if value != 0 or not self._slot_launch_button.is_momentary(): if self.song().view.highlighted_clip_slot != None: self.song().view.highlighted_clip_slot.fire() self._slot_launch_button.turn_on() else: self._slot_launch_button.turn_off() def set_show_highlight(self, show_highlight): super(SpecialSessionComponent, self).set_show_highlight(True)
class NoteRepeatComponent(ModesComponent): """ Component for setting up the note repeat """ def __init__(self, *a, **k): super(NoteRepeatComponent, self).__init__(*a, **k) self._last_record_quantization = None self._note_repeat = None self._options = self.register_component(OptionsComponent()) self._options.set_enabled(False) self._options.selected_color = 'NoteRepeat.RateSelected' self._options.unselected_color = 'NoteRepeat.RateUnselected' self._options.default_option_names = map(str, range(8)) self._options.selected_option = 5 self.add_mode('disabled', None) self.add_mode('enabled', [self._options, (self._enable_note_repeat, self._disable_note_repeat)], 'DefaultButton.On') self.selected_mode = 'disabled' self._on_selected_option_changed.subject = self._options self.set_note_repeat(None) options_layer = forward_property('_options')('layer') def set_note_repeat(self, note_repeat): if not note_repeat: note_repeat = DummyNoteRepeat() self._note_repeat.enabled = self._note_repeat != None and False self._note_repeat = note_repeat self._update_note_repeat(enabled=self.selected_mode == 'enabled') def _enable_note_repeat(self): self._last_record_quantization = self._song.midi_recording_quantization self._set_recording_quantization(False) self._update_note_repeat(enabled=True) def _disable_note_repeat(self): if not self.song().midi_recording_quantization and self._last_record_quantization: self._set_recording_quantization(self._last_record_quantization) self._update_note_repeat(enabled=False) def _set_recording_quantization(self, value): def doit(): self.song().midi_recording_quantization = value self._tasks.parent_task.add(Task.run(doit)) @subject_slot('selected_option') def _on_selected_option_changed(self, option): frequency = NOTE_REPEAT_FREQUENCIES[option] self._note_repeat.repeat_rate = 1.0 / frequency * 4.0 def _update_note_repeat(self, enabled = False): self._on_selected_option_changed(self._options.selected_option) self._note_repeat.enabled = enabled
class DialogComponent(CompoundComponent): """ Handles representing modal dialogs from the application. The script can also request dialogs. """ def __init__(self, *a, **k): super(DialogComponent, self).__init__(*a, **k) self._message_box = self.register_component(MessageBoxComponent()) self._message_box.set_enabled(False) self._next_message = None self._on_open_dialog_count.subject = self.application() self._on_message_cancel.subject = self._message_box message_box_layer = forward_property('_message_box')('layer') def expect_dialog(self, message): """ Expects a dialog from Live to appear soon. The dialog will be shown on the controller with the given message regardless of wether a dialog actually appears. This dialog can be cancelled. """ self._next_message = message self._update_dialog() @subject_slot('open_dialog_count') def _on_open_dialog_count(self): self._update_dialog(open_dialog_changed=True) self._next_message = None @subject_slot('cancel') def _on_message_cancel(self): self._next_message = None try: self.application().press_current_dialog_button(0) except RuntimeError: pass self._update_dialog() def _update_dialog(self, open_dialog_changed=False): message = self._next_message or MessageBoxText.LIVE_DIALOG can_cancel = self._next_message != None self._message_box.text = message self._message_box.can_cancel = can_cancel self._message_box.set_enabled( self.application().open_dialog_count > 0 or not open_dialog_changed and self._next_message) def update(self): pass
class ValueComponentBase(CompoundComponent): """ Component to control one continuous property with a infinite touch-sensitive encoder. You can optionally give it a display and a button such that the value will be displayed while its pressed. """ def create_display_component(self, *a, **k): raise NotImplementedError encoder = EncoderControl() def __init__(self, display_label=' ', display_seg_start=0, *a, **k): super(ValueComponentBase, self).__init__(*a, **k) self._display = self.register_component( self.create_display_component(display_label=display_label, display_seg_start=display_seg_start)) self._display.set_enabled(False) display_layer = forward_property('_display')('layer') @encoder.touched def encoder(self, encoder): self._update_display_state() @encoder.released def encoder(self, encoder): self._update_display_state() @encoder.value def encoder(self, value, encoder): self._on_value(value) def _on_value(self, value): pass def _update_display_state(self): self._display.set_enabled(self.encoder.is_touched)
class InstrumentScalesComponent(CompoundComponent): __subject_events__ = ('scales_changed',) is_absolute = False is_diatonic = True key_center = 0 def __init__(self, *a, **k): super(InstrumentScalesComponent, self).__init__(*a, **k) self._key_center_slots = self.register_slot_manager() self._key_center_buttons = [] self._encoder_touch_button_slots = self.register_slot_manager() self._encoder_touch_buttons = [] self._top_key_center_buttons = None self._bottom_key_center_buttons = None self._absolute_relative_button = None self._diatonic_chromatic_button = None table = consts.MUSICAL_MODES self._info_sources = map(DisplayDataSource, ('Scale selection:', '', '')) self._line_sources = recursive_map(DisplayDataSource, (('', '', '', '', '', '', ''), ('', '', '', '', '', '', ''))) self._modus_sources = map(partial(DisplayDataSource, adjust_string_fn=adjust_string_crop), ('', '', '', '')) self._presets = self.register_component(InstrumentPresetsComponent()) self._presets_enabler = self.register_component(EnablingModesComponent(component=self._presets, toggle_value='Scales.PresetsEnabled')) self._presets_enabler.momentary_toggle = True self._presets.selected_mode = 'scale_p4_vertical' self._modus_list = self.register_component(ListComponent(data_sources=self._modus_sources)) self._modus_list.scrollable_list.fixed_offset = 1 self._modus_list.scrollable_list.assign_items([ Modus(name=table[i], notes=table[i + 1]) for i in xrange(0, len(consts.MUSICAL_MODES), 2) ]) self._on_selected_modus.subject = self._modus_list.scrollable_list self._update_data_sources() presets_layer = forward_property('_presets')('layer') @property def modus(self): return self._modus_list.scrollable_list.selected_item.content @property def available_scales(self): return self.modus.scales(KEY_CENTERS) @property def notes(self): return self.modus.scale(self.key_center).notes def set_modus_line1(self, display): self._set_modus_line(display, 0) def set_modus_line2(self, display): self._set_modus_line(display, 1) def set_modus_line3(self, display): self._set_modus_line(display, 2) def set_modus_line4(self, display): self._set_modus_line(display, 3) def _set_modus_line(self, display, index): if display: display.set_data_sources([self._modus_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]) def set_presets_toggle_button(self, button): raise button is None or button.is_momentary() or AssertionError self._presets_enabler.set_toggle_button(button) 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_modus_up_button(buttons[0]) else: self.set_absolute_relative_button(None) self._top_key_center_buttons = None self.set_modus_up_button(None) if self._top_key_center_buttons and self._bottom_key_center_buttons: self.set_key_center_buttons(self._top_key_center_buttons + self._bottom_key_center_buttons) else: self.set_key_center_buttons(tuple()) def set_bottom_buttons(self, buttons): if buttons: buttons.reset() self.set_diatonic_chromatic_button(buttons[7]) self._bottom_key_center_buttons = buttons[1:7] self.set_modus_down_button(buttons[0]) else: self.set_diatonic_chromatic_button(None) self._bottom_key_center_buttons = None self.set_modus_down_button(None) if self._top_key_center_buttons and self._bottom_key_center_buttons: self.set_key_center_buttons(self._top_key_center_buttons + self._bottom_key_center_buttons) else: self.set_key_center_buttons([]) def set_modus_down_button(self, button): self._modus_list.select_next_button.set_control_element(button) def set_modus_up_button(self, button): self._modus_list.select_prev_button.set_control_element(button) def set_encoder_controls(self, encoders): self._modus_list.encoders.set_control_element([encoders[0]] if encoders else []) def set_key_center_buttons(self, buttons): if not (not buttons or len(buttons) == 12): raise AssertionError buttons = buttons or [] self._key_center_buttons = self._key_center_buttons != buttons and buttons self._key_center_slots.disconnect() for button in buttons: self._key_center_slots.register_slot(button, self._on_key_center_button_value, 'value', extra_kws=dict(identify_sender=True)) self._update_key_center_buttons() def set_absolute_relative_button(self, absolute_relative_button): if 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): if 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() 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.key_center = KEY_CENTERS[index] self._update_key_center_buttons() self._update_data_sources() self.notify_scales_changed() @subject_slot('value') def _on_absolute_relative_value(self, value): if self.is_enabled(): if value != 0 or not self._absolute_relative_button.is_momentary(): self.is_absolute = not self.is_absolute self._update_absolute_relative_button() self._update_data_sources() self.notify_scales_changed() @subject_slot('value') def _on_diatonic_chromatic_value(self, value): if self.is_enabled(): if value != 0 or not self._diatonic_chromatic_button.is_momentary(): self.is_diatonic = not self.is_diatonic self._update_diatonic_chromatic_button() self._update_data_sources() self.notify_scales_changed() @subject_slot('selected_item') def _on_selected_modus(self): self._update_data_sources() self.notify_scales_changed() 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() def _update_key_center_buttons(self): if self.is_enabled(): for index, button in enumerate(self._key_center_buttons): if button: button.set_on_off_values('Scales.Selected', 'Scales.Unselected') button.set_light(self.key_center == KEY_CENTERS[index]) def _update_absolute_relative_button(self): if self.is_enabled() and self._absolute_relative_button != None: self._absolute_relative_button.set_on_off_values('Scales.FixedOn', 'Scales.FixedOff') self._absolute_relative_button.set_light(self.is_absolute) def _update_diatonic_chromatic_button(self): if self.is_enabled() and self._diatonic_chromatic_button != None: self._diatonic_chromatic_button.set_on_off_values('Scales.Diatonic', 'Scales.Chromatic') self._diatonic_chromatic_button.set_light(self.is_diatonic) def _update_data_sources(self): key_index = list(KEY_CENTERS).index(self.key_center) 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.is_absolute else 'Fixed: N') self._line_sources[1][6].set_display_string('In Key' if self.is_diatonic else 'Chromatc') self._info_sources[1].set_display_string(str(self._modus_list.scrollable_list.selected_item))
class InstrumentComponent(CompoundComponent): """ Class that sets up the button matrix as a piano, using different selectable layouts for the notes. """ midi_channels = range(5, 13) def __init__(self, *a, **k): super(InstrumentComponent, self).__init__(*a, **k) self._matrix = None self._octave_index = 3 self._scales = self.register_component(InstrumentScalesComponent()) self._scales.set_enabled(False) self._scales_modes = self.register_component(ModesComponent()) self._scales_modes.add_mode('disabled', None) self._scales_modes.add_mode('enabled', self._scales, 'DefaultButton.On') self._scales_modes.selected_mode = 'disabled' self._paginator = self.register_component(ScrollComponent()) self._paginator.can_scroll_up = self._can_scroll_octave_up self._paginator.can_scroll_down = self._can_scroll_octave_down self._paginator.scroll_up = self._scroll_octave_up self._paginator.scroll_down = self._scroll_octave_down self.register_slot(self._scales, self._update_matrix, 'scales_changed') self.register_slot(self._scales._presets, lambda _: self._update_matrix(), 'selected_mode') scales_layer = forward_property('_scales')('layer') @subject_slot('value') def _on_matrix_value(self, *a): pass def set_matrix(self, matrix): self._matrix = matrix self._on_matrix_value.subject = matrix if matrix: matrix.reset() self._update_matrix() def set_touch_strip(self, control): if control: control.reset() def set_scales_toggle_button(self, button): raise button is None or button.is_momentary() or AssertionError self._scales_modes.set_toggle_button(button) def set_presets_toggle_button(self, button): self._scales.set_presets_toggle_button(button) def set_octave_up_button(self, button): self._paginator.set_scroll_up_button(button) def set_octave_down_button(self, button): self._paginator.set_scroll_down_button(button) def _can_scroll_octave_up(self): return self._octave_index + self._scales._presets.octave_index_offset < 10 def _can_scroll_octave_down(self): return self._octave_index + self._scales._presets.octave_index_offset > -2 def _scroll_octave_up(self): if self._can_scroll_octave_up(): self._octave_index += 1 self._update_matrix() def _scroll_octave_down(self): if self._can_scroll_octave_down(): self._octave_index -= 1 self._update_matrix() def update(self): if self.is_enabled(): self._update_matrix() def _update_matrix(self): self._setup_instrument_mode(self._scales._presets.interval) def _setup_instrument_mode(self, interval): if self.is_enabled() and self._matrix: self._matrix.reset() pattern = self._get_pattern(interval) max_j = self._matrix.width() - 1 for button, (i, j) in self._matrix.iterbuttons(): if button: button.sensitivity_profile = 'instrument' note_info = pattern.note(i, max_j - j) if note_info.index != None: button.set_on_off_values(note_info.color, 'Instrument.NoteOff') button.turn_on() button.set_enabled(False) button.set_channel(note_info.channel) button.set_identifier(note_info.index) else: button.set_channel(NON_FEEDBACK_CHANNEL) button.set_light(note_info.color) button.set_enabled(True) def _get_pattern(self, interval): notes = self._scales.notes if not self._scales.is_absolute: origin = 0 elif self._scales.is_diatonic: origin = 0 for k in xrange(len(notes)): if notes[k] >= 12: origin = k - len(notes) break else: origin = -notes[0] if interval == None: interval = 8 elif not self._scales.is_diatonic: interval = [0, 2, 4, 5, 7, 9, 10, 11][interval] if self._scales._presets.is_horizontal: steps = [1, interval] origin = [origin, 0] else: steps = [interval, 1] origin = [0, origin] return MelodicPattern(steps=steps, scale=notes, origin=origin, base_note=self._octave_index * 12, chromatic_mode=not self._scales.is_diatonic)
class DeviceNavigationComponent(CompoundComponent): """ Component that displays an overview of the devices in the current track and navigates in its hierarchy. """ def __init__(self, device_bank_registry=None, *a, **k): super(DeviceNavigationComponent, self).__init__(*a, **k) self._make_navigation_node = partial( make_navigation_node, device_bank_registry=device_bank_registry) self._state_buttons = None self._delete_button = None self._device_list = self.register_component( ScrollableListWithTogglesComponent()) self._on_selection_clicked_in_controller.subject = self._device_list self._on_selection_changed_in_controller.subject = self._device_list self._on_state_changed_in_controller.subject = self._device_list self._current_node = None self._message_box = self.register_component(MessageBoxComponent()) self._message_box.text = consts.MessageBoxText.EMPTY_DEVICE_CHAIN self._message_box.set_enabled(False) self._selected_track = None self._on_selected_track_changed.subject = self.song().view with inject(selection=lambda: NamedTuple(selected_device=None) ).everywhere(): self._on_selected_track_changed() info_layer = forward_property('_message_box')('layer') @property def current_node(self): return self._current_node def set_select_buttons(self, select_buttons): self._device_list.set_select_buttons(select_buttons) def set_state_buttons(self, state_buttons): self._state_buttons = state_buttons self._device_list.set_state_buttons(state_buttons) def set_exit_button(self, exit_button): raise not exit_button or exit_button.is_momentary() or AssertionError self._on_exit_value.subject = exit_button self._update_exit_button() def set_enter_button(self, enter_button): raise not enter_button or enter_button.is_momentary() or AssertionError self._on_enter_value.subject = enter_button self._update_enter_button() def set_delete_button(self, button): raise not button or button.is_momentary() or AssertionError self._delete_button = button def set_display_line(self, line): self._device_list.set_display_line(line) def set_blank_display_line(self, line): if line: line.reset() @property def selected_object(self): selected = None if self._current_node: children = self._current_node.children option = self._device_list.selected_option if children and in_range(option, 0, len(children)): _, selected = children[option] return selected def back_to_top(self): if consts.PROTO_SONG_IS_ROOT: self._set_current_node(self._make_navigation_node(self.song())) else: self._set_current_node( self._make_navigation_node(self._selected_track)) @subject_slot('selected_track') def _on_selected_track_changed(self): self._selected_track = self.song().view.selected_track self._on_selected_device_changed.subject = self._selected_track.view self.back_to_top() @subject_slot('selected_device') def _on_selected_device_changed(self): selected_device = self._selected_track.view.selected_device if selected_device == None: self._set_current_node(self._make_exit_node()) return is_just_default_child_selection = False if self._current_node and self._current_node.children: selected = self.selected_object if isinstance(selected, Live.DrumPad.DrumPad) and find_if( lambda pad: pad.chains and pad.chains[0].devices and pad. chains[0].devices[0] == selected_device, selected.canonical_parent.drum_pads): is_just_default_child_selection = True if isinstance( selected, Live.Chain.Chain ) and selected_device and selected_device.canonical_parent == selected and selected.devices[ 0] == selected_device: is_just_default_child_selection = True if not is_just_default_child_selection: if selected_device: target = selected_device.canonical_parent node = (not self._current_node or self._current_node.object != target) and self._make_navigation_node( target, is_entering=False) self._set_current_node(node) def _set_current_node(self, node): if node is None: return self.disconnect_disconnectable(self._current_node) self._current_node = node self.register_slot_manager(node) self._on_children_changed_in_node.subject = node self._on_selected_child_changed_in_node.subject = node self._on_state_changed_in_node.subject = node self._on_children_changed_in_node() for index, value in enumerate(node.state): self._on_state_changed_in_node(index, value) node.preselect() @depends(selection=lambda: NamedTuple(selected_device=None)) def _update_info(self, selection=None): if self._selected_track != None and len( self._selected_track.devices ) == 0 and selection.selected_device == None: self._message_box.set_enabled(True) else: self._message_box.set_enabled(False) def update(self): if self.is_enabled(): self._update_enter_button() self._update_exit_button() self._update_info() @subject_slot('state') def _on_state_changed_in_node(self, index, value): self._device_list.set_option_state(index, value) @subject_slot('children') def _on_children_changed_in_node(self): names = map(lambda x: x[0], self._current_node.children) self._device_list.option_names = names self._device_list.selected_option = self._current_node.selected_child self._update_enter_button() self._update_exit_button() @subject_slot('selected_child') def _on_selected_child_changed_in_node(self, index): self._device_list.selected_option = index self._update_enter_button() self._update_exit_button() self._update_info() @subject_slot('toggle_option') def _on_state_changed_in_controller(self, index, value): if self._current_node: self._current_node.set_state(index, value) if self._current_node.state[index] != value: self._device_list.set_option_state( index, self._current_node.state[index]) @subject_slot('change_option') def _on_selection_changed_in_controller(self, value): self._current_node.selected_child = value self._update_enter_button() self._update_exit_button() @subject_slot('press_option', in_front=True) def _on_selection_clicked_in_controller(self, index): if self._delete_button and self._delete_button.is_pressed(): if self._current_node: self._current_node.delete_child(index) return True elif consts.PROTO_FAST_DEVICE_NAVIGATION: if self._device_list.selected_option == index: self._set_current_node(self._make_enter_node()) return True elif not in_range(index, 0, len(self._device_list.option_names)): self._set_current_node(self._make_exit_node()) return True return index == None @subject_slot('value') def _on_enter_value(self, value): if self.is_enabled(): self._update_enter_button() if value: self._set_current_node(self._make_enter_node()) @subject_slot('value') def _on_exit_value(self, value): if self.is_enabled(): self._update_exit_button() if value: self._set_current_node(self._make_exit_node()) def _make_enter_node(self): if self._device_list.selected_option >= 0: if self._device_list.selected_option < len( self._current_node.children): child = self._current_node.children[ self._device_list.selected_option][1] return self._make_navigation_node(child, is_entering=True) def _make_exit_node(self): return self._make_navigation_node(self._current_node and self._current_node.parent, is_entering=False) def _update_enter_button(self): button = self._on_enter_value.subject if self.is_enabled() and button: with disconnectable(self._make_enter_node()) as node: if node: button.set_light(button.is_pressed()) else: button.set_light('DefaultButton.Disabled') def _update_exit_button(self): button = self._on_exit_value.subject if self.is_enabled() and button: with disconnectable(self._make_exit_node()) as node: if node: button.set_light(button.is_pressed()) else: button.set_light('DefaultButton.Disabled')
class AutoArmComponent(CompoundComponent): """ Component that implictly arms tracks to keep the selected track always armed while there is no compatible red-armed track. """ def __init__(self, *a, **k): super(AutoArmComponent, self).__init__(*a, **k) self._auto_arm_restore_behaviour = AutoArmRestoreBehaviour( auto_arm=self) self._notification = self.register_component( NotificationComponent(notification_time=10.0)) self._on_tracks_changed.subject = self.song() self._on_exclusive_arm_changed.subject = self.song() self._on_tracks_changed() notification_layer = forward_property('_notification')('message_box_layer') @property def auto_arm_restore_behaviour(self): return self._auto_arm_restore_behaviour def can_auto_arm_track(self, track): return track.can_be_armed and track.has_midi_input def on_selected_track_changed(self): self.update() def _update_notification(self): if self.needs_restore_auto_arm: self._notification.show_notification( ' Press [Note] to arm the track: ' + self.song().view.selected_track.name, blink_text=' Press to arm the track: ' + self.song().view.selected_track.name) else: self._notification.hide_notification() def update(self): song = self.song() enabled = self.is_enabled() and not self.needs_restore_auto_arm selected_track = song.view.selected_track for track in song.tracks: if self.can_auto_arm_track(track): track.implicit_arm = enabled and selected_track == track self._auto_arm_restore_behaviour.update() self._update_notification() def restore_auto_arm(self): song = self.song() exclusive_arm = song.exclusive_arm for track in song.tracks: if exclusive_arm or self.can_auto_arm_track(track): if track.can_be_armed: track.arm = False @property def needs_restore_auto_arm(self): song = self.song() exclusive_arm = song.exclusive_arm return self.is_enabled() and self.can_auto_arm_track( song.view.selected_track ) and not song.view.selected_track.arm and any( ifilter( lambda track: (exclusive_arm or self.can_auto_arm_track(track)) and track.can_be_armed and track.arm, song.tracks)) @subject_slot('tracks') def _on_tracks_changed(self): tracks = filter(lambda t: t.can_be_armed, self.song().tracks) self._on_arm_changed.replace_subjects(tracks) self._on_current_input_routing_changed.replace_subjects(tracks) @subject_slot('exclusive_arm') def _on_exclusive_arm_changed(self): self.update() @subject_slot_group('arm') def _on_arm_changed(self, track): self.update() @subject_slot_group('current_input_routing') def _on_current_input_routing_changed(self, track): self.update()
class ListComponent(CompoundComponent): """ Component that handles a ScrollableList. If an action button is passed, it can handle an ActionList. """ __subject_events__ = ('item_action', ) 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() @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() 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() @subject_slot('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))) @subject_slot('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) @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) 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() def _execute_action(self): """ Is called by the execute action task and should not be called directly use _trigger_action instead """ if self._current_action_item != None: self.do_trigger_action(self._current_action_item) self._last_action_item = self._current_action_item self._current_action_item = None self.update() @property def selected_item(self): return self._scrollable_list.selected_item if self._scrollable_list != None else None @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 def _update_display(self): visible_items = self._scrollable_list.visible_items if self._scrollable_list else [] for index, data_source in enumerate(self._data_sources): item = visible_items[index] if index < len(visible_items) else None action_in_progress = item and item == self._current_action_item display_string = self.item_formatter(index, item, action_in_progress) data_source.set_display_string(display_string) if not visible_items and self._data_sources and self.empty_list_message: self._data_sources[0].set_display_string(self.empty_list_message) def update(self): super(ListComponent, self).update() if self.is_enabled(): self._update_action_feedback() self._update_display()
class NoteEditorSettingsComponent(ModesComponent): def __init__(self, grid_resolution = None, initial_encoder_layer = None, encoder_layer = None, settings_layer = None, *a, **k): super(NoteEditorSettingsComponent, self).__init__(*a, **k) raise encoder_layer or AssertionError self._bottom_data_sources = [ DisplayDataSource() for _ in xrange(8) ] self._request_hide = False self.settings = self.register_component(NoteSettingsComponent(grid_resolution=grid_resolution, layer=settings_layer)) self.settings.set_enabled(False) self._automation = self.register_component(AutomationComponent()) self._automation.set_enabled(False) self._mode_selector = self.register_component(OptionsComponent(num_options=2, num_labels=0, num_display_segments=8)) self._mode_selector.set_enabled(False) self._on_selected_option.subject = self._mode_selector self._update_available_modes() self._mode_selector.selected_option = 0 self._visible_detail_view = 'Detail/DeviceChain' self._show_settings_task = self._tasks.add(Task.sequence(Task.wait(Defaults.MOMENTARY_DELAY), Task.run(self._show_settings))) self._show_settings_task.kill() self._update_infos_task = self._tasks.add(Task.run(self._update_note_infos)) self._update_infos_task.kill() self._settings_modes = self.register_component(ModesComponent()) self._settings_modes.set_enabled(False) self._settings_modes.add_mode('automation', [self._automation, self._mode_selector, partial(self._set_envelope_view_visible, True), self._show_clip_view]) self._settings_modes.add_mode('note_settings', [self.settings, self._update_note_infos, self._mode_selector, partial(self._set_envelope_view_visible, False), self._show_clip_view]) self._settings_modes.add_mode('pad_settings', [self.settings, partial(self._set_envelope_view_visible, False), self._show_clip_view]) self._encoders = None self._initial_encoders = None self.add_mode('disabled', []) self.add_mode('about_to_show', [AddLayerMode(self, initial_encoder_layer), (self._show_settings_task.restart, self._show_settings_task.kill)]) self.add_mode('enabled', [DetailViewRestorerMode(self.application()), AddLayerMode(self, encoder_layer), self._update_available_modes, self._settings_modes]) self.selected_mode = 'disabled' self._editors = [] self._on_detail_clip_changed.subject = self.song().view self._on_selected_track_changed.subject = self.song().view automation_layer = forward_property('_automation')('layer') mode_selector_layer = forward_property('_mode_selector')('layer') selected_setting = forward_property('_settings_modes')('selected_mode') def add_editor(self, editor): raise editor != None or AssertionError self._editors.append(editor) self._on_active_steps_changed.add_subject(editor) self._on_notes_changed.replace_subjects(self._editors) def set_display_line(self, line): self._mode_selector.set_display_line(line) def set_initial_encoders(self, encoders): self._initial_encoders = encoders self._on_init_encoder_touch.replace_subjects(encoders or []) self._on_init_encoder_value.replace_subjects(encoders or []) self._try_immediate_show_settings() def set_encoders(self, encoders): self._encoders = encoders self._on_encoder_touch.replace_subjects(encoders or []) self._on_encoder_value.replace_subjects(encoders or []) self.settings.set_encoder_controls(encoders) self._automation.set_parameter_controls(encoders) def _get_parameter_provider(self): self._automation.parameter_provider def _set_parameter_provider(self, value): self._automation.parameter_provider = value if self.selected_mode != 'disabled': self._update_available_modes() parameter_provider = property(_get_parameter_provider, _set_parameter_provider) def _update_available_modes(self): available_modes = ['Notes'] if self._automation.can_automate_parameters: available_modes.append('Automat') self._mode_selector.option_names = available_modes def _show_clip_view(self): try: view = self.application().view if view.is_view_visible('Detail/DeviceChain', False) and not view.is_view_visible('Detail/Clip', False): self.application().view.show_view('Detail/Clip') except RuntimeError: pass def _set_envelope_view_visible(self, visible): clip = self.song().view.detail_clip if clip: if visible: clip.view.show_envelope() else: clip.view.hide_envelope() def _try_immediate_show_settings(self): if self.selected_mode == 'about_to_show' and any(imap(lambda e: e and e.is_pressed(), self._initial_encoders or [])): self._show_settings() @subject_slot_group('active_steps') def _on_active_steps_changed(self, editor): if self.is_enabled(): all_steps = list(set(chain_from_iterable(imap(lambda e: e.active_steps, self._editors)))) self._automation.selected_time = all_steps self._update_note_infos() if len(all_steps) > 0: self._request_hide = False if self.selected_mode == 'disabled': self.selected_mode = 'about_to_show' self._try_immediate_show_settings() else: self._request_hide = True self._try_hide_settings() @subject_slot_group('notes_changed') def _on_notes_changed(self, editor): self._update_infos_task.restart() @subject_slot('detail_clip') def _on_detail_clip_changed(self): clip = self.song().view.detail_clip if self.is_enabled() else None self._automation.clip = clip @subject_slot('selected_track') def _on_selected_track_changed(self): self.selected_mode = 'disabled' @subject_slot('selected_option') def _on_selected_option(self, option): self._update_selected_setting(option) @subject_slot_group('touch_value') def _on_init_encoder_touch(self, value, encoder): self._show_settings() @subject_slot_group('value') def _on_init_encoder_value(self, value, encoder): self._show_settings() @subject_slot_group('touch_value') def _on_encoder_touch(self, value, encoder): if not value: self._try_hide_settings() @subject_slot_group('value') def _on_encoder_value(self, value, encoder): self._notify_modification() def _notify_modification(self): for editor in self._editors: editor.notify_modification() def _update_note_infos(self): if self.settings.is_enabled(): def min_max((l_min, l_max), (r_min, r_max)): return (min(l_min, r_min), max(l_max, r_max)) all_min_max_attributes = filter(None, imap(lambda e: e.get_min_max_note_values(), self._editors)) min_max_values = [(99999, -99999)] * 4 if len(all_min_max_attributes) > 0 else None for min_max_attribute in all_min_max_attributes: for i, attribute in enumerate(min_max_attribute): min_max_values[i] = min_max(min_max_values[i], attribute) for i in xrange(4): self.settings.set_min_max(i, min_max_values[i] if min_max_values else None) edit_all_notes_active = find_if(lambda e: e.modify_all_notes_enabled, self._editors) != None self.settings.set_info_message('Tweak to add note' if not edit_all_notes_active and not min_max_values else '')
class NotificationComponent(CompoundComponent): """ Displays notifications to the user for a given amount of time. To adjust the way notifications are shown in special cases, assign a generated control using use_single_line or use_full_display to a layer. If the layer is on top, it will set the preferred view. This will show the notification on line 1 if my_component is enabled and the priority premise of the layer is met: my_component.layer = Layer( _notification = notification_component.use_single_line(1)) """ def __init__(self, notification_time=2.5, blinking_time=0.3, display_lines=[], *a, **k): super(NotificationComponent, self).__init__(*a, **k) self._display_lines = display_lines self._token_control = _TokenControlElement() self._message_box = self.register_component(MessageBoxComponent()) self._message_box.set_enabled(False) self._notification_timeout_task = self._tasks.add( Task.sequence(Task.wait(notification_time), Task.run(self.hide_notification))).kill() self._blink_text_task = self._tasks.add( Task.loop( Task.sequence( Task.run(lambda: self._message_box. __setattr__('text', self._original_text)), Task.wait(blinking_time), Task.run(lambda: self._message_box.__setattr__( 'text', self._blink_text)), Task.wait(blinking_time)))).kill() self._original_text = None self._blink_text = None message_box_layer = forward_property('_message_box')('layer') def show_notification(self, text, blink_text=None): """ Triggers a notification with the given text. """ if blink_text is not None: self._original_text = text self._blink_text = blink_text self._blink_text_task.restart() self._message_box.text = text self._message_box.set_enabled(True) self._notification_timeout_task.restart() def hide_notification(self): """ Hides the current notification, if any existing. """ self._blink_text_task.kill() self._message_box.set_enabled(False) def use_single_line(self, line_index): """ Returns a control, that will change the notification to a single line view, if it is grabbed. """ return _CallbackControl(self._token_control, partial(self._set_single_line, line_index)) def use_full_display(self, message_line_index=2): """ Returns a control, that will change the notification to use the whole display, if it is grabbed. """ return _CallbackControl( self._token_control, partial(self._set_full_display, message_line_index=message_line_index)) def _set_single_line(self, line_index): raise line_index >= 0 and line_index < len( self._display_lines) or AssertionError layer = Layer(**{'display_line1': self._display_lines[line_index]}) layer.priority = consts.MESSAGE_BOX_PRIORITY self._message_box.layer = layer def _set_full_display(self, message_line_index=2): layer = Layer( **dict([('display_line1' if i == message_line_index else 'bg%d' % i, line) for i, line in enumerate(self._display_lines)])) layer.priority = consts.MESSAGE_BOX_PRIORITY self._message_box.layer = layer def update(self): pass
class PushMonomodComponent(MonomodComponent): def __init__(self, *a, **k): super(PushMonomodComponent, self).__init__(*a, **k) self._buttons = None self._shift = None self._is_modlocked = False self._nav_up_button = None self._nav_down_button = None self._nav_right_button = None self._nav_left_button = None self._nav_locked = False self.nav_buttons_layer = None self.is_push = True self._device_component = None for index in range(16): self._color_maps[index][1:8] = [3, 85, 33, 95, 5, 21, 67] self._color_maps[index][127] = 67 self._alt_display = MonomodDisplayComponent(self, [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '], [' ', ' ', ' ', ' ', 'Display', 'Mute', 'Enable', 'Select']) self._shift_display = MonomodDisplayComponent(self, ['ModLock', ' ', ' ', ' ', ' ', ' ', 'Channel', 'Name'], [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ']) self._shift_modes = self.register_component(ModesComponent()) self._shift_modes.add_mode('disabled', None) self._shift_modes.add_mode('alt', self._alt_display, groups = 'alt', behaviour = ModShiftBehaviour()) self._shift_modes.add_mode('shift', self._shift_display, groups = 'shift', behaviour = ModShiftBehaviour()) self._shift_modes.selected_mode = 'disabled' self._shift_display.set_value_string(self._is_modlocked, 0) alt_display_layer = forward_property('_alt_display')('layer') shift_display_layer = forward_property('_shift_display')('layer') def _matrix_value(self, value, x, y, is_momentary): value = int(value>0) super(PushMonomodComponent, self)._matrix_value(value, x, y, is_momentary) def set_device_component(self, device_component): if not device_component is self._device_component: self._device_component = device_component #self._device_component.udpate() def _notify_new_connection(self, device): self._script._select_note_mode(device) def _select_client(self, *a, **k): super(PushMonomodComponent, self)._select_client(*a, **k) if self._active_client != None and self._active_client.device != None: self._shift_display.set_value_string(str(self._active_client.device.name), 7) self._shift_display.set_value_string(str(self._active_client._channel), 6) else: self._shift_display.set_value_string('Empty', 7) self._shift_display.set_value_string(str(self._active_client._channel), 6) def udpate(self): self.log_message('updating Monomodcomponent') super(PushMonomodComponent, self).update() if self.is_enabled() and not self._active_client is None: self._active_client._device_component.update() if not self._device_component is None: self._device_component.update() def select_active_client(self): if not self._active_client.linked_device() is None: #super(PushMonomodComponent, self).select_active_client() self.song().view.select_device(self._active_client.linked_device()) for client in self._client: client._send('pop', client.is_active()) def set_button_matrix(self, buttons): self._set_button_matrix(buttons) def set_lock_button(self, button): self._on_lock_value.subject = button self._on_lock_value(self.is_modlocked()) def set_shift_button(self, button): if self._shift_button != None: self._shift_button.remove_value_listener(self._shift_value) self._shift_button = button if self._shift_button != None: self._shift_button.add_value_listener(self._shift_value) #self._shift_modes.set_toggle_button(button) self._shift_modes.set_mode_button('shift', self._shift_button) #self._shift_modes.set_mode_toggle(self._shift_button) @subject_slot('value') def _on_lock_value(self, value): if value: self._is_modlocked = not self._is_modlocked self._shift_display.set_value_string("Locked" if self._is_modlocked else "Unlocked") button = self._on_lock_value.subject if not button is None: button.set_light('DefaultButton.Alert' if self.is_modlocked() else True) def is_modlocked(self): return bool(self.is_enabled() and self._is_modlocked) def set_alt_button(self, button): if self._alt_button != None: self._alt_button.remove_value_listener(self._alt_value) self._alt_button = button if self._alt_button != None: self._alt_button.add_value_listener(self._alt_value) self._shift_modes.set_mode_button('alt', self._alt_button) def _alt_value(self, value): super(PushMonomodComponent, self)._alt_value(value) if self._shift_pressed > 0 and value > 0: self._nav_locked = not self._nav_locked self.set_nav_buttons() self._device_component._alt_pressed = (value > 0) self._device_component.update() def set_nav_up_button(self, button): self._nav_up_button = button #self.set_nav_buttons() def set_nav_down_button(self, button): self._nav_down_button = button #self.set_nav_buttons() def set_nav_left_button(self, button): self._nav_left_button = button #self.set_nav_buttons() def set_nav_right_button(self, button): self._nav_right_button = button #self.set_nav_buttons() def set_nav_buttons(self): #self._script.log_message('set nav buttons ' + str(self._nav_locked)) if self.nav_buttons_layer: if self._nav_locked: self.nav_buttons_layer.enter_mode() else: self.nav_buttons_layer.leave_mode() nav_buttons = [self._nav_up_button, self._nav_down_button, self._nav_left_button, self._nav_right_button] if not None in nav_buttons and self._nav_locked: self._set_nav_buttons(nav_buttons) else: self._set_nav_buttons(None) def _set_nav_buttons(self, buttons): if self._nav_buttons != None: self._nav_buttons[0].remove_value_listener(self._nav_up_value) self._nav_buttons[1].remove_value_listener(self._nav_down_value) self._nav_buttons[2].remove_value_listener(self._nav_left_value) self._nav_buttons[3].remove_value_listener(self._nav_right_value) self._nav_buttons = buttons if buttons != None: assert len(buttons) == 4 self._nav_buttons[0].add_value_listener(self._nav_up_value) self._nav_buttons[1].add_value_listener(self._nav_down_value) self._nav_buttons[2].add_value_listener(self._nav_left_value) self._nav_buttons[3].add_value_listener(self._nav_right_value) def set_device_controls(self, controls): if controls != self._device_controls: self._device_controls = controls if self._client != None: if not controls is None: #elf._on_device_control_value.subject = self._device_controls self._set_parameter_controls([control for control in controls]) else: self._set_parameter_controls(None) def set_key_buttons(self, buttons): key_buttons = None if isinstance(buttons, ButtonMatrixElement): key_buttons = tuple([button for button in buttons]) self._set_key_buttons(key_buttons) def set_lcd_displays(self, lcds): if lcds != self._lcd_displays: self._lcd_displays = lcds def _send_to_lcd(self, column, row, wheel): #self._script.log_message('send lcd ' + str(column) + ' ' + str(row) + ' ' + str(wheel['pn'])) if self.is_enabled() and not self._active_client._device_component.is_enabled(): #self._script.notification_to_bridge(str(wheel['pn']), str(wheel['pv']), self._dial_matrix.get_dial(column, row)) self._script.log_message(str(wheel['pn']) + ' ' + str(wheel['pv']) + ' ' + str(self._dial_matrix.get_dial(column, row))) def on_enabled_changed(self, *a, **k): super(PushMonomodComponent, self).on_enabled_changed(*a, **k) if not self._is_enabled: self._is_modlocked = False self._script._on_selected_track_changed() else: button = self._on_lock_value.subject if button: button.set_light(True)
class SpecialTransportComponent(TransportComponent): """ Transport component that takes buttons for Undo and Redo """ def __init__(self, *a, **k): super(SpecialTransportComponent, self).__init__(*a, **k) self._undo_button = None self._redo_button = None self._shift_button = None self._tempo_encoder_control = None self._shift_pressed = False self._seek_ticks_delay = -1 self._quantization = self.register_component(QuantizationComponent()) self._play_toggle.model_transform = lambda val: False if self._shift_button and self._shift_button.is_pressed( ) else val quantization_layer = forward_property('_quantization')('settings_layer') def update(self): super(SpecialTransportComponent, self).update() self._update_undo_button() def set_quantization_button(self, button): self._quantization.set_action_button(button) def set_shift_button(self, button): if self._shift_button != button: self._shift_button = button self._shift_value.subject = button self.update() def set_undo_button(self, undo_button): if undo_button != self._undo_button: self._undo_button = undo_button self._undo_value.subject = undo_button self._update_undo_button() def set_redo_button(self, redo_button): if redo_button != self._redo_button: self._redo_button = redo_button self._redo_value.subject = redo_button self.update() def set_tempo_encoder(self, control): if not (not control or control.message_map_mode() is Live.MidiMap.MapMode.relative_smooth_two_compliment): raise AssertionError self._tempo_encoder_control = control != self._tempo_encoder_control and control self._tempo_encoder_value.subject = control self.update() @subject_slot('value') def _shift_value(self, value): self._shift_pressed = value != 0 if self.is_enabled(): self.update() @subject_slot('value') def _undo_value(self, value): if self.is_enabled(): if value != 0 or not self._undo_button.is_momentary(): if not self._shift_button.is_pressed(): if self.song().can_undo: self.song().undo() elif self.song().can_redo: self.song().redo() self._update_undo_button() def _update_undo_button(self): if self.is_enabled() and self._undo_button: self._undo_button.set_light(self._undo_button.is_pressed()) @subject_slot('value') def _redo_value(self, value): if self.is_enabled(): if value != 0 or not self._redo_button.is_momentary(): if self.song().can_redo: self.song().redo() @subject_slot('value') def _tempo_encoder_value(self, value): if self.is_enabled(): step = 0.1 if self._shift_pressed else 1.0 amount = value - 128 if value >= 64 else value self.song().tempo = clamp(self.song().tempo + amount * step, 20, 999)
class MonoScaleComponent(CompoundComponent): def __init__(self, script, *a, **k): super(MonoScaleComponent, self).__init__(*a, **k) self._script = script self._matrix = None self._setup_selected_session_control() self._touchstrip = None self._display = MonoScaleDisplayComponent(self) self._display.set_enabled(False) self._scales_modes = self.register_component(ModesComponent()) self._scales_modes.add_mode('disabled', None) self._scales_modes.add_mode('enabled', self._display, 'DefaultButton.On') self._scales_modes.selected_mode = 'disabled' self._offsets = [{'offset':DEFAULT_OFFSET, 'vertoffset':DEFAULT_VERTOFFSET, 'drumoffset':DEFAULT_DRUMOFFSET, 'scale':DEFAULT_SCALE, 'split':DEFAULT_SPLIT} for index in range(16)] self._split_mode_selector = SplitModeSelector(self._split_mode_value) self._vertical_offset_component = ScrollingOffsetComponent(self._vertical_offset_value) self._offset_component = ScrollingOffsetComponent(self._offset_value) self._offset_component._shifted_value = 11 self._shift_is_momentary = OFFSET_SHIFT_IS_MOMENTARY self._scale_offset_component = ScrollingOffsetComponent(self._scale_offset_value) self._scale_offset_component._minimum = 0 self._scale_offset_component._maximum = len(SCALES.keys())-1 display_layer = forward_property('_display')('layer') def _setup_selected_session_control(self): self._selected_session = ScaleSessionComponent(1, 32, self) self._selected_session.name = "SelectedSession" self._selected_session.set_offsets(0, 0) #self._selected_session.set_stop_clip_value(STOP_CLIP) self._selected_scene = [None for index in range(32)] for row in range(32): self._selected_scene[row] = self._selected_session.scene(row) self._selected_scene[row].name = 'SelectedScene_' + str(row) clip_slot = self._selected_scene[row].clip_slot(0) clip_slot.name = 'Selected_Clip_Slot_' + str(row) #clip_slot.set_triggered_to_play_value(CLIP_TRG_PLAY) #clip_slot.set_triggered_to_record_value(CLIP_TRG_REC) #clip_slot.set_stopped_value(CLIP_STOP) #clip_slot.set_started_value(CLIP_STARTED) #clip_slot.set_recording_value(CLIP_RECORDING) def set_touchstrip(self, control): #if control is None and not self._touchstrip is None: # self._touchstrip.use_default_message() self._touchstrip = control if control: control.reset() def set_name_display_line(self, display_line): self._name_display_line = display_line def set_value_display_line(self, display_line): self._value_display_line = display_line def _set_display_line(self, line, sources): if line: line.set_num_segments(len(sources)) for segment in xrange(len(sources)): line.segment(segment).set_data_source(sources[segment]) def set_scales_toggle_button(self, button): assert(button is None or button.is_momentary()) self._scales_modes.set_toggle_button(button) def set_button_matrix(self, matrix): if not matrix is self._matrix_value.subject: if self._matrix_value.subject: for button in self._matrix_value.subject: button.set_enabled(True) button.use_default_message() self._matrix_value.subject = matrix if self._matrix_value.subject: self._script.schedule_message(1, self.update) @subject_slot('value') def _matrix_value(self, value, x, y, *a, **k): self._script.log_message('monoscale grid in: ' + str(x) + ' ' + str(y) + ' ' + str(value)) #pass def set_octave_up_button(self, button): self._octave_up_value.subject = button if button: button.turn_on() @subject_slot('value') def _octave_up_value(self, value): if value: self._offset_component.set_enabled(True) self._offset_component._shifted = True self._offset_component._scroll_up_value(1) self._offset_component._shifted = False self._offset_component.set_enabled(False) def set_octave_down_button(self, button): self._octave_down_value.subject = button if button: button.turn_on() @subject_slot('value') def _octave_down_value(self, value): if value: self._offset_component.set_enabled(True) self._offset_component._shifted = True self._offset_component._scroll_down_value(1) self._offset_component._shifted = False self._offset_component.set_enabled(False) def update(self): if not self.is_enabled(): self._selected_session.deassign_all() self._script.set_highlighting_session_component(self._script._session) self._script._session._do_show_highlight() def _detect_instrument_type(self, track): scale = DEFAULT_AUTO_SCALE for device in track.devices: if isinstance(device, Live.Device.Device): self._script.log_message('device: ' + str(device.class_name)) if device.class_name == 'DrumGroupDevice': scale = 'DrumPad' break return scale def _offset_value(self, offset): cur_track = self._script._mixer._selected_strip._track if cur_track.has_midi_input: cur_chan = cur_track.current_input_sub_routing if cur_chan in CHANNELS: cur_chan = (CHANNELS.index(cur_chan))+1 scale = self._offsets[cur_chan]['scale'] if scale is 'Auto': scale = self._detect_instrument_type(cur_track) if scale is 'DrumPad': old_offset = self._offsets[cur_chan]['drumoffset'] self._offsets[cur_chan]['drumoffset'] = offset self._script.show_message('New drum root is ' + str(self._offsets[cur_chan]['drumoffset'])) self._display.set_value_string(str(self._offsets[cur_chan]['drumoffset']), 3) else: self._offsets[cur_chan]['offset'] = offset self._script.show_message('New root is Note# ' + str(self._offsets[cur_chan]['offset']) + ', ' + str(NOTENAMES[self._offsets[cur_chan]['offset']])) self._display.set_value_string(str(self._offsets[cur_chan]['offset']) + ', ' + str(NOTENAMES[self._offsets[cur_chan]['offset']]), 3) self._assign_midi_layer() def _vertical_offset_value(self, offset): cur_track = self._script._mixer._selected_strip._track if cur_track.has_midi_input: cur_chan = cur_track.current_input_sub_routing if cur_chan in CHANNELS: cur_chan = (CHANNELS.index(cur_chan))+1 self._offsets[cur_chan]['vertoffset'] = offset self._script.show_message('New vertical offset is ' + str(self._offsets[cur_chan]['vertoffset'])) self._display.set_value_string(str(self._offsets[cur_chan]['vertoffset']), 1) self._assign_midi_layer() def _scale_offset_value(self, offset): cur_track = self._script._mixer._selected_strip._track if cur_track.has_midi_input: cur_chan = cur_track.current_input_sub_routing if cur_chan in CHANNELS: cur_chan = (CHANNELS.index(cur_chan))+1 self._offsets[cur_chan]['scale'] = SCALENAMES[offset] self._script.show_message('New scale is ' + str(self._offsets[cur_chan]['scale'])) self._display.set_value_string(str(self._offsets[cur_chan]['scale']), 2) if len(SCALES[self._offsets[cur_chan]['scale']])>8: self._offsets[cur_chan]['vert_offset'] = 8 self._assign_midi_layer() def _split_mode_value(self, mode): cur_track = self._script._mixer._selected_strip._track if cur_track.has_midi_input: cur_chan = cur_track.current_input_sub_routing if cur_chan in CHANNELS: cur_chan = (CHANNELS.index(cur_chan))+1 self._offsets[cur_chan]['split'] = bool(mode) self._display.set_value_string(str(bool(mode)), 0) self._assign_midi_layer() def _assign_midi_layer(self): cur_track = self._script.song().view.selected_track is_midi = False matrix = self._matrix_value.subject if cur_track.has_midi_input and not matrix is None: is_midi = True cur_chan = cur_track.current_input_sub_routing #self._script.log_message('cur_chan ' + str(cur_chan) + str(type(cur_chan)) + str(len(cur_chan))) if cur_chan in CHANNELS: cur_chan = (CHANNELS.index(cur_chan))+1 offset, vertoffset, scale, split = self._offsets[cur_chan]['offset'], self._offsets[cur_chan]['vertoffset'], self._offsets[cur_chan]['scale'], self._offsets[cur_chan]['split'] if scale is 'Auto': scale = self._detect_instrument_type(cur_track) #self._script.log_message('auto found: ' + str(scale)) self._split_mode_selector._mode_index = int(self._offsets[cur_chan]['split']) self._split_mode_selector.update() self._vertical_offset_component._offset = self._offsets[cur_chan]['vertoffset'] self._vertical_offset_component.update() self._scale_offset_component._offset = SCALENAMES.index(self._offsets[cur_chan]['scale']) self._scale_offset_component.update() if scale is 'DrumPad': self._offset_component._offset = self._offsets[cur_chan]['drumoffset'] else: self._offset_component._offset = self._offsets[cur_chan]['offset'] self._offset_component.update() if scale is 'Session': is_midi = False elif scale is 'Mod': is_midi = True elif scale in SPLIT_SCALES or split: #self._send_midi(SPLITBUTTONMODE) scale_len = len(SCALES[scale]) for row in range(8): for column in range(4): button = matrix.get_button(column, row) if scale is 'DrumPad': button.set_identifier((DRUMNOTES[column + (row*8)] + (self._offsets[cur_chan]['drumoffset']*4))%127) button.scale_color = DRUMCOLORS[row<4] button.send_value(button.scale_color) self._offset_component._shifted_value = 3 else: note_pos = column + (abs(7-row)*int(vertoffset)) note = offset + SCALES[scale][note_pos%scale_len] + (12*int(note_pos/scale_len)) button.set_identifier(note%127) button.scale_color = KEYCOLORS[(note%12 in WHITEKEYS) + (((note_pos%scale_len)==0)*2)] button.send_value(button.scale_color) self._offset_component._shifted_value = 11 button.set_enabled(False) button.set_channel(cur_chan) #self._selected_session.deassign_all() matrix = self._matrix_value.subject matrix.get_button(column + 4, row).use_default_message() matrix.get_button(column + 4, row).set_enabled(True) self._selected_scene[column+(row*4)].clip_slot(0).set_launch_button(matrix.get_button(column + 4, row)) #self._selected_session.set_scene_bank_buttons(self._button[5], self._button[4]) self._script.set_highlighting_session_component(self._selected_session) self._selected_session._do_show_highlight() else: #self._send_midi(MIDIBUTTONMODE) scale_len = len(SCALES[scale]) for row in range(8): for column in range(8): button = matrix.get_button(column, row) if scale is 'DrumPad': button.set_identifier((DRUMNOTES[column + (row*8)] + (self._offsets[cur_chan]['drumoffset']*4))%127) button.scale_color = DRUMCOLORS[(column<4)+((row<4)*2)] button.send_value(button.scale_color) self._offset_component._shifted_value = 3 else: note_pos = column + (abs(7-row)*vertoffset) note = offset + SCALES[scale][note_pos%scale_len] + (12*int(note_pos/scale_len)) button.set_identifier(note%127) button.scale_color = KEYCOLORS[(note%12 in WHITEKEYS) + (((note_pos%scale_len)==0)*2)] button.send_value(button.scale_color) self._offset_component._shifted_value = 11 button.set_enabled(False) button.set_channel(cur_chan) self._selected_session.deassign_all() self._script.set_highlighting_session_component(self._script._session) self._script._session._do_show_highlight() #if not self._touchstrip is None: # self._touchstrip.set_channel(cur_chan) self._display.set_value_string(str(bool(self._split_mode_selector._mode_index)), 0) self._display.set_value_string(str(self._offsets[cur_chan]['vertoffset']), 1) self._display.set_value_string(str(self._offsets[cur_chan]['scale']), 2) self._display.set_value_string(str(self._offsets[cur_chan]['offset']) + ', ' + str(NOTENAMES[self._offsets[cur_chan]['offset']]), 3) else: is_midi = False self._script.set_feedback_channels([]) return is_midi def _assign_midi_shift_layer(self): cur_track = self._script._mixer._selected_strip._track is_midi = False if cur_track.has_midi_input: #self._send_midi(LIVEBUTTONMODE) #if AUTO_ARM_SELECTED: # if not cur_track.arm: # self.schedule_message(1, self._arm_current_track, cur_track) is_midi = True cur_chan = cur_track.current_input_sub_routing if cur_chan in CHANNELS: cur_chan = (CHANNELS.index(cur_chan))+1 scale = self._offsets[cur_chan]['scale'] if scale is 'Auto': scale = self._detect_instrument_type(cur_track) #self.log_message('auto found: ' + str(scale)) if scale is 'Session': is_midi = False elif scale is 'Mod': is_midi = True else: """for button in self._touchpad[0:1]: button.set_on_off_values(SPLITMODE, 0) for button in self._touchpad[1:2]: button.set_on_off_values(OVERDUB, 0) self._transport.set_overdub_button(self._touchpad[1]) self._split_mode_selector._mode_index = int(self._offsets[cur_chan]['split']) self._split_mode_selector.set_enabled(True) if not self._offsets[cur_chan]['scale'] is 'DrumPad': for button in self._touchpad[2:4]: button.set_on_off_values(VERTOFFSET, 0) self._vertical_offset_component._offset = self._offsets[cur_chan]['vertoffset'] self._vertical_offset_component.set_offset_change_buttons(self._touchpad[3], self._touchpad[2]) for button in self._touchpad[4:6]: button.set_on_off_values(SCALEOFFSET, 0) self._scale_offset_component._offset = SCALENAMES.index(self._offsets[cur_chan]['scale']) self._scale_offset_component.set_offset_change_buttons(self._touchpad[5], self._touchpad[4]) for button in self._touchpad[6:8]: button.set_on_off_values(OFFSET, 0) if scale is 'Auto': scale = self._detect_instrument_type(cur_track) if scale is 'DrumPad': self._offset_component._offset = self._offsets[cur_chan]['drumoffset'] else: self._offset_component._offset = self._offsets[cur_chan]['offset'] self._offset_component.set_offset_change_buttons(self._touchpad[7], self._touchpad[6])""" is_midi = True return is_midi def update(self): if self.is_enabled() and self._button_matrix: self. cur_track = self._mixer._selected_strip._track is_midi = False scale, offset, vertoffset = ' ', ' ', ' ' if cur_track.has_midi_input: if AUTO_ARM_SELECTED: if not cur_track.arm: self.schedule_message(1, self._arm_current_track, cur_track) is_midi = True cur_chan = cur_track.current_input_sub_routing #self.log_message('cur_chan ' + str(cur_chan) + str(type(cur_chan)) + str(len(cur_chan))) if len(cur_chan) == 0: cur_chan = 'All Channels' if cur_chan in CHANNELS: cur_chan = (CHANNELS.index(cur_chan)%15)+1 offsets = self._current_device_offsets(self._offsets[cur_chan]) offset, vertoffset, scale, split, sequencer = offsets['offset'], offsets['vertoffset'], offsets['scale'], offsets['split'], offsets['sequencer'] if scale == 'Auto': scale = self._detect_instrument_type(cur_track) #self.log_message('auto found: ' + str(scale)) elif scale in SPLIT_SCALES or split: #self._send_midi(SPLITBUTTONMODE) scale_len = len(SCALES[scale]) if scale is 'DrumPad': for row in range(4): for column in range(4): #if scale is 'DrumPad': self._pad[column + (row*8)].set_identifier((DRUMNOTES[column + (row*8)] + (self._offsets[cur_chan]['drumoffset']*4))%127) self._pad[column + (row*8)].scale_color = DRUMCOLORS[0] #self._pad[column + (row*8)].send_value(DRUMCOLORS[column<4], True) self._pad[column + (row*8)].display_press = True self._pad[column + (row*8)].press_flash(0, True) self._pad_CC[column + (row*8)].set_identifier((DRUMNOTES[column + (row*8)] + (self._offsets[cur_chan]['drumoffset']*4))%127) #self._drumgroup.set_drum_matrix(self._drumpad_grid) self._offset_component._shifted_value = 3 self._pad[column + (row*8)].set_enabled(False) self._pad[column + (row*8)].set_channel(cur_chan) self._pad[column + (row*8)]._descriptor = str(NOTENAMES[self._pad[column + (row*8)]._msg_identifier]) if not sequencer: self._selected_scene[column+(row*4)].clip_slot(0).set_launch_button(self._pad[column + 4 + (row*8)]) else: current_note = self._note_sequencer._note_editor.editing_note for row in range(2, 4): for column in range(8): note_pos = column + (abs(3-row)*int(vertoffset)) note = offset + SCALES[scale][note_pos%scale_len] + (12*int(note_pos/scale_len)) self._pad[column + (row*8)].set_identifier(note%127) self._pad[column + (row*8)].scale_color = KEYCOLORS[(note%12 in WHITEKEYS) + (((note_pos%scale_len)==0)*2)] if note is current_note and sequencer: self._pad[column + (row*8)].scale_color = SELECTED_NOTE #self._pad[column + (row*8)].send_value(self._pad[column + (row*8)].scale_color, True) self._pad[column + (row*8)].display_press = True self._pad[column + (row*8)].press_flash(0, True) self._pad_CC[column + (row*8)].set_identifier(note%127) self._offset_component._shifted_value = 11 self._pad[column + (row*8)].set_enabled(False) self._pad[column + (row*8)].set_channel(cur_chan) self._pad[column + (row*8)]._descriptor = str(NOTENAMES[self._pad[column + (row*8)]._msg_identifier]) self._pad_CC[column + (row*8)].set_enabled(False) self._pad_CC[column + (row*8)].set_channel(cur_chan) #self._selected_session.deassign_all() if not sequencer: self._selected_scene[column+((row-2)*4)].clip_slot(0).set_launch_button(self._pad[column + ((row-2)*8)]) #self._selected_session.set_scene_bank_buttons(self._button[5], self._button[4]) if sequencer: self.set_feedback_channels(range(cur_chan, cur_chan+1)) if scale is 'DrumPad': self.set_pad_translations(make_pad_translations(cur_chan)) self._step_sequencer.set_playhead(self._playhead_element) self._step_sequencer._drum_group.set_select_button(self._button[self._layer]) self._step_sequencer.set_button_matrix(self._base_grid.submatrix[4:8, :4]) self._step_sequencer.set_drum_matrix(self._base_grid.submatrix[:4, :4]) vals = [-1, -1, -1, -1, 0, 1, 2, 3, -1, -1, -1, -1, 4, 5, 6, 7, -1, -1, -1, -1, 8, 9, 10, 11, -1, -1, -1, -1, 12, 13, 14, 15] for x, pad in enumerate(self._pad): pad.display_press = False if vals[x]>-1: pad.set_channel(cur_chan) pad.set_identifier(vals[x]) else: pad.set_identifier(vals[x+4]+16) pad.set_channel(cur_chan) else: #self.log_message('assign stuff to note sequencer') self._note_sequencer.set_playhead(self._playhead_element) self._note_sequencer.set_button_matrix(self._base_grid.submatrix[:8, :2]) #vals = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, -1, 4, 5, 6, 7, -1, -1, -1, -1, 8, 9, 10, 11, -1, -1, -1, -1, 12, 13, 14, 15] for x, pad in enumerate(self._pad): pad.display_press = False if x<16: pad.set_channel(cur_chan) pad.set_identifier(x) self._on_note_matrix_pressed.subject = self._base_grid self.reset_controlled_track() else: self.set_highlighting_session_component(self._selected_session) self._selected_session._do_show_highlight() else: self._send_midi(MIDIBUTTONMODE) scale_len = len(SCALES[scale]) for row in range(4): for column in range(8): if scale is 'DrumPad': self._pad[column + (row*8)].set_identifier((DRUMNOTES[column + (row*8)] + (self._offsets[cur_chan]['drumoffset']*4))%127) self._pad[column + (row*8)].scale_color = DRUMCOLORS[column<4] #self._pad[column + (row*8)].send_value(DRUMCOLORS[column<4], True) self._pad[column + (row*8)].display_press = True self._pad[column + (row*8)].press_flash(0, True) self._pad[column + (row*8)]._descriptor = str(NOTENAMES[self._pad[column + (row*8)]._msg_identifier]) self._pad_CC[column + (row*8)].set_identifier((DRUMNOTES[column + (row*8)] + (self._offsets[cur_chan]['drumoffset']*4))%127) self._offset_component._shifted_value = 3 else: note_pos = column + (abs(3-row)*vertoffset) note = offset + SCALES[scale][note_pos%scale_len] + (12*int(note_pos/scale_len)) self._pad[column + (row*8)].set_identifier(note%127) self._pad[column + (row*8)].scale_color = KEYCOLORS[(note%12 in WHITEKEYS) + (((note_pos%scale_len)==0)*2)] #self._pad[column + (row*8)].send_value(self._pad[column + (row*8)].scale_color, True) self._pad[column + (row*8)].display_press = True self._pad[column + (row*8)].press_flash(0, True) self._pad[column + (row*8)]._descriptor = str(NOTENAMES[self._pad[column + (row*8)]._msg_identifier]) self._pad_CC[column + (row*8)].set_identifier(note%127) self._offset_component._shifted_value = 11 self._pad[column + (row*8)].set_enabled(False) self._pad[column + (row*8)].set_channel(cur_chan) self._pad_CC[column + (row*8)].set_enabled(False) self._pad_CC[column + (row*8)].set_channel(cur_chan) #self._session.set_scene_bank_buttons(self._button[5], self._button[4]) #self._session.set_track_bank_buttons(self._button[6], self._button[7]) if self.pad_held(): for index in range(len(self._last_pad_stream)): self._stream_pads[index].press_flash(self._last_pad_stream[index]) else: is_midi = False #return is_midi def assign_keypad(self, grid, scale): if grid and scale in SCALES: scale_len = len(SCALES[scale]) height = grid.height() offset, vertoffset = offsets['offset'], offsets['vertoffset'] for button, (x, y) in grid.iterbuttons(): note_pos = x + (abs(height-y)*vertoffset) note = offset + SCALES[scale][note_pos%scale_len] + (12*int(note_pos/scale_len)) button.set_identifier(note%127) button.scale_color = KEYCOLORS[(note%12 in WHITEKEYS) + (((note_pos%scale_len)==0)*2)] button.send_value(button.scale_color, True) #button.display_press = True #button.press_flash(0, True) #button._descriptor = str(NOTENAMES[self._pad[column + (row*8)]._msg_identifier]) #button_CC.set_identifier(note%127) self._offset_component._shifted_value = 11 def assign_drumpad(self, grid): if grid: height = grid.height() width = grid.width() offset, vertoffset = offsets['offset'], offsets['vertoffset'] if (width, height) is (4, 4): self.set_feedback_channels(range(cur_chan, cur_chan+1)) self.set_pad_translations(make_pad_translations(cur_chan)) self._step_sequencer.set_playhead(self._playhead_element) #self._step_sequencer._drum_group.set_select_button(self._button[self._layer]) #self._step_sequencer.set_button_matrix(self._base_grid.submatrix[4:8, :4]) self._step_sequencer.set_drum_matrix(grid) for button, (x, y) in grid: button.set_identifier(vals[x+4]+16) button.set_channel(cur_chan) #pad.display_press = False else: for button, (x, y) in grid.iterbuttons(): button.set_identifier((DRUMNOTES[column + (row*8)] + (self._offsets[cur_chan]['drumoffset']*4))%127) button.scale_color = DRUMCOLORS[column<4] button.send_value(DRUMCOLORS[column<4], True) #button.display_press = True #button.press_flash(0, True) #button._descriptor = str(NOTENAMES[self._pad[column + (row*8)]._msg_identifier]) #button_CC.set_identifier((DRUMNOTES[column + (row*8)] + (self._offsets[cur_chan]['drumoffset']*4))%127) self._offset_component._shifted_value = 3 @subject_slot('value') def on_selected_track_changed(self): track = self._script._mixer.selected_strip()._track track_list = [] for t in self._script._mixer.tracks_to_use(): track_list.append(t) if track in track_list: self._selected_session._track_offset = track_list.index(track) self._selected_session._reassign_tracks() self._selected_session._reassign_scenes() self.update()
class MonoScaleComponent(CompoundComponent): def __init__(self, script, *a, **k): super(MonoScaleComponent, self).__init__(*a, **k) self._script = script self._setup_selected_session_control() self._touchstrip = None self._display = MonoScaleDisplayComponent(self) self._display.set_enabled(False) self._scales_modes = self.register_component(ModesComponent()) self._scales_modes.add_mode('disabled', None) self._scales_modes.add_mode('enabled', self._display, 'DefaultButton.On') self._scales_modes.selected_mode = 'disabled' self._offsets = [{ 'offset': DEFAULT_OFFSET, 'vertoffset': DEFAULT_VERTOFFSET, 'drumoffset': DEFAULT_DRUMOFFSET, 'scale': DEFAULT_SCALE, 'split': DEFAULT_SPLIT } for index in range(16)] self._split_mode_selector = SplitModeSelector(self._split_mode_value) self._vertical_offset_component = ScrollingOffsetComponent( self._vertical_offset_value) self._offset_component = ScrollingOffsetComponent(self._offset_value) self._offset_component._shifted_value = 11 self._shift_is_momentary = OFFSET_SHIFT_IS_MOMENTARY self._scale_offset_component = ScrollingOffsetComponent( self._scale_offset_value) self._scale_offset_component._minimum = 0 self._scale_offset_component._maximum = len(SCALES.keys()) - 1 display_layer = forward_property('_display')('layer') def _setup_selected_session_control(self): self._selected_session = BaseSessionComponent(1, 32, self) self._selected_session.name = "SelectedSession" self._selected_session.set_offsets(0, 0) self._selected_session.set_stop_track_clip_value(STOP_CLIP) self._selected_scene = [None for index in range(32)] for row in range(32): self._selected_scene[row] = self._selected_session.scene(row) self._selected_scene[row].name = 'SelectedScene_' + str(row) clip_slot = self._selected_scene[row].clip_slot(0) clip_slot.name = 'Selected_Clip_Slot_' + str(row) clip_slot.set_triggered_to_play_value(CLIP_TRG_PLAY) clip_slot.set_triggered_to_record_value(CLIP_TRG_REC) clip_slot.set_stopped_value(CLIP_STOP) clip_slot.set_started_value(CLIP_STARTED) clip_slot.set_recording_value(CLIP_RECORDING) def set_touchstrip(self, control): #if control is None and not self._touchstrip is None: # self._touchstrip.use_default_message() self._touchstrip = control if control: control.reset() def set_name_display_line(self, display_line): self._name_display_line = display_line def set_value_display_line(self, display_line): self._value_display_line = display_line def _set_display_line(self, line, sources): if line: line.set_num_segments(len(sources)) for segment in xrange(len(sources)): line.segment(segment).set_data_source(sources[segment]) def set_scales_toggle_button(self, button): assert (button is None or button.is_momentary()) self._scales_modes.set_toggle_button(button) def set_button_matrix(self, matrix): if not matrix is self._matrix_value.subject: if self._matrix_value.subject: for button in self._matrix_value.subject: button.set_enabled(True) button.use_default_message() self._matrix_value.subject = matrix if self._matrix_value.subject: self._script.schedule_message(1, self._assign_midi_layer) @subject_slot('value') def _matrix_value(self, value, x, y, *a, **k): self._script.log_message('monoscale grid in: ' + str(x) + ' ' + str(y) + ' ' + str(value)) #pass def set_octave_up_button(self, button): self._octave_up_value.subject = button if button: button.turn_on() @subject_slot('value') def _octave_up_value(self, value): if value: self._offset_component.set_enabled(True) self._offset_component._shifted = True self._offset_component._scroll_up_value(1) self._offset_component._shifted = False self._offset_component.set_enabled(False) def set_octave_down_button(self, button): self._octave_down_value.subject = button if button: button.turn_on() @subject_slot('value') def _octave_down_value(self, value): if value: self._offset_component.set_enabled(True) self._offset_component._shifted = True self._offset_component._scroll_down_value(1) self._offset_component._shifted = False self._offset_component.set_enabled(False) def update(self): if not self.is_enabled(): self._selected_session.deassign_all() self._script.set_highlighting_session_component( self._script._session) self._script._session._do_show_highlight() def _is_mod(self, device): mod_device = None if isinstance(device, Live.Device.Device): if device.can_have_chains and not device.can_have_drum_pads and len( device.view.selected_chain.devices) > 0: device = device.view.selected_chain.devices[0] if not device is None: if self._script._monomodular and self._script.monomodular._mods: for mod in self._script.monomodular._mods: if mod.device == device: mod_device = mod break return mod_device def _assign_mod(self): mod = self._is_mod(self._device._device) if not mod is None: #self._send_midi(MIDIBUTTONMODE) self._script.modhandler._assign_base_grid(self._base_grid) self._script.modhandler._assign_base_grid_CC(self._base_grid_CC) if self.shift_pressed(): self.modhandler._assign_keys(self._keys) else: self.modhandler._assign_keys(self._keys_display) if self._layer == 2: self.modhandler._fader_color_override = True self.modhandler.select_mod(mod) return not mod is None def _detect_instrument_type(self, track): scale = DEFAULT_AUTO_SCALE #for device in self._get_devices(track): #if self._assign_mod(): # scale = 'Mod' #else: for device in track.devices: if isinstance(device, Live.Device.Device): self._script.log_message('device: ' + str(device.class_name)) if device.class_name == 'DrumGroupDevice': scale = 'DrumPad' break return scale def _offset_value(self, offset): cur_track = self._script._mixer._selected_strip._track if cur_track.has_midi_input: cur_chan = cur_track.current_input_sub_routing if cur_chan in CHANNELS: cur_chan = (CHANNELS.index(cur_chan)) + 1 scale = self._offsets[cur_chan]['scale'] if scale is 'Auto': scale = self._detect_instrument_type(cur_track) if scale is 'DrumPad': old_offset = self._offsets[cur_chan]['drumoffset'] self._offsets[cur_chan]['drumoffset'] = offset self._script.show_message( 'New drum root is ' + str(self._offsets[cur_chan]['drumoffset'])) self._display.set_value_string( str(self._offsets[cur_chan]['drumoffset']), 3) else: self._offsets[cur_chan]['offset'] = offset self._script.show_message( 'New root is Note# ' + str(self._offsets[cur_chan]['offset']) + ', ' + str(NOTENAMES[self._offsets[cur_chan]['offset']])) self._display.set_value_string( str(self._offsets[cur_chan]['offset']) + ', ' + str(NOTENAMES[self._offsets[cur_chan]['offset']]), 3) self._assign_midi_layer() def _vertical_offset_value(self, offset): cur_track = self._script._mixer._selected_strip._track if cur_track.has_midi_input: cur_chan = cur_track.current_input_sub_routing if cur_chan in CHANNELS: cur_chan = (CHANNELS.index(cur_chan)) + 1 self._offsets[cur_chan]['vertoffset'] = offset self._script.show_message( 'New vertical offset is ' + str(self._offsets[cur_chan]['vertoffset'])) self._display.set_value_string( str(self._offsets[cur_chan]['vertoffset']), 1) self._assign_midi_layer() def _scale_offset_value(self, offset): cur_track = self._script._mixer._selected_strip._track if cur_track.has_midi_input: cur_chan = cur_track.current_input_sub_routing if cur_chan in CHANNELS: cur_chan = (CHANNELS.index(cur_chan)) + 1 self._offsets[cur_chan]['scale'] = SCALENAMES[offset] self._script.show_message( 'New scale is ' + str(self._offsets[cur_chan]['scale'])) self._display.set_value_string( str(self._offsets[cur_chan]['scale']), 2) if len(SCALES[self._offsets[cur_chan]['scale']]) > 8: self._offsets[cur_chan]['vert_offset'] = 8 self._assign_midi_layer() def _split_mode_value(self, mode): cur_track = self._script._mixer._selected_strip._track if cur_track.has_midi_input: cur_chan = cur_track.current_input_sub_routing if cur_chan in CHANNELS: cur_chan = (CHANNELS.index(cur_chan)) + 1 self._offsets[cur_chan]['split'] = bool(mode) self._display.set_value_string(str(bool(mode)), 0) self._assign_midi_layer() def _assign_midi_layer(self): cur_track = self._script.song().view.selected_track is_midi = False matrix = self._matrix_value.subject if cur_track.has_midi_input and not matrix is None: is_midi = True cur_chan = cur_track.current_input_sub_routing #self._script.log_message('cur_chan ' + str(cur_chan) + str(type(cur_chan)) + str(len(cur_chan))) if cur_chan in CHANNELS: cur_chan = (CHANNELS.index(cur_chan)) + 1 offset, vertoffset, scale, split = self._offsets[cur_chan][ 'offset'], self._offsets[cur_chan][ 'vertoffset'], self._offsets[cur_chan][ 'scale'], self._offsets[cur_chan]['split'] if scale is 'Auto': scale = self._detect_instrument_type(cur_track) #self._script.log_message('auto found: ' + str(scale)) self._split_mode_selector._mode_index = int( self._offsets[cur_chan]['split']) self._split_mode_selector.update() self._vertical_offset_component._offset = self._offsets[ cur_chan]['vertoffset'] self._vertical_offset_component.update() self._scale_offset_component._offset = SCALENAMES.index( self._offsets[cur_chan]['scale']) self._scale_offset_component.update() if scale is 'DrumPad': self._offset_component._offset = self._offsets[cur_chan][ 'drumoffset'] else: self._offset_component._offset = self._offsets[cur_chan][ 'offset'] self._offset_component.update() if scale is 'Session': is_midi = False elif scale is 'Mod': is_midi = True elif scale in SPLIT_SCALES or split: #self._send_midi(SPLITBUTTONMODE) scale_len = len(SCALES[scale]) for row in range(8): for column in range(4): button = matrix.get_button(column, row) if scale is 'DrumPad': button.set_identifier( (DRUMNOTES[column + (row * 8)] + (self._offsets[cur_chan]['drumoffset'] * 4)) % 127) button.scale_color = DRUMCOLORS[row < 4] button.send_value(button.scale_color) self._offset_component._shifted_value = 3 else: note_pos = column + (abs(7 - row) * int(vertoffset)) note = offset + SCALES[scale][ note_pos % scale_len] + ( 12 * int(note_pos / scale_len)) button.set_identifier(note % 127) button.scale_color = KEYCOLORS[ (note % 12 in WHITEKEYS) + (((note_pos % scale_len) == 0) * 2)] button.send_value(button.scale_color) self._offset_component._shifted_value = 11 button.set_enabled(False) button.set_channel(cur_chan) #self._selected_session.deassign_all() matrix = self._matrix_value.subject matrix.get_button(column + 4, row).use_default_message() matrix.get_button(column + 4, row).set_enabled(True) self._selected_scene[column + (row * 4)].clip_slot( 0).set_launch_button( matrix.get_button(column + 4, row)) #self._selected_session.set_scene_bank_buttons(self._button[5], self._button[4]) self._script.set_highlighting_session_component( self._selected_session) self._selected_session._do_show_highlight() else: #self._send_midi(MIDIBUTTONMODE) scale_len = len(SCALES[scale]) for row in range(8): for column in range(8): button = matrix.get_button(column, row) if scale is 'DrumPad': button.set_identifier( (DRUMNOTES[column + (row * 8)] + (self._offsets[cur_chan]['drumoffset'] * 4)) % 127) button.scale_color = DRUMCOLORS[(column < 4) + ((row < 4) * 2)] button.send_value(button.scale_color) self._offset_component._shifted_value = 3 else: note_pos = column + (abs(7 - row) * vertoffset) note = offset + SCALES[scale][ note_pos % scale_len] + ( 12 * int(note_pos / scale_len)) button.set_identifier(note % 127) button.scale_color = KEYCOLORS[ (note % 12 in WHITEKEYS) + (((note_pos % scale_len) == 0) * 2)] button.send_value(button.scale_color) self._offset_component._shifted_value = 11 button.set_enabled(False) button.set_channel(cur_chan) self._selected_session.deassign_all() self._script.set_highlighting_session_component( self._script._session) self._script._session._do_show_highlight() #if not self._touchstrip is None: # self._touchstrip.set_channel(cur_chan) self._display.set_value_string( str(bool(self._split_mode_selector._mode_index)), 0) self._display.set_value_string( str(self._offsets[cur_chan]['vertoffset']), 1) self._display.set_value_string( str(self._offsets[cur_chan]['scale']), 2) self._display.set_value_string( str(self._offsets[cur_chan]['offset']) + ', ' + str(NOTENAMES[self._offsets[cur_chan]['offset']]), 3) else: is_midi = False self._script.set_feedback_channels([]) return is_midi def _assign_midi_shift_layer(self): cur_track = self._script._mixer._selected_strip._track is_midi = False if cur_track.has_midi_input: #self._send_midi(LIVEBUTTONMODE) #if AUTO_ARM_SELECTED: # if not cur_track.arm: # self.schedule_message(1, self._arm_current_track, cur_track) is_midi = True cur_chan = cur_track.current_input_sub_routing if cur_chan in CHANNELS: cur_chan = (CHANNELS.index(cur_chan)) + 1 scale = self._offsets[cur_chan]['scale'] if scale is 'Auto': scale = self._detect_instrument_type(cur_track) #self.log_message('auto found: ' + str(scale)) if scale is 'Session': is_midi = False elif scale is 'Mod': is_midi = True else: """for button in self._touchpad[0:1]: button.set_on_off_values(SPLITMODE, 0) for button in self._touchpad[1:2]: button.set_on_off_values(OVERDUB, 0) self._transport.set_overdub_button(self._touchpad[1]) self._split_mode_selector._mode_index = int(self._offsets[cur_chan]['split']) self._split_mode_selector.set_enabled(True) if not self._offsets[cur_chan]['scale'] is 'DrumPad': for button in self._touchpad[2:4]: button.set_on_off_values(VERTOFFSET, 0) self._vertical_offset_component._offset = self._offsets[cur_chan]['vertoffset'] self._vertical_offset_component.set_offset_change_buttons(self._touchpad[3], self._touchpad[2]) for button in self._touchpad[4:6]: button.set_on_off_values(SCALEOFFSET, 0) self._scale_offset_component._offset = SCALENAMES.index(self._offsets[cur_chan]['scale']) self._scale_offset_component.set_offset_change_buttons(self._touchpad[5], self._touchpad[4]) for button in self._touchpad[6:8]: button.set_on_off_values(OFFSET, 0) if scale is 'Auto': scale = self._detect_instrument_type(cur_track) if scale is 'DrumPad': self._offset_component._offset = self._offsets[cur_chan]['drumoffset'] else: self._offset_component._offset = self._offsets[cur_chan]['offset'] self._offset_component.set_offset_change_buttons(self._touchpad[7], self._touchpad[6])""" is_midi = True return is_midi def on_selected_track_changed(self): track = self._script._mixer.selected_strip()._track track_list = [] for t in self._script._mixer.tracks_to_use(): track_list.append(t) if track in track_list: self._selected_session._track_offset = track_list.index(track) self._selected_session._reassign_tracks() self._selected_session._reassign_scenes() if self.is_enabled() and self._matrix_value.subject: self._assign_midi_layer()
class MelodicComponent(ModesComponent, Messenger): def __init__(self, clip_creator=None, parameter_provider=None, grid_resolution=None, note_editor_settings=None, skin=None, instrument_play_layer=None, instrument_sequence_layer=None, layer=None, *a, **k): super(MelodicComponent, self).__init__(*a, **k) self._matrices = None self._grid_resolution = grid_resolution self._instrument = self.register_component(InstrumentComponent()) self._note_editors = self.register_components(*[ NoteEditorComponent(settings_mode=note_editor_settings, clip_creator=clip_creator, grid_resolution=self._grid_resolution, is_enabled=False) for _ in xrange(NUM_NOTE_EDITORS) ]) self._paginator = NoteEditorPaginator(self._note_editors) self._loop_selector = self.register_component( LoopSelectorComponent(clip_creator=clip_creator, paginator=self._paginator, is_enabled=False)) self._playhead = None self._playhead_component = self.register_component( PlayheadComponent(grid_resolution=grid_resolution, paginator=self._paginator, follower=self._loop_selector, is_enabled=False)) self.add_mode('play', LayerMode(self._instrument, instrument_play_layer)) self.add_mode('sequence', [ LayerMode(self._instrument, instrument_sequence_layer), self._loop_selector, note_editor_settings, LayerMode(self, layer), self._playhead_component ] + self._note_editors) self.selected_mode = 'play' scales = self._instrument.scales self._on_detail_clip_changed.subject = self.song().view self._on_scales_changed.subject = scales self._on_scales_preset_changed.subject = scales._presets self._on_notes_changed.subject = self._instrument self._on_selected_mode_changed.subject = self self._on_detail_clip_changed() self._update_note_editors() self._skin = skin self._playhead_color = 'Melodic.Playhead' self._update_playhead_color() scales_menu = forward_property('_instrument')('scales_menu') scales = forward_property('_instrument')('scales') def set_playhead(self, playhead): self._playhead = playhead self._playhead_component.set_playhead(playhead) self._update_playhead_color() @forward_property('_loop_selector') def set_loop_selector_matrix(self, matrix): pass @forward_property('_loop_selector') def set_short_loop_selector_matrix(self, matrix): pass next_loop_page_button = forward_property('_loop_selector')( 'next_page_button') prev_loop_page_button = forward_property('_loop_selector')( 'prev_page_button') def set_note_editor_matrices(self, matrices): raise not matrices or len( matrices) <= NUM_NOTE_EDITORS or AssertionError self._matrices = matrices for editor, matrix in map(None, self._note_editors, matrices or []): if editor: editor.set_button_matrix(matrix) self._update_matrix_channels_for_playhead() def _get_playhead_color(self): self._playhead_color def _set_playhead_color(self, value): self._playhead_color = 'Melodic.' + value self._update_playhead_color() playhead_color = property(_get_playhead_color, _set_playhead_color) @subject_slot('detail_clip') def _on_detail_clip_changed(self): if self.is_enabled(): clip = self.song().view.detail_clip clip = clip if self.is_enabled( ) and clip and clip.is_midi_clip else None for note_editor in self._note_editors: note_editor.set_detail_clip(clip) self._loop_selector.set_detail_clip(clip) self._playhead_component.set_clip(clip) self._instrument.set_detail_clip(clip) def _set_full_velocity(self, enable): for note_editor in self._note_editors: note_editor.full_velocity = enable def _get_full_velocity(self): self._note_editors[0].full_velocity full_velocity = property(_get_full_velocity, _set_full_velocity) def set_quantization_buttons(self, buttons): self._grid_resolution.set_buttons(buttons) def set_mute_button(self, button): for e in self._note_editors: e.set_mute_button(button) @subject_slot('selected_mode') def _on_selected_mode_changed(self, mode): self._show_notes_information(mode) @subject_slot('position') def _on_notes_changed(self, *args): self._update_note_editors() self._show_notes_information() @subject_slot('selected_mode') def _on_scales_preset_changed(self, mode): self._update_note_editors() @subject_slot('scales_changed') def _on_scales_changed(self): self._update_note_editors() def _update_note_editors(self, *a): for row, note_editor in enumerate(self._note_editors): note_info = self._instrument.pattern[row] note_editor.background_color = 'NoteEditor.' + note_info.color note_editor.editing_note = note_info.index self._update_matrix_channels_for_playhead() def _update_matrix_channels_for_playhead(self): if self.is_enabled() and self._matrices != None: pattern = self._instrument.pattern for matrix, (y, _) in self._matrices.iterbuttons(): if matrix: for x, button in enumerate(matrix): if button: if pattern[y].index != None: button.set_identifier(x) button.set_channel(FEEDBACK_CHANNELS[y]) else: button.set_identifier( button._original_identifier) button.set_channel(NON_FEEDBACK_CHANNEL) def _update_playhead_color(self): if self.is_enabled() and self._skin and self._playhead: self._playhead.velocity = int(self._skin[self._playhead_color]) def update(self): super(MelodicComponent, self).update() self._on_detail_clip_changed() self._update_playhead_color() def _show_notes_information(self, mode=None): if self.is_enabled(): if mode is None: mode = self.selected_mode if mode == 'sequence': message = 'Sequence %s to %s' first = find_if(lambda editor: editor.editing_note != None, self._note_editors) last = find_if(lambda editor: editor.editing_note != None, reversed(self._note_editors)) start_note = first.editing_note if first != None else None end_note = last.editing_note if last != None else None else: message = 'Play %s to %s' start_note = self._instrument._pattern.note(0, 0).index end_note = self._instrument._pattern.note(7, 7).index self.show_notification(message % (pitch_index_to_string(start_note), pitch_index_to_string(end_note)))
class SessionRecordingComponent(CompoundComponent, Messenger): """ Orchestrates the session recording (clip slot based) workflow. """ def __init__(self, clip_creator=None, view_controller=None, *a, **k): super(SessionRecordingComponent, self).__init__(*a, **k) raise clip_creator or AssertionError raise view_controller or AssertionError self._target_slots = [] self._clip_creator = clip_creator self._view_controller = view_controller self._new_button = None self._scene_list_new_button = None self._record_button = None self._length_press_state = None self._new_scene_button = None self._fixed_length = self.register_component( ToggleWithOptionsComponent()) self._length_selector = self._fixed_length.options self._length_selector.option_names = LENGTH_OPTION_NAMES self._length_selector.selected_option = 3 self._length_selector.labels = LENGTH_LABELS self._on_selected_fixed_length_option_changed.subject = self._length_selector length, _ = self._get_selected_length() self._clip_creator.fixed_length = length song = self.song() self._automation_toggle, self._re_enable_automation_toggle, self._delete_automation = self.register_components( ToggleComponent('session_automation_record', song), ToggleComponent('re_enable_automation_enabled', song, read_only=True), ToggleComponent('has_envelopes', None, read_only=True)) self._on_tracks_changed_in_live.subject = song self._on_is_playing_changed_in_live.subject = song self._track_subject_slots = self.register_slot_manager() self._reconnect_track_listeners() self.register_slot(song, self.update, 'overdub') self.register_slot(song, self.update, 'session_record_status') self.register_slot(song.view, self.update, 'selected_track') self.register_slot(song.view, self.update, 'selected_scene') self.register_slot(song.view, self.update, 'detail_clip') length_layer = forward_property('_length_selector')('layer') def set_record_button(self, button): self._record_button = button self._on_record_button_value.subject = button self._update_record_button() def set_automation_button(self, button): self._automation_toggle.set_toggle_button(button) def set_re_enable_automation_button(self, button): self._re_enable_automation_toggle.set_toggle_button(button) self._on_re_enable_automation_value.subject = button def set_delete_automation_button(self, button): self._delete_automation.set_toggle_button(button) self._on_delete_automation_value.subject = button def set_scene_list_new_button(self, button): self._scene_list_new_button = button self._on_scene_list_new_button_value.subject = button self._update_scene_list_new_button() def set_new_button(self, button): self._new_button = button self._on_new_button_value.subject = button self._update_new_button() def set_length_button(self, button): self._fixed_length.set_action_button(button) self._on_length_value.subject = button self._length_press_state = None def set_new_scene_button(self, button): self._new_scene_button = button self._on_new_scene_button_value.subject = button self._update_new_scene_button() def deactivate_recording(self): self._stop_recording() def update(self): if self.is_enabled(): self._delete_automation.subject = self._get_playing_clip() self._update_record_button() self._update_new_button() self._update_scene_list_new_button() self._update_new_scene_button() def _update_scene_list_new_button(self): self._update_generic_new_button(self._scene_list_new_button) def _update_new_button(self): self._update_generic_new_button(self._new_button) def _update_generic_new_button(self, new_button): if new_button and self.is_enabled(): song = self.song() selected_track = song.view.selected_track clip_slot = song.view.highlighted_clip_slot can_new = clip_slot != None and clip_slot.clip or selected_track.can_be_armed and selected_track.playing_slot_index >= 0 new_button.set_light(new_button.is_pressed( ) if can_new else 'DefaultButton.Disabled') def _update_new_scene_button(self): if self._new_scene_button and self.is_enabled(): song = self.song() track_is_playing = find_if(lambda x: x.playing_slot_index >= 0, song.tracks) can_new = not song.view.selected_scene.is_empty or track_is_playing self._new_scene_button.set_light(self._new_scene_button.is_pressed( ) if can_new else 'DefaultButton.Disabled') def _update_record_button(self): if self._record_button and self.is_enabled(): song = self.song() status = song.session_record_status if status == Live.Song.SessionRecordStatus.transition: self._record_button.set_light('Recording.Transition') elif status == Live.Song.SessionRecordStatus.on or song.session_record: self._record_button.turn_on() else: self._record_button.turn_off() @subject_slot('value') def _on_re_enable_automation_value(self, value): if self.is_enabled() and value: self.song().re_enable_automation() @subject_slot('value') def _on_delete_automation_value(self, value): if self.is_enabled() and value: clip = self._get_playing_clip() if clip: clip.clear_all_envelopes() def _get_playing_clip(self): playing_clip = None selected_track = self.song().view.selected_track try: playing_slot_index = selected_track.playing_slot_index if playing_slot_index >= 0: playing_clip = selected_track.clip_slots[ playing_slot_index].clip except RuntimeError: pass return playing_clip @subject_slot('tracks') def _on_tracks_changed_in_live(self): self._reconnect_track_listeners() @subject_slot('is_playing') def _on_is_playing_changed_in_live(self): if self.is_enabled(): self._update_record_button() @subject_slot('value') def _on_record_button_value(self, value): if self.is_enabled() and value: if not self._stop_recording(): self._start_recording() @subject_slot('value') def _on_new_scene_button_value(self, value): if self.is_enabled() and value and self._prepare_new_action(): song = self.song() selected_scene_index = list(song.scenes).index( song.view.selected_scene) try: self._create_silent_scene(selected_scene_index) except Live.Base.LimitationError: self.expect_dialog(MessageBoxText.SCENE_LIMIT_REACHED) @subject_slot('value') def _on_scene_list_new_button_value(self, value): if self.is_enabled() and value and self._prepare_new_action(): song = self.song() view = song.view try: if view.highlighted_clip_slot.clip != None: song.capture_and_insert_scene( Live.Song.CaptureMode.all_except_selected) else: view.selected_track.stop_all_clips(False) except Live.Base.LimitationError: self.expect_dialog(MessageBoxText.SCENE_LIMIT_REACHED) self._view_selected_clip_detail() @subject_slot('value') def _on_new_button_value(self, value): if self.is_enabled() and value and self._prepare_new_action(): song = self.song() view = song.view try: selected_track = view.selected_track selected_scene_index = list(song.scenes).index( view.selected_scene) selected_track.stop_all_clips(False) self._jump_to_next_slot(selected_track, selected_scene_index) except Live.Base.LimitationError: self.expect_dialog(MessageBoxText.SCENE_LIMIT_REACHED) self._view_selected_clip_detail() def _prepare_new_action(self): song = self.song() selected_track = song.view.selected_track if selected_track.can_be_armed: song.overdub = False return True def _has_clip(self, scene_or_track): return find_if(lambda x: x.clip != None, scene_or_track.clip_slots) != None def _create_silent_scene(self, scene_index): song = self.song() song.stop_all_clips(False) selected_scene = song.view.selected_scene if not selected_scene.is_empty: new_scene_index = list(song.scenes).index(selected_scene) + 1 song.view.selected_scene = song.create_scene(new_scene_index) def _jump_to_next_slot(self, track, start_index): song = self.song() new_scene_index = self._next_empty_slot(track, start_index) song.view.selected_scene = song.scenes[new_scene_index] def _stop_recording(self): """ Retriggers all new recordings and returns true if there was any recording at all """ song = self.song() status = song.session_record_status if not status != Live.Song.SessionRecordStatus.off: was_recording = song.session_record song.session_record = was_recording and False return was_recording def _start_recording(self): song = self.song() song.overdub = True selected_scene = song.view.selected_scene scene_index = list(song.scenes).index(selected_scene) track = song.view.selected_track if track.can_be_armed and (track.arm or track.implicit_arm): self._record_in_slot(track, scene_index) if not song.is_playing: song.is_playing = True def _find_last_clip(self): """ Finds the last clip of the session and returns the scene index """ scenes = self.song().scenes return len(scenes) - index_if(self._has_clip, reversed(scenes)) - 1 def _next_empty_slot(self, track, scene_index): """ Finds an empty slot in the track after the given position, creating new scenes if needed, and returns the found scene index """ song = self.song() scene_count = len(song.scenes) while track.clip_slots[scene_index].has_clip: scene_index += 1 if scene_index == scene_count: song.create_scene(scene_count) return scene_index def _record_in_slot(self, track, scene_index): song = self.song() clip_slot = track.clip_slots[scene_index] if self._fixed_length.is_active and not clip_slot.has_clip: length, quant = self._get_selected_length() if track_can_overdub(track): self._clip_creator.create(clip_slot, length) else: clip_slot.fire(record_length=length, launch_quantization=quant) elif not clip_slot.is_playing: if clip_slot.has_clip: clip_slot.fire(force_legato=True, launch_quantization=_Q.q_no_q) else: clip_slot.fire() if song.view.selected_track == track: song.view.selected_scene = song.scenes[scene_index] self._view_selected_clip_detail() @subject_slot('value') def _on_length_value(self, value): if value: self._on_length_press() else: self._on_length_release() @subject_slot('selected_option') def _on_selected_fixed_length_option_changed(self, _): length, _ = self._get_selected_length() self._clip_creator.fixed_length = length def _on_length_press(self): song = self.song() slot = song_selected_slot(song) if slot == None: return clip = slot.clip if slot.is_recording and not clip.is_overdubbing: self._length_press_state = (slot, clip.playing_position) def _on_length_release(self): song = self.song() slot = song_selected_slot(song) if slot == None: return clip = slot.clip if self._length_press_state is not None: press_slot, press_position = self._length_press_state if press_slot == slot and self._fixed_length.is_active and slot.is_recording and not clip.is_overdubbing: length, _ = self._get_selected_length() one_bar = 4.0 * song.signature_numerator / song.signature_denominator loop_end = int(press_position / one_bar) * one_bar loop_start = loop_end - length if loop_start >= 0.0: clip.loop_end = loop_end clip.end_marker = loop_end clip.loop_start = loop_start clip.start_marker = loop_start self._tasks.add( Task.sequence( Task.delay(0), Task.run( partial(slot.fire, force_legato=True, launch_quantization=_Q.q_no_q)))) self.song().overdub = False self._fixed_length.is_active = False self._length_press_state = None def _get_selected_length(self): song = self.song() length = 2.0**self._length_selector.selected_option quant = LAUNCH_QUANTIZATION[self._length_selector.selected_option] if self._length_selector.selected_option > 1: length = length * song.signature_numerator / song.signature_denominator return (length, quant) def _view_selected_clip_detail(self): view = self.song().view if view.highlighted_clip_slot.clip: view.detail_clip = view.highlighted_clip_slot.clip self._view_controller.show_view('Detail/Clip') def _reconnect_track_listeners(self): manager = self._track_subject_slots manager.disconnect() for track in self.song().tracks: if track.can_be_armed: manager.register_slot(track, self.update, 'arm') manager.register_slot(track, self.update, 'playing_slot_index') manager.register_slot(track, self.update, 'fired_slot_index') def _set_scene_list_mode(self, scene_list_mode): self._scene_list_mode = scene_list_mode self._update_new_button() def _get_scene_list_mode(self): return self._scene_list_mode scene_list_mode = property(_get_scene_list_mode, _set_scene_list_mode)
class NoteEditorComponent(CompoundComponent, Subject): __subject_events__ = ('page_length', 'active_steps', 'notes_changed') def __init__(self, settings_mode=None, clip_creator=None, grid_resolution=None, *a, **k): super(NoteEditorComponent, self).__init__(*a, **k) self.loop_steps = False self.full_velocity = False self._selected_page_point = 0 self._page_index = 0 self._clip_creator = clip_creator self._matrix = None self._width = 0 self._height = 0 self._sequencer_clip = None self._step_colors = [] if settings_mode: self._settings_mode = self.register_component(settings_mode) self._mute_button = None 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._note_index = 36 self._grid_resolution = grid_resolution self._on_resolution_changed.subject = self._grid_resolution self._nudge_offset = 0 self._length_offset = 0 self._velocity_offset = 0 self._settings_mode and self._settings_mode.add_editor(self) self._settings = settings_mode.settings self._on_setting_changed.subject = self._settings self._triplet_factor = 1.0 self._update_from_grid() self.background_color = 'NoteEditor.StepEmpty' note_settings_layer = forward_property('_settings')('layer') @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 def set_selected_page_point(self, point): if not self.can_change_page: raise AssertionError self._selected_page_point = point index = int(point / self.page_length) if self.page_length != 0 else 0 self._page_index = index != self._page_index and 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 if self._settings_mode: self._settings_mode.selected_mode = 'enabled' if enabled else 'disabled' self._settings_mode.selected_setting = 'pad_settings' self._on_clip_notes_changed() modify_all_notes_enabled = property(_get_modify_all_notes_enabled, _set_modify_all_notes_enabled) def set_detail_clip(self, clip): self._sequencer_clip = clip self._on_clip_notes_changed.subject = clip self._on_clip_notes_changed() def _get_editing_note(self): return self._note_index def _set_editing_note(self, note_index): self._note_index = note_index self._on_clip_notes_changed() editing_note = property(_get_editing_note, _set_editing_note) def set_mute_button(self, button): self._mute_button = button self._on_mute_value.subject = button def set_button_matrix(self, matrix): last_page_length = self.page_length self._matrix = matrix self._on_matrix_value.subject = matrix if matrix: self._width = matrix.width() self._height = matrix.height() matrix.reset() for button, _ in ifilter(first, matrix.iterbuttons()): button.set_channel(PAD_FEEDBACK_CHANNEL) for task in self._step_tap_tasks.itervalues(): task.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._width), xrange(self._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 update(self): super(NoteEditorComponent, self).update() self._update_editor_matrix_leds() self._grid_resolution.update() 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) @subject_slot('notes') def _on_clip_notes_changed(self): """ get notes from clip for offline array """ if self._sequencer_clip and self._note_index != None: time_start, time_length = self._get_clip_notes_time_range() self._clip_notes = self._sequencer_clip.get_notes( time_start, self._note_index, time_length, 1) 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 = ['NoteEditor.StepDisabled'] * self._get_step_count() width = self._width coords_to_index = lambda (x, y): x + y * 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 = 'NoteEditor.StepSelected' elif index in editing_indices: note_color = color_for_note(most_significant_note(notes)) color = 'NoteEditor.StepEditing.' + note_color last_editing_notes = notes else: note_color = color_for_note(most_significant_note(notes)) color = 'NoteEditor.Step.' + note_color elif any(imap(time_step.overlaps_note, last_editing_notes)): color = 'NoteEditor.StepEditing.' + note_color elif index in editing_indices or index in selected_indices: color = 'NoteEditor.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 _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 self._is_triplet_quantization(): 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): """ update hardware LEDS to match offline array values """ if self.is_enabled() and self._matrix: for row, col in product(xrange(self._height), xrange(self._width)): index = row * self._width + col color = self._step_colors[index] self._matrix.set_light(col, row, color) def _get_step_count(self): return self._width * self._height def _get_step_start_time(self, x, y): """ returns step starttime in beats, based on step coordinates """ raise in_range(x, 0, self._width) or AssertionError raise in_range(y, 0, self._height) or AssertionError page_time = self._page_index * self._get_step_count( ) * self._triplet_factor step_time = x + y * self._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 _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 self._sequencer_clip: self._sequencer_clip.view.grid_quantization = quantization self._sequencer_clip.view.grid_is_triplet = is_triplet @subject_slot('value') def _on_mute_value(self, value): if self.is_enabled() and value: self._trigger_modification(immediate=True) @subject_slot('index') def _on_resolution_changed(self): 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() @subject_slot('value') def _on_matrix_value(self, value, x, y, is_momentary): if self.is_enabled(): if self._sequencer_clip == None and value or not is_momentary: clip = create_clip_in_selected_slot(self._clip_creator, self.song()) self.set_detail_clip(clip) if self._note_index != None: width = self._width * self._triplet_factor if self._is_triplet_quantization( ) else self._width if x < width and y < self._height: if value or not is_momentary: self._on_press_step((x, y)) else: self._on_release_step((x, y)) self._update_editor_matrix() @subject_slot('value') def _on_any_touch_value(self, value, x, y, is_momentary): pass @property def active_steps(self): def get_time_range((x, y)): time = self._get_step_start_time(x, y) return (time, time + self._get_step_length()) return imap(get_time_range, chain(self._pressed_steps, self._modified_steps)) 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) self._add_note_in_step(step) if step in self._modified_steps: self._modified_steps.remove(step) self.notify_active_steps() def _on_press_step(self, step): if self._sequencer_clip != None and step not in self._pressed_steps and step not in self._modified_steps: self._step_tap_tasks[step].restart() self._pressed_steps.append(step) self.notify_active_steps() def _time_step(self, time): if self.loop_steps and self._sequencer_clip != None and self._sequencer_clip.looping: return LoopingTimeStep(time, self._get_step_length(), self._sequencer_clip.loop_start, self._sequencer_clip.loop_end) else: return TimeStep(time, self._get_step_length()) def _add_note_in_step(self, step, modify_existing=True): """ Add note in given step if there are none in there, otherwise select the step for potential deletion or modification """ if self._sequencer_clip != None: x, y = step time = self._get_step_start_time(x, y) notes = self._time_step(time).filter_notes(self._clip_notes) if notes: if modify_existing: most_significant_velocity = most_significant_note(notes)[3] if self._mute_button and self._mute_button.is_pressed( ) or most_significant_velocity != 127 and self.full_velocity: self._trigger_modification(step, immediate=True) else: pitch = self._note_index mute = self._mute_button and self._mute_button.is_pressed() velocity = 127 if self.full_velocity else DEFAULT_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) return True return False def _delete_notes_in_step(self, (x, y)): """ Delete all notes in the given step """ if self._sequencer_clip: time_step = self._time_step(self._get_step_start_time(x, y)) for time, length in time_step.connected_time_ranges(): self._sequencer_clip.remove_notes(time, self._note_index, length, 1)
class NotificationComponent(CompoundComponent): """ Displays notifications to the user for a given amount of time. A notification time of -1 creates an infinite duration notification. To adjust the way notifications are shown in special cases, assign a generated control using use_single_line or use_full_display to a layer. If the layer is on top, it will set the preferred view. This will show the notification on line 1 if my_component is enabled and the priority premise of the layer is met: my_component.layer = Layer( _notification = notification_component.use_single_line(1)) """ _default_align_text_fn = partial(maybe(partial(align_none, DISPLAY_LENGTH))) def __init__(self, notification_time=2.5, blinking_time=0.3, display_lines=[], *a, **k): super(NotificationComponent, self).__init__(*a, **k) self._display_lines = display_lines self._token_control = _TokenControlElement() self._align_text_fn = self._default_align_text_fn self._message_box = self.register_component(MessageBoxComponent()) self._message_box.set_enabled(False) self._notification_timeout_task = self._tasks.add( Task.sequence(Task.wait(notification_time), Task.run(self.hide_notification)) ).kill() if notification_time != -1 else self._tasks.add(Task.Task()) self._blink_text_task = self._tasks.add( Task.loop( Task.sequence( Task.run(lambda: self._message_box. __setattr__('text', self._original_text)), Task.wait(blinking_time), Task.run(lambda: self._message_box.__setattr__( 'text', self._blink_text)), Task.wait(blinking_time)))).kill() self._original_text = None self._blink_text = None message_box_layer = forward_property('_message_box')('layer') def show_notification(self, text, blink_text=None): """ Triggers a notification with the given text. """ text = self._align_text_fn(text) blink_text = self._align_text_fn(blink_text) if blink_text is not None: self._original_text = text self._blink_text = blink_text self._blink_text_task.restart() self._message_box.text = text self._message_box.set_enabled(True) self._notification_timeout_task.restart() def hide_notification(self): """ Hides the current notification, if any existing. """ self._blink_text_task.kill() self._message_box.set_enabled(False) def use_single_line(self, line_index, line_slice=None, align=align_none): """ Returns a control, that will change the notification to a single line view, if it is grabbed. """ if not (line_index >= 0 and line_index < len(self._display_lines)): raise AssertionError display = self._display_lines[line_index] display = line_slice is not None and display.subdisplay[line_slice] layer = Layer(priority=MESSAGE_BOX_PRIORITY, display_line1=display) return _CallbackControl( self._token_control, partial(self._set_message_box_layout, layer, maybe(partial(align, display.width)))) def use_full_display(self, message_line_index=2): """ Returns a control, that will change the notification to use the whole display, if it is grabbed. """ layer = Layer( priority=MESSAGE_BOX_PRIORITY, **dict([ ('display_line1' if i == message_line_index else 'bg%d' % i, line) for i, line in enumerate(self._display_lines) ])) return _CallbackControl(self._token_control, partial(self._set_message_box_layout, layer)) def _set_message_box_layout(self, layer, align_text_fn=None): self._message_box.layer = layer self._align_text_fn = partial(align_text_fn or self._default_align_text_fn)
class InstrumentScalesComponent(CompoundComponent, Scrollable): __subject_events__ = ('scales_changed', ) release_info_display_with_encoders = True is_absolute = False is_diatonic = True key_center = 0 def __init__(self, *a, **k): super(InstrumentScalesComponent, self).__init__(*a, **k) self._key_center_slots = self.register_slot_manager() self._key_center_buttons = [] self._encoder_touch_button_slots = self.register_slot_manager() self._encoder_touch_buttons = [] self._top_key_center_buttons = None self._bottom_key_center_buttons = None self._absolute_relative_button = None self._diatonic_chromatic_button = None table = consts.MUSICAL_MODES self._modus_list = [ Modus(table[k], table[k + 1]) for k in xrange(0, len(consts.MUSICAL_MODES), 2) ] self._selected_modus = 0 self._line_sources = recursive_map(DisplayDataSource, SCALES_DISPLAY_STRINGS) self._presets = self.register_component(InstrumentPresetsComponent()) self._presets.set_enabled(False) self._presets_modes = self.register_component(ModesComponent()) self._presets_modes.add_mode('disabled', None) self._presets_modes.add_mode('enabled', self._presets, 'Scales.PresetsEnabled') self._presets_modes.selected_mode = 'disabled' self._presets_modes.momentary_toggle = True self._presets.selected_mode = 'scale_p4_vertical' self._scales_info = self.register_component(ScalesInfoComponent()) self._scales_info.set_enabled(True) self._modus_scroll = self.register_component(ScrollComponent()) self._modus_scroll.scrollable = self self._update_data_sources() presets_layer = forward_property('_presets')('layer') scales_info_layer = forward_property('_scales_info')('layer') @property def modus(self): return self._modus_list[self._selected_modus] @property def available_scales(self): return self.modus.scales(KEY_CENTERS) @property def notes(self): return self.modus.scale(self.key_center).notes def set_top_display_line(self, display): if display: self._set_display_line(display, 0) def set_bottom_display_line(self, display): if display: self._set_display_line(display, 1) def _set_display_line(self, display, line): if display: display.set_num_segments(8) for idx in xrange(8): display.segment(idx).set_data_source( self._line_sources[line][idx]) def set_presets_toggle_button(self, button): raise button is None or button.is_momentary() or AssertionError self._presets_modes.set_toggle_button(button) def set_top_buttons(self, buttons): if buttons: self.set_absolute_relative_button(buttons[7]) self._top_key_center_buttons = buttons[1:7] self.set_modus_up_button(buttons[0]) else: self.set_absolute_relative_button(None) self._top_key_center_buttons = None self.set_modus_up_button(None) if self._top_key_center_buttons and self._bottom_key_center_buttons: self.set_key_center_buttons(self._top_key_center_buttons + self._bottom_key_center_buttons) else: self.set_key_center_buttons(tuple()) def set_bottom_buttons(self, buttons): if buttons: self.set_diatonic_chromatic_button(buttons[7]) self._bottom_key_center_buttons = buttons[1:7] self.set_modus_down_button(buttons[0]) else: self.set_diatonic_chromatic_button(None) self._bottom_key_center_buttons = None self.set_modus_down_button(None) if self._top_key_center_buttons and self._bottom_key_center_buttons: self.set_key_center_buttons(self._top_key_center_buttons + self._bottom_key_center_buttons) else: self.set_key_center_buttons([]) def set_modus_down_button(self, button): if button: button.set_on_off_values('List.ScrollerOn', 'List.ScrollerOff') self._modus_scroll.set_scroll_down_button(button) def set_modus_up_button(self, button): if button: button.set_on_off_values('List.ScrollerOn', 'List.ScrollerOff') self._modus_scroll.set_scroll_up_button(button) def set_key_center_buttons(self, buttons): if not (not buttons or len(buttons) == 12): raise AssertionError buttons = buttons or [] self._key_center_buttons = self._key_center_buttons != buttons and buttons self._key_center_slots.disconnect() for button in buttons: self._key_center_slots.register_slot( button, self._on_key_center_button_value, 'value', extra_kws=dict(identify_sender=True)) self._update_key_center_buttons() def set_absolute_relative_button(self, absolute_relative_button): if 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): if 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() 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.key_center = KEY_CENTERS[index] self._update_key_center_buttons() self._update_data_sources() self.notify_scales_changed() @subject_slot('value') def _on_absolute_relative_value(self, value): if self.is_enabled(): if value != 0 or not self._absolute_relative_button.is_momentary(): self.is_absolute = not self.is_absolute self._update_absolute_relative_button() self._update_data_sources() self.notify_scales_changed() @subject_slot('value') def _on_diatonic_chromatic_value(self, value): if self.is_enabled(): if value != 0 or not self._diatonic_chromatic_button.is_momentary( ): self.is_diatonic = not self.is_diatonic self._update_diatonic_chromatic_button() self._update_data_sources() self.notify_scales_changed() def can_scroll_up(self): return self._selected_modus > 0 def can_scroll_down(self): return self._selected_modus < len(self._modus_list) - 1 def scroll_up(self): self._set_selected_modus(self._selected_modus - 1) def scroll_down(self): self._set_selected_modus(self._selected_modus + 1) def _set_selected_modus(self, n): if n > -1 and n < len(self._modus_list): self._selected_modus = n self._update_data_sources() self.notify_scales_changed() def update(self): if self.is_enabled(): self._update_key_center_buttons() self._update_absolute_relative_button() self._update_diatonic_chromatic_button() self._update_scales_info() def _update_key_center_buttons(self): if self.is_enabled(): for index, button in enumerate(self._key_center_buttons): button.set_on_off_values('Scales.Selected', 'Scales.Unselected') button.set_light(self.key_center == KEY_CENTERS[index]) def _update_absolute_relative_button(self): if self.is_enabled() and self._absolute_relative_button != None: self._absolute_relative_button.set_on_off_values( 'Scales.FixedOn', 'Scales.FixedOff') self._absolute_relative_button.set_light(self.is_absolute) def _update_diatonic_chromatic_button(self): if self.is_enabled() and self._diatonic_chromatic_button != None: self._diatonic_chromatic_button.set_on_off_values( 'Scales.Diatonic', 'Scales.Chromatic') self._diatonic_chromatic_button.set_light(self.is_diatonic) def _update_data_sources(self): key_index = list(KEY_CENTERS).index(self.key_center) key_sources = self._line_sources[0][1:7] + self._line_sources[1][1:7] 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][7].set_display_string( 'Fixed: Y' if self.is_absolute else 'Fixed: N') self._line_sources[1][7].set_display_string( 'In Key' if self.is_diatonic else 'Chromatic') self._line_sources[0][0].set_display_string( consts.CHAR_SELECT + self._modus_list[self._selected_modus].name) if self._selected_modus + 1 < len(self._modus_list): self._line_sources[1][0].set_display_string( ' ' + self._modus_list[self._selected_modus + 1].name) else: self._line_sources[1][0].set_display_string(' ') self._scales_info.set_info_display_string('Scale Selection:') self._scales_info.set_info_display_string( self._modus_list[self._selected_modus].name, 1) def set_encoder_touch_buttons(self, encoder_touch_buttons): self._encoder_touch_buttons = encoder_touch_buttons or [] self._on_encoder_touch_buttons_value.replace_subjects( self._encoder_touch_buttons) self._update_scales_info() @subject_slot_group('value') def _on_encoder_touch_buttons_value(self, value, sender): self._update_scales_info() def _update_scales_info(self): if self.is_enabled(): self._scales_info.set_enabled( not self.release_info_display_with_encoders or not any( map(lambda button: button.is_pressed(), self._encoder_touch_buttons)))
class SelectPlayingClipComponent(ModesComponent): 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._notification = self.register_component(NotificationComponent(notification_time=-1, is_enabled=False)) self.add_mode('default', None) self.add_mode('above', [AddLayerMode(self, playing_clip_above_layer), self._notification, partial(self._show_notification, MessageBoxText.PLAYING_CLIP_ABOVE_SELECTED_CLIP)]) self.add_mode('below', [AddLayerMode(self, playing_clip_below_layer), self._notification, partial(self._show_notification, MessageBoxText.PLAYING_CLIP_BELOW_SELECTED_CLIP)]) self.selected_mode = 'default' self._on_detail_clip_changed.subject = self.song().view self._on_playing_slot_index_changed.subject = self.song().view.selected_track notification_layer = forward_property('_notification')('message_box_layer') def set_action_button(self, button): self._on_action_button_value.subject = button self._update_action_button() @subject_slot('value') def _on_action_button_value(self, value): self._go_to_playing_clip() @subject_slot('detail_clip') def _on_detail_clip_changed(self): self._update_mode_task.restart() @subject_slot('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 def _show_notification(self, display_text): self._notification.show_notification(display_text, blink_text=MessageBoxText.SELECTED_CLIP_BLINK) def _selected_track_clip_is_playing(self): playing_clip_slot = self._playing_clip_slot() return playing_clip_slot and not 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 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' def _update_action_button(self): action_button = self._on_action_button_value.subject if action_button: action_button.set_light('DefaultButton.Alert') def update(self): if self.is_enabled(): self._update_action_button() self._update_mode()
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 def __init__(self, *a, **k): super(SpecialSessionComponent, self).__init__(*a, **k) self._slot_launch_button = None self._duplicate_button = None self._duplicate, self._duplicate_modes, self._paginator = self.register_components( DuplicateSceneComponent(self), ModesComponent(), ScrollComponent()) self._paginator.can_scroll_up = self._can_scroll_page_up self._paginator.can_scroll_down = self._can_scroll_page_down self._paginator.scroll_up = self._scroll_page_up self._paginator.scroll_down = self._scroll_page_down self._duplicate.set_enabled(False) self._duplicate_modes.add_mode('disabled', None) self._duplicate_modes.add_mode('enabled', self._duplicate) self._duplicate_modes.selected_mode = 'disabled' self._duplicate_modes.momentary_toggle = True self._track_playing_slots = self.register_slot_manager() self._end_initialisation() duplicate_layer = forward_property('_duplicate')('layer') def set_duplicate_button(self, button): self._duplicate_modes.set_toggle_button(button) def set_page_up_button(self, page_up_button): self._paginator.set_scroll_up_button(page_up_button) def set_page_down_button(self, page_down_button): self._paginator.set_scroll_down_button(page_down_button) def set_slot_launch_button(self, button): self._slot_launch_button = button self._on_slot_launch_value.subject = button def set_stop_track_clip_buttons(self, buttons): for button in buttons or []: if button: button.set_on_off_values('Option.On', 'Option.Off') super(SpecialSessionComponent, self).set_stop_track_clip_buttons(buttons) def set_clip_launch_buttons(self, buttons): if buttons: buttons.reset() super(SpecialSessionComponent, self).set_clip_launch_buttons(buttons) def _reassign_scenes(self): super(SpecialSessionComponent, self)._reassign_scenes() self._paginator.update() def _reassign_tracks(self): super(SpecialSessionComponent, self)._reassign_tracks() self._track_playing_slots.disconnect() tracks_to_use = self.tracks_to_use() for index in range(self._num_tracks): listener = lambda index=index: self._on_playing_slot_index_changed( index) if self._track_offset + index < len(tracks_to_use): track = tracks_to_use[self._track_offset + index] if track in self.song().tracks: self._track_slots.register_slot(track, listener, 'playing_slot_index') listener() def _on_fired_slot_index_changed(self, index): self._update_stop_clips_led(index) def _on_playing_slot_index_changed(self, index): self._update_stop_clips_led(index) def _update_stop_clips_led(self, index): """ self.canonical_parent.log_message('update_Stop called with parameter ' + str(index)) """ track_index = index + self.track_offset() tracks_to_use = self.tracks_to_use() if self.is_enabled( ) and self._stop_track_clip_buttons != None and index < len( self._stop_track_clip_buttons): button = self._stop_track_clip_buttons[index] if button != None: if track_index < len(tracks_to_use) and tracks_to_use[ track_index].clip_slots: check_for_group_track = True if tracks_to_use[track_index].fired_slot_index == -2: button.set_light('Mixer.StoppingTrack') elif tracks_to_use[track_index].playing_slot_index >= 0: button.set_light('Mixer.StopTrack') else: button.turn_off() if tracks_to_use[track_index].is_foldable: check_for_group_track = False for actual_slot in tracks_to_use[ track_index].clip_slots: if actual_slot.is_playing: button.set_light('Mixer.StopTrack') if check_for_group_track: tracklist = list( tracks_to_use[track_index].canonical_parent.tracks) tracklist_index = tracklist.index( tracks_to_use[track_index]) x = 1 while x < 8: if tracklist_index - x >= 0: if tracklist[tracklist_index - x].is_foldable: if index - x >= 0: self._update_stop_clips_led(index - x) break x = x + 1 else: button.turn_off() @subject_slot('value') def _on_slot_launch_value(self, value): if self.is_enabled(): if value != 0 or not self._slot_launch_button.is_momentary(): if self.song().view.highlighted_clip_slot != None: self.song().view.highlighted_clip_slot.fire() self._slot_launch_button.turn_on() else: self._slot_launch_button.turn_off() def _can_scroll_page_up(self): return self.scene_offset() > 0 def _can_scroll_page_down(self): return self.scene_offset() < len(self.song().scenes) - self.height() def _scroll_page_up(self): height = self.height() track_offset = self.track_offset() scene_offset = self.scene_offset() if scene_offset > 0: new_scene_offset = scene_offset if scene_offset % height > 0: new_scene_offset -= scene_offset % height else: new_scene_offset = max(0, scene_offset - height) self.set_offsets(track_offset, new_scene_offset) def _scroll_page_down(self): height = self.height() track_offset = self.track_offset() scene_offset = self.scene_offset() new_scene_offset = scene_offset + height - scene_offset % height self.set_offsets(track_offset, new_scene_offset)
class FixedLengthSessionRecordingComponent(SessionRecordingComponent, Messenger): def __init__(self, *a, **k): super(FixedLengthSessionRecordingComponent, self).__init__(*a, **k) self._fixed_length = self.register_component( ToggleWithOptionsComponent()) self._length_selector = self._fixed_length.options self._length_selector.option_names = LENGTH_OPTION_NAMES self._length_selector.selected_option = 3 self._length_selector.labels = LENGTH_LABELS self._on_selected_fixed_length_option_changed.subject = self._length_selector length, _ = self._get_selected_length() self._clip_creator.fixed_length = length length_layer = forward_property('_length_selector')('layer') def _length_should_be_fixed(self): return self._fixed_length.is_active def _get_selected_length(self): song = self.song() length = 2.0**self._length_selector.selected_option quant = LAUNCH_QUANTIZATION[self._length_selector.selected_option] if self._length_selector.selected_option > 1: length = length * song.signature_numerator / song.signature_denominator return (length, quant) def set_length_button(self, button): self._fixed_length.action_button.set_control_element(button) self._on_length_value.subject = button self._length_press_state = None @subject_slot('selected_option') def _on_selected_fixed_length_option_changed(self, _): length, _ = self._get_selected_length() self._clip_creator.fixed_length = length @subject_slot('value') def _on_length_value(self, value): if value: self._on_length_press() else: self._on_length_release() def _on_length_press(self): song = self.song() slot = song_selected_slot(song) if slot == None: return clip = slot.clip if slot.is_recording and not clip.is_overdubbing: self._length_press_state = (slot, clip.playing_position) def _on_length_release(self): song = self.song() slot = song_selected_slot(song) if slot == None: return clip = slot.clip if self._length_press_state is not None: press_slot, press_position = self._length_press_state if press_slot == slot and self._length_should_be_fixed( ) and slot.is_recording and not clip.is_overdubbing: length, _ = self._get_selected_length() one_bar = 4.0 * song.signature_numerator / song.signature_denominator loop_end = int(press_position / one_bar) * one_bar loop_start = loop_end - length if loop_start >= 0.0: clip.loop_end = loop_end clip.end_marker = loop_end clip.loop_start = loop_start clip.start_marker = loop_start self._tasks.add( Task.sequence( Task.delay(0), Task.run( partial(slot.fire, force_legato=True, launch_quantization=_Q.q_no_q)))) self.song().overdub = False self._fixed_length.is_active = False self._length_press_state = None def _handle_limitation_error_on_scene_creation(self): self.expect_dialog(MessageBoxText.SCENE_LIMIT_REACHED)
class SelectComponent(CompoundComponent): """ This component handles selection of objects. """ def __init__(self, *a, **k): super(SelectComponent, self).__init__(*a, **k) self._selected_clip = None self._select_button = None self._selection_display = self.register_component( SelectionDisplayComponent()) self._selection_display.set_enabled(False) selection_display_layer = forward_property('_selection_display')('layer') def set_select_button(self, button): if self._select_button != button: self._select_button = button self._on_select_value.subject = button def set_selected_clip(self, clip): self._selected_clip = clip self._on_playing_position_changed.subject = clip def on_select_clip(self, clip_slot): if clip_slot != None: if self.song().view.highlighted_clip_slot != clip_slot: self.song().view.highlighted_clip_slot = clip_slot if clip_slot.has_clip: clip = clip_slot.clip clip_name = clip.name if clip.name != '' else '[unnamed]' self.set_selected_clip(clip) else: clip_name = '[empty slot]' self.set_selected_clip(None) else: clip_name = '[none]' self._selection_display.set_display_string('Clip Selection:') self._selection_display.set_display_string(clip_name, 1) self._do_show_time_remaining() self._selection_display.set_enabled(True) @subject_slot('playing_position') def _on_playing_position_changed(self): self._do_show_time_remaining() def _do_show_time_remaining(self): clip = self._selected_clip if clip != None and (clip.is_triggered or clip.is_playing): if clip.is_recording: label = 'Record Count:' length = ( clip.playing_position - clip.loop_start ) * clip.signature_denominator / clip.signature_numerator time = convert_length_to_bars_beats_sixteenths(length) else: label = 'Time Remaining:' length = clip.loop_end - clip.playing_position if clip.is_audio_clip and not clip.warping: time = convert_length_to_mins_secs(length) else: time = convert_beats_to_mins_secs(length, self.song().tempo) else: label = ' ' time = ' ' self._selection_display.set_display_string(label, 2) self._selection_display.set_display_string(time, 3) def on_select_scene(self, scene): if scene != None: if self.song().view.selected_scene != scene: self.song().view.selected_scene = scene scene_name = scene.name if scene.name != '' else '[unnamed]' else: scene_name = '[none]' self._selection_display.set_display_string('Scene Selection:') self._selection_display.set_display_string(scene_name, 1) self._selection_display.reset_display_right() self._selection_display.set_enabled(True) def on_select_track(self, track): if track != None: track_name = track.name if track.name != '' else '[unnamed]' else: track_name = '[none]' self._selection_display.set_display_string('Track Selection:') self._selection_display.set_display_string(track_name, 1) self._selection_display.reset_display_right() self._selection_display.set_enabled(True) def on_select_drum_pad(self, drum_pad): if drum_pad != None: drum_pad_name = drum_pad.name if drum_pad.name != '' else '[unnamed]' else: drum_pad_name = '[none]' self._selection_display.set_display_string('Pad Selection:') self._selection_display.set_display_string(drum_pad_name, 1) self._selection_display.reset_display_right() self._selection_display.set_enabled(True) @subject_slot('value') def _on_select_value(self, value): if value == 0: self._selection_display.set_enabled(False) self._selection_display.reset_display() self.set_selected_clip(None) def update(self): pass
class ValueComponentBase(CompoundComponent): """ Component to control one continuous property with a infinite touch-sensitive encoder. You can optionally give it a display and a button such that the value will be displayed while its pressed. """ TOUCH_BASED = 0 TIMER_BASED = 1 AUTO_HIDE_IN_SEC = 0.5 def create_display_component(self, *a, **k): raise NotImplementedError def __init__(self, display_label = ' ', display_seg_start = 0, encoder = None, *a, **k): super(ValueComponentBase, self).__init__(*a, **k) self._display_mode = self.TOUCH_BASED self._button = None self._on_encoder_changed.subject = encoder self._display = self.register_component(self.create_display_component(display_label=display_label, display_seg_start=display_seg_start)) self._display.set_enabled(False) self._hide_display_task = self._tasks.add(Task.sequence(Task.wait(self.AUTO_HIDE_IN_SEC), Task.run(partial(self._display.set_enabled, False)))) self._hide_display_task.kill() display_layer = forward_property('_display')('layer') def _get_display_mode(self): return self._display_mode def _set_display_mode(self, mode): if self._display_mode != mode: self._display_mode = mode self._update_display_state() display_mode = property(_get_display_mode, _set_display_mode) def set_encoder(self, encoder): raise NotImplementedError def set_button(self, button): self._button = button self._on_button_value.subject = button self._update_display_state() @subject_slot('value') def _on_button_value(self, value): self._update_display_state() @subject_slot('value') def _on_encoder_changed(self, value): if self.display_mode == self.TIMER_BASED: self._display.set_enabled(True) self._hide_display_task.restart() def _update_display_state(self): if self.display_mode == self.TOUCH_BASED: self._display.set_enabled(self._button and self._button.is_pressed()) if self._button: self._hide_display_task.kill() elif self.display_mode == self.TIMER_BASED: self._display.set_enabled(False) def update(self): button = self._on_button_value.subject self._display.set_enabled(button and button.is_pressed())