class MonoNoteEditorComponent(NoteEditorComponent):
    """Custom function for displaying triplets for different grid sizes, called by _visible steps"""
    # _visible_steps_model = lambda self, indices: filter(lambda k: k % 4 != 3, indices)
    _visible_steps_model = lambda self, indices: [
        k for k in indices if (k % 16) < 12
    ]
    matrix = control_matrix(PadControl,
                            channel=15,
                            sensitivity_profile='loop',
                            mode=PlayableControl.Mode.listenable)

    @matrix.pressed
    def matrix(self, button):
        super(MonoNoteEditorComponent, self)._on_pad_pressed(button.coordinate)

    @matrix.released
    def matrix(self, button):
        super(MonoNoteEditorComponent,
              self)._on_pad_released(button.coordinate)

    # def _on_pad_pressed(self, coordinate):
    # 	y, x = coordinate
    # 	#debug('MonoNoteEditorComponent._on_pad_pressed:', y, x)
    # 	super(MonoNoteEditorComponent, self)._on_pad_pressed(coordinate)

    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 = list(range(steps_per_page))
        if is_triplet_quantization(self._triplet_factor):
            indices = [k for k in indices if k % 16 not in (12, 13, 14, 15)]
        return [(self._time_step(first_time + k * step_length), index)
                for k, index in enumerate(indices)]
class ColorChooserComponent(Component, Messenger):
    matrix = control_matrix(ButtonControl, dimensions=(8, 8))

    def __init__(self, *a, **k):
        (super(ColorChooserComponent, self).__init__)(a, is_enabled=False, **k)
        self._object = None
        self._notification_ref = nop
        for button in self.matrix:
            row, column = button.coordinate
            button.color_index = COLOR_CHOOSER_LAYOUT[row][column]

    @property
    def object(self):
        return self._object

    @object.setter
    def object(self, obj):
        if liveobj_changed(self._object, obj):
            self._object = obj
            if obj is None:
                notification = self._notification_ref()
                if notification:
                    notification.hide()
                self.set_enabled(False)
            else:
                self._render_color_palette(
                    translate_color_index(obj.color_index))
                self.set_enabled(True)
                self._notification_ref = self.show_notification(
                    ('Select a color for: %s' % obj.name),
                    notification_time=(-1))

    @matrix.pressed
    def matrix(self, button):
        if liveobj_valid(self.object):
            if button.color_index is None:
                if old_hasattr(self.object, 'is_auto_colored'):
                    self.object.is_auto_colored = True
                    self.show_notification(
                        'Color automatically enabled for: %s' %
                        self.object.name)
            else:
                self.object.color_index = inverse_translate_color_index(
                    button.color_index)
            self.object = None

    def _render_color_palette(self, selected_color_index):
        for button in self.matrix:
            color_index = button.color_index
            if color_index is not None:
                if color_index == selected_color_index:
                    button.color = Pulse(
                        IndexedColor.from_push_index(color_index,
                                                     shade_level=2),
                        IndexedColor.from_push_index(color_index),
                        SELECTION_PULSE_SPEED)
                else:
                    button.color = IndexedColor.from_push_index(color_index)
            else:
                button.color = Rgb.BLACK
示例#3
0
class MonoNoteEditorComponent(NoteEditorComponent):
    """Custom function for displaying triplets for different grid sizes, called by _visible steps"""
    _visible_steps_model = lambda self, indices: [
        k for k in indices if k % 4 != 3
    ]
    #_matrix = None
    matrix = control_matrix(PadControl,
                            channel=15,
                            sensitivity_profile='loop',
                            mode=PlayableControl.Mode.listenable)
    """First we need to reset the state (chan, id) of each button of the matrix that was previously grabbed so that it doesn't display the playhead when it's given to something else"""
    """Next we need to override the channel that each control is set to in this function, as it is hardcoded from a header definition in the module"""
    """def set_button_matrix(self, matrix):
		if self._matrix:
			for button, _ in filter(first, self._matrix.iterbuttons()):
				button.reset_state()
		super(MonoNoteEditorComponent, self).set_button_matrix(matrix)
		if matrix:
			for button, _ in filter(first, matrix.iterbuttons()):
				button.set_channel(15)"""
    """
	def set_matrix(self, matrix):
		if self._matrix:
			for button, _ in filter(first, self._matrix.iterbuttons()):
				button.reset_state()
		self._matrix = matrix
		super(MonoNoteEditorComponent, self).set_matrix(matrix)
		if matrix:
			for button, _ in filter(first, matrix.iterbuttons()):
				button.set_channel(15)
	"""
    @matrix.pressed
    def matrix(self, button):
        super(MonoNoteEditorComponent, self)._on_pad_pressed(button.coordinate)

    @matrix.released
    def matrix(self, button):
        super(MonoNoteEditorComponent,
              self)._on_pad_released(button.coordinate)

    def _on_pad_pressed(self, coordinate):
        y, x = coordinate
        debug('MonoNoteEditorComponent._on_pad_pressed:', y, x)
        super(MonoNoteEditorComponent, self)._on_pad_pressed(coordinate)

    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 = list(range(steps_per_page))
        if is_triplet_quantization(self._triplet_factor):
            indices = self._visible_steps_model(indices)
        return [(self._time_step(first_time + k * step_length), index)
                for k, index in enumerate(indices)]
示例#4
0
class LoopSelectorComponent(Component, Messenger):
    u"""
    Component that uses a button matrix to display the timeline of a
    clip. It allows you to select the loop of the clip and a page
    within it of a given Paginator object.
    """
    next_page_button = ButtonControl()
    prev_page_button = ButtonControl()
    delete_button = ButtonControl()
    select_button = ButtonControl()
    loop_selector_matrix = control_matrix(PadControl, sensitivity_profile=u'loop', mode=PlayableControl.Mode.listenable)
    short_loop_selector_matrix = control_matrix(ButtonControl)
    is_following = listenable_property.managed(False)

    def __init__(self, clip_creator = None, measure_length = 4.0, follow_detail_clip = False, paginator = None, default_size = None, *a, **k):
        super(LoopSelectorComponent, self).__init__(*a, **k)
        assert default_size is not None
        self._clip_creator = clip_creator
        self._sequencer_clip = None
        self._paginator = Paginator()
        self._loop_start = 0
        self._loop_end = 0
        self._loop_length = 0
        self._default_size = default_size
        self._pressed_pages = []
        self._page_colors = []
        self._measure_length = measure_length
        self._last_playhead_page = -1

        def set_is_following_true():
            self.is_following = True

        self._follow_task = self._tasks.add(task.sequence(task.wait(defaults.MOMENTARY_DELAY), task.run(set_is_following_true)))
        self._follow_task.kill()
        self.set_step_duplicator(None)
        self._notification_reference = partial(nop, None)
        self.is_deleting = False
        if follow_detail_clip:
            self._on_detail_clip_changed.subject = self.song.view
            self._on_detail_clip_changed()
        self._on_session_record_changed.subject = self.song
        self._on_song_playback_status_changed.subject = self.song
        if paginator is not None:
            self.set_paginator(paginator)
        return

    def set_paginator(self, paginator):
        self._paginator = paginator or Paginator()
        self._on_page_index_changed.subject = paginator
        self._on_page_length_changed.subject = paginator
        self._update_page_colors()

    @listens(u'page_index')
    def _on_page_index_changed(self):
        self._update_page_colors()

    @listens(u'page_length')
    def _on_page_length_changed(self):
        self._update_page_colors()
        self._select_start_page()

    def set_step_duplicator(self, duplicator):
        self._step_duplicator = duplicator or NullStepDuplicator()
        self._step_duplicator.set_clip(self._sequencer_clip)

    @listens(u'detail_clip')
    def _on_detail_clip_changed(self):
        self.set_detail_clip(self.song.view.detail_clip)

    def set_detail_clip(self, clip):
        if liveobj_changed(clip, self._sequencer_clip):
            self.is_following = liveobj_valid(clip) and (self.is_following or clip_is_new_recording(clip))
            self._on_playing_position_changed.subject = clip
            self._on_playing_status_changed.subject = clip
            self._on_loop_start_changed.subject = clip
            self._on_loop_end_changed.subject = clip
            self._on_is_recording_changed.subject = clip
            self._sequencer_clip = clip
            self._step_duplicator.set_clip(clip)
            self._on_loop_changed()

    def _select_start_page(self):
        if liveobj_valid(self._sequencer_clip):
            page_start = self._paginator.page_index * self._paginator.page_length
            to_select = page_start
            if page_start <= self._sequencer_clip.loop_start:
                to_select = self._sequencer_clip.loop_start
            elif page_start >= self._sequencer_clip.loop_end:
                to_select = max(self._sequencer_clip.loop_end - self._paginator.page_length, self._sequencer_clip.loop_start)
            self._paginator.select_page_in_point(to_select)

    @listens(u'loop_start')
    def _on_loop_start_changed(self):
        self._on_loop_changed()

    @listens(u'loop_end')
    def _on_loop_end_changed(self):
        self._on_loop_changed()

    def _on_loop_changed(self):
        if liveobj_valid(self._sequencer_clip):
            self._loop_start = self._sequencer_clip.loop_start
            self._loop_end = self._sequencer_clip.loop_end
            self._loop_length = self._loop_end - self._loop_start
        else:
            self._loop_start = 0
            self._loop_end = 0
            self._loop_length = 0
        self._select_start_page()
        self._update_page_colors()

    def set_loop_selector_matrix(self, matrix):
        self.loop_selector_matrix.set_control_element(matrix)
        self._update_page_colors()

    def set_short_loop_selector_matrix(self, matrix):
        self.short_loop_selector_matrix.set_control_element(matrix)
        self._update_page_colors()

    def update(self):
        super(LoopSelectorComponent, self).update()
        self._update_page_and_playhead_leds()

    @listens(u'is_recording')
    def _on_is_recording_changed(self):
        self.is_following = self.is_following or clip_is_new_recording(self._sequencer_clip)

    @listens(u'playing_position')
    def _on_playing_position_changed(self):
        self._update_page_and_playhead_leds()
        self._update_page_selection()

    @listens(u'playing_status')
    def _on_playing_status_changed(self):
        self._update_page_and_playhead_leds()

    @listens(u'session_record')
    def _on_session_record_changed(self):
        self._update_page_and_playhead_leds()

    @listens(u'is_playing')
    def _on_song_playback_status_changed(self):
        self._update_page_and_playhead_leds()

    def _has_running_clip(self):
        return liveobj_valid(self._sequencer_clip) and (self._sequencer_clip.is_playing or self._sequencer_clip.is_recording)

    def _update_page_selection(self):
        if self.is_enabled() and self.is_following and self._has_running_clip():
            position = self._sequencer_clip.playing_position
            self._paginator.select_page_in_point(position)

    def _update_page_and_playhead_leds(self):

        @contextmanager
        def save_page_color(page_colors, page):
            old_page_value = page_colors[page]
            yield
            page_colors[page] = old_page_value

        @contextmanager
        def replace_and_restore_tail_colors(page_colors, page):
            if clip_is_new_recording(self._sequencer_clip):
                old_tail_values = page_colors[page + 1:]
                page_colors[page + 1:] = [u'LoopSelector.OutsideLoop'] * len(old_tail_values)
            yield
            if clip_is_new_recording(self._sequencer_clip):
                page_colors[page + 1:] = old_tail_values

        if self.is_enabled() and self._has_running_clip():
            position = self._sequencer_clip.playing_position
            visible_page = int(position / self._page_length_in_beats) - self.page_offset
            page_colors = self._page_colors
            if 0 <= visible_page < len(page_colors):
                with save_page_color(page_colors, visible_page):
                    if self.song.is_playing:
                        page_colors[visible_page] = u'LoopSelector.PlayheadRecord' if self.song.session_record else u'LoopSelector.Playhead'
                    with replace_and_restore_tail_colors(page_colors, visible_page):
                        self._update_page_leds()
            else:
                self._update_page_leds()
            self._last_playhead_page = visible_page
        else:
            self._update_page_leds()

    def _get_size(self):
        return max(self.loop_selector_matrix.control_count, self.short_loop_selector_matrix.control_count, self._default_size)

    def _get_loop_in_pages(self):
        page_length = self._page_length_in_beats
        loop_start = int(self._loop_start / page_length)
        loop_end = int(self._loop_end / page_length)
        loop_length = loop_end - loop_start + int(self._loop_end % page_length != 0)
        return (loop_start, loop_length)

    def _selected_pages_range(self):
        size = self._get_size()
        page_length = self._page_length_in_beats
        seq_page_length = max(self._paginator.page_length / page_length, 1)
        seq_page_start = int(self._paginator.page_index * self._paginator.page_length / page_length)
        seq_page_end = int(min(seq_page_start + seq_page_length, self.page_offset + size))
        return (seq_page_start, seq_page_end)

    def _update_page_colors(self):
        u"""
        Update the offline array mapping the timeline of the clip to buttons.
        """
        page_length = self._page_length_in_beats
        size = self._get_size()

        def calculate_page_colors():
            l_start, l_length = self._get_loop_in_pages()
            page_offset = self.page_offset
            pages_per_measure = int(self._one_measure_in_beats / page_length)

            def color_for_page(absolute_page):
                if l_start <= absolute_page < l_start + l_length:
                    if absolute_page % pages_per_measure == 0:
                        return u'LoopSelector.InsideLoopStartBar'
                    return u'LoopSelector.InsideLoop'
                else:
                    return u'LoopSelector.OutsideLoop'

            return map(color_for_page, xrange(page_offset, page_offset + size))

        def mark_selected_pages(page_colors):
            for page_index in xrange(*self._selected_pages_range()):
                button_index = page_index - self.page_offset
                if page_colors[button_index].startswith(u'LoopSelector.InsideLoop'):
                    page_colors[button_index] = u'LoopSelector.SelectedPage'

        page_colors = calculate_page_colors()
        mark_selected_pages(page_colors)
        self._page_colors = page_colors
        self._update_page_and_playhead_leds()

    def _update_page_leds(self):
        self._update_page_leds_in_matrix(self.loop_selector_matrix)
        self._update_page_leds_in_matrix(self.short_loop_selector_matrix)

    def _update_page_leds_in_matrix(self, matrix):
        u""" update hardware leds to match precomputed map """
        if self.is_enabled() and matrix:
            for button, color in izip(matrix, self._page_colors):
                button.color = color

    def _jump_to_page(self, next_page):
        start, length = self._get_loop_in_pages()
        if next_page >= start + length:
            next_page = start
        elif next_page < start:
            next_page = start + length - 1
        self._paginator.select_page_in_point(next_page * self._page_length_in_beats)

    @next_page_button.pressed
    def next_page_button(self, button):
        if self.is_following:
            self.is_following = False
        else:
            _, end = self._selected_pages_range()
            self._jump_to_page(end)
            self._follow_task.restart()

    @next_page_button.released
    def next_page_button(self, button):
        self._follow_task.kill()

    @prev_page_button.pressed
    def prev_page_button(self, button):
        if self.is_following:
            self.is_following = False
        else:
            begin, end = self._selected_pages_range()
            self._jump_to_page(begin - (end - begin))
            self._follow_task.restart()

    @prev_page_button.released
    def prev_page_button(self, button):
        self._follow_task.kill()

    @short_loop_selector_matrix.pressed
    def short_loop_selector_matrix(self, button):
        if self.is_enabled():
            page = self._get_corresponding_page(button, self.short_loop_selector_matrix)
            self._pressed_pages = [page]
            self._try_set_loop()
            self._pressed_pages = []

    @loop_selector_matrix.pressed
    def loop_selector_matrix(self, button):
        if self.is_enabled():
            page = self._get_corresponding_page(button, self.loop_selector_matrix)
            if page not in self._pressed_pages:
                self._on_press_loop_selector_matrix(page)

    @loop_selector_matrix.released
    def loop_selector_matrix(self, button):
        page = self._get_corresponding_page(button, self.loop_selector_matrix)
        if page in self._pressed_pages:
            self._pressed_pages.remove(page)

    def _get_corresponding_page(self, button, matrix):
        y, x = button.coordinate
        return x + y * matrix.width

    def _quantize_page_index(self, page_index, quant):
        page_length = self._page_length_in_beats
        return quant * float(int(page_length * page_index / quant))

    def _clear_page(self, page):
        page_start, page_end = self._selected_pages_time_range(page)
        notes = self._sequencer_clip.get_notes(page_start, 0, page_end, 128)
        if len(notes) > 0:
            self._sequencer_clip.remove_notes(page_start, 0, page_end - page_start, 128)
            self._notification_reference = self.show_notification(MessageBoxText.PAGE_CLEARED)
        else:
            self._notification_reference = self.show_notification(MessageBoxText.CANNOT_CLEAR_EMPTY_PAGE)

    def _selected_pages_time_range(self, page):
        page_start = 0
        page_end = 0
        page_length = self._page_length_in_beats
        if self._loop_length > page_length:
            range_start, range_end = self._selected_pages_range()
            page_start = range_start * page_length
            page_end = range_end * page_length
        else:
            page_start = page * page_length
            page_end = page_start + page_length
        return (page_start, page_end)

    def _add_page_to_duplicator(self, page):
        page_start, page_end = self._selected_pages_time_range(page)
        self._step_duplicator.add_step(page_start, page_end, nudge_offset=0, is_page=True)

    def _on_press_loop_selector_matrix(self, page):

        def create_clip(pages):
            measure = self._one_measure_in_beats
            length = self._quantize_page_index(pages, measure) + measure
            create_clip_in_selected_slot(self._clip_creator, self.song, length)

        def handle_page_press_on_clip(page):
            l_start, l_length = self._get_loop_in_pages()
            page_in_loop = l_start <= page < l_start + l_length
            buttons_pressed = len(self._pressed_pages)
            if buttons_pressed == 1 and page_in_loop:
                self._try_select_page(page)
            elif buttons_pressed > 1 or not page_in_loop:
                self._try_set_loop()
            if self._step_duplicator.is_duplicating:
                self._add_page_to_duplicator(page)
            if self.delete_button.is_pressed:
                self._clear_page(page)

        self._pressed_pages.append(page)
        absolute_page = page + self.page_offset
        if not self.select_button.is_pressed:
            if not liveobj_valid(self._sequencer_clip) and not self.song.view.highlighted_clip_slot.has_clip:
                create_clip(absolute_page)
            elif liveobj_valid(self._sequencer_clip):
                handle_page_press_on_clip(absolute_page)
        elif not self.is_following:
            self._try_select_page(absolute_page)

    def _try_select_page(self, page):
        step_time = page * self._page_length_in_beats
        if self._paginator.select_page_in_point(step_time):
            self.is_following = False
            return True
        return False

    def _try_set_loop(self):
        did_set_loop = False
        if liveobj_valid(self._sequencer_clip):
            if not clip_is_new_recording(self._sequencer_clip):
                lowest_page = min(self._pressed_pages) + self.page_offset
                if self._try_select_page(lowest_page):
                    self._set_loop_in_live()
                    did_set_loop = True
            if did_set_loop:
                self.is_following = True
        return did_set_loop

    def _set_loop_in_live(self):
        quant = self._page_length_in_beats
        start_page = min(self._pressed_pages) + self.page_offset
        end_page = max(self._pressed_pages) + self.page_offset
        loop_start = self._quantize_page_index(start_page, quant)
        loop_end = self._quantize_page_index(end_page, quant) + quant
        set_loop(self._sequencer_clip, loop_start, loop_end)
        self._sequencer_clip.view.show_loop()

    @property
    def _page_length_in_beats(self):
        return clamp(self._paginator.page_length, 0.25, self._one_measure_in_beats)

    @property
    def _one_measure_in_beats(self):
        return self._measure_length * self.song.signature_numerator / self.song.signature_denominator

    @property
    def page_offset(self):

        def zero_if_none(n):
            if n is None:
                return 0
            else:
                return n

        width = zero_if_none(self.loop_selector_matrix.width)
        height = zero_if_none(self.loop_selector_matrix.height)
        size = max(width * height, 1)
        page_index = self._paginator.page_index
        page_length = self._paginator.page_length
        selected_page_index = int(page_index * page_length / self._page_length_in_beats)
        return size * int(selected_page_index / size)
