class ActionsComponent(ControlSurfaceComponent):
    """'
    Simple component that provides undo/redo, record quantization toggle
    and clip quantization handling.
    """
    __module__ = __name__
    undo_button = ButtonControl(**ACTION_BUTTON_COLORS)
    redo_button = ButtonControl(color='Misc.Shift',
                                pressed_color='Misc.ShiftOn',
                                disabled_color='DefaultButton.Disabled')
    quantization_on_button = ToggleButtonControl(untoggled_color='Misc.Shift',
                                                 toggled_color='Misc.ShiftOn')

    def __init__(self, *a, **k):
        self.suppressing_control_notifications = BooleanContext()
        super(ActionsComponent, self).__init__(*a, **k)
        self._record_quantization = RecordingQuantization.rec_q_sixtenth
        self._on_record_quantization_changed_in_live.subject = self.song()
        self._on_record_quantization_changed_in_live()
        self._metronome_toggle = ToggleComponent('metronome', self.song())

    def control_notifications_enabled(self):
        return self.is_enabled() and not self.suppressing_control_notifications

    def quantize_clip(self, clip):
        assert isinstance(clip, Live.Clip.Clip)
        clip.quantize(self._record_quantization, 1.0)

    @undo_button.pressed
    def undo_button(self, button):
        if self.song().can_undo:
            self.song().undo()

    @redo_button.pressed
    def redo_button(self, button):
        if self.song().can_redo:
            self.song().redo()

    @quantization_on_button.toggled
    def quantization_on_button(self, is_toggled, button):
        self._record_quantization_on = is_toggled
        self.song(
        ).midi_recording_quantization = self._record_quantization if self._record_quantization_on else RecordingQuantization.rec_q_no_q

    @subject_slot('midi_recording_quantization')
    def _on_record_quantization_changed_in_live(self):
        quant_value = self.song().midi_recording_quantization
        quant_on = quant_value != RecordingQuantization.rec_q_no_q
        if quant_on:
            self._record_quantization = quant_value
        self._record_quantization_on = quant_on
        with self.suppressing_control_notifications():
            self.quantization_on_button.is_toggled = quant_on

    def set_metronome_button(self, button):
        self._metronome_toggle.set_toggle_button(button)

    def update(self):
        super(ActionsComponent, self).update()
        self._metronome_toggle.update()
class BankToggleComponent(ControlSurfaceComponent):
    bank_toggle_button = ToggleButtonControl()

    def __init__(self, *a, **k):
        super(BankToggleComponent, self).__init__(*a, **k)
        self._toggle_elements = []

    @bank_toggle_button.toggled
    def bank_toggle_button(self, toggled, button):
        for e in self._toggle_elements:
            e.set_toggled(toggled)

    def create_toggle_element(self, *a, **k):
        element = ToggleElement(*a, **k)
        element.toggled = self.bank_toggle_button.is_toggled
        self._toggle_elements.append(element)
        return element
