Beispiel #1
0
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
Beispiel #6
0
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
Beispiel #7
0
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)
Beispiel #8
0
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)
Beispiel #10
0
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')
Beispiel #11
0
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()
Beispiel #12
0
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()
Beispiel #13
0
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 '')
Beispiel #14
0
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
Beispiel #15
0
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)
Beispiel #17
0
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()
Beispiel #18
0
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)))
Beispiel #24
0
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
Beispiel #28
0
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())