class VelocityLevelsComponent(PlayableComponent):
    SOURCE_NOTES = list(reversed(range(64, 128)))
    DEFAULT_VELOCITY = 100
    matrix = control_matrix(PadControl)
    select_button = ButtonControl()

    def __init__(self,
                 velocity_levels=None,
                 target_note_provider=None,
                 skin_base_key=None,
                 *a,
                 **k):
        super(VelocityLevelsComponent, self).__init__(*a, **k)
        self._target_note_provider = target_note_provider or NullTargetNoteProvider(
        )
        self.__on_selected_target_note_changed.subject = self._target_note_provider
        self._played_level = INVALID_LEVEL
        self.set_skin_base_key(skin_base_key or 'VelocityLevels')
        self._notification_task = self._tasks.add(
            task.run(self._update_velocity))
        self._notification_task.kill()
        self.set_velocity_levels(velocity_levels)

    @listenable_property
    def velocity(self):
        if 0 <= self._played_level < 128:
            return self._played_level
        return self.DEFAULT_VELOCITY

    def set_velocities_playable(self, playable):
        self._notification_task.kill()
        self._set_control_pads_from_script(not playable)

    def set_velocity_levels(self, velocity_levels):
        self.velocity_levels = velocity_levels
        self.__on_last_played_level.subject = velocity_levels
        self.update()

    def set_matrix(self, matrix):
        super(VelocityLevelsComponent, self).set_matrix(matrix)
        self._update_sensitivity_profile()

    def _update_control_from_script(self):
        super(VelocityLevelsComponent, self)._update_control_from_script()
        self._update_sensitivity_profile()

    def _update_sensitivity_profile(self):
        profile = 'default' if self._takeover_pads else 'drums'
        for button in self.matrix:
            button.sensitivity_profile = profile

    def _update_velocity(self):
        self._notification_task.kill()
        self.notify_velocity()

    def set_skin_base_key(self, base_key):
        self._skin_base_key = base_key
        self._update_led_feedback()

    @matrix.pressed
    def matrix(self, button):
        self._on_matrix_pressed(button)

    @matrix.released
    def matrix(self, button):
        self._on_matrix_released(button)

    @select_button.pressed
    def select_button(self, _value):
        self._set_control_pads_from_script(True)

    @select_button.released
    def select_button(self, _value):
        self._set_control_pads_from_script(False)

    def _on_matrix_pressed(self, button):
        has_levels = liveobj_valid(self.velocity_levels)
        levels = self.velocity_levels.levels if has_levels else []
        index = self._button_index(button)
        self._played_level = levels[index] if index < len(
            levels) else INVALID_LEVEL
        self._update_led_feedback()
        self.notify_velocity()

    @listens('selected_target_note')
    def __on_selected_target_note_changed(self):
        self.update()

    @listens('last_played_level')
    def __on_last_played_level(self):
        if self._takeover_pads or liveobj_valid(self.velocity_levels):
            played = self.velocity_levels.last_played_level if 1 else INVALID_LEVEL
            self._played_level = played
            self._update_led_feedback()
            self._notification_task.restart()

    def _button_index(self, button):
        y, x = button.coordinate
        return (self.height - 1 - y) * self.width + x

    def _note_translation_for_button(self, button):
        return (self.SOURCE_NOTES[self._button_index(button)],
                NON_FEEDBACK_CHANNEL)

    def _update_button_color(self, button):
        index = self._button_index(button)
        levels = self.velocity_levels.levels if liveobj_valid(
            self.velocity_levels) else []
        if index < len(levels) and self._played_level == levels[index]:
            color = 'SelectedLevel'
        else:
            y, _ = button.coordinate
            color = 'MidLevel'
            if y == 0:
                color = 'HighLevel'
            else:
                if y == self.height - 1:
                    color = 'LowLevel'
        button.color = self._skin_base_key + '.' + color

    def update(self):
        super(VelocityLevelsComponent, self).update()
        if liveobj_valid(self.velocity_levels):
            self.velocity_levels.enabled = self.is_enabled()
            self.velocity_levels.source_channel = NON_FEEDBACK_CHANNEL
            self.velocity_levels.notes = self.SOURCE_NOTES[:self.width *
                                                           self.height]
            target_note = self._target_note_provider.selected_target_note
            self.velocity_levels.target_note = target_note.note
            self.velocity_levels.target_channel = target_note.channel
        if not self.is_enabled():
            self._notification_task.kill()
示例#6
0
class MonoDrumGroupComponent(DrumGroupComponent):

    mute_button = ButtonControl(color='DrumGroup.PadMuted')
    solo_button = ButtonControl(color='DrumGroup.PadSoloed')
    _raw_position = 0
    _selected_note = DEFAULT_DRUMOFFSET * 4
    select_matrix = control_matrix(PlayableControl)
    create_translation_entry = lambda self, button: (button.coordinate[
        1], button.coordinate[0], button.identifier, button.channel)

    def __init__(self,
                 channel_list=CHANNELS,
                 settings=DEFAULT_INSTRUMENT_SETTINGS,
                 *a,
                 **k):
        self._channel_list = channel_list
        self._settings = settings
        super(MonoDrumGroupComponent, self).__init__(*a, **k)
        self._on_selected_track_changed.subject = self.song.view

    def _get_current_channel(self):
        cur_track = self.song.view.selected_track
        cur_chan = cur_track.current_input_sub_routing
        if len(cur_chan) == 0:
            cur_chan = 'All Channels'
        if cur_chan == 'All Channels':
            cur_chan = 1
        if cur_chan in self._channel_list:
            cur_chan = (self._channel_list.index(cur_chan) % 15) + 1
        else:
            cur_chan = 14
        return cur_chan

    @listens('selected_track')
    def _on_selected_track_changed(self):
        #debug('keys settings track changed')
        self.translation_channel = self._get_current_channel()
        self.update_matrix()

    @property
    def translation_channel(self, translation_channel):
        return self._translation_channel

    @translation_channel.setter
    def translation_channel(self, channel):
        debug('drumpad set_translation_channel', channel)
        self._translation_channel = channel
        self.update_matrix()

    @property
    def position(self):
        if liveobj_valid(self._drum_group_device):
            return self._drum_group_device.view.drum_pads_scroll_position
        return 0

    @position.setter
    def position(self, index):
        if not 0 <= index <= 28:
            raise AssertionError
        self._raw_position = index
        if liveobj_valid(self._drum_group_device):
            self._drum_group_device.view.drum_pads_scroll_position = index
            self.update_matrix()
        else:
            self.update_matrix()

    def update_matrix(self):
        self._reset_selected_pads()
        self._update_led_feedback()
        self._update_note_translations()

    def set_translation_channel(self, translation_channel):
        self._translation_channel = translation_channel
        self._update_assigned_drum_pads()
        self._create_and_set_pad_translations()

    def _create_and_set_pad_translations(self):
        if self._can_set_pad_translations():
            translations = []
            for button in self.matrix:
                button.channel = self._translation_channel
                button.identifier = self._button_coordinates_to_pad_index(
                    self._raw_position * 4, button.coordinate)
                button.enabled = True
                translations.append(self.create_translation_entry(button))

            self._set_pad_translations(tuple(translations))
        else:
            self._update_note_translations()
            self._set_pad_translations(None)

    def _button_coordinates_to_pad_index(self, first_note, coordinates):
        y, x = coordinates
        y = self.height - y - 1
        if x < 4 and y >= 4:
            first_note += 32
        elif x >= 4 and y < 4:
            first_note += 2 * self.width
        elif x >= 4 and y >= 4:
            first_note += 4 * self.width + 16
        index = x % 4 + y % 4 * 4 + first_note
        return index

    def _note_translation_for_button(self, button):
        if liveobj_valid(self._drum_group_device):
            identifier = None
            channel = None
            if self.has_assigned_pads:
                identifier = self._button_coordinates_to_pad_index(
                    first(self._assigned_drum_pads).note, button.coordinate)
                channel = self._translation_channel
            return (identifier, channel)
        else:
            identifier = self._button_coordinates_to_pad_index(
                self._raw_position * 4, button.coordinate)
            channel = self._translation_channel
            return (identifier, channel)

    def _update_led_feedback(self):
        if liveobj_valid(self._drum_group_device):
            super(MonoDrumGroupComponent, self)._update_led_feedback()
        else:
            super(DrumGroupComponent, self)._update_led_feedback()
        for button in self.select_matrix:
            self._update_button_color(button)

    def _update_button_color(self, button):
        if liveobj_valid(self._drum_group_device):
            super(MonoDrumGroupComponent, self)._update_button_color(button)
        elif button._control_element:
            note = self._button_coordinates_to_pad_index(
                self._raw_position * 4, button.coordinate)
            register = (note - 4) % 32
            if note is self._selected_note:
                button.color = 'MonoInstrument.Drums.SelectedNote'
            else:
                button.color = 'MonoInstrument.Drums.EvenValue' if (
                    0 <= register < 16) else 'MonoInstrument.Drums.OddValue'
            #debug('button color:', button.color)
        if button._control_element:
            button._control_element.scale_color = button.color

    def _color_for_pad(self, pad):
        has_soloed_pads = bool(
            find_if(lambda pad: pad.solo, self._all_drum_pads))
        button_color = 'DrumGroup.PadEmpty'
        if pad == self._selected_drum_pad:
            button_color = 'DrumGroup.PadSelected'
            if has_soloed_pads and not pad.solo and not pad.mute:
                button_color = 'DrumGroup.PadSelectedNotSoloed'
            elif pad.mute and not pad.solo:
                button_color = 'DrumGroup.PadMutedSelected'
            elif has_soloed_pads and pad.solo:
                button_color = 'DrumGroup.PadSoloedSelected'
        elif pad.chains:
            register = (pad.note - 4) % 32
            button_color = 'DrumGroup.PadFilled' if (
                0 <= register < 16) else 'DrumGroup.PadFilledAlt'
            if has_soloed_pads and not pad.solo:
                button_color = 'DrumGroup.PadFilled' if not pad.mute else 'DrumGroup.PadMuted'
            elif not has_soloed_pads and pad.mute:
                button_color = 'DrumGroup.PadMuted'
            elif has_soloed_pads and pad.solo:
                button_color = 'DrumGroup.PadSoloed'
        return button_color

    def set_drum_group_device(self, *a, **k):
        super(MonoDrumGroupComponent, self).set_drum_group_device(*a, **k)
        self.update_matrix()

    def _update_selected_drum_pad(self):
        super(MonoDrumGroupComponent, self)._update_selected_drum_pad()
        self.notify_selected_note()

    @listenable_property
    def selected_note(self):
        selected_drum_pad = self._drum_group_device.view.selected_drum_pad if liveobj_valid(
            self._drum_group_device) else None
        if liveobj_valid(selected_drum_pad):
            debug('selected note:', selected_drum_pad.note)
            return selected_drum_pad.note
        else:
            debug('selected note:', self._selected_note)
            return int(self._selected_note)

    def set_matrix(self, matrix):
        self._set_matrix_special_attributes(False)
        super(MonoDrumGroupComponent, self).set_matrix(matrix)
        self._set_matrix_special_attributes(True)

    def _set_matrix_special_attributes(self, enabled):
        for button in self.matrix:
            if button._control_element:
                button._control_element.display_press = enabled
                button._control_element._last_flash = 0
                not enabled and button._control_element.reset_state()

    def _on_matrix_pressed(self, button):
        super(MonoDrumGroupComponent, self)._on_matrix_pressed(button)

    @select_matrix.pressed
    def select_matrix(self, button):
        debug('on select matrix pressed:', button)
        if liveobj_valid(self._drum_group_device):
            if self.mute_button.is_pressed or self.solo_button.is_pressed:
                super(MonoDrumGroupComponent, self)._on_matrix_pressed(button)
            else:
                self._drum_group_device.view.selected_drum_pad = self._pad_for_button(
                    button)
        else:
            self._selected_note = self._button_coordinates_to_pad_index(
                self._raw_position * 4, button.coordinate)
        self.notify_selected_note()
        self._update_led_feedback()

    def select_drum_pad(self, drum_pad):
        pass

    def set_select_matrix(self, matrix):
        debug('set select matrix:', matrix)
        self.select_matrix.set_control_element(matrix)
        for button in self.select_matrix:
            button.set_playable(False)
        self._update_led_feedback()

    def _update_note_translations(self):
        if liveobj_valid(self._drum_group_device):
            super(MonoDrumGroupComponent, self)._update_note_translations()
        else:
            super(DrumGroupComponent, self)._update_note_translations()

    def _button_should_be_enabled(self, button):
        #this was throwing an error so I overrode, sometimes drumcomponent sends a non-iterable
        identifier = 128
        if button:
            identifier, _ = self._note_translation_for_button(button)
        return identifier < 128

    def _on_selected_drum_pad_changed(self):
        debug('on selected drumpad changed')
        self.notify_selected_note()