class DetailViewCntrlComponent(ControlSurfaceComponent):
    u"""
    Component that can toggle the device chain- and clip view
    of the selected track
    """
    device_clip_toggle_button = ButtonControl(color='DefaultButton.Off')
    device_nav_left_button = ButtonControl(color='DefaultButton.Off')
    device_nav_right_button = ButtonControl(color='DefaultButton.Off')
    detail_toggle_button = ToggleButtonControl()

    def __init__(self, *a, **k):
        super(DetailViewCntrlComponent, self).__init__(*a, **k)
        self._detail_view_visibility_changed.subject = self.application().view
        self._detail_view_visibility_changed()
        self._go_to_playing_clip_task = self._tasks.add(Task.sequence(Task.wait(SHOW_PLAYING_CLIP_DELAY), Task.run(self._go_to_playing_clip)))
        self._go_to_playing_clip_task.kill()
        self.set_device_clip_toggle_button = self.device_clip_toggle_button.set_control_element
        self.set_detail_toggle_button = self.detail_toggle_button.set_control_element

    def set_device_nav_buttons(self, left_button, right_button):
        self.set_device_nav_left_button(left_button)
        self.set_device_nav_right_button(right_button)

    @device_clip_toggle_button.pressed
    def device_clip_toggle_button(self, button):
        if not self.application().view.is_view_visible('Detail'):
            self.application().view.show_view('Detail')
        if not self.application().view.is_view_visible('Detail/DeviceChain'):
            self.application().view.show_view('Detail/DeviceChain')
        else:
            self.application().view.show_view('Detail/Clip')
        self._go_to_playing_clip_task.restart()

    @device_clip_toggle_button.released
    def device_clip_toggle_button(self, button):
        self._go_to_playing_clip_task.kill()

    @device_nav_left_button.pressed
    def device_nav_left_button(self, value):
        self._scroll_device_chain(NavDirection.left)

    @device_nav_right_button.pressed
    def device_nav_right_button(self, value):
        self._scroll_device_chain(NavDirection.right)

    def _scroll_device_chain(self, direction):
        view = self.application().view
        if not view.is_view_visible('Detail') or not view.is_view_visible('Detail/DeviceChain'):
            view.show_view('Detail')
            view.show_view('Detail/DeviceChain')
        else:
            view.scroll_view(direction, 'Detail/DeviceChain', False)

    def _go_to_playing_clip(self):
        song = self.song()
        playing_slot_index = song.view.selected_track.playing_slot_index
        if playing_slot_index > -1:
            song.view.selected_scene = song.scenes[playing_slot_index]
            if song.view.highlighted_clip_slot.has_clip:
                self.application().view.show_view('Detail/Clip')

    @detail_toggle_button.toggled
    def detail_toggle_button(self, is_toggled, button):
        if is_toggled:
            self.application().view.show_view('Detail')
        else:
            self.application().view.hide_view('Detail')

    @subject_slot('is_view_visible', 'Detail')
    def _detail_view_visibility_changed(self):
        self.detail_toggle_button.is_toggled = self.application().view.is_view_visible('Detail')

    def show_view(self, view):
        app_view = self.application().view
        try:
            if view == 'Detail/DeviceChain' or 'Detail/Clip':
                if not app_view.is_view_visible('Detail'):
                    app_view.show_view('Detail')
            if not app_view.is_view_visible(view):
                app_view.show_view(view)
        except RuntimeError:
            pass