示例#7
0
class InstrumentComponent(PlayableComponent, Slideable, Messenger):
    __events__ = ('pattern', )
    matrix = control_matrix(PadControl)
    delete_button = ButtonControl()

    def __init__(self, note_layout=None, *a, **k):
        (super(InstrumentComponent, self).__init__)(*a, **k)
        self._note_layout = note_layout
        self._first_note = self.page_length * 3 + self.page_offset
        self._last_page_length = self.page_length
        self._last_page_offset = self.page_offset
        self._detail_clip = None
        self._has_notes = [False] * 128
        self._has_notes_pattern = self._get_pattern(0)
        self._aftertouch_control = None
        self._aftertouch_mode = 'mono'
        self._show_notifications = True
        self._InstrumentComponent__on_detail_clip_changed.subject = self.song.view
        self._InstrumentComponent__on_detail_clip_changed()
        self._slider = SlideComponent(slideable=self, parent=self)
        self._touch_slider = SlideableTouchStripComponent(touch_slideable=self,
                                                          parent=self)
        for event in ('scale', 'root_note', 'is_in_key', 'is_fixed',
                      'is_horizontal', 'interval'):
            self.register_slot(self._note_layout, self._on_note_layout_changed,
                               event)

        self._update_pattern()

    @listens('detail_clip')
    def __on_detail_clip_changed(self):
        clip = self.song.view.detail_clip
        self.set_detail_clip(clip if (liveobj_valid(clip)) and (
            clip.is_midi_clip) else None)

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

    @listens('notes')
    def _on_clip_notes_changed(self):
        if self._detail_clip:
            self._has_notes = [False] * 128
            loop_start = self._detail_clip.loop_start
            loop_length = self._detail_clip.loop_end - loop_start
            notes = self._detail_clip.get_notes_extended(from_time=loop_start,
                                                         from_pitch=0,
                                                         time_span=loop_length,
                                                         pitch_span=128)
            for note in notes:
                self._has_notes[note.pitch] = True

        self.notify_contents()

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

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

    def _on_loop_selection_changed(self):
        self._on_clip_notes_changed()

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

    @property
    def show_notifications(self):
        return self._show_notifications

    @show_notifications.setter
    def show_notifications(self, value):
        self._show_notifications = value

    @property
    def page_length(self):
        if self._note_layout.is_in_key:
            return len(self._note_layout.notes)
        return 12

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

    def _first_scale_note_offset(self):
        if not self._note_layout.is_in_key:
            return self._note_layout.notes[0]
        if self._note_layout.notes[0] == 0:
            return 0
        return len(self._note_layout.notes) - index_if(lambda n: n >= 12,
                                                       self._note_layout.notes)

    @property
    def page_offset(self):
        if self._note_layout.is_fixed:
            return 0
        return self._first_scale_note_offset()

    def _get_position(self):
        return self._first_note

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

    position = property(_get_position, _set_position)

    @property
    def min_pitch(self):
        return self.pattern[0].index

    @property
    def max_pitch(self):
        identifiers = [control.identifier for control in self.matrix]
        if len(identifiers) > 0:
            return max(identifiers)
        return 127

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

    @matrix.pressed
    def matrix(self, button):
        self._on_matrix_pressed(button)

    def _on_matrix_pressed(self, button):
        if self.delete_button.is_pressed:
            pitch = self._get_note_info_for_coordinate(button.coordinate).index
            if pitch:
                if self._detail_clip:
                    self._do_delete_pitch(pitch)

    @matrix.released
    def matrix(self, button):
        self._on_matrix_released(button)

    def _on_matrix_released(self, button):
        pass

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

    @delete_button.pressed
    def delete_button(self, value):
        self._set_control_pads_from_script(True)

    @delete_button.released
    def delete_button(self, value):
        self._set_control_pads_from_script(False)

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

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

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

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

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

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

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

    def set_aftertouch_mode(self, mode):
        if self._aftertouch_mode != mode:
            self._aftertouch_mode = mode
            self._update_aftertouch()

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

    def _on_note_layout_changed(self, _):
        self._update_scale()

    def show_pitch_range_notification(self):
        if self.is_enabled():
            if self.show_notifications:
                self.show_notification(
                    'Play {start_note} to {end_note}'.format(
                        start_note=(pitch_index_to_string(
                            self.pattern.note(0, 0).index)),
                        end_note=(pitch_index_to_string(
                            self.pattern.note(self.width - 1,
                                              self.height - 1).index))))

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

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

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

    def _invert_and_swap_coordinates(self, coordinates):
        return (coordinates[1], self.height - 1 - coordinates[0])

    def _get_note_info_for_coordinate(self, coordinate):
        x, y = self._invert_and_swap_coordinates(coordinate)
        return self.pattern.note(x, y)

    def _update_button_color(self, button):
        note_info = self._get_note_info_for_coordinate(button.coordinate)
        button.color = 'Instrument.' + note_info.color

    def _button_should_be_enabled(self, button):
        return self._get_note_info_for_coordinate(
            button.coordinate).index != None

    def _note_translation_for_button(self, button):
        note_info = self._get_note_info_for_coordinate(button.coordinate)
        return (note_info.index, note_info.channel)

    def _set_button_control_properties(self, button):
        super(InstrumentComponent, self)._set_button_control_properties(button)
        button.sensitivity_profile = 'default' if self._takeover_pads else 'instrument'

    def _update_matrix(self):
        self._update_control_from_script()
        self._update_note_translations()
        self._update_led_feedback()

    def _get_pattern(self, first_note=None):
        if first_note is None:
            first_note = int(round(self._first_note))
        interval = self._note_layout.interval
        notes = self._note_layout.notes
        width = None
        height = None
        octave = old_div(first_note, self.page_length)
        offset = first_note % self.page_length - self._first_scale_note_offset(
        )
        if interval == None:
            if self._note_layout.is_in_key:
                interval = len(self._note_layout.notes)
                if self._note_layout.is_horizontal:
                    width = interval + 1
                else:
                    height = interval + 1
            else:
                interval = 8
        elif not self._note_layout.is_in_key:
            interval = [0, 2, 4, 5, 7, 9, 10, 11][interval]
        if self._note_layout.is_horizontal:
            steps = [1, interval]
            origin = [offset, 0]
        else:
            steps = [interval, 1]
            origin = [0, offset]
        return MelodicPattern(steps=steps,
                              scale=notes,
                              origin=origin,
                              root_note=(octave * 12),
                              chromatic_mode=(not self._note_layout.is_in_key),
                              width=width,
                              height=height)

    def _update_aftertouch(self):
        if self.is_enabled():
            if self._aftertouch_control != None:
                self._aftertouch_control.send_value(self._aftertouch_mode)
示例#8
0
class DrumGroupComponent(SlideableTouchStripComponent, DrumGroupComponent,
                         Messenger):
    """
    Class representing a drum group pads in a matrix.
    """
    matrix = control_matrix(PadControl)
    duplicate_button = ButtonControl()

    def __init__(self, quantizer=None, *a, **k):
        super(DrumGroupComponent,
              self).__init__(touch_slideable=self,
                             translation_channel=PAD_FEEDBACK_CHANNEL,
                             dragging_enabled=True,
                             *a,
                             **k)
        self._copy_handler = self._make_copy_handler()
        self._notification_reference = partial(nop, None)
        self._quantizer = quantizer
        return

    position_count = 32
    page_length = 4
    page_offset = 1

    def update(self):
        super(DrumGroupComponent, self).update()
        if self._copy_handler:
            self._copy_handler.stop_copying()

    def set_drum_group_device(self, drum_group_device):
        super(DrumGroupComponent,
              self).set_drum_group_device(drum_group_device)
        self._on_chains_changed.subject = self._drum_group_device
        self.notify_contents()

    def quantize_pitch(self, note):
        self._quantizer.quantize_pitch(note, 'pad')

    def _update_selected_drum_pad(self):
        super(DrumGroupComponent, self)._update_selected_drum_pad()
        self.notify_selected_note()
        self.notify_selected_target_note()

    def _update_assigned_drum_pads(self):
        super(DrumGroupComponent, self)._update_assigned_drum_pads()
        self.notify_selected_target_note()

    def _make_copy_handler(self):
        return DrumPadCopyHandler(self.show_notification)

    @matrix.pressed
    def matrix(self, pad):
        self._on_matrix_pressed(pad)

    @matrix.released
    def matrix(self, pad):
        self._on_matrix_released(pad)

    def _on_matrix_pressed(self, pad):
        super(DrumGroupComponent, self)._on_matrix_pressed(pad)
        if self.duplicate_button.is_pressed:
            self._duplicate_pad(pad)

    def set_select_button(self, button):
        self.select_button.set_control_element(button)

    def set_mute_button(self, button):
        self.mute_button.set_control_element(button)

    def set_solo_button(self, button):
        self.solo_button.set_control_element(button)

    def set_quantize_button(self, button):
        self.quantize_button.set_control_element(button)

    def set_delete_button(self, button):
        self.delete_button.set_control_element(button)

    @duplicate_button.pressed
    def duplicate_button(self, button):
        self._set_control_pads_from_script(True)

    @duplicate_button.released
    def duplicate_button(self, button):
        self._set_control_pads_from_script(False)
        if self._copy_handler:
            self._copy_handler.stop_copying()
        if self._notification_reference() is not None:
            self._notification_reference().hide()
        return

    @listens('chains')
    def _on_chains_changed(self):
        self._update_led_feedback()
        self.notify_contents()

    def delete_pitch(self, drum_pad):
        clip = self.song.view.detail_clip
        if clip and any(clip.get_notes(0, drum_pad.note, DISTANT_FUTURE, 1)):
            clip.remove_notes(0, drum_pad.note, DISTANT_FUTURE, 1)
            self.show_notification(MessageBoxText.DELETE_NOTES % drum_pad.name)
        else:
            self.show_notification(MessageBoxText.DELETE_DRUM_RACK_PAD %
                                   drum_pad.name)
            self.delete_drum_pad_content(drum_pad)

    def delete_drum_pad_content(self, drum_pad):
        drum_pad.delete_all_chains()

    def _duplicate_pad(self, pad):
        if self._copy_handler and self._drum_group_device:
            drum_pad = self._pad_for_button(pad)
            self._notification_reference = self._copy_handler.duplicate_pad(
                self._drum_group_device, drum_pad)

    def set_matrix(self, matrix):
        super(DrumGroupComponent, self).set_matrix(matrix)
        self._update_sensitivity_profile()

    def _update_control_from_script(self):
        super(DrumGroupComponent, self)._update_control_from_script()
        self._update_sensitivity_profile()

    def _update_sensitivity_profile(self):
        profile = 'default' if self._takeover_pads or self._selected_pads else 'drums'
        for button in self.matrix:
            button.sensitivity_profile = profile

    @listenable_property
    def selected_note(self):
        if liveobj_valid(self._selected_drum_pad):
            return self._selected_drum_pad.note
        return -1

    @listenable_property
    def selected_target_note(self):
        note_and_channel = (-1, -1)
        if liveobj_valid(self._drum_group_device) and liveobj_valid(
                self._selected_drum_pad):
            if self._selected_drum_pad in self.assigned_drum_pads:
                predicate = lambda b: self._pad_for_button(
                    b) == self._selected_drum_pad
                button = find_if(predicate, self.matrix)
                if button != None and None not in (button.identifier,
                                                   button.channel):
                    note_and_channel = (button.identifier, button.channel)
            else:
                note_and_channel = (self._selected_drum_pad.note,
                                    NON_FEEDBACK_CHANNEL)
        return NamedTuple(note=note_and_channel[0],
                          channel=note_and_channel[1])
class InstrumentComponent(PlayableComponent, CompoundComponent, Slideable,
                          Messenger):
    """
    Class that sets up the button matrix as a piano, using different
    selectable layouts for the notes.
    """
    __events__ = ('pattern', )
    touch_strip_toggle = ToggleButtonControl()
    matrix = control_matrix(PadControl, pressed_color='Instrument.NoteAction')

    def __init__(self, note_layout=None, *a, **k):
        raise note_layout is not None or AssertionError
        super(InstrumentComponent, self).__init__(*a, **k)
        self._note_layout = note_layout
        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._aftertouch_control = None
        self._slider = self.register_component(SlideComponent(self))
        self._touch_slider = self.register_component(
            SlideableTouchStripComponent(self))
        for event in ('scale', 'root_note', 'is_in_key', 'is_fixed',
                      'is_horizontal', 'interval'):
            self.register_slot(self._note_layout, self._on_note_layout_changed,
                               event)

        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()

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

        self.notify_contents()

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

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

    def _on_loop_selection_changed(self):
        self._on_clip_notes_changed()

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

    @property
    def page_length(self):
        return len(
            self._note_layout.notes) if self._note_layout.is_in_key else 12

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

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

    @property
    def page_offset(self):
        return 0 if self._note_layout.is_fixed 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 pattern(self):
        return self._pattern

    @matrix.pressed
    def matrix(self, button):
        self._on_matrix_pressed(button)

    def _on_matrix_pressed(self, button):
        if self._delete_button and self._delete_button.is_pressed():
            pitch = self._get_note_info_for_coordinate(button.coordinate).index
            if pitch and self._detail_clip:
                self._do_delete_pitch(pitch)

    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)

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

    @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([
                (TouchStripStates.STATE_FULL
                 if self.touch_strip_toggle.is_toggled else
                 TouchStripStates.STATE_HALF)
                for _ in xrange(self._touch_strip_indication.state_count)
            ])

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

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

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

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

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

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

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

    def 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

    def _on_note_layout_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)
        self.notify_pattern()

    def _invert_and_swap_coordinates(self, coordinates):
        return (coordinates[1], self.width - 1 - coordinates[0])

    def _get_note_info_for_coordinate(self, coordinate):
        x, y = self._invert_and_swap_coordinates(coordinate)
        return self.pattern.note(x, y)

    def _update_button_color(self, button):
        note_info = self._get_note_info_for_coordinate(button.coordinate)
        button.color = 'Instrument.' + note_info.color

    def _button_should_be_enabled(self, button):
        return self._get_note_info_for_coordinate(
            button.coordinate).index != None

    def _note_translation_for_button(self, button):
        note_info = self._get_note_info_for_coordinate(button.coordinate)
        return (note_info.index, note_info.channel)

    def _set_button_control_properties(self, button):
        super(InstrumentComponent, self)._set_button_control_properties(button)
        button.sensitivity_profile = 'default' if self._takeover_pads else 'instrument'

    def _update_matrix(self):
        self._update_control_from_script()
        self._update_led_feedback()
        self._update_note_translations()

    def _get_pattern(self, first_note=None):
        if first_note is None:
            first_note = int(round(self._first_note))
        interval = self._note_layout.interval
        notes = self._note_layout.notes
        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._note_layout.is_in_key:
            interval = [0, 2, 4, 5, 7, 9, 10, 11][interval]
        if self._note_layout.is_horizontal:
            steps = [1, interval]
            origin = [offset, 0]
        else:
            steps = [interval, 1]
            origin = [0, offset]
        return MelodicPattern(steps=steps,
                              scale=notes,
                              origin=origin,
                              root_note=octave * 12,
                              chromatic_mode=not self._note_layout.is_in_key)

    def _update_aftertouch(self):
        if self.is_enabled() and self._aftertouch_control != None:
            self._aftertouch_control.send_value('mono')