Esempio n. 4
0
class InstrumentComponent(CompoundComponent, Slideable, Messenger):
    """
    Class that sets up the button matrix as a piano, using different
    selectable layouts for the notes.
    """
    touch_strip_toggle = ToggleButtonControl()
    midi_channels = range(5, 13)

    def __init__(self, *a, **k):
        super(InstrumentComponent, self).__init__(*a, **k)
        self._scales = self.register_component(InstrumentScalesComponent())
        self._matrix = None
        self._delete_button = None
        self._first_note = self.page_length * 3 + self.page_offset
        self._last_page_length = self.page_length
        self._delete_button = None
        self._last_page_offset = self.page_offset
        self._touch_strip = None
        self._touch_strip_indication = None
        self._detail_clip = None
        self._has_notes = [False] * 128
        self._has_notes_pattern = self._get_pattern(0)
        self._takeover_pads = False
        self._aftertouch_control = None
        self._scales_menu = self.register_component(
            EnablingModesComponent(component=self._scales,
                                   toggle_value='DefaultButton.On'))
        self._slider, self._touch_slider = self.register_components(
            SlideComponent(self), SlideableTouchStripComponent(self))
        self._on_scales_changed.subject = self._scales
        self._on_scales_mode_changed.subject = self._scales._presets
        self._update_pattern()

    def set_detail_clip(self, clip):
        if clip != self._detail_clip:
            self._detail_clip = clip
            self._on_clip_notes_changed.subject = clip
            self._on_loop_start_changed.subject = clip
            self._on_loop_end_changed.subject = clip
            self._on_clip_notes_changed()

    @subject_slot('notes')
    def _on_clip_notes_changed(self):
        if self._detail_clip:
            self._has_notes = [False] * 128
            loop_start = self._detail_clip.loop_start
            loop_length = self._detail_clip.loop_end - loop_start
            notes = self._detail_clip.get_notes(loop_start, 0, loop_length,
                                                128)
            for note in notes:
                self._has_notes[note[0]] = True

        self.notify_contents()

    @subject_slot('loop_start')
    def _on_loop_start_changed(self):
        self._on_loop_selection_changed()

    @subject_slot('loop_end')
    def _on_loop_end_changed(self):
        self._on_loop_selection_changed()

    def _on_loop_selection_changed(self):
        self._on_clip_notes_changed()

    def contents(self, index):
        if self._detail_clip:
            note = self._has_notes_pattern[index].index
            return self._has_notes[note] if note is not None else False
        return False

    @property
    def page_length(self):
        return len(self._scales.notes) if self._scales.is_diatonic else 12

    @property
    def position_count(self):
        if not self._scales.is_diatonic:
            return 139
        else:
            offset = self.page_offset
            octaves = 11 if self._scales.notes[0] < 8 else 10
            return offset + len(self._scales.notes) * octaves

    def _first_scale_note_offset(self):
        if not self._scales.is_diatonic:
            return self._scales.notes[0]
        elif self._scales.notes[0] == 0:
            return 0
        else:
            return len(self._scales.notes) - index_if(lambda n: n >= 12,
                                                      self._scales.notes)

    @property
    def page_offset(self):
        return 0 if self._scales.is_absolute else self._first_scale_note_offset(
        )

    def _get_position(self):
        return self._first_note

    def _set_position(self, note):
        self._first_note = note
        self._update_pattern()
        self._update_matrix()
        self.notify_position()

    position = property(_get_position, _set_position)

    @property
    def scales(self):
        return self._scales

    @property
    def scales_menu(self):
        return self._scales_menu

    @property
    def pattern(self):
        return self._pattern

    @subject_slot('value')
    def _on_matrix_value(self, value, x, y, is_momentary):
        if self._delete_button and self._delete_button.is_pressed():
            if value:
                max_y = self._matrix.width() - 1
                pitch = self._get_pattern().note(x, max_y - y).index
                if pitch and self._detail_clip:
                    self._matrix.get_button(x, y).turn_on()
                    self._do_delete_pitch(pitch)
            else:
                self._matrix.get_button(x, y).turn_off()

    def _do_delete_pitch(self, pitch):
        clip = self._detail_clip
        if clip:
            note_name = pitch_index_to_string(pitch)
            loop_length = clip.loop_end - clip.loop_start
            clip.remove_notes(clip.loop_start, pitch, loop_length, 1)
            self.show_notification(consts.MessageBoxText.DELETE_NOTES %
                                   note_name)

    @subject_slot('value')
    def _on_delete_value(self, value):
        self._set_control_pads_from_script(bool(value))

    def set_matrix(self, matrix):
        self._matrix = matrix
        self._on_matrix_value.subject = matrix
        if matrix:
            matrix.reset()
        self._update_matrix()

    @touch_strip_toggle.toggled
    def touch_strip_toggle(self, toggled, button):
        self._update_touch_strip()
        self._update_touch_strip_indication()
        self.show_notification(
            consts.MessageBoxText.TOUCHSTRIP_MODWHEEL_MODE
            if toggled else consts.MessageBoxText.TOUCHSTRIP_PITCHBEND_MODE)

    def set_touch_strip(self, control):
        self._touch_strip = control
        self._update_touch_strip()

    def _update_touch_strip(self):
        if self._touch_strip:
            self._touch_strip.behaviour = MODWHEEL_BEHAVIOUR if self.touch_strip_toggle.is_toggled else DEFAULT_BEHAVIOUR

    def set_touch_strip_indication(self, control):
        self._touch_strip_indication = control
        self._update_touch_strip_indication()

    def _update_touch_strip_indication(self):
        if self._touch_strip_indication:
            self._touch_strip_indication.set_mode(TouchStripModes.CUSTOM_FREE)
            self._touch_strip_indication.send_state([
                (TouchStripElement.STATE_FULL
                 if self.touch_strip_toggle.is_toggled else
                 TouchStripElement.STATE_HALF) for _ in xrange(24)
            ])

    def set_note_strip(self, strip):
        self._touch_slider.set_scroll_strip(strip)

    def set_octave_strip(self, strip):
        self._touch_slider.set_page_strip(strip)

    def set_scales_toggle_button(self, button):
        raise button is None or button.is_momentary() or AssertionError
        self._scales_menu.set_toggle_button(button)

    def set_octave_up_button(self, button):
        self._slider.set_scroll_page_up_button(button)

    def set_octave_down_button(self, button):
        self._slider.set_scroll_page_down_button(button)

    def set_scale_up_button(self, button):
        self._slider.set_scroll_up_button(button)

    def set_scale_down_button(self, button):
        self._slider.set_scroll_down_button(button)

    def set_aftertouch_control(self, control):
        self._aftertouch_control = control
        self._update_aftertouch()

    def set_delete_button(self, button):
        self._delete_button = button
        self._on_delete_value.subject = button
        self._set_control_pads_from_script(button and button.is_pressed())

    def _align_first_note(self):
        self._first_note = self.page_offset + (
            self._first_note - self._last_page_offset) * float(
                self.page_length) / float(self._last_page_length)
        if self._first_note >= self.position_count:
            self._first_note -= self.page_length
        self._last_page_length = self.page_length
        self._last_page_offset = self.page_offset

    @subject_slot('scales_changed')
    def _on_scales_changed(self):
        self._update_scale()

    @subject_slot('scale_mode')
    def _on_scales_mode_changed(self):
        self._update_scale()

    def _update_scale(self):
        self._align_first_note()
        self._update_pattern()
        self._update_matrix()
        self.notify_position_count()
        self.notify_position()
        self.notify_contents()

    def update(self):
        super(InstrumentComponent, self).update()
        if self.is_enabled():
            self._update_matrix()
            self._update_aftertouch()
            self._update_touch_strip()
            self._update_touch_strip_indication()

    def _update_pattern(self):
        self._pattern = self._get_pattern()
        self._has_notes_pattern = self._get_pattern(0)

    def _update_matrix(self):
        self._setup_instrument_mode()

    def _setup_instrument_mode(self):
        if self.is_enabled() and self._matrix:
            self._matrix.reset()
            pattern = self._pattern
            max_j = self._matrix.width() - 1
            for button, (i, j) in ifilter(first, self._matrix.iterbuttons()):
                profile = 'default' if self._takeover_pads else 'instrument'
                button.sensitivity_profile = profile
                note_info = pattern.note(i, max_j - j)
                if note_info.index != None:
                    button.set_on_off_values('Instrument.NoteAction',
                                             'Instrument.' + note_info.color)
                    button.turn_off()
                    button.set_enabled(self._takeover_pads)
                    button.set_channel(note_info.channel)
                    button.set_identifier(note_info.index)
                else:
                    button.set_channel(NON_FEEDBACK_CHANNEL)
                    button.set_light('Instrument.' + note_info.color)
                    button.set_enabled(True)

    def _get_pattern(self, first_note=None):
        if first_note is None:
            first_note = int(round(self._first_note))
        interval = self._scales._presets.interval
        notes = self._scales.notes
        octave = first_note / self.page_length
        offset = first_note % self.page_length - self._first_scale_note_offset(
        )
        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 = [offset, 0]
        else:
            steps = [interval, 1]
            origin = [0, offset]
        return MelodicPattern(steps=steps,
                              scale=notes,
                              origin=origin,
                              base_note=octave * 12,
                              chromatic_mode=not self._scales.is_diatonic)

    def _update_aftertouch(self):
        if self.is_enabled() and self._aftertouch_control != None:
            self._aftertouch_control.send_value(Sysex.MONO_AFTERTOUCH)

    def _set_control_pads_from_script(self, takeover_pads):
        """
        If takeover_pads is True, the matrix buttons will be controlled from
        the script. Otherwise they send midi notes to the track.
        """
        if takeover_pads != self._takeover_pads:
            self._takeover_pads = takeover_pads
            self._update_matrix()