示例#10
0
class NoteEditorComponent(Component):
    __events__ = (u'page_length', u'active_note_regions', u'active_steps', u'notes_changed',
                  u'modify_all_notes')
    matrix = control_matrix(PadControl, channel=PLAYHEAD_FEEDBACK_CHANNELS[0], sensitivity_profile='loop', mode=PlayableControl.Mode.listenable)
    mute_button = ButtonControl(color='DefaultButton.Transparent')

    def __init__(self, clip_creator=None, grid_resolution=None, skin_base_key='NoteEditor', velocity_range_thresholds=None, velocity_provider=None, get_notes_handler=get_single_note, remove_notes_handler=remove_single_note, duplicate_all_notes=False, *a, **k):
        assert skin_base_key is not None
        super(NoteEditorComponent, self).__init__(*a, **k)
        self._duplicate_all_notes = duplicate_all_notes
        self._get_notes_handler = get_notes_handler
        self._remove_notes_handler = remove_notes_handler
        self._skin_base_key = skin_base_key
        self.full_velocity = False
        self._provided_velocity = None
        self._selected_page_point = 0
        self._page_index = 0
        self._clip_creator = clip_creator
        self._sequencer_clip = None
        self._step_colors = []
        self._pressed_steps = []
        self._modified_steps = []
        self._pressed_step_callback = None
        self._modify_task = self._tasks.add(task.run(self._do_modification))
        self._modify_task.kill()
        self._modify_all_notes_enabled = False
        self._step_tap_tasks = {}
        self._clip_notes = []
        self._pitches = [
         DEFAULT_START_NOTE]
        self._grid_resolution = grid_resolution
        self.__on_resolution_changed.subject = self._grid_resolution
        self.set_step_duplicator(None)
        self._nudge_offset = 0
        self._length_offset = 0
        self._velocity_offset = 0
        self._triplet_factor = 1.0
        self._update_from_grid()
        self.background_color = self._skin_base_key + '.StepEmpty'
        self._velocity_range_thresholds = velocity_range_thresholds or DEFAULT_VELOCITY_RANGE_THRESHOLDS
        self._velocity_provider = velocity_provider or NullVelocityProvider()
        self.__on_provided_velocity_changed.subject = self._velocity_provider
        return

    @property
    def page_index(self):
        return self._page_index

    @property
    def page_length(self):
        return self._get_step_count() * self._get_step_length() * self._triplet_factor

    @property
    def can_change_page(self):
        return not self._pressed_steps and not self._modified_steps

    @property
    def notes_in_selected_step(self):
        time_steps = []
        if self._modify_all_notes_enabled:
            time_steps = [
             TimeStep(0.0, ONE_YEAR_AT_120BPM_IN_BEATS)]
        else:
            time_steps = [ self._time_step(self.get_step_start_time(start_time)) for start_time in chain(self._pressed_steps, self._modified_steps) ]
        time_steps_with_notes = [ time_step.filter_notes(self._clip_notes) for time_step in time_steps ]
        notes_in_step = []
        for time_step in time_steps_with_notes:
            for note in time_step:
                notes_in_step.append(note_pitch(note))

        return notes_in_step

    def set_selected_page_point(self, point):
        assert self.can_change_page
        self._selected_page_point = point
        index = int(point / self.page_length) if self.page_length != 0 else 0
        if index != self._page_index:
            self._page_index = index
            self._on_clip_notes_changed()

    def _get_modify_all_notes_enabled(self):
        return self._modify_all_notes_enabled

    def _set_modify_all_notes_enabled(self, enabled):
        if enabled != self._modify_all_notes_enabled:
            self._modify_all_notes_enabled = enabled
            self._on_clip_notes_changed()
            self.notify_modify_all_notes()

    modify_all_notes_enabled = property(_get_modify_all_notes_enabled, _set_modify_all_notes_enabled)

    def set_detail_clip(self, clip):
        if liveobj_changed(clip, self._sequencer_clip):
            self._sequencer_clip = clip
            self._step_duplicator.set_clip(clip)
            if self.can_change_page:
                self.set_selected_page_point(0)
            self._on_clip_notes_changed.subject = clip
            self._on_clip_notes_changed()

    def _can_edit(self):
        return len(self._pitches) != 0

    def _get_editing_notes(self):
        return self._pitches

    def _set_editing_notes(self, pitches):
        self._pitches = pitches
        enabled = self._can_edit()
        for button in self.matrix:
            button.enabled = enabled

        if enabled:
            self._on_clip_notes_changed()

    editing_notes = property(_get_editing_notes, _set_editing_notes)

    def _get_width(self):
        if self.matrix.width:
            return self.matrix.width
        return 4

    def _get_height(self):
        if self.matrix.height:
            return self.matrix.height
        return 4

    def set_matrix(self, matrix):
        last_page_length = self.page_length
        self.matrix.set_control_element(matrix)
        for t in self._step_tap_tasks.itervalues():
            t.kill()

        def trigger_modification_task(x, y):
            trigger = partial(self._trigger_modification, (x, y), done=True)
            return self._tasks.add(task.sequence(task.wait(defaults.MOMENTARY_DELAY), task.run(trigger))).kill()

        self._step_tap_tasks = dict([ ((x, y), trigger_modification_task(x, y)) for x, y in product(xrange(self._get_width()), xrange(self._get_height()))
                                    ])
        if matrix and last_page_length != self.page_length:
            self._on_clip_notes_changed()
            self.notify_page_length()
        else:
            self._update_editor_matrix()

    def set_step_duplicator(self, duplicator):
        self._step_duplicator = duplicator or NullStepDuplicator()
        self._step_duplicator.set_clip(self._sequencer_clip)

    def update(self):
        super(NoteEditorComponent, self).update()
        self._update_editor_matrix_leds()

    def _get_clip_notes_time_range(self):
        if self._modify_all_notes_enabled:
            time_length = self._get_step_count() * 4.0
            time_start = 0
        else:
            time_length = self.page_length
            time_start = self._page_index * time_length
        return (time_start - self._time_step(0).offset, time_length)

    @listens('notes')
    def _on_clip_notes_changed(self):
        u""" get notes from clip for offline array """
        if liveobj_valid(self._sequencer_clip) and self._can_edit():
            time_start, time_length = self._get_clip_notes_time_range()
            self._clip_notes = self._get_notes_handler(self._sequencer_clip, time_start, self._pitches, time_length)
        else:
            self._clip_notes = []
        self._update_editor_matrix()
        self.notify_notes_changed()

    def _update_editor_matrix(self):
        u"""
        update offline array of button LED values, based on note
        velocity and mute states
        """
        step_colors = [
         self._skin_base_key + '.StepDisabled'] * self._get_step_count()

        def coords_to_index(coord):
            return coord[0] + coord[1] * self._get_width()

        editing_indices = set(map(coords_to_index, self._modified_steps))
        selected_indices = set(map(coords_to_index, self._pressed_steps))
        last_editing_notes = []
        for time_step, index in self._visible_steps():
            notes = time_step.filter_notes(self._clip_notes)
            if len(notes) > 0:
                last_editing_notes = []
                if index in selected_indices:
                    color = self._skin_base_key + '.StepSelected'
                elif index in editing_indices:
                    note_color = self._determine_color(notes)
                    color = self._skin_base_key + '.StepEditing.' + note_color
                    last_editing_notes = notes
                else:
                    note_color = self._determine_color(notes)
                    color = self._skin_base_key + '.Step.' + note_color
            elif any(imap(time_step.overlaps_note, last_editing_notes)):
                color = self._skin_base_key + '.StepEditing.' + note_color
            elif index in editing_indices or index in selected_indices:
                color = self._skin_base_key + '.StepSelected'
                last_editing_notes = []
            else:
                color = self.background_color
                last_editing_notes = []
            step_colors[index] = color

        self._step_colors = step_colors
        self._update_editor_matrix_leds()

    def _determine_color(self, notes):
        return color_for_note(most_significant_note(notes), velocity_range_thresholds=self._velocity_range_thresholds)

    def _visible_steps(self):
        first_time = self.page_length * self._page_index
        steps_per_page = self._get_step_count()
        step_length = self._get_step_length()
        indices = range(steps_per_page)
        if is_triplet_quantization(self._triplet_factor):
            indices = filter(lambda k: k % 8 not in (6, 7), indices)
        return [ (self._time_step(first_time + k * step_length), index) for k, index in enumerate(indices)
               ]

    def _update_editor_matrix_leds(self):
        if self.is_enabled():
            for control in self.matrix:
                control.color = self._step_colors[control.index]

    def _get_step_count(self):
        return self._get_width() * self._get_height()

    def get_step_start_time(self, step):
        x, y = step
        assert in_range(x, 0, self._get_width())
        assert in_range(y, 0, self._get_height())
        page_time = self._page_index * self._get_step_count() * self._triplet_factor
        step_time = x + y * self._get_width() * self._triplet_factor
        return (page_time + step_time) * self._get_step_length()

    def _get_step_length(self):
        return self._grid_resolution.step_length

    def get_row_start_times(self):
        return [ self.get_step_start_time((0, row)) for row in xrange(self._get_height())
               ]

    def _update_from_grid(self):
        quantization, is_triplet = self._grid_resolution.clip_grid
        self._triplet_factor = 1.0 if not is_triplet else 0.75
        if self._clip_creator:
            self._clip_creator.grid_quantization = quantization
            self._clip_creator.is_grid_triplet = is_triplet
        if liveobj_valid(self._sequencer_clip):
            self._sequencer_clip.view.grid_quantization = quantization
            self._sequencer_clip.view.grid_is_triplet = is_triplet

    @mute_button.pressed
    def mute_button(self, button):
        self._trigger_modification(immediate=True)

    @listens('index')
    def __on_resolution_changed(self, *a):
        self._release_active_steps()
        self._update_from_grid()
        self.set_selected_page_point(self._selected_page_point)
        self.notify_page_length()
        self._on_clip_notes_changed()

    @matrix.pressed
    def matrix(self, button):
        self._on_pad_pressed(button.coordinate)

    @matrix.released
    def matrix(self, button):
        self._on_pad_released(button.coordinate)

    def _on_pad_pressed(self, coordinate):
        y, x = coordinate
        if self.is_enabled():
            if not liveobj_valid(self._sequencer_clip):
                self.set_detail_clip(create_clip_in_selected_slot(self._clip_creator, self.song))
            if self._can_press_or_release_step(x, y):
                self._on_press_step((x, y))
                self._update_editor_matrix()

    def _on_pad_released(self, coordinate):
        y, x = coordinate
        if self.is_enabled() and self._can_press_or_release_step(x, y):
            self._on_release_step((x, y))
            self._update_editor_matrix()

    def _can_press_or_release_step(self, x, y):
        width = self._get_width() * self._triplet_factor if is_triplet_quantization(self._triplet_factor) else self._get_width()
        return x < width and y < self._get_height()

    @listens('velocity')
    def __on_provided_velocity_changed(self):
        if len(self._pressed_steps) + len(self._modified_steps) > 0:
            self._provided_velocity = self._velocity_provider.velocity
            self._trigger_modification(immediate=True)
            self._provided_velocity = None
        return

    def _get_step_time_range(self, step):
        time = self.get_step_start_time(step)
        return (
         time, time + self._get_step_length())

    @property
    def editing_note_regions(self):
        active_time_spans = self.active_time_spans
        editing_notes = set(self.notes_in_selected_step)
        if len(editing_notes) > 1:
            editing_notes = [
             -1]
        return list(product(editing_notes, list(active_time_spans)))

    @property
    def active_time_spans(self):
        if self._modify_all_notes_enabled:
            return [(0.0, ONE_YEAR_AT_120BPM_IN_BEATS)]
        else:

            def time_span(step):
                time_step = self._time_step(self.get_step_start_time(step))
                return (
                 time_step.left_boundary(), time_step.right_boundary())

            return imap(time_span, chain(self._pressed_steps, self._modified_steps))

    @property
    def active_steps(self):
        return imap(self._get_step_time_range, chain(self._pressed_steps, self._modified_steps))

    @property
    def active_note_regions(self):
        return imap(self._get_time_range, chain(self._pressed_steps, self._modified_steps))

    def _get_time_range(self, step):

        def note_compare(left, right):
            return note_start_time(left) - note_start_time(right)

        time = self.get_step_start_time(step)
        notes = self._time_step(time).filter_notes(self._clip_notes)
        if notes:
            beginning_note = first(sorted(notes, key=cmp_to_key(note_compare)))
            start = note_start_time(beginning_note)
            end = start + note_length(beginning_note)
            if len(notes) > 1:
                end_note = notes[(-1)]
                end = note_start_time(end_note) + note_length(end_note)
            return (start, end)
        else:
            return (
             time, time + self._get_step_length())

    def _release_active_steps(self):
        for step in self._pressed_steps + self._modified_steps:
            self._on_release_step(step, do_delete_notes=False)

    def _on_release_step(self, step, do_delete_notes=True):
        self._step_tap_tasks[step].kill()
        if step in self._pressed_steps:
            if do_delete_notes:
                self._delete_notes_in_step(step)
            self._pressed_steps.remove(step)
            for pitch in self._pitches:
                self._add_or_modify_note_in_step(step, pitch)

        if step in self._modified_steps:
            self._modified_steps.remove(step)
        if len(self._modified_steps) + len(self._pressed_steps) == 0:
            self._velocity_provider.set_velocities_playable(True)
        self.notify_active_steps()
        self.notify_active_note_regions()

    def _find_continued_step(self, step):

        def steps_to_note_start(steps):
            return map(lambda step: self.get_step_start_time(step), steps)

        time = self.get_step_start_time(step)
        all_steps_with_notes = [ time_step for time_step, index in self._visible_steps() if time_step.filter_notes(self._clip_notes)
                               ]
        all_time_step_starts = map(lambda ts: ts.start, all_steps_with_notes)
        if all_time_step_starts:
            insert_point = bisect(all_time_step_starts, time)
            if insert_point > 0:
                prev_filled_step = all_steps_with_notes[(insert_point - 1)]
                notes_in_modified_steps = steps_to_note_start(self._modified_steps)
                if prev_filled_step.start in notes_in_modified_steps:
                    return prev_filled_step
        return

    def _add_step_to_duplicator(self, step):
        nudge_offset = 0
        time = self.get_step_start_time(step)
        notes = self._time_step(time).filter_notes(self._clip_notes)
        step_start, step_end = self._get_time_range(step)
        if notes:
            nudge_offset = min(imap(note_start_time, notes)) - time
        if self._duplicate_all_notes:
            self._step_duplicator.add_step(step_start, step_end, nudge_offset)
        else:
            self._step_duplicator.add_step_with_pitch(self.editing_notes[0], step_start, step_end, nudge_offset)

    def _on_press_step(self, step):
        if liveobj_valid(self._sequencer_clip) and step not in self._pressed_steps and step not in self._modified_steps:
            if self._step_duplicator.is_duplicating:
                self._add_step_to_duplicator(step)
            else:
                self._step_tap_tasks[step].restart()
                continued_step = self._find_continued_step(step)
                if continued_step:
                    self._modify_length_of_notes_within_existing_step(continued_step, step)
                else:
                    self._pressed_steps.append(step)
                self._velocity_provider.set_velocities_playable(False)
        self.notify_active_steps()
        self.notify_active_note_regions()

    def _modify_length_of_notes_within_existing_step(self, existing_time_step, new_step):
        notes_in_step = existing_time_step.filter_notes(self._clip_notes)
        new_end = float(self.get_step_start_time(new_step) + self._get_step_length())
        step_mute = all(map(lambda note: note_muted(note), notes_in_step))
        new_notes = []
        for note in self._clip_notes:
            length_offset = new_end - (note_length(note) + note_start_time(note)) if note in notes_in_step else 0
            new_notes.append(self._modify_single_note(step_mute, existing_time_step, length_offset, note))

        self._replace_notes(tuple(new_notes))

    def _time_step(self, time):
        return TimeStep(time, self._get_step_length())

    def _get_notes_info_from_step(self, step):
        time = self.get_step_start_time(step)
        notes = self._time_step(time).filter_notes(self._clip_notes)
        pitches = [ note_pitch(note) for note in notes ]
        return (
         time, notes, pitches)

    def toggle_pitch_for_all_modified_steps(self, pitch):
        assert liveobj_valid(self._sequencer_clip)
        for step in set(chain(self._pressed_steps, self._modified_steps)):
            time, notes, pitches = self._get_notes_info_from_step(step)
            if pitch not in pitches:
                self._add_new_note_in_step(step, pitch, time)
            else:
                time_step = self._time_step(self.get_step_start_time(step))
                for time, length in time_step.connected_time_ranges():
                    remove_single_note(self._sequencer_clip, time, (pitch,), length)

    def _add_new_note_in_step(self, step, pitch, time):
        mute = self.mute_button.is_pressed
        velocity = 127 if self.full_velocity else self._velocity_provider.velocity
        note = (pitch, time, self._get_step_length(), velocity, mute)
        self._sequencer_clip.set_notes((note,))
        self._sequencer_clip.deselect_all_notes()
        self._trigger_modification(step, done=True)

    def _add_or_modify_note_in_step(self, step, pitch, modify_existing=True):
        u"""
        Add note in given step if there are none in there, otherwise
        select the step for potential deletion or modification
        """
        if liveobj_valid(self._sequencer_clip):
            time, notes, pitches = self._get_notes_info_from_step(step)
            if notes:
                if pitch in pitches and modify_existing:
                    most_significant_velocity = most_significant_note(notes)[3]
                    if self.mute_button.is_pressed or most_significant_velocity != 127 and self.full_velocity:
                        self._trigger_modification(step, immediate=True)
            else:
                self._add_new_note_in_step(step, pitch, time)
                return True
        return False

    def _delete_notes_in_step(self, step):
        u""" Delete all notes in the given step """
        if liveobj_valid(self._sequencer_clip) and self._can_edit():
            time_step = self._time_step(self.get_step_start_time(step))
            for time, length in time_step.connected_time_ranges():
                self._remove_notes_handler(self._sequencer_clip, time, self._pitches, length)

    def set_nudge_offset(self, value):
        self._modify_note_property('_nudge_offset', value)

    def set_length_offset(self, value):
        self._modify_note_property('_length_offset', value)

    def set_velocity_offset(self, value):
        self._modify_note_property('_velocity_offset', value)

    def _modify_note_property(self, note_property, value):
        if self.is_enabled():
            with self._full_velocity_context(False):
                setattr(self, note_property, getattr(self, note_property) + value)
                self._trigger_modification(immediate=True)

    @contextmanager
    def _full_velocity_context(self, desired_full_velocity_state):
        saved_velocity = self.full_velocity
        self.full_velocity = desired_full_velocity_state
        yield
        self.full_velocity = saved_velocity

    def set_full_velocity(self):
        if self.is_enabled():
            with self._full_velocity_context(True):
                self._trigger_modification()

    def notify_modification(self):
        u"""
        Tell the note editor about changes to pressed steps, so further modifications
        by the note editor are ignored.
        """
        self._trigger_modification(done=True)

    def _trigger_modification(self, step=None, done=False, immediate=False):
        u"""
        Because the modification of notes is slow, we
        accumulate modification events and perform all of them
        alltogether in a task. Call this function whenever a given set
        of steps (or potentially all steps) are to be modified.

        If done=True, we just notify that the given steps have been
        modified without actually executing the task.
        """
        needs_update = False
        if step is None:
            needs_update = bool(self._pressed_steps)
            self._modified_steps += self._pressed_steps
            self._pressed_steps = []
        elif step not in self._modified_steps:
            self._modified_steps.append(step)
        else:
            if step in self._pressed_steps:
                self._pressed_steps.remove(step)
            needs_update = True

        if not done:
            if immediate:
                self._do_modification()
                self._modify_task.kill()
            elif self._modify_task.is_killed:
                self._do_modification()
                self._modify_task.restart()
        if needs_update:
            self._update_editor_matrix()
        return

    def _reset_modifications(self):
        self._velocity_offset = 0
        self._length_offset = 0
        self._nudge_offset = 0

    def _do_modification(self):
        if self._modify_all_notes_enabled:
            new_notes = self._modify_all_notes()
            self._replace_notes(new_notes)
        elif self._modified_steps:
            notes_added = map(lambda step_and_pitch: self._add_or_modify_note_in_step(modify_existing=False, *step_and_pitch), product(self._modified_steps, self._pitches))
            if any(notes_added):
                self._modify_task.restart()
            else:
                new_notes = self._modify_step_notes(self._modified_steps)
                self._replace_notes(new_notes)
        self._reset_modifications()

    def _replace_notes(self, new_notes):
        if new_notes != self._clip_notes and self._can_edit():
            time_start, time_length = self._get_clip_notes_time_range()
            self._remove_notes_handler(self._sequencer_clip, time_start, self._pitches, time_length)
            self._sequencer_clip.set_notes(tuple(new_notes))
            self._sequencer_clip.deselect_all_notes()

    def _modify_all_notes(self):
        u""" modify all notes in the current pitch """
        return self._modify_notes_in_time(TimeStep(0.0, MAX_CLIP_LENGTH), self._clip_notes, self._length_offset)

    def _limited_nudge_offset(self, steps, notes, nudge_offset):
        limited_nudge_offset = MAX_CLIP_LENGTH
        for step in steps:
            time_step = self._time_step(self.get_step_start_time(step))
            for note in time_step.filter_notes(notes):
                start_time = note_start_time(note)
                time_after_nudge = time_step.clamp(start_time, nudge_offset)
                limited_nudge_offset = min(limited_nudge_offset, abs(start_time - time_after_nudge))

        return sign(nudge_offset) * limited_nudge_offset

    def _modify_step_notes(self, steps):
        u""" Return a new list with all notes within steps modified. """
        notes = self._clip_notes
        self._nudge_offset = self._limited_nudge_offset(steps, notes, self._nudge_offset)
        for step in steps:
            time_step = self._time_step(self.get_step_start_time(step))
            notes = self._modify_notes_in_time(time_step, notes, self._length_offset)

        return notes

    def _modify_notes_in_time(self, time_step, notes, length_offset):
        step_notes = time_step.filter_notes(self._clip_notes)
        step_mute = all(map(lambda note: note_muted(note), step_notes))
        return map(partial(self._modify_single_note, step_mute, time_step, length_offset), notes)

    def _modify_single_note(self, step_mute, time_step, length_offset, note):
        u"""
        Return a modified version of the passed in note taking into
        account current modifiers. If the note is not within
        the given step, returns the note as-is.

        If the time_step is inside a loop, the last part of the loop
        is considered as being the same as the part just before the
        loop, so the resulting note may, in this case, jump between
        the beginning and the end.
        """
        pitch, time, length, velocity, mute = note
        if time_step.includes_time(time):
            time = time_step.clamp(time, self._nudge_offset)
            if length_offset <= -time_step.length and length + length_offset < time_step.length:
                if length > time_step.length:
                    length = time_step.length
            else:
                length = max(0, length + length_offset)
            if self._provided_velocity:
                velocity = self._provided_velocity
            elif self.full_velocity:
                velocity = 127
            else:
                velocity = clamp(velocity + self._velocity_offset, 1, 127)
            mute = not step_mute if self.mute_button.is_pressed else mute
        return (
         pitch, time, length, velocity, mute)

    def get_min_max_note_values(self):
        if self._modify_all_notes_enabled and len(self._clip_notes) > 0:
            return min_max_for_notes(self._clip_notes, 0.0)
        else:
            if len(self._pressed_steps) + len(self._modified_steps) > 0:
                min_max_values = None
                for step in chain(self._modified_steps, self._pressed_steps):
                    start_time = self.get_step_start_time(step)
                    min_max_values = min_max_for_notes(self._time_step(start_time).filter_notes(self._clip_notes), start_time, min_max_values)

                return min_max_values
            return
示例#11
0
class DrumGroupComponent(ResettableSlideComponent, Slideable):
    __events__ = (u'pressed_pads', )
    mute_button = ButtonControl()
    solo_button = ButtonControl()
    delete_button = ButtonControl(**ACTION_BUTTON_COLORS)
    quantize_button = ButtonControl()
    select_button = ButtonControl(color=u'Misc.Shift',
                                  pressed_color=u'Misc.ShiftOn')
    drum_matrix = control_matrix(PlayableControl)

    @depends(set_pad_translations=None)
    def __init__(self,
                 pitch_deleter,
                 translation_channel=None,
                 set_pad_translations=None,
                 *a,
                 **k):
        self._pitch_deleter = pitch_deleter
        self._takeover_drums = False
        self._drum_group_device = None
        self._selected_drum_pad = None
        self._all_drum_pads = []
        self._selected_pads = []
        self._visible_drum_pads = []
        self._translation_channel = translation_channel
        self._coordinate_to_pad_map = {}
        super(DrumGroupComponent, self).__init__(*a, **k)
        self._set_pad_translations = set_pad_translations
        self._on_selected_clip_changed.subject = self._pitch_deleter
        self._layout_set = False

    position_count = 32
    page_length = 4
    page_offset = 1

    def contents_range(self, pmin, pmax):
        pos_count = self.position_count
        first_pos = max(int(pmin - 0.05), 0)
        last_pos = min(int(pmax + 0.2), pos_count)
        return xrange(first_pos, last_pos)

    def contents(self, index):
        drum = self._drum_group_device
        if drum:
            return any(
                imap(lambda pad: pad.chains,
                     drum.drum_pads[index * 4:index * 4 + 4]))
        return False

    def _get_position(self):
        if self._drum_group_device:
            return self._drum_group_device.view.drum_pads_scroll_position
        return 0

    def _set_position(self, index):
        if not 0 <= index <= 28:
            raise AssertionError
            self._drum_group_device.view.drum_pads_scroll_position = self._drum_group_device and index

    position = property(_get_position, _set_position)

    @property
    def width(self):
        if self.drum_matrix.width:
            return self.drum_matrix.width
        return 4

    @property
    def height(self):
        if self.drum_matrix.height:
            return self.drum_matrix.height
        return 4

    @property
    def pressed_pads(self):
        return self._selected_pads

    @property
    def visible_drum_pads(self):
        if self._visible_drum_pads and self._all_drum_pads:
            first_pad = first(self._visible_drum_pads)
            if first_pad:
                size = self.width * self.height
                first_note = first_pad.note
                if first_note > 128 - size:
                    size = 128 - first_note
                offset = clamp(first_note, 0,
                               128 - len(self._visible_drum_pads))
                return self._all_drum_pads[offset:offset + size]
        return []

    def update(self):
        super(DrumGroupComponent, self).update()
        self._set_control_pads_from_script(False)
        self._update_led_feedback()

    def set_drum_matrix(self, matrix):
        if not matrix or not self._layout_set:
            self.drum_matrix.set_control_element(matrix)
            for button in self.drum_matrix:
                button.channel = self._translation_channel

            if self._selected_pads:
                self._selected_pads = []
                self.notify_pressed_pads()
            self._create_and_set_pad_translations()
            self._update_control_from_script()
            self._update_identifier_translations()
            self._layout_set = bool(matrix)
            self._update_led_feedback()

    @listens(u'selected_clip')
    def _on_selected_clip_changed(self):
        if self.is_enabled():
            self.delete_button.enabled = self._pitch_deleter.can_perform_midi_clip_action(
            )

    def set_drum_group_device(self, drum_group_device):
        if drum_group_device and not drum_group_device.can_have_drum_pads:
            drum_group_device = None
        if drum_group_device != self._drum_group_device:
            self._on_visible_drum_pads_changed.subject = drum_group_device
            drum_group_view = drum_group_device.view if drum_group_device else None
            self._on_selected_drum_pad_changed.subject = drum_group_view
            self._on_drum_pads_scroll_position_changed.subject = drum_group_view
            self._drum_group_device = drum_group_device
            self._update_drum_pad_listeners()
            self._on_selected_drum_pad_changed()
            self._update_identifier_translations()
            super(DrumGroupComponent, self).update()

    def _update_drum_pad_listeners(self):
        u"""
        add and remove listeners for visible drum pads, including
        mute and solo state
        """
        if self._drum_group_device:
            self._all_drum_pads = self._drum_group_device.drum_pads
            self._visible_drum_pads = self._drum_group_device.visible_drum_pads
            self._on_solo_changed.replace_subjects(self._visible_drum_pads)
            self._on_mute_changed.replace_subjects(self._visible_drum_pads)
            self._update_identifier_translations()

    @listens_group(u'solo')
    def _on_solo_changed(self, pad):
        self._update_led_feedback()

    @listens_group(u'mute')
    def _on_mute_changed(self, pad):
        self._update_led_feedback()

    def _update_led_feedback(self):
        if self._drum_group_device:
            soloed_pads = find_if(lambda pad: pad.solo, self._all_drum_pads)
            for button in self.drum_matrix:
                pad = self._coordinate_to_pad_map.get(button.coordinate, None)
                if pad:
                    self._update_pad_led(pad, button, soloed_pads)

    def _update_pad_led(self, pad, button, soloed_pads):
        button_color = u'DrumGroup.PadEmpty'
        if pad == self._selected_drum_pad:
            if soloed_pads and not pad.solo and not pad.mute:
                button_color = u'DrumGroup.PadSelectedNotSoloed'
            elif pad.mute and not pad.solo:
                button_color = u'DrumGroup.PadMutedSelected'
            elif soloed_pads and pad.solo:
                button_color = u'DrumGroup.PadSoloedSelected'
            else:
                button_color = u'DrumGroup.PadSelected'
        elif pad.chains:
            if soloed_pads and not pad.solo:
                if not pad.mute:
                    button_color = u'DrumGroup.PadFilled'
                else:
                    button_color = u'DrumGroup.PadMuted'
            elif not soloed_pads and pad.mute:
                button_color = u'DrumGroup.PadMuted'
            elif soloed_pads and pad.solo:
                button_color = u'DrumGroup.PadSoloed'
            else:
                button_color = u'DrumGroup.PadFilled'
        else:
            button_color = u'DrumGroup.PadEmpty'
        button.color = button_color

    def _button_coordinates_to_pad_index(self, first_note, coordinates):
        y, x = coordinates
        y = self.height - y - 1
        if x < 4 and y >= 4:
            first_note += 16
        elif x >= 4 and y < 4:
            first_note += 4 * self.width
        elif x >= 4 and y >= 4:
            first_note += 4 * self.width + 16
        index = x % 4 + y % 4 * 4 + first_note
        return index

    @drum_matrix.pressed
    def drum_matrix(self, pad):
        self._on_matrix_pressed(pad)

    @drum_matrix.released
    def drum_matrix(self, pad):
        self._on_matrix_released(pad)

    def _on_matrix_released(self, pad):
        selected_drum_pad = self._coordinate_to_pad_map[pad.coordinate]
        if selected_drum_pad in self._selected_pads:
            self._selected_pads.remove(selected_drum_pad)
            if not self._selected_pads:
                self._update_control_from_script()
            self.notify_pressed_pads()
        self._update_led_feedback()

    def _on_matrix_pressed(self, pad):
        selected_drum_pad = self._coordinate_to_pad_map[pad.coordinate]
        if self.mute_button.is_pressed:
            selected_drum_pad.mute = not selected_drum_pad.mute
        if self.solo_button.is_pressed:
            selected_drum_pad.solo = not selected_drum_pad.solo
        if self.quantize_button.is_pressed:
            pad.color = u'DrumGroup.PadAction'
            self.quantize_pitch(selected_drum_pad.note)
        if self.delete_button.is_pressed:
            pad.color = u'DrumGroup.PadAction'
            self.delete_pitch(selected_drum_pad)
        if self.select_button.is_pressed:
            self._drum_group_device.view.selected_drum_pad = selected_drum_pad
            self.select_drum_pad(selected_drum_pad)
            self._selected_pads.append(selected_drum_pad)
            if len(self._selected_pads) == 1:
                self._update_control_from_script()
            self.notify_pressed_pads()
        if self.mute_button.is_pressed or self.solo_button.is_pressed:
            self._update_led_feedback()

    @listens(u'visible_drum_pads')
    def _on_visible_drum_pads_changed(self):
        self._update_drum_pad_listeners()
        self._update_led_feedback()

    @listens(u'drum_pads_scroll_position')
    def _on_drum_pads_scroll_position_changed(self):
        self._update_identifier_translations()
        self._update_led_feedback()
        self.notify_position()

    @listens(u'selected_drum_pad')
    def _on_selected_drum_pad_changed(self):
        self._selected_drum_pad = self._drum_group_device.view.selected_drum_pad if self._drum_group_device else None
        self._update_led_feedback()

    @mute_button.value
    def mute_button(self, value, button):
        self._set_control_pads_from_script(bool(value))

    @solo_button.value
    def solo_button(self, value, button):
        self._set_control_pads_from_script(bool(value))

    @delete_button.value
    def delete_button(self, value, button):
        self._set_control_pads_from_script(bool(value))

    @quantize_button.value
    def quantize_button(self, value, button):
        self._set_control_pads_from_script(bool(value))

    @select_button.value
    def select_button(self, value, button):
        self._set_control_pads_from_script(bool(value))

    def _set_control_pads_from_script(self, takeover_drums):
        u"""
        If takeover_drums, the matrix buttons will be controlled from
        the script. Otherwise they send midi notes to the track
        associated to this drum group.
        """
        if takeover_drums != self._takeover_drums:
            self._takeover_drums = takeover_drums
            self._update_control_from_script()

    def _update_control_from_script(self):
        takeover_drums = self._takeover_drums or bool(self._selected_pads)
        for button in self.drum_matrix:
            button.set_playable(not takeover_drums)

    def _update_identifier_translations(self):
        if not self._can_set_pad_translations():
            self._set_non_pad_translated_identifiers()
        else:
            self._set_pad_translated_identifiers()

    def _set_non_pad_translated_identifiers(self):
        visible_drum_pads = self.visible_drum_pads
        if visible_drum_pads:
            for button in self.drum_matrix:
                identifier = self._button_coordinates_to_pad_index(
                    first(visible_drum_pads).note, button.coordinate)
                if identifier < 128:
                    button.identifier = identifier
                    button.enabled = True
                    self._coordinate_to_pad_map[
                        button.coordinate] = self._all_drum_pads[
                            button.identifier]
                else:
                    button.enabled = False

    def _set_pad_translated_identifiers(self):
        visible_drum_pads = self.visible_drum_pads
        if visible_drum_pads:
            for index, button in enumerate(self.drum_matrix):
                row, col = button.coordinate
                self._coordinate_to_pad_map[self.width - 1 - row,
                                            col] = visible_drum_pads[index]

    def _can_set_pad_translations(self):
        return self.width <= 4 and self.height <= 4

    def _create_and_set_pad_translations(self):
        def create_translation_entry(button):
            row, col = button.coordinate
            button.identifier = self._button_coordinates_to_pad_index(
                BASE_DRUM_RACK_NOTE, button.coordinate)
            return (col, row, button.identifier, button.channel)

        if self._can_set_pad_translations():
            translations = tuple(
                map(create_translation_entry, self.drum_matrix))
            self._set_pad_translated_identifiers()
        else:
            translations = None
            self._set_non_pad_translated_identifiers()
        self._set_pad_translations(translations)

    def select_drum_pad(self, drum_pad):
        u""" Override when you give it a select button """
        pass

    def quantize_pitch(self, note):
        u""" Override when you give it a quantize button """
        raise NotImplementedError

    def delete_pitch(self, drum_pad):
        self._pitch_deleter.delete_pitch(drum_pad.note)
示例#12
0
class MonoKeyGroupComponent(PlayableComponent):

    _scales = SCALES
    _scale = DEFAULT_AUTO_SCALE
    _vertoffset = DEFAULT_VERTOFFSET
    _offset = DEFAULT_OFFSET
    _translation_channel = 0
    _selected_note = 0
    _selected_notes = tuple([0])
    select_matrix = control_matrix(PlayableControl)

    def __init__(self,
                 channel_list=CHANNELS,
                 settings=DEFAULT_INSTRUMENT_SETTINGS,
                 *a,
                 **k):
        self._channel_list = channel_list
        self._settings = settings
        self._scales = settings['Scales']
        super(MonoKeyGroupComponent, self).__init__(*a, **k)
        self.selected_notes_provider = self
        self.select_button._send_current_color = lambda: None
        self._on_selected_track_changed.subject = self.song.view

    def _get_current_channel(self):
        cur_track = self.song.view.selected_track
        cur_chan = cur_track.current_input_sub_routing
        if len(cur_chan) == 0:
            cur_chan = 'All Channels'
        if cur_chan == 'All Channels':
            cur_chan = 1
        if cur_chan in self._channel_list:
            cur_chan = (self._channel_list.index(cur_chan) % 15) + 1
        else:
            cur_chan = 14
        return cur_chan

    @listens('selected_track')
    def _on_selected_track_changed(self):
        self.translation_channel = self._get_current_channel()
        self.update_matrix()

    @property
    def translation_channel(self, translation_channel):
        return self._translation_channel

    @translation_channel.setter
    def translation_channel(self, channel):
        self._translation_channel = channel
        self.update_matrix()

    @property
    def scale(self):
        return self._scale

    @scale.setter
    def scale(self, scale):
        self._scale = scale
        self.update_matrix()

    @property
    def vertical_offset(self):
        return self._vertoffset

    @vertical_offset.setter
    def vertical_offset(self, offset):
        self._vertoffset = offset
        self.update_matrix()

    @property
    def offset(self):
        return self._offset

    @offset.setter
    def offset(self, offset):
        self._offset = offset
        self.update_matrix()

    def update_matrix(self):
        self._reset_selected_pads()
        self._update_led_feedback()
        self._update_note_translations()

    def _update_led_feedback(self):
        super(MonoKeyGroupComponent, self)._update_led_feedback()
        for button in self.select_matrix:
            self._update_button_color(button)

    def set_select_matrix(self, matrix):
        self.select_matrix.set_control_element(matrix)
        for button in self.select_matrix:
            #button.set_playable(False)
            button.set_mode(PlayableControl.Mode.listenable)
        self._update_led_feedback()

    def _note_translation_for_button(self, button):
        y, x = button.coordinate
        scale_len = len(self._scales[self._scale])
        note_pos = x + (abs((self.height - 1) - y) * self._vertoffset)
        note = self._offset + self._scales[self._scale][
            note_pos % scale_len] + (12 * int(note_pos / scale_len))
        return (note, self._translation_channel)

    def set_matrix(self, matrix):
        self._set_matrix_special_attributes(False)
        super(MonoKeyGroupComponent, self).set_matrix(matrix)
        self._set_matrix_special_attributes(True)

    def _set_matrix_special_attributes(self, enabled):
        for button in self.matrix:
            if button._control_element:
                button._control_element.display_press = enabled
                button._control_element._last_flash = 0
                not enabled and button._control_element.reset_state()

    def _update_button_color(self, button):
        y, x = button.coordinate
        scale_len = len(self._scales[self._scale])
        note_pos = x + (abs((self.height - 1) - y) * self._vertoffset)
        note = self._offset + self._scales[self._scale][
            note_pos % scale_len] + (12 * int(note_pos / scale_len))
        if note is self.selected_note:
            button.color = SELECTED_NOTE
        else:
            button.color = KEYCOLORS[(note % 12 in WHITEKEYS) +
                                     (((note_pos % scale_len) == 0) * 2)]
        if button._control_element:
            button._control_element.scale_color = button.color

    @select_matrix.pressed
    def select_matrix(self, button):
        self._on_matrix_pressed(button)

    def _on_matrix_pressed(self, button):
        super(MonoKeyGroupComponent, self)._on_matrix_pressed(button)
        self._selected_note = self._note_translation_for_button(button)[0]
        self.notify_selected_note()
        self.selected_notes = [int(self._selected_note)]
        self._update_led_feedback()

    @listenable_property
    def selected_note(self):
        return int(self._selected_note)

    @listenable_property
    def selected_notes(self):
        return tuple([int(self._selected_note)])

    @selected_notes.setter
    def selected_notes(self, notes):
        self._selected_notes = tuple(notes)
        self.notify_selected_notes(self._selected_notes)
示例#13
0
class NoteEditorComponent(Component):
    __events__ = ('page_length', 'active_note_regions', 'active_steps',
                  'notes_changed', 'modify_all_notes')
    matrix = control_matrix(PadControl,
                            channel=(PLAYHEAD_FEEDBACK_CHANNELS[0]),
                            sensitivity_profile='loop',
                            mode=(PlayableControl.Mode.listenable))
    mute_button = ButtonControl(color='DefaultButton.Transparent')

    def __init__(self,
                 clip_creator=None,
                 grid_resolution=None,
                 skin_base_key='NoteEditor',
                 velocity_range_thresholds=None,
                 velocity_provider=None,
                 get_notes_handler=get_single_note,
                 remove_notes_handler=remove_single_note,
                 duplicate_all_notes=False,
                 *a,
                 **k):
        (super(NoteEditorComponent, self).__init__)(*a, **k)
        self._duplicate_all_notes = duplicate_all_notes
        self._get_notes_handler = get_notes_handler
        self._remove_notes_handler = remove_notes_handler
        self._skin_base_key = skin_base_key
        self.full_velocity = False
        self._provided_velocity = None
        self._selected_page_point = 0
        self._page_index = 0
        self._clip_creator = clip_creator
        self._sequencer_clip = None
        self._step_colors = []
        self._pressed_steps = []
        self._modified_steps = []
        self._pressed_step_callback = None
        self._modify_task = self._tasks.add(task.run(self._do_modification))
        self._modify_task.kill()
        self._modify_all_notes_enabled = False
        self._step_tap_tasks = {}
        self._clip_notes = []
        self._pitches = [DEFAULT_START_NOTE]
        self._grid_resolution = grid_resolution
        self._NoteEditorComponent__on_resolution_changed.subject = self._grid_resolution
        self.set_step_duplicator(None)
        self._nudge_offset = 0
        self._length_offset = 0
        self._velocity_offset = 0
        self._velocity_deviation_offset = 0
        self._probability_offset = 0
        self._triplet_factor = 1.0
        self._update_from_grid()
        self.background_color = self._skin_base_key + '.StepEmpty'
        self._velocity_range_thresholds = velocity_range_thresholds or DEFAULT_VELOCITY_RANGE_THRESHOLDS
        self._velocity_provider = velocity_provider or NullVelocityProvider()
        self._NoteEditorComponent__on_provided_velocity_changed.subject = self._velocity_provider

    @property
    def page_index(self):
        return self._page_index

    @property
    def page_length(self):
        return self._get_step_count() * self._get_step_length(
        ) * self._triplet_factor

    @property
    def can_change_page(self):
        return not self._pressed_steps and not self._modified_steps

    @property
    def notes_in_selected_step(self):
        time_steps = []
        if self._modify_all_notes_enabled:
            time_steps = [TimeStep(0.0, ONE_YEAR_AT_120BPM_IN_BEATS)]
        else:
            time_steps = [
                self._time_step(self.get_step_start_time(start_time)) for
                start_time in chain(self._pressed_steps, self._modified_steps)
            ]
        time_steps_with_notes = [
            time_step.filter_notes(self._clip_notes)
            for time_step in time_steps
        ]
        notes_in_step = []
        for time_step in time_steps_with_notes:
            for note in time_step:
                notes_in_step.append(note.pitch)

        return notes_in_step

    def set_selected_page_point(self, point):
        self._selected_page_point = point
        index = int(old_div(point,
                            self.page_length)) if self.page_length != 0 else 0
        if index != self._page_index:
            self._page_index = index
            self._on_clip_notes_changed()

    def _get_modify_all_notes_enabled(self):
        return self._modify_all_notes_enabled

    def _set_modify_all_notes_enabled(self, enabled):
        if enabled != self._modify_all_notes_enabled:
            self._modify_all_notes_enabled = enabled
            self._on_clip_notes_changed()
            self.notify_modify_all_notes()

    modify_all_notes_enabled = property(_get_modify_all_notes_enabled,
                                        _set_modify_all_notes_enabled)

    def set_detail_clip(self, clip):
        if liveobj_changed(clip, self._sequencer_clip):
            self._sequencer_clip = clip
            self._step_duplicator.set_clip(clip)
            if self.can_change_page:
                self.set_selected_page_point(0)
            self._on_clip_notes_changed.subject = clip
            self._on_clip_notes_changed()

    def _can_edit(self):
        return len(self._pitches) != 0

    def _get_editing_notes(self):
        return self._pitches

    def _set_editing_notes(self, pitches):
        self._pitches = pitches
        enabled = self._can_edit()
        for button in self.matrix:
            button.enabled = enabled

        if enabled:
            self._on_clip_notes_changed()

    editing_notes = property(_get_editing_notes, _set_editing_notes)

    def _get_width(self):
        if self.matrix.width:
            return self.matrix.width
        return 4

    def _get_height(self):
        if self.matrix.height:
            return self.matrix.height
        return 4

    def set_matrix(self, matrix):
        last_page_length = self.page_length
        self.matrix.set_control_element(matrix)
        for t in self._step_tap_tasks.values():
            t.kill()

        def trigger_modification_task(x, y):
            trigger = partial((self._trigger_modification), (x, y), done=True)
            return self._tasks.add(
                task.sequence(task.wait(defaults.MOMENTARY_DELAY),
                              task.run(trigger))).kill()

        self._step_tap_tasks = dict([
            ((x, y), trigger_modification_task(x, y)) for x, y in product(
                range(self._get_width()), range(self._get_height()))
        ])
        if matrix and last_page_length != self.page_length:
            self._on_clip_notes_changed()
            self.notify_page_length()
        else:
            self._update_editor_matrix()

    def set_step_duplicator(self, duplicator):
        self._step_duplicator = duplicator or NullStepDuplicator()
        self._step_duplicator.set_clip(self._sequencer_clip)

    def update(self):
        super(NoteEditorComponent, self).update()
        self._update_editor_matrix_leds()

    def _get_clip_notes_time_range(self):
        if self._modify_all_notes_enabled:
            time_length = self._get_step_count() * 4.0
            time_start = 0
        else:
            time_length = self.page_length
            time_start = self._page_index * time_length
        return (time_start - self._time_step(0).offset, time_length)

    @listens('notes')
    def _on_clip_notes_changed(self):
        if liveobj_valid(self._sequencer_clip) and self._can_edit():
            time_start, time_length = self._get_clip_notes_time_range()
            self._clip_notes = self._get_notes_handler(self._sequencer_clip,
                                                       time_start,
                                                       self._pitches,
                                                       time_length)
        else:
            self._clip_notes = []
        self._update_editor_matrix()
        self.notify_notes_changed()

    def _update_editor_matrix(self):
        step_colors = [self._skin_base_key + '.StepDisabled'
                       ] * self._get_step_count()

        def coords_to_index(coord):
            return coord[0] + coord[1] * self._get_width()

        editing_indices = set(map(coords_to_index, self._modified_steps))
        selected_indices = set(map(coords_to_index, self._pressed_steps))
        last_editing_notes = []
        for time_step, index in self._visible_steps():
            notes = time_step.filter_notes(self._clip_notes)
            if len(notes) > 0:
                last_editing_notes = []
                if index in selected_indices:
                    color = self._skin_base_key + '.StepSelected'
                elif index in editing_indices:
                    note_color = self._determine_color(notes)
                    color = self._skin_base_key + '.StepEditing.' + note_color
                    last_editing_notes = notes
                else:
                    note_color = self._determine_color(notes)
                    color = self._skin_base_key + '.Step.' + note_color
            elif any(map(time_step.overlaps_note, last_editing_notes)):
                color = self._skin_base_key + '.StepEditing.' + note_color
            elif index in editing_indices or index in selected_indices:
                color = self._skin_base_key + '.StepSelected'
                last_editing_notes = []
            else:
                color = self.background_color
                last_editing_notes = []
            step_colors[index] = color

        self._step_colors = step_colors
        self._update_editor_matrix_leds()

    def _determine_color(self, notes):
        return color_for_note(
            (most_significant_note(notes)),
            velocity_range_thresholds=(self._velocity_range_thresholds))

    def _visible_steps(self):
        first_time = self.page_length * self._page_index
        steps_per_page = self._get_step_count()
        step_length = self._get_step_length()
        indices = list(range(steps_per_page))
        if is_triplet_quantization(self._triplet_factor):
            indices = [k for k in indices if k % 8 not in (6, 7)]
        return [(self._time_step(first_time + k * step_length), index)
                for k, index in enumerate(indices)]

    def _update_editor_matrix_leds(self):
        if self.is_enabled():
            for control in self.matrix:
                control.color = self._step_colors[control.index]

    def _get_step_count(self):
        return self._get_width() * self._get_height()

    def get_step_start_time(self, step):
        x, y = step
        page_time = self._page_index * self._get_step_count(
        ) * self._triplet_factor
        step_time = x + y * self._get_width() * self._triplet_factor
        return (page_time + step_time) * self._get_step_length()

    def _get_step_length(self):
        return self._grid_resolution.step_length

    def get_row_start_times(self):
        return [
            self.get_step_start_time((0, row))
            for row in range(self._get_height())
        ]

    def _update_from_grid(self):
        quantization, is_triplet = self._grid_resolution.clip_grid
        self._triplet_factor = 1.0 if (not is_triplet) else 0.75
        if self._clip_creator:
            self._clip_creator.grid_quantization = quantization
            self._clip_creator.is_grid_triplet = is_triplet
        if liveobj_valid(self._sequencer_clip):
            self._sequencer_clip.view.grid_quantization = quantization
            self._sequencer_clip.view.grid_is_triplet = is_triplet

    @mute_button.pressed
    def mute_button(self, button):
        self._trigger_modification(immediate=True)

    @listens('index')
    def __on_resolution_changed(self, *a):
        self._release_active_steps()
        self._update_from_grid()
        self.set_selected_page_point(self._selected_page_point)
        self.notify_page_length()
        self._on_clip_notes_changed()

    @matrix.pressed
    def matrix(self, button):
        self._on_pad_pressed(button.coordinate)

    @matrix.released
    def matrix(self, button):
        self._on_pad_released(button.coordinate)

    def _on_pad_pressed(self, coordinate):
        y, x = coordinate
        if self.is_enabled():
            if not liveobj_valid(self._sequencer_clip):
                self.set_detail_clip(
                    create_clip_in_selected_slot(self._clip_creator,
                                                 self.song))
            if self._can_press_or_release_step(x, y):
                self._on_press_step((x, y))
                self._update_editor_matrix()

    def _on_pad_released(self, coordinate):
        y, x = coordinate
        if self.is_enabled():
            if self._can_press_or_release_step(x, y):
                should_delete_first = not self.mute_button.is_pressed and not self.full_velocity
                self._on_release_step((x, y),
                                      do_delete_notes=should_delete_first)
                self._update_editor_matrix()

    def _can_press_or_release_step(self, x, y):
        width = self._get_width(
        ) * self._triplet_factor if is_triplet_quantization(
            self._triplet_factor) else self._get_width()
        return x < width and y < self._get_height()

    @listens('velocity')
    def __on_provided_velocity_changed(self):
        if len(self._pressed_steps) + len(self._modified_steps) > 0:
            self._provided_velocity = self._velocity_provider.velocity
            self._trigger_modification(immediate=True)
            self._provided_velocity = None

    def _get_step_time_range(self, step):
        time = self.get_step_start_time(step)
        return (time, time + self._get_step_length())

    @property
    def editing_note_regions(self):
        active_time_spans = self.active_time_spans
        editing_notes = set(self.notes_in_selected_step)
        if len(editing_notes) > 1:
            editing_notes = [-1]
        return list(product(editing_notes, list(active_time_spans)))

    @property
    def active_time_spans(self):
        if self._modify_all_notes_enabled:
            return [(0.0, ONE_YEAR_AT_120BPM_IN_BEATS)]

        def time_span(step):
            time_step = self._time_step(self.get_step_start_time(step))
            return (time_step.left_boundary(), time_step.right_boundary())

        return list(
            map(time_span, chain(self._pressed_steps, self._modified_steps)))

    @property
    def active_steps(self):
        return list(
            map(self._get_step_time_range,
                chain(self._pressed_steps, self._modified_steps)))

    @property
    def active_note_regions(self):
        return list(
            map(self._get_time_range,
                chain(self._pressed_steps, self._modified_steps)))

    def _get_time_range(self, step):
        def note_compare(left, right):
            return left.start_time - right.start_time

        time = self.get_step_start_time(step)
        notes = self._time_step(time).filter_notes(self._clip_notes)
        if notes:
            beginning_note = first(
                sorted(notes, key=(cmp_to_key(note_compare))))
            start = beginning_note.start_time
            end = start + beginning_note.duration
            if len(notes) > 1:
                end_note = notes[(-1)]
                end = end_note.start_time + end_note.duration
            return (start, end)
        return (time, time + self._get_step_length())

    def _release_active_steps(self):
        for step in self._pressed_steps + self._modified_steps:
            self._on_release_step(step, do_delete_notes=False)

    def _on_release_step(self, step, do_delete_notes=True):
        self._step_tap_tasks[step].kill()
        if step in self._pressed_steps:
            if do_delete_notes:
                self._delete_notes_in_step(step)
            self._pressed_steps.remove(step)
            for pitch in self._pitches:
                self._add_or_modify_note_in_step(step, pitch)

        if step in self._modified_steps:
            self._modified_steps.remove(step)
        if len(self._modified_steps) + len(self._pressed_steps) == 0:
            self._velocity_provider.set_velocities_playable(True)
        self.notify_active_steps()
        self.notify_active_note_regions()

    def _find_continued_step(self, step):
        def steps_to_note_start(steps):
            return [self.get_step_start_time(step) for step in steps]

        time = self.get_step_start_time(step)
        all_steps_with_notes = [
            time_step for time_step, index in self._visible_steps()
            if time_step.filter_notes(self._clip_notes)
        ]
        all_time_step_starts = [ts.start for ts in all_steps_with_notes]
        if all_time_step_starts:
            insert_point = bisect(all_time_step_starts, time)
            if insert_point > 0:
                prev_filled_step = all_steps_with_notes[(insert_point - 1)]
                notes_in_modified_steps = steps_to_note_start(
                    self._modified_steps)
                if prev_filled_step.start in notes_in_modified_steps:
                    return prev_filled_step

    def _add_step_to_duplicator(self, step):
        nudge_offset = 0
        time = self.get_step_start_time(step)
        notes = self._time_step(time).filter_notes(self._clip_notes)
        step_start, step_end = self._get_time_range(step)
        if notes:
            nudge_offset = min(map(lambda n: n.start_time, notes)) - time
        if self._duplicate_all_notes:
            self._step_duplicator.add_step(step_start, step_end, nudge_offset)
        else:
            self._step_duplicator.add_step_with_pitch(self.editing_notes[0],
                                                      step_start, step_end,
                                                      nudge_offset)

    def _on_press_step(self, step):
        if liveobj_valid(self._sequencer_clip):
            if step not in self._pressed_steps:
                if step not in self._modified_steps:
                    if self._step_duplicator.is_duplicating:
                        self._add_step_to_duplicator(step)
                    else:
                        self._step_tap_tasks[step].restart()
                        continued_step = self._find_continued_step(step)
                        if continued_step:
                            self._modify_length_of_notes_within_existing_step(
                                continued_step, step)
                        else:
                            self._pressed_steps.append(step)
                        self._velocity_provider.set_velocities_playable(False)
        self.notify_active_steps()
        self.notify_active_note_regions()

    def _modify_length_of_notes_within_existing_step(self, existing_time_step,
                                                     new_step):
        notes_in_step = existing_time_step.filter_notes(self._clip_notes)
        new_end = float(
            self.get_step_start_time(new_step) + self._get_step_length())
        step_mute = all([note.mute for note in notes_in_step])
        for note in self._clip_notes:
            length_offset = new_end - (note.duration + note.start_time
                                       ) if note in notes_in_step else 0
            self._modify_single_note(step_mute, existing_time_step,
                                     length_offset, note)

        self._replace_notes()

    def _time_step(self, time):
        return TimeStep(time, self._get_step_length())

    def _get_notes_info_from_step(self, step):
        time = self.get_step_start_time(step)
        notes = self._time_step(time).filter_notes(self._clip_notes)
        pitches = [note.pitch for note in notes]
        return (time, notes, pitches)

    def toggle_pitch_for_all_modified_steps(self, pitch):
        for step in set(chain(self._pressed_steps, self._modified_steps)):
            time, notes, pitches = self._get_notes_info_from_step(step)
            if pitch not in pitches:
                self._add_new_note_in_step(step, pitch, time)
            else:
                time_step = self._time_step(self.get_step_start_time(step))
                for time, length in time_step.connected_time_ranges():
                    remove_single_note(self._sequencer_clip, time, (pitch, ),
                                       length)

    def _add_new_note_in_step(self, step, pitch, time):
        mute = self.mute_button.is_pressed
        velocity = 127 if self.full_velocity else self._velocity_provider.velocity
        note = Live.Clip.MidiNoteSpecification(
            pitch=pitch,
            start_time=time,
            duration=(self._get_step_length()),
            velocity=velocity,
            mute=mute)
        self._sequencer_clip.add_new_notes((note, ))
        self._sequencer_clip.deselect_all_notes()
        self._trigger_modification(step, done=True)

    def _add_or_modify_note_in_step(self, step, pitch, modify_existing=True):
        if liveobj_valid(self._sequencer_clip):
            time, notes, pitches = self._get_notes_info_from_step(step)
            if notes:
                if not pitch in pitches or modify_existing:
                    most_significant_velocity = most_significant_note(
                        notes).velocity
                    if self.mute_button.is_pressed or (
                            most_significant_velocity != 127
                            and self.full_velocity):
                        self._trigger_modification(step, immediate=True)
                    if not most_significant_velocity == 127 or self.full_velocity:
                        self._delete_notes_in_step(step)
            else:
                self._add_new_note_in_step(step, pitch, time)
                return True
        return False

    def _delete_notes_in_step(self, step):
        if liveobj_valid(self._sequencer_clip):
            if self._can_edit():
                time_step = self._time_step(self.get_step_start_time(step))
                for time, length in time_step.connected_time_ranges():
                    self._remove_notes_handler(self._sequencer_clip, time,
                                               self._pitches, length)

    def set_nudge_offset(self, value):
        self._modify_note_property('_nudge_offset', value)

    def set_length_offset(self, value):
        self._modify_note_property('_length_offset', value)

    def set_velocity_offset(self, value):
        self._modify_note_property('_velocity_offset', value)

    def set_velocity_deviation_offset(self, value):
        self._modify_note_property('_velocity_deviation_offset', value)

    def set_probability_offset(self, value):
        self._modify_note_property('_probability_offset', value)

    def _modify_note_property(self, note_property, value):
        if self.is_enabled():
            with self._full_velocity_context(False):
                setattr(self, note_property,
                        getattr(self, note_property) + value)
                self._trigger_modification(immediate=True)

    @contextmanager
    def _full_velocity_context(self, desired_full_velocity_state):
        saved_velocity = self.full_velocity
        self.full_velocity = desired_full_velocity_state
        (yield)
        self.full_velocity = saved_velocity

    def set_full_velocity(self):
        if self.is_enabled():
            with self._full_velocity_context(True):
                self._trigger_modification()

    def notify_modification(self):
        self._trigger_modification(done=True)

    def _trigger_modification(self, step=None, done=False, immediate=False):
        needs_update = False
        if step is None:
            needs_update = bool(self._pressed_steps)
            self._modified_steps += self._pressed_steps
            self._pressed_steps = []
        else:
            if step not in self._modified_steps:
                self._modified_steps.append(step)
            if step in self._pressed_steps:
                self._pressed_steps.remove(step)
            needs_update = True
        if not done:
            if immediate:
                self._do_modification()
                self._modify_task.kill()
            elif self._modify_task.is_killed:
                self._do_modification()
                self._modify_task.restart()
        if needs_update:
            self._update_editor_matrix()

    def _reset_modifications(self):
        self._velocity_offset = 0
        self._velocity_deviation_offset = 0
        self._length_offset = 0
        self._nudge_offset = 0
        self._probability_offset = 0

    def _do_modification(self):
        if self._modify_all_notes_enabled:
            self._modify_all_notes()
            self._replace_notes()
        elif self._modified_steps:
            notes_added = list(
                map(
                    lambda step_and_pitch: (self._add_or_modify_note_in_step)
                    (*step_and_pitch, **{
                        'modify_existing': False
                    }), product(self._modified_steps, self._pitches)))
            if any(notes_added):
                self._modify_task.restart()
            else:
                self._modify_step_notes(self._modified_steps)
                self._replace_notes()
        self._reset_modifications()

    def _replace_notes(self):
        if self._can_edit():
            time_start, time_length = self._get_clip_notes_time_range()
            self._sequencer_clip.apply_note_modifications(self._clip_notes)

    def _modify_all_notes(self):
        self._modify_notes_in_time(TimeStep(0.0, MAX_CLIP_LENGTH),
                                   self._clip_notes, self._length_offset)

    def _limited_nudge_offset(self, steps, notes, nudge_offset):
        limited_nudge_offset = MAX_CLIP_LENGTH
        for step in steps:
            time_step = self._time_step(self.get_step_start_time(step))
            for note in time_step.filter_notes(notes):
                start_time = note.start_time
                time_after_nudge = time_step.clamp(start_time, nudge_offset)
                limited_nudge_offset = min(limited_nudge_offset,
                                           abs(start_time - time_after_nudge))

        return sign(nudge_offset) * limited_nudge_offset

    def _modify_step_notes(self, steps):
        notes = self._clip_notes
        self._nudge_offset = self._limited_nudge_offset(
            steps, notes, self._nudge_offset)
        for step in steps:
            time_step = self._time_step(self.get_step_start_time(step))
            self._modify_notes_in_time(time_step, notes, self._length_offset)

    def _modify_notes_in_time(self, time_step, notes, length_offset):
        step_notes = time_step.filter_notes(self._clip_notes)
        step_mute = all([note.mute for note in step_notes])
        for note in notes:
            self._modify_single_note(step_mute, time_step, length_offset, note)

    def _modify_single_note(self, step_mute, time_step, length_offset, note):
        if time_step.includes_time(note.start_time):
            note.start_time = time_step.clamp(note.start_time,
                                              self._nudge_offset)
            if length_offset <= -time_step.length and note.duration + length_offset < time_step.length:
                if note.duration > time_step.length:
                    note.duration = time_step.length
            else:
                note.duration = max(0, note.duration + length_offset)
            if self._provided_velocity:
                note.velocity = self._provided_velocity
            elif self.full_velocity:
                note.velocity = 127
            else:
                note.velocity = clamp(note.velocity + self._velocity_offset, 1,
                                      127)
            note.velocity_deviation = clamp(
                note.velocity_deviation + self._velocity_deviation_offset,
                -127, 127)
            note.probability = clamp(
                note.probability + self._probability_offset / 100.0, 0, 1)
            note.mute = not step_mute if self.mute_button.is_pressed else note.mute

    def get_min_max_note_values(self):
        if self._modify_all_notes_enabled:
            if len(self._clip_notes) > 0:
                return min_max_for_notes(self._clip_notes, 0.0)
        if len(self._pressed_steps) + len(self._modified_steps) > 0:
            min_max_values = None
            for step in chain(self._modified_steps, self._pressed_steps):
                start_time = self.get_step_start_time(step)
                min_max_values = min_max_for_notes(
                    self._time_step(start_time).filter_notes(self._clip_notes),
                    start_time, min_max_values)

            return min_max_values
示例#14
0
class InstrumentComponent(PlayableComponent, CompoundComponent, Slideable,
                          Messenger):
    u"""
    Class that sets up the button matrix as a piano, using different
    selectable layouts for the notes.
    """
    __events__ = (u'pattern', )
    matrix = control_matrix(PadControl)
    delete_button = ButtonControl()

    def __init__(self, note_layout=None, *a, **k):
        assert note_layout is not None
        super(InstrumentComponent, self).__init__(*a, **k)
        self._note_layout = note_layout
        self._first_note = self.page_length * 3 + self.page_offset
        self._last_page_length = self.page_length
        self._last_page_offset = self.page_offset
        self._detail_clip = None
        self._has_notes = [False] * 128
        self._has_notes_pattern = self._get_pattern(0)
        self._aftertouch_control = None
        self._show_notifications = True
        self.__on_detail_clip_changed.subject = self.song.view
        self.__on_detail_clip_changed()
        self._slider = self.register_component(SlideComponent(self))
        self._touch_slider = self.register_component(
            SlideableTouchStripComponent(self))
        for event in (u'scale', u'root_note', u'is_in_key', u'is_fixed',
                      u'is_horizontal', u'interval'):
            self.register_slot(self._note_layout, self._on_note_layout_changed,
                               event)

        self._update_pattern()
        return

    @listens(u'detail_clip')
    def __on_detail_clip_changed(self):
        clip = self.song.view.detail_clip
        self.set_detail_clip(
            clip if liveobj_valid(clip) and clip.is_midi_clip else None)
        return

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

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

        self.notify_contents()

    @listens(u'loop_start')
    def _on_loop_start_changed(self):
        self._on_loop_selection_changed()

    @listens(u'loop_end')
    def _on_loop_end_changed(self):
        self._on_loop_selection_changed()

    def _on_loop_selection_changed(self):
        self._on_clip_notes_changed()

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

    @property
    def show_notifications(self):
        return self._show_notifications

    @show_notifications.setter
    def show_notifications(self, value):
        self._show_notifications = value

    @property
    def page_length(self):
        if self._note_layout.is_in_key:
            return len(self._note_layout.notes)
        return 12

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

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

    @property
    def page_offset(self):
        if self._note_layout.is_fixed:
            return 0
        return self._first_scale_note_offset()

    def _get_position(self):
        return self._first_note

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

    position = property(_get_position, _set_position)

    @property
    def min_pitch(self):
        return self.pattern[0].index

    @property
    def max_pitch(self):
        identifiers = [control.identifier for control in self.matrix]
        if len(identifiers) > 0:
            return max(identifiers)
        return 127

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

    @matrix.pressed
    def matrix(self, button):
        self._on_matrix_pressed(button)

    def _on_matrix_pressed(self, button):
        if self.delete_button.is_pressed:
            pitch = self._get_note_info_for_coordinate(button.coordinate).index
            if pitch and self._detail_clip:
                self._do_delete_pitch(pitch)

    @matrix.released
    def matrix(self, button):
        self._on_matrix_released(button)

    def _on_matrix_released(self, button):
        pass

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

    @delete_button.pressed
    def delete_button(self, value):
        self._set_control_pads_from_script(True)

    @delete_button.released
    def delete_button(self, value):
        self._set_control_pads_from_script(False)

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

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

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

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

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

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

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

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

    def _on_note_layout_changed(self, _):
        self._update_scale()

    def show_pitch_range_notification(self):
        if self.is_enabled() and self.show_notifications:
            self.show_notification(u'Play {start_note} to {end_note}'.format(
                start_note=pitch_index_to_string(
                    self.pattern.note(0, 0).index),
                end_note=pitch_index_to_string(
                    self.pattern.note(self.width - 1, self.height - 1).index)))

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

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

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

    def _invert_and_swap_coordinates(self, coordinates):
        return (coordinates[1], self.height - 1 - coordinates[0])

    def _get_note_info_for_coordinate(self, coordinate):
        x, y = self._invert_and_swap_coordinates(coordinate)
        return self.pattern.note(x, y)

    def _update_button_color(self, button):
        note_info = self._get_note_info_for_coordinate(button.coordinate)
        button.color = u'Instrument.' + note_info.color

    def _button_should_be_enabled(self, button):
        return self._get_note_info_for_coordinate(
            button.coordinate).index != None

    def _note_translation_for_button(self, button):
        note_info = self._get_note_info_for_coordinate(button.coordinate)
        return (note_info.index, note_info.channel)

    def _set_button_control_properties(self, button):
        super(InstrumentComponent, self)._set_button_control_properties(button)
        button.sensitivity_profile = u'default' if self._takeover_pads else u'instrument'

    def _update_matrix(self):
        self._update_control_from_script()
        self._update_note_translations()
        self._update_led_feedback()

    def _get_pattern(self, first_note=None):
        if first_note is None:
            first_note = int(round(self._first_note))
        interval = self._note_layout.interval
        notes = self._note_layout.notes
        width = None
        height = None
        octave = first_note / self.page_length
        offset = first_note % self.page_length - self._first_scale_note_offset(
        )
        if interval in CUSTOM_PATTERNS:
            interval = 8
        elif interval == None:
            if self._note_layout.is_in_key:
                interval = len(self._note_layout.notes)
                if self._note_layout.is_horizontal:
                    width = interval + 1
                else:
                    height = interval + 1
            else:
                interval = 8
        elif not self._note_layout.is_in_key:
            interval = [0, 2, 4, 5, 7, 9, 10, 11][interval]
        if self._note_layout.is_horizontal:
            steps = [1, interval]
            origin = [offset, 0]
        else:
            steps = [interval, 1]
            origin = [0, offset]
        log.info(
            "_get_pattern(%r) interval=%r notes=%r pagelen=%r octave=%r, offset=%r is_fixed=%r nlinterval=%r steps=%r origin=%r base_note=%r is_in_key=%r width=%r height %r",
            first_note, interval, notes, self.page_length, octave, offset,
            self._note_layout.is_fixed, self._note_layout.interval, steps,
            origin, octave * 12, self._note_layout.is_in_key, width, height)
        custom_pattern = CUSTOM_PATTERNS.get(self._note_layout.interval)
        if custom_pattern:
            return custom_pattern.impl(first_note=first_note,
                                       steps=steps,
                                       scale=notes,
                                       origin=origin,
                                       octave=octave,
                                       is_diatonic=self._note_layout.is_in_key,
                                       is_absolute=self._note_layout.is_fixed,
                                       direction=custom_pattern.direction)
        else:
            return MelodicPattern(
                steps=steps,
                scale=notes,
                origin=origin,
                root_note=octave * 12,
                chromatic_mode=not self._note_layout.is_in_key,
                width=width,
                height=height)

    def _update_aftertouch(self):
        if self.is_enabled() and self._aftertouch_control != None:
            self._aftertouch_control.send_value(u'mono')
        return