class SpecialMixerComponent(MixerComponent, ModifierMixin, ShowMessageMixin):
    """ SpecialMixerComponent extends standard to add convenience methods for several
    features of the SpecialChannelStripComponent.  It also allows for easily including
    or excluding returns. And also provides an offset listener similar to the
    SessionComponent. """
    __subject_events__ = ('offset', 'send_index', 'tracks')
    send_toggle_button = ButtonControl()

    def __init__(self, num_tracks, include_returns=True, alt_select_arms=True, targets_comp=None, right_just_returns=True, handle_modifier_leds=True, limit_send_index=True, display_send_index=False, use_0_db_volume=False, *a, **k):
        self._width = num_tracks
        self._include_returns = bool(include_returns)
        self._right_justify_returns = bool(right_just_returns)
        self._alt_select_arms = bool(alt_select_arms)
        self._limit_send_index = bool(limit_send_index)
        self._display_send_index = bool(display_send_index)
        self._use_0_db_volume = bool(use_0_db_volume)
        self._cue_volume_control = None
        self._has_targets_comp = targets_comp is not None
        self._send_select_buttons = None
        self._send_index = 0
        self._is_return_mixer = num_tracks == 0
        super(SpecialMixerComponent, self).__init__(num_tracks, auto_name=True, invert_mute_feedback=True, handle_modifier_leds=handle_modifier_leds, *a, **k)
        self.name = 'Mixer_Control'
        self._on_target_track_changed.subject = targets_comp
        if self._is_return_mixer:
            self._channel_strips = self._return_strips
        self._empty_send_controls = [ None for _ in xrange(len(self._channel_strips)) ]
        return

    def disconnect(self):
        release_control(self._cue_volume_control)
        super(SpecialMixerComponent, self).disconnect()
        self._cue_volume_control = None
        self._send_select_buttons = None
        return

    @property
    def is_return_mixer(self):
        """ Returns whether this is a returns-only mixer. """
        return self._is_return_mixer

    def track_offset(self):
        """ Added for proper slave/link handling. """
        return self._track_offset

    @staticmethod
    def scene_offset():
        """ Added for proper slave/link handling. """
        return 0

    def width(self):
        """ Added for proper slave/link handling. """
        return self._width

    @staticmethod
    def height():
        """ Added for proper slave/link handling. """
        return 0

    def set_offsets(self, track_offset, _):
        """ Added for proper slave/link handling. """
        self.set_track_offset(track_offset)

    def channel_strips(self):
        """ Returns all of this component's channel strips. """
        return self._channel_strips

    def set_physical_display_element(self, element):
        """ Extends standard to set display element of channel strips. """
        super(SpecialMixerComponent, self).set_physical_display_element(element)
        for strip in self._channel_strips or []:
            strip.set_physical_display_element(element)

    def set_send_select_buttons(self, buttons):
        """ Sets the buttons to use for directly selecting the send index to use. """
        self._send_select_buttons = list(buttons) if buttons else []
        self._on_send_select_button_value.replace_subjects(self._send_select_buttons)
        self._update_send_select_buttons()

    def set_alt_mute_buttons(self, buttons):
        """ Set buttons to use for momentary/toggle muting. """
        for strip, button in izip_longest(self._channel_strips, buttons or []):
            strip.alt_mute_button.set_control_element(button)

    def set_alt_solo_buttons(self, buttons):
        """ Set buttons to use for momentary/toggle soloing. """
        for strip, button in izip_longest(self._channel_strips, buttons or []):
            strip.alt_solo_button.set_control_element(button)

    def set_alt_select_buttons(self, buttons):
        """ Sets buttons to use for multi-function (delete, duplicate, fold, arm,
        show playing clip) select. """
        for strip, button in izip_longest(self._channel_strips, buttons or []):
            strip.alt_select_button.set_control_element(button)

    def __getattribute__(self, name):
        """ Extends standard to set send controls by index (like send_index_00_controls)
        to make it easier to assign controls to sends without dealing with the base
        class's send index. """
        if name.startswith('set_send_index_'):
            send_index = int(name[15:17])
            return partial(self._set_send_controls, send_index)
        return object.__getattribute__(self, name)

    def _set_send_controls(self, send_index, controls):
        """ Sets send controls for the given send_index. """
        controls = list(controls) if controls else self._empty_send_controls
        for index, strip in enumerate(self._channel_strips):
            strip.set_indexed_send_control(controls[index], send_index)

    def set_master_volume_control(self, control):
        """ Convenience method for setting master volume control in a layer. """
        self.master_strip().set_volume_control(control)

    def set_master_select_button(self, button):
        """ Convenience method for setting master select button in a layer. """
        self.master_strip().set_select_button(button)

    def set_cue_volume_control(self, control):
        """ Sets the control to use for controlling cue volume. """
        if control != self._cue_volume_control:
            release_control(self._cue_volume_control)
            self._cue_volume_control = control
            self._update_cue_volume_connection()

    def set_unmute_all_button(self, button):
        """ Sets the button to use for unmuting all tracks. """
        self._on_unmute_all_button_value.subject = button
        self._update_unall_buttons()

    def set_unsolo_all_button(self, button):
        """ Sets the button to use for unsoloing all tracks. """
        self._on_unsolo_all_button_value.subject = button
        self._update_unall_buttons()

    def set_unarm_all_button(self, button):
        """ Sets the button to use for unarming all tracks. """
        self._on_unarm_all_button_value.subject = button
        self._update_unall_buttons()

    def set_bank_down_button(self, button):
        """ Sets the button to use for banking down. This is added so bank buttons can
        be used in layers. """
        self.set_bank_buttons(self._bank_up_button, button)

    def set_bank_up_button(self, button):
        """ Sets the button to use for banking up. This is added so bank buttons can
        be used in layers. """
        self.set_bank_buttons(button, self._bank_down_button)

    def set_track_offset(self, new_offset):
        """ Extends standard to notify offset listeners. """
        super(SpecialMixerComponent, self).set_track_offset(new_offset)
        self.notify_offset()

    def set_shift_button(self, button):
        """ Overrides standard to use ModifierMixin's methods. """
        self._set_modifier(button, 'shift')

    def _set_modifier(self, button, modifier_name):
        """ Extends standard to set up modifiers for sub-components. """
        for strip in self._channel_strips or []:
            getattr(strip, 'set_%s_button' % modifier_name)(button)

        super(SpecialMixerComponent, self)._set_modifier(button, modifier_name)

    @send_toggle_button.pressed
    def send_toggle_button(self, _):
        if self.num_sends:
            self.send_index = (self.send_index + 1) % self.num_sends

    @subject_slot_group('value')
    def _on_send_select_button_value(self, value, button):
        if value:
            index = self._send_select_buttons.index(button)
            if index < self.num_sends:
                self.send_index = index

    def _get_send_index(self):
        """ Same as standard, needed to properly override. """
        return self._send_index

    def _set_send_index(self, index):
        """ Overrides standard to allow index to be greater than num_sends if flag set
        by init. """
        if index is None or 0 <= index and (index < self.num_sends or not self._limit_send_index):
            if self._send_index != index:
                self._send_index = index
                self.notify_send_index()
                self.set_send_controls(self._send_controls)
                self.on_send_index_changed()
        else:
            raise IndexError
        return

    send_index = property(_get_send_index, _set_send_index)

    def on_send_index_changed(self, force=False):
        """ Displays the send being controlled if elected. """
        if self._display_send_index and (self._send_controls or force):
            self.component_message('Controlling Send', chr(ASCII_A + self.send_index))
        self._update_send_select_buttons()

    def on_num_sends_changed(self):
        self._update_send_select_buttons()

    @subject_slot('value')
    def _on_unmute_all_button_value(self, value):
        if self.is_enabled():
            if value:
                tracks = tuple(self.song().tracks) + tuple(self.song().return_tracks)
                for track in tracks:
                    track.mute = False

            button = self._on_unmute_all_button_value.subject
            button.set_light('Track.NotMuted' if value else 'Track.Muted')

    @subject_slot('value')
    def _on_unsolo_all_button_value(self, value):
        if self.is_enabled():
            if value:
                tracks = tuple(self.song().tracks) + tuple(self.song().return_tracks)
                for track in tracks:
                    track.solo = False

            button = self._on_unsolo_all_button_value.subject
            button.set_light('Track.Soloed' if value else 'Track.NotSoloed')

    @subject_slot('value')
    def _on_unarm_all_button_value(self, value):
        if self.is_enabled():
            if value:
                tracks = tuple(self.song().tracks) + tuple(self.song().return_tracks)
                for track in tracks:
                    if track.can_be_armed:
                        track.arm = False

            button = self._on_unarm_all_button_value.subject
            button.set_light('Track.Armed' if value else 'Track.NotArmed')

    def update(self):
        super(SpecialMixerComponent, self).update()
        self.update_modifier_leds()
        self._update_send_select_buttons()
        self._update_bank_buttons()
        self._update_unall_buttons()
        self._update_cue_volume_connection()

    def _reassign_tracks(self):
        """ Extended standard to right justify returns if elected, properly update
        bank buttons and notify. """
        if self._right_justify_returns:
            justify_function(self.song(), self.tracks_to_use(), self._track_offset, self._channel_strips)
        else:
            if self._is_return_mixer:
                self._track_offset = 1 if len(self.song().return_tracks) == 0 else 0
            super(SpecialMixerComponent, self)._reassign_tracks()
        self._update_bank_buttons()
        self.notify_tracks()

    def _update_cue_volume_connection(self):
        release_control(self._cue_volume_control)
        if self.is_enabled() and self._cue_volume_control:
            control = self._cue_volume_control
            control.connect_to(self.song().master_track.mixer_device.cue_volume)

    def _update_send_select_buttons(self):
        if self.is_enabled() and self._send_select_buttons:
            for i, button in enumerate(self._send_select_buttons):
                if button:
                    if i < self.num_sends:
                        button.set_light('Sends.Selected%s' % i if i == self.send_index else 'Sends.NotSelected%s' % i)
                    else:
                        button.set_light('DefaultButton.Off')

    def _update_bank_buttons(self):
        if self.is_enabled():
            colors = ('Navigation.SessionEnabled', 'Navigation.Disabled')
            if self._bank_up_button:
                tracks = self.tracks_to_use()
                num_strips = len(self._channel_strips)
                turn_on = len(tracks) > self._track_offset + num_strips
                self._bank_up_button.set_light(colors[0] if turn_on else colors[1])
            if self._bank_down_button:
                self._bank_down_button.set_light(colors[0] if self._track_offset > 0 else colors[1])

    def _update_unall_buttons(self):
        if self.is_enabled():
            mute = self._on_unmute_all_button_value.subject
            if mute:
                mute.set_light('Track.Muted')
            solo = self._on_unsolo_all_button_value.subject
            if solo:
                solo.set_light('Track.NotSoloed')
            arm = self._on_unarm_all_button_value.subject
            if arm:
                arm.set_light('Track.NotArmed')

    @subject_slot('target_track')
    def _on_target_track_changed(self, track):
        if self._selected_strip is not None:
            self._selected_strip.set_track(track)
        return

    def on_selected_track_changed(self):
        """ Overrides standard to not set track of selected strip if has a
        TargetsComponent. """
        if self._has_targets_comp:
            return
        super(SpecialMixerComponent, self).on_selected_track_changed()

    def tracks_to_use(self):
        """ Overrides standard so that returns will be included in tracks to control if
        specified or only returns will be returned if specified. """
        if self._is_return_mixer:
            return self.song().return_tracks
        if self._include_returns:
            return tuple(self.song().visible_tracks) + tuple(self.song().return_tracks)
        return self.song().visible_tracks

    def _create_strip(self):
        """ Overrides standard to return specialized version. """
        return SpecialChannelStripComponent(self._alt_select_arms, use_0_db_volume=self._use_0_db_volume)
Esempio n. 2
0
class ClipActionsComponent(ControlSurfaceComponent, Subject):
    delete_button = ButtonControl(**ACTION_BUTTON_COLORS)
    duplicate_button = ButtonControl(**ACTION_BUTTON_COLORS)
    double_button = ButtonControl(**ACTION_BUTTON_COLORS)
    quantize_button = ButtonControl(**ACTION_BUTTON_COLORS)
    __subject_events__ = ('selected_clip', )

    def __init__(self, target_track_component, quantization_component, *a, **k):
        (super(ClipActionsComponent, self).__init__)(*a, **k)
        self._target_track_component = target_track_component
        self._on_track_changed.subject = self._target_track_component
        self._quantization_component = quantization_component
        self._use_selected_track = False
        self._selected_clip = None
        self._track = self.song().view.selected_track
        self._on_selection_changed()

    def use_selected_track(self, use_selected_track):
        self._use_selected_track = use_selected_track
        if use_selected_track:
            self._track = self.song().view.selected_track
        else:
            self._track = self._target_track_component.target_track
        self._on_selection_changed()

    @delete_button.pressed
    def delete_button(self, button):
        if self.can_perform_clip_action():
            self._selected_clip.canonical_parent.delete_clip()

    @duplicate_button.pressed
    def duplicate_button(self, button):
        if self.can_perform_clip_action():
            duplicate_clip((self.song()),
              (self._selected_clip.canonical_parent), should_launch=True)

    @double_button.pressed
    def double_button(self, button):
        if self.can_perform_midi_clip_action():
            double_clip(self._selected_clip)

    @quantize_button.pressed
    def quantize_button(self, button):
        if self.can_perform_clip_action():
            if self._quantization_component:
                self._quantization_component.quantize_clip(self._selected_clip)

    def delete_pitch(self, pitch):
        if self.can_perform_midi_clip_action():
            clip = self._selected_clip
            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)

    def on_selected_track_changed(self):
        if self._use_selected_track:
            self._track = self.song().view.selected_track
            self._on_selection_changed()

    @subject_slot('target_track')
    def _on_track_changed(self):
        if not self._use_selected_track:
            self._track = self._target_track_component.target_track
            self._on_selection_changed()

    @subject_slot('playing_slot_index')
    def _on_selection_changed(self):
        self._selected_clip = None
        if self._track in self.song().tracks:
            slot_index = self._track.playing_slot_index
            if slot_index >= 0:
                if self._track.clip_slots[slot_index].has_clip:
                    self._selected_clip = self._track.clip_slots[slot_index].clip
            self._on_selection_changed.subject = self._track
        else:
            self._on_selection_changed.subject = None
        self._update_control_states()

    def _update_control_states(self):
        can_perform_clip_action = self.can_perform_clip_action()
        self.delete_button.enabled = can_perform_clip_action
        self.duplicate_button.enabled = can_perform_clip_action
        self.quantize_button.enabled = can_perform_clip_action
        self.double_button.enabled = self.can_perform_midi_clip_action()
        self.notify_selected_clip()

    def can_perform_clip_action(self):
        return self._selected_clip is not None

    def can_perform_midi_clip_action(self):
        return self._selected_clip is not None and self._selected_clip.is_midi_clip
class MixerComponent(MixerComponentBase):
    encoder_rings = control_list(ButtonControl,
                                 control_count=24,
                                 enabled=False)
    next_sends_button = ButtonControl()
    prev_sends_button = ButtonControl()

    def __init__(self,
                 parent,
                 num_tracks,
                 sends_rows=1,
                 show_returns=False,
                 fold_on_reselect=False,
                 select_track_on_0_val=True,
                 max_returns=-1,
                 show_master=False,
                 delayed_update=False,
                 *a,
                 **k):
        self._parent = parent
        self._sends_rows = sends_rows
        self._show_returns = show_returns
        self._max_returns = max_returns
        self._num_sends_to_display = 0
        self._track_changed = False
        self._fold_on_reselect = fold_on_reselect
        self._select_track_on_0_val = select_track_on_0_val
        self._show_master = show_master
        self._delayed_update = delayed_update
        self._skip_delayed = False
        self._selected_track_index = 0
        self._last_selected_track_index = 0
        self._show_send_buttons = True
        super(MixerComponent, self).__init__(num_tracks, *a, **k)
        self._update_send_buttons()
        self._encoder_state = []
        self._button_state = []
        for i in range(32):
            if i < 24:
                self._button_state.append(False)
            self._encoder_state.append(False)

    def log(self, msg, force=False):
        if hasattr(self, '_parent'):
            self._parent.log(msg, force)

    def debug(self, msg, force=False):
        if hasattr(self, '_parent') and hasattr(self._parent, 'debug'):
            self._parent.debug(msg, force)

    def msg(self, msg):
        if hasattr(self, '_parent'):
            self._parent.show_message(msg)

    def on_selected_track_changed(self):
        self.log('MixerComponent:on_selected_track_changed')
        self._track_changed = True
        self._selected_track = self._parent.song().view.selected_track
        self._last_selected_track_index = self._selected_track_index
        self._selected_track_index = 0
        for index, channel_strip in enumerate(self._channel_strips):
            if self._selected_track == channel_strip._track:
                self._selected_track_index = index
                break

        self.log('MixerComponent:on_selected_track_changed: index: ' +
                 str(self._selected_track_index) + ', last: ' +
                 str(self._last_selected_track_index))
        if hasattr(self, '_parent'):
            self._parent.on_selected_track_changed()

    def _create_strip(self):
        self.log('_create_strip')
        return ChannelStripComponent(self._parent, self)

    def same_track_selected(self):
        self.log('same_track_selected: fold: ' + str(self._fold_on_reselect))
        cont = True
        if hasattr(self, '_parent') and hasattr(self._parent,
                                                'same_track_selected'):
            cont = self._parent.same_track_selected()
        if cont and self._fold_on_reselect:
            selected = self._parent.song().view.selected_track
            if selected.is_foldable:
                selected.fold_state = not selected.fold_state

    def set_send_controls(self, controls):
        self._send_controls = controls
        for index, channel_strip in enumerate(self._channel_strips):
            if self.send_index is None:
                channel_strip.set_send_controls([None])
            else:
                send_controls = [
                    controls.get_button(index, i)
                    for i in xrange(self._sends_rows)
                ] if controls else [None]
                skipped_sends = [None for _ in xrange(self.send_index)]
                channel_strip.set_send_controls(skipped_sends + send_controls)

    def set_volume_controls(self, controls):
        for strip, control in map(None, self._channel_strips, controls or []):
            strip.set_volume_control(control)

    def set_send_lights(self, lights):
        for index, channel_strip in enumerate(self._channel_strips):
            elements = None
            if lights is not None:
                lights.reset()
                elements = None if self.send_index is None else [
                    lights.get_button(index, i)
                    for i in xrange(self._sends_rows)
                ]
            channel_strip.send_lights.set_control_element(elements)

    def set_pan_lights(self, lights):
        for strip, light in map(None, self._channel_strips, lights or []):
            strip.pan_light.set_control_element(light)

    def _get_send_index(self):
        return super(MixerComponent, self)._get_send_index()

    def _set_send_index(self, index):
        self.log('mixer:_set_send_index: ' + str(index))
        if index is not None and index % self._sends_rows > 0:
            index -= index % self._sends_rows
        self.log('mixer:_set_send_index: real_index: ' + str(index))
        super(MixerComponent, self)._set_send_index(index)
        self._update_send_buttons()

    send_index = property(_get_send_index, _set_send_index)

    def _update_send_buttons(self):
        self.log('_update_send_buttons')
        self.next_sends_button.enabled = self._show_send_buttons and self.send_index is not None and self.send_index < self.num_sends - self._sends_rows
        self.prev_sends_button.enabled = self._show_send_buttons and self.send_index is not None and self.send_index > 0
        num_to_display = min(
            self._sends_rows, self.num_sends -
            (self.send_index if self.send_index != None else 0))
        self._num_sends_to_display = num_to_display
        self.log('_update_send_buttons: up: ' +
                 str(self.next_sends_button.enabled) + ', down: ' +
                 str(self.prev_sends_button.enabled) + ', to_display: ' +
                 str(num_to_display))
        if self.send_index != None:
            self.msg('Controlling sends ' + str(self.send_index + 1) + ' to ' +
                     str(self.send_index + num_to_display))
        for index in range(len(self._channel_strips)):
            self._channel_strips[index].set_num_sends_to_display(
                num_to_display)

        updated = self._setup_mixer_controls(self._num_vis_tracks,
                                             self._num_vis_returns,
                                             self._sends_rows, num_to_display)

    @next_sends_button.pressed
    def next_sends_button(self, button):
        self.log('next_sends_button: ' + str(button))
        self.send_index = min(self.send_index + self._sends_rows,
                              self.num_sends - 1)

    @prev_sends_button.pressed
    def prev_sends_button(self, button):
        self.log('prev_sends_button: ' + str(button))
        self.send_index = max(self.send_index - self._sends_rows, 0)

    def set_track_select_buttons(self, buttons):
        for strip, button in map(None, self._channel_strips, buttons or []):
            if button:
                if hasattr(button, 'set_on_off_values'):
                    button.set_on_off_values('Mixer.TrackSelected',
                                             'Mixer.TrackUnselected')
                button._strip = strip
            strip.set_select_button(button)

    def set_solo_buttons(self, buttons):
        for strip, button in map(None, self._channel_strips, buttons or []):
            if button:
                if hasattr(button, 'set_on_off_values'):
                    button.set_on_off_values('Mixer.SoloOn', 'Mixer.SoloOff')
                button._strip = strip
            strip.set_solo_button(button)

    def set_mute_buttons(self, buttons):
        for strip, button in map(None, self._channel_strips, buttons or []):
            if button:
                if hasattr(button, 'set_on_off_values'):
                    button.set_on_off_values('Mixer.MuteOn', 'Mixer.MuteOff')
                button._strip = strip
            strip.set_mute_button(button)

    def set_arm_buttons(self, buttons):
        for strip, button in map(None, self._channel_strips, buttons or []):
            if button:
                if hasattr(button, 'set_on_off_values'):
                    button.set_on_off_values('Mixer.ArmSelected',
                                             'Mixer.ArmUnselected')
                button._strip = strip
            strip.set_arm_button(button)

    def set_track_offset(self, new_offset):
        self.log('set_track_offset: ' + str(new_offset))
        super(MixerComponent, self).set_track_offset(new_offset)

    def _is_track_enabled(self,
                          x,
                          num_vis_tracks,
                          num_vis_returns,
                          toggle_returns=False):
        if self._show_returns:
            if self._show_returns == 3:
                enabled = toggle_returns == False and x < num_vis_tracks or toggle_returns == True and (
                    x < num_vis_returns or self._show_master and x == 7)
            elif self._show_returns == 4:
                enabled = toggle_returns == False and x < num_vis_tracks or toggle_returns == True and (
                    x < num_vis_tracks or x >=
                    (8 if not self._show_master else 7) - num_vis_returns)
            else:
                enabled = x < num_vis_tracks or x >= (
                    8 if not self._show_master else 7) - num_vis_returns
        else:
            enabled = x < num_vis_tracks
        self.debug('_is_track_enabled: x: ' + str(x) + ', tracks: ' +
                   str(num_vis_tracks) + ', returns: ' + str(num_vis_returns) +
                   ', returns_toggled: ' + str(toggle_returns) +
                   ', enabled: ' + str(enabled))
        return enabled

    def _is_return_track(self,
                         x,
                         num_vis_tracks,
                         num_vis_returns,
                         toggle_returns=False):
        if self._show_returns:
            if self._show_returns == 3:
                ret = toggle_returns and x < num_vis_returns
            elif self._show_master and self._master_visible:
                ret = x >= 7 - num_vis_returns and x != 7
            else:
                ret = x >= 8 - num_vis_returns
        else:
            ret = False
        self.debug('_is_return_track: x: ' + str(x) + ', tracks: ' +
                   str(num_vis_tracks) + ', returns: ' + str(num_vis_returns) +
                   ', return: ' + str(ret))
        return ret

    def _is_master_track(self, x, toggle_returns=False):
        if self._show_returns and self._show_master:
            if self._show_returns == 3:
                ret = toggle_returns and self._master_visible and x == 7
            else:
                ret = self._master_visible and x == 7
        else:
            ret = False
        self.debug('_is_master_track: x: ' + str(x) + ', master: ' + str(ret))
        return ret

    def dump_state(self):
        self.log('DUMP: _reassign_tracks: show_returns: ' +
                 str(self._show_returns) + ', vis_tracks: ' +
                 str(self._num_vis_tracks) + ', vis_returns: ' +
                 str(self._num_vis_returns) + ', empty: ' +
                 str(self._num_empty_tracks) + ', offset: ' +
                 str(self._track_offset))

    def _reassign_tracks(self, toggled_returns_action=-1):
        self.log('_reassign_tracks: toggled: ' + str(toggled_returns_action) +
                 ', skip_delayed: ' + str(self._skip_delayed))
        tracks = self.tracks_to_use()
        returns = self.song().return_tracks
        track_offset = 0
        show_returns = self._show_returns
        toggled_returns = self._parent._returns_toggled
        if hasattr(self._parent, '_session'):
            track_offset = self._parent._session.track_offset()
        else:
            track_offset = self._track_offset
        self._num_empty_tracks = max(
            0,
            len(self._channel_strips) + self._track_offset - len(tracks))
        if show_returns == 0 or show_returns == 1:
            self._num_vis_tracks = min(8, len(tracks) - track_offset)
            self._num_empty_tracks = max(0, 8 - self._num_vis_tracks)
            self._num_vis_returns = min(len(returns), self._num_empty_tracks)
            if self._max_returns != -1:
                self._num_vis_returns = min(self._num_vis_returns,
                                            self._max_returns)
            self._num_empty_tracks -= self._num_vis_returns
            self._master_visible = self._show_master and show_returns == 1 and self._num_empty_tracks
        elif show_returns == 2:
            self._num_vis_returns = min(8, len(returns))
            if self._max_returns != -1:
                self._num_vis_returns = min(self._num_vis_returns,
                                            self._max_returns)
            self._num_vis_tracks = min(8 - self._num_vis_returns,
                                       len(tracks) - track_offset)
            self._num_empty_tracks = max(
                0, self._num_vis_returns + self._num_vis_tracks)
            self._master_visible = self._show_master
        elif show_returns == 3:
            self._num_vis_tracks = min(
                8,
                len(tracks) - track_offset) if toggled_returns == False else 0
            self._num_vis_returns = min(
                8, len(returns)) if toggled_returns == True else 0
            self._num_empty_tracks = 8 - self._num_vis_tracks - self._num_vis_returns
            self._master_visible = self._show_master
        elif show_returns == 4:
            self._num_vis_returns = min(
                8, len(returns)) if toggled_returns == True else 0
            if self._max_returns != -1:
                self._num_vis_returns = min(self._num_vis_returns,
                                            self._max_returns)
            self._num_vis_tracks = min(8 - self._num_vis_returns,
                                       len(tracks) - track_offset)
            self._num_empty_tracks = max(
                0, self._num_vis_returns + self._num_vis_tracks)
            self._master_visible = self._show_master
        self._num_empty_tracks -= 1 if self._master_visible else 0
        self.log('_reassign_tracks: show_returns: ' + str(show_returns) +
                 ', vis_tracks: ' + str(self._num_vis_tracks) +
                 ', vis_returns: ' + str(self._num_vis_returns) + ', empty: ' +
                 str(self._num_empty_tracks) + ', toggled_returns: ' +
                 str(toggled_returns) + ', offset: ' +
                 str(self._track_offset) + ', master_vis: ' +
                 str(self._master_visible))
        if self._delayed_update and self._skip_delayed:
            self._skip_delayed = False
            self.map_controls()
        else:
            if self._show_returns:
                updated = self._setup_mixer_controls(
                    self._num_vis_tracks, self._num_vis_returns,
                    self._sends_rows, self._num_sends_to_display,
                    toggled_returns_action)
            else:
                updated = self._setup_mixer_controls(
                    self._num_vis_tracks, 0, self._sends_rows,
                    self._num_sends_to_display)
            if not self._delayed_update or not updated:
                self.map_controls()

    def map_controls(self):
        self.log('MixerComponent:map_controls')
        ret_index = 0
        tracks = self.tracks_to_use()
        if self._show_returns:
            returns = self.song().return_tracks
            toggled_returns = self._parent._returns_toggled
            for index in range(len(self._channel_strips)):
                track = None
                if self._is_return_track(index, self._num_vis_tracks,
                                         self._num_vis_returns,
                                         toggled_returns):
                    track = returns[ret_index]
                    self._channel_strips[index].set_track(
                        track, index, self._num_sends_to_display, True)
                    ret_index += 1
                elif self._is_master_track(index, toggled_returns):
                    track = self._parent.song().master_track
                    self._channel_strips[index].set_track(
                        track, index, self._num_sends_to_display, False, True)
                elif index < self._num_vis_tracks:
                    track = tracks[self._track_offset + index]
                    self._channel_strips[index].set_track(
                        track, index, self._num_sends_to_display)
                else:
                    self._channel_strips[index].set_track(None)
                if track != None:
                    self.log('MixerComponent:map_controls: Assigned track: ' +
                             repr3(track.name) + ', index: ' + str(index))

        else:
            self.log(
                'MixerComponent:map_controls: num_tracks: ' +
                str(len(tracks)) + ', num_strips: ' +
                str(len(self._channel_strips)) + ', offset: ' +
                str(self._track_offset) + ', num_vis: ' +
                str(self._num_vis_tracks), False)
            for index in range(min(len(tracks), len(self._channel_strips))):
                self.log('track: ' + str(index) + ': ' +
                         repr3(tracks[index].name))
                track = None
                if index < self._num_vis_tracks:
                    track = tracks[self._track_offset + index]
                    self._channel_strips[index].set_track(
                        track, index, self._num_sends_to_display)
                else:
                    self._channel_strips[index].set_track(None)
                if track != None:
                    self.log('MixerComponent:map_controls: Assigned track: ' +
                             repr3(track.name) + ', index: ' + str(index))

    def on_num_sends_changed(self):
        self.log('on_num_sends_changed: ' + str(self.num_sends))
        super(MixerComponent, self).on_num_sends_changed()

    def _setup_mixer_controls(self,
                              num_vis_tracks=-1,
                              num_vis_returns=-1,
                              num_sends_rows=-1,
                              num_sends_to_display=-1,
                              toggled_returns_action=-1):
        pass
Esempio n. 4
0
class BrowserComponent(CompoundComponent):
    """
    Component for controlling the Live library browser.  It has 4
    browsing columns that are controlled by encoders and state
    buttons.  The contents of these lists are provided by a browser
    model -- see BrowserModel and derivatives.
    """
    __subject_events__ = ('load_item', )
    NUM_COLUMNS = 4
    COLUMN_SIZE = 4
    enter_button = ButtonControl(**consts.SIDE_BUTTON_COLORS)
    exit_button = ButtonControl(**consts.SIDE_BUTTON_COLORS)
    shift_button = ButtonControl()

    def __init__(self, browser=None, *a, **k):
        super(BrowserComponent, self).__init__(*a, **k)
        self._browser = browser or self.application().browser
        self._browser_model = make_fallback_browser_model(self._browser)
        num_data_sources = self.NUM_COLUMNS * self.COLUMN_SIZE
        self._data_sources = map(DisplayDataSource, ('', ) * num_data_sources)
        self._last_loaded_item = None
        self._default_item_formatter = DefaultItemFormatter()
        self._list_components = self.register_components(
            *[ListComponent() for _ in xrange(self.NUM_COLUMNS)])
        for i, component in enumerate(self._list_components):
            component.do_trigger_action = lambda item: self._do_load_item(item)
            component.last_action_item = lambda: self._last_loaded_item
            component.item_formatter = partial(self._item_formatter, i)

        self._select_buttons = []
        self._state_buttons = []
        self._encoder_controls = []
        self._on_list_item_action.replace_subjects(self._list_components)
        self._on_hotswap_target_changed.subject = self._browser
        self._on_filter_type_changed.subject = self._browser
        self._on_browser_full_refresh.subject = self._browser
        self._scroll_offset = 0
        self._max_scroll_offset = 0
        self._max_hierarchy = 0
        self._last_filter_type = None
        self._skip_next_preselection = False
        self._browser_model_dirty = True
        self._on_content_lists_changed()

    def set_display_line1(self, display):
        self.set_display_line_with_index(display, 0)

    def set_display_line2(self, display):
        self.set_display_line_with_index(display, 1)

    def set_display_line3(self, display):
        self.set_display_line_with_index(display, 2)

    def set_display_line4(self, display):
        self.set_display_line_with_index(display, 3)

    def set_display_line_with_index(self, display, index):
        if display:
            sources = self._data_sources[index::self.COLUMN_SIZE]
            display.set_data_sources(sources)

    def set_select_buttons(self, buttons):
        for button in buttons or []:
            if button:
                button.reset()

        self._on_select_matrix_value.subject = buttons or None
        self._select_buttons = buttons
        buttons = buttons or (None, None, None, None, None, None, None, None)
        for component, button in izip(self._list_components, buttons[1::2]):
            self._set_button_if_enabled(component, 'action_button', button)

        for component, button in izip(self._list_components, buttons[::2]):
            if self.shift_button.is_pressed:
                self._set_button_if_enabled(component, 'prev_page_button',
                                            button)
                self._set_button_if_enabled(component, 'select_prev_button',
                                            None)
            else:
                self._set_button_if_enabled(component, 'prev_page_button',
                                            None)
                self._set_button_if_enabled(component, 'select_prev_button',
                                            button)

    def set_state_buttons(self, buttons):
        for button in buttons or []:
            if button:
                button.reset()

        self._on_state_matrix_value.subject = buttons or None
        self._state_buttons = buttons
        buttons = buttons or (None, None, None, None, None, None, None, None)
        for component, button in izip(self._list_components, buttons[::2]):
            if self.shift_button.is_pressed:
                self._set_button_if_enabled(component, 'next_page_button',
                                            button)
                self._set_button_if_enabled(component, 'select_next_button',
                                            None)
            else:
                self._set_button_if_enabled(component, 'next_page_button',
                                            None)
                self._set_button_if_enabled(component, 'select_next_button',
                                            button)

        for button in buttons[1::2]:
            if button and self.is_enabled():
                button.set_light('DefaultButton.Disabled')

    @shift_button.value
    def shift_button(self, value, control):
        self.set_select_buttons(self._select_buttons)
        self.set_state_buttons(self._state_buttons)

    def _set_button_if_enabled(self, component, name, button):
        control = getattr(component, name)
        if component.is_enabled(explicit=True):
            control.set_control_element(button)
        else:
            control.set_control_element(None)
            if button and self.is_enabled():
                button.set_light('DefaultButton.Disabled')

    def set_encoder_controls(self, encoder_controls):
        if encoder_controls:
            num_active_lists = len(
                self._browser_model.content_lists) - self._scroll_offset
            num_assignable_lists = min(num_active_lists,
                                       len(encoder_controls) / 2)
            index = 0
            for component in self._list_components[:num_assignable_lists - 1]:
                component.encoders.set_control_element(
                    encoder_controls[index:index + 2])
                index += 2

            self._list_components[num_assignable_lists -
                                  1].encoders.set_control_element(
                                      encoder_controls[index:])
        else:
            for component in self._list_components:
                component.encoders.set_control_element([])

        self._encoder_controls = encoder_controls

    def update(self):
        super(BrowserComponent, self).update()
        if self.is_enabled():
            self.set_state_buttons(self._state_buttons)
            self.set_select_buttons(self._select_buttons)
            self._update_browser_model()

    def reset_load_memory(self):
        self._update_load_memory(None)

    def _do_load_item(self, item):
        self.do_load_item(item)
        self._update_load_memory(item)
        self._skip_next_preselection = True

        def reset_skip_next_preselection():
            self._skip_next_preselection = False

        self._tasks.add(Task.run(reset_skip_next_preselection))

    def _update_load_memory(self, item):
        self._last_loaded_item = item
        for component in self._list_components:
            component.update()

    def do_load_item(self, item):
        item.action()
        self.notify_load_item(item.content)

    def back_to_top(self):
        self._set_scroll_offset(0)

    def _set_scroll_offset(self, offset):
        self._scroll_offset = offset
        self._on_content_lists_changed()
        scrollable_list = self._list_components[-1].scrollable_list
        if scrollable_list:
            scrollable_list.request_notify_item_activated()

    def _update_navigation_button_state(self):
        self.exit_button.enabled = self._scroll_offset > 0
        self.enter_button.enabled = self._scroll_offset < self._max_scroll_offset

    def _shorten_item_name(self, shortening_limit, list_index, item_name):
        """
        Creates the name of an item shortened by removing words from the parents name
        """
        def is_short_enough(item_name):
            return len(item_name) <= 9

        content_lists = self._browser_model.content_lists
        parent_lists = reversed(content_lists[max(0, list_index -
                                                  3):list_index])
        for content_list in parent_lists:
            if is_short_enough(item_name):
                break
            parent_name = unicode(content_list.selected_item)
            stems = split_stem(parent_name)
            for stem in stems:
                short_name = make_stem_cleaner(stem)(item_name)
                short_name = full_strip(short_name)
                item_name = short_name if len(short_name) > 4 else item_name
                if is_short_enough(item_name):
                    break

        return item_name[:-1] if len(
            item_name) >= shortening_limit and item_name[
                -1] == consts.CHAR_ELLIPSIS else item_name

    def _item_formatter(self, depth, index, item, action_in_progress):
        display_string = ''
        separator_length = len(self._data_sources[self.COLUMN_SIZE *
                                                  depth].separator)
        shortening_limit = 16 - separator_length
        if item:
            item_name = 'Loading...' if action_in_progress else self._shorten_item_name(
                shortening_limit, depth + self._scroll_offset, unicode(item))
            display_string = consts.CHAR_SELECT if item and item.is_selected else ' '
            display_string += item_name
            if depth == len(
                    self._list_components
            ) - 1 and item.is_selected and self._scroll_offset < self._max_hierarchy:
                display_string = string.ljust(display_string,
                                              consts.DISPLAY_LENGTH / 4 - 1)
                shortening_limit += 1
                display_string = display_string[:shortening_limit] + consts.CHAR_ARROW_RIGHT
            if depth == 0 and self._scroll_offset > 0:
                prefix = consts.CHAR_ARROW_LEFT if index == 0 else ' '
                display_string = prefix + display_string
        return display_string[:shortening_limit + 1]

    @enter_button.pressed
    def enter_button(self, control):
        self._set_scroll_offset(
            min(self._max_scroll_offset, self._scroll_offset + 1))

    @exit_button.pressed
    def exit_button(self, control):
        self._set_scroll_offset(max(0, self._scroll_offset - 1))

    @subject_slot('hotswap_target')
    def _on_hotswap_target_changed(self):
        if not self._skip_next_preselection:
            self._set_scroll_offset(0)
        self._update_browser_model()

    @subject_slot('filter_type')
    def _on_filter_type_changed(self):
        self._update_browser_model()

    @subject_slot('full_refresh')
    def _on_browser_full_refresh(self):
        self._browser_model_dirty = True

    def _update_browser_model(self):
        if self.is_enabled():
            self._do_update_browser_model()

    def _do_update_browser_model(self):
        filter_type = filter_type_for_browser(self._browser)
        if filter_type != self._last_filter_type:
            self._last_filter_type = filter_type
            new_model = make_browser_model(self._browser, filter_type)
            if self._browser_model and self._browser_model.can_be_exchanged(
                    new_model) and new_model.can_be_exchanged(
                        self._browser_model):
                self._browser_model.exchange_model(new_model)
                new_model.disconnect()
            else:
                self.disconnect_disconnectable(self._browser_model)
                self._browser_model = self.register_slot_manager(new_model)
                self._on_content_lists_changed.subject = self._browser_model
                self._on_selection_updated.subject = self._browser_model
            for contents in self._browser_model.content_lists:
                contents.selected_item_index = 0

            self._browser_model.update_content()
        elif self._browser_model_dirty:
            self._browser_model.update_content()
        elif not self._skip_next_preselection:
            self._browser_model.update_selection()
        self._skip_next_preselection = False
        self._browser_model_dirty = False

    @subject_slot_group('item_action')
    def _on_list_item_action(self, item, _):
        self.notify_load_item(item.content)

    @subject_slot('selection_updated')
    def _on_selection_updated(self, index):
        more_content_available = len(self._browser_model.content_lists
                                     ) > self.NUM_COLUMNS + self._scroll_offset
        required_scroll_offset = index - (self.NUM_COLUMNS - 1)
        if more_content_available and required_scroll_offset > self._scroll_offset:
            self._set_scroll_offset(self._scroll_offset + 1)
            self._browser_model.update_selection()

    @subject_slot('content_lists')
    def _on_content_lists_changed(self):
        components = self._list_components
        contents = self._browser_model.content_lists[self._scroll_offset:]
        messages = self._browser_model.empty_list_messages
        scroll_depth = len(self._browser_model.content_lists) - len(
            self._list_components)
        self._max_scroll_offset = max(0, scroll_depth + 2)
        self._max_hierarchy = max(0, scroll_depth)
        for component, content, message in map(None, components, contents,
                                               messages):
            if component != None:
                component.scrollable_list = content
                component.empty_list_message = message

        active_lists = len(contents)
        num_head = clamp(active_lists - 1, 0, self.NUM_COLUMNS - 1)
        head = components[:num_head]
        last = components[num_head:]

        def set_data_sources_with_separator(component, sources, separator):
            for source in sources:
                source.separator = separator

            component.set_data_sources(sources)
            component.set_enabled(True)

        for idx, component in enumerate(head):
            offset = idx * self.COLUMN_SIZE
            sources = self._data_sources[offset:offset + self.COLUMN_SIZE]
            set_data_sources_with_separator(component, sources, '|')

        if last:
            offset = num_head * self.COLUMN_SIZE
            scrollable_list = last[0].scrollable_list
            if scrollable_list and find_if(lambda item: item.content.is_folder,
                                           scrollable_list.items):
                sources = self._data_sources[offset:offset + self.COLUMN_SIZE]
                map(DisplayDataSource.clear,
                    self._data_sources[offset + self.COLUMN_SIZE:])
            else:
                sources = self._data_sources[offset:]
            set_data_sources_with_separator(last[0], sources, '')
            for component in last[1:]:
                component.set_enabled(False)

        self.set_select_buttons(self._select_buttons)
        self.set_state_buttons(self._state_buttons)
        self.set_encoder_controls(self._encoder_controls)
        self._update_navigation_button_state()

    @subject_slot('value')
    def _on_select_matrix_value(self, value, *_):
        pass

    @subject_slot('value')
    def _on_state_matrix_value(self, value, *_):
        pass

    @subject_slot('value')
    def _on_encoder_matrix_value(self, value, *_):
        pass
Esempio n. 5
0
class StopClipComponent(ControlSurfaceComponent):
    stop_all_clips_button = ButtonControl()
    stop_track_clips_buttons = control_list(ButtonControl, color='Session.StoppedClip')

    def __init__(self, *a, **k):
        super(StopClipComponent, self).__init__(*a, **k)
        self._track_offset = 0
        self._on_tracks_changed.subject = self.song()
        self._on_tracks_changed()

    def _get_track_offset(self):
        return self._track_offset

    def _set_track_offset(self, value):
        if not 0 <= value < len(tracks_to_use_from_song(self.song())):
            raise IndexError
        self._track_offset = value
        self._update_all_stop_buttons()

    track_offset = property(_get_track_offset, _set_track_offset)

    @stop_all_clips_button.pressed
    def stop_all_clips_button(self, button):
        self.song().stop_all_clips()

    @stop_track_clips_buttons.pressed
    def stop_track_clips_buttons(self, button):
        button.track.stop_all_clips()

    @subject_slot('visible_tracks')
    def _on_tracks_changed(self):
        tracks = tracks_to_use_from_song(self.song())
        self._track_offset = clamp(self._track_offset, 0, len(tracks) - 1)
        self._on_fired_slot_index_changed.replace_subjects(tracks, count())
        self._on_playing_slot_index_changed.replace_subjects(tracks, count())
        self._update_all_stop_buttons()

    @subject_slot_group('fired_slot_index')
    def _on_fired_slot_index_changed(self, track_index):
        self._update_stop_button_by_index(track_index - self._track_offset)

    @subject_slot_group('playing_slot_index')
    def _on_playing_slot_index_changed(self, track_index):
        self._update_stop_button_by_index(track_index - self._track_offset)

    def _update_all_stop_buttons(self):
        tracks = tracks_to_use_from_song(self.song())[self._track_offset:]
        self.stop_track_clips_buttons.control_count = len(tracks)
        for track, button in izip(tracks, self.stop_track_clips_buttons):
            self._update_stop_button(track, button)

    def _update_stop_button_by_index(self, index):
        button = self.stop_track_clips_buttons[index]
        self._update_stop_button(button.track, button)

    def _update_stop_button(self, track, button):
        has_clip_slots = bool(track.clip_slots)
        if has_clip_slots:
            if track.fired_slot_index == -2:
                button.color = 'Session.StopClipTriggered'
            elif track.playing_slot_index >= 0:
                button.color = 'Session.StopClip'
            else:
                button.color = 'Session.StoppedClip'
        button.enabled = bool(has_clip_slots)
        button.track = track
Esempio n. 6
0
class LedLightingComponent(ControlSurfaceComponent):
    button = ButtonControl(color='Misc.Shift', pressed_color='Misc.ShiftOn')
class MixerComponent(MixerComponentBase):
    unmute_all_button = ButtonControl(color='Mixer.Mute.Off',
                                      pressed_color='Mixer.Mute.On')
    unsolo_all_button = ButtonControl(color='Mixer.Solo.Off',
                                      pressed_color='Mixer.Solo.On')
    unarm_all_button = ButtonControl(color='Mixer.Arm.Off',
                                     pressed_color='Mixer.Arm.On')

    def __init__(self, enable_skinning=False, *a, **k):
        (super(MixerComponent, self).__init__)(*a, **k)
        self._volume_on_value = 127
        self._volume_off_value = 0
        self._pan_on_value = 127
        self._pan_off_value = 0
        if enable_skinning:
            self._enable_skinning()

    def _create_strip(self):
        return ChannelStripComponent()

    def _enable_skinning(self):
        self.set_volume_values('Mixer.Volume.On', 'Mixer.Volume.Off')
        self.set_pan_values('Mixer.Pan.On', 'Mixer.Pan.Off')
        for strip in self._channel_strips:
            strip.empty_color = 'Mixer.Disabled'
            strip.set_arm_values('Mixer.Arm.On', 'Mixer.Arm.Off')
            strip.set_solo_values('Mixer.Solo.On', 'Mixer.Solo.Off')
            strip.set_mute_values('Mixer.Mute.On', 'Mixer.Mute.Off')

    def set_volume_values(self, volume_on_value, volume_off_value):
        self._volume_on_value = volume_on_value
        self._volume_off_value = volume_off_value

    def set_pan_values(self, pan_on_value, pan_off_value):
        self._pan_on_value = pan_on_value
        self._pan_off_value = pan_off_value

    def set_volume_controls(self, controls):
        if controls is not None:
            for control in controls:
                if control is not None:
                    control.set_channel(consts.VOLUME_MODE_CHANNEL)

        super(MixerComponent, self).set_volume_controls(controls)
        if controls is not None:
            for index, control in enumerate(controls):
                control.index = index
                control.type = consts.FADER_STANDARD_TYPE
                control.color = self._volume_on_value

    def set_pan_controls(self, controls):
        if controls is not None:
            for control in controls:
                if control is not None:
                    control.set_channel(consts.PAN_MODE_CHANNEL)

        super(MixerComponent, self).set_pan_controls(controls)
        if controls is not None:
            for index, control in enumerate(controls):
                control.index = index
                control.type = consts.FADER_BIPOLAR_TYPE
                control.color = self._pan_on_value

    def set_send_a_controls(self, controls):
        self._set_send_controls(controls, 0)

    def set_send_b_controls(self, controls):
        self._set_send_controls(controls, 1)

    def _set_send_controls(self, controls, send_index):
        translation_channel = 0
        if send_index == 0:
            translation_channel = consts.SEND_A_MODE_CHANNEL
        elif send_index == 1:
            translation_channel = consts.SEND_B_MODE_CHANNEL
        if controls is not None:
            for index, control in enumerate(controls):
                if control is not None:
                    self.channel_strip(index).set_send_controls((None, ) *
                                                                send_index +
                                                                (control, ))
                    control.set_channel(translation_channel)
                    control.index = index
                    control.type = consts.FADER_STANDARD_TYPE
                    control.color = 'Sends.Send%d.On' % send_index

        else:
            for strip in self._channel_strips:
                strip.set_send_controls(None)

    def set_volume_reset_buttons(self, buttons):
        if buttons is not None:
            for index, strip in enumerate(self._channel_strips):
                strip.volume_reset_button.set_control_element(
                    buttons.get_button(index, 0))

        else:
            for strip in self._channel_strips:
                strip.volume_reset_button.set_control_element(None)

    def set_pan_reset_buttons(self, buttons):
        if buttons is not None:
            for index, strip in enumerate(self._channel_strips):
                strip.pan_reset_button.set_control_element(
                    buttons.get_button(index, 0))

        else:
            for strip in self._channel_strips:
                strip.pan_reset_button.set_control_element(None)

    def set_send_a_reset_buttons(self, buttons):
        if buttons is not None:
            for index, strip in enumerate(self._channel_strips):
                strip.send_a_reset_button.set_control_element(
                    buttons.get_button(index, 0))

        else:
            for strip in self._channel_strips:
                strip.send_a_reset_button.set_control_element(None)

    def set_send_b_reset_buttons(self, buttons):
        if buttons is not None:
            for index, strip in enumerate(self._channel_strips):
                strip.send_b_reset_button.set_control_element(
                    buttons.get_button(index, 0))

        else:
            for strip in self._channel_strips:
                strip.send_b_reset_button.set_control_element(None)

    @unmute_all_button.pressed
    def unmute_all_button(self, button):
        for track in tuple(self.song().tracks) + tuple(
                self.song().return_tracks):
            if track.mute:
                track.mute = False

    @unsolo_all_button.pressed
    def unsolo_all_button(self, button):
        for track in tuple(self.song().tracks) + tuple(
                self.song().return_tracks):
            if track.solo:
                track.solo = False

    @unarm_all_button.pressed
    def unarm_all_button(self, button):
        for track in self.song().tracks:
            if track.arm:
                track.arm = False
Esempio n. 8
0
class DetailViewCntrlComponent(ControlSurfaceComponent):
    device_clip_toggle_button = ButtonControl(color='DefaultButton.Off')
    device_nav_left_button = ButtonControl(color='DefaultButton.Off')
    device_nav_right_button = ButtonControl(color='DefaultButton.Off')
    detail_toggle_button = ToggleButtonControl()

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

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

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

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

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

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

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

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

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

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

    def show_view(self, view):
        app_view = self.application().view
        try:
            if not view == 'Detail/DeviceChain':
                pass
            if not app_view.is_view_visible('Detail'):
                app_view.show_view('Detail')
            if not app_view.is_view_visible(view):
                app_view.show_view(view)
        except RuntimeError:
            pass
Esempio n. 9
0
class DrumGroupComponent(ResettableSlideComponent, Slideable):
    __subject_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 list(range(first_pos, last_pos))

    def contents(self, index):
        drum = self._drum_group_device
        if drum:
            return any(map(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):
        assert 0 <= index <= 28
        if self._drum_group_device:
            self._drum_group_device.view.drum_pads_scroll_position = 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()

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

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

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

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

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

    @subject_slot(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)
class ListComponent(CompoundComponent):
    """
    Component that handles a ScrollableList.  If an action button is
    passed, it can handle an ActionList.
    """
    __subject_events__ = ('item_action', )
    SELECTION_DELAY = 0.5
    ENCODER_FACTOR = 10.0
    empty_list_message = ''
    _current_action_item = None
    _last_action_item = None
    action_button = ButtonControl(color='Browser.Load')
    encoders = control_list(EncoderControl)

    def __init__(self, scrollable_list=None, data_sources=tuple(), *a, **k):
        super(ListComponent, self).__init__(*a, **k)
        self._data_sources = data_sources
        self._activation_task = Task.Task()
        self._action_on_scroll_task = Task.Task()
        self._scrollable_list = None
        self._scroller = self.register_component(ScrollComponent())
        self._pager = self.register_component(ScrollComponent())
        self.last_action_item = lambda: self._last_action_item
        self.item_formatter = DefaultItemFormatter()
        for c in (self._scroller, self._pager):
            for button in (c.scroll_up_button, c.scroll_down_button):
                button.color = 'List.ScrollerOn'
                button.pressed_color = None
                button.disabled_color = 'List.ScrollerOff'

        if scrollable_list == None:
            self.scrollable_list = ActionList(
                num_visible_items=len(data_sources))
        else:
            self.scrollable_list = scrollable_list
        self._scrollable_list.num_visible_items = len(data_sources)
        self._delay_activation = BooleanContext()
        self._selected_index_float = 0.0
        self._in_encoder_selection = BooleanContext(False)
        self._execute_action_task = self._tasks.add(
            Task.sequence(Task.delay(1), Task.run(self._execute_action)))
        self._execute_action_task.kill()
        return

    @property
    def _trigger_action_on_scrolling(self):
        return self.action_button.is_pressed

    def _get_scrollable_list(self):
        return self._scrollable_list

    def _set_scrollable_list(self, new_list):
        if new_list != self._scrollable_list:
            self._scrollable_list = new_list
            if new_list != None:
                new_list.num_visible_items = len(self._data_sources)
                self._scroller.scrollable = new_list
                self._pager.scrollable = new_list.pager
                self._on_scroll.subject = new_list
                self._selected_index_float = new_list.selected_item_index
            else:
                self._scroller.scrollable = ScrollComponent.default_scrollable
                self._scroller.scrollable = ScrollComponent.default_pager
            self._on_selected_item_changed.subject = new_list
            self.update_all()
        return

    scrollable_list = property(_get_scrollable_list, _set_scrollable_list)

    def set_data_sources(self, sources):
        self._data_sources = sources
        if self._scrollable_list:
            self._scrollable_list.num_visible_items = len(sources)
        self._update_display()

    select_next_button = forward_property('_scroller')('scroll_down_button')
    select_prev_button = forward_property('_scroller')('scroll_up_button')
    next_page_button = forward_property('_pager')('scroll_down_button')
    prev_page_button = forward_property('_pager')('scroll_up_button')

    def on_enabled_changed(self):
        super(ListComponent, self).on_enabled_changed()
        if not self.is_enabled():
            self._execute_action_task.kill()

    @subject_slot('scroll')
    def _on_scroll(self):
        if self._trigger_action_on_scrolling:
            trigger_selected = partial(self._trigger_action,
                                       self.selected_item)
            self._action_on_scroll_task.kill()
            self._action_on_scroll_task = self._tasks.add(
                Task.sequence(Task.wait(Defaults.MOMENTARY_DELAY),
                              Task.delay(1), Task.run(trigger_selected)))

    @subject_slot('selected_item')
    def _on_selected_item_changed(self):
        self._scroller.update()
        self._pager.update()
        self._update_display()
        self._update_action_feedback()
        self._activation_task.kill()
        self._action_on_scroll_task.kill()
        if self.SELECTION_DELAY and self._delay_activation:
            self._activation_task = self._tasks.add(
                Task.sequence(
                    Task.wait(self.SELECTION_DELAY),
                    Task.run(
                        self._scrollable_list.request_notify_item_activated)))
        else:
            self._scrollable_list.request_notify_item_activated()
        if not self._in_encoder_selection:
            self._selected_index_float = float(
                self._scrollable_list.selected_item_index)

    @encoders.value
    def encoders(self, value, encoder):
        self._add_offset_to_selected_index(value)

    def _add_offset_to_selected_index(self, offset):
        if self.is_enabled() and self._scrollable_list:
            with self._delay_activation():
                with self._in_encoder_selection():
                    self._selected_index_float = clamp(
                        self._selected_index_float +
                        offset * self.ENCODER_FACTOR, 0,
                        len(self._scrollable_list.items))
                    self._scrollable_list.select_item_index_with_border(
                        int(self._selected_index_float), 1)

    @action_button.pressed
    def action_button(self, button):
        if self._current_action_item == None:
            self._trigger_action(
                self.next_item if self._action_target_is_next_item(
                ) else self.selected_item)
        return

    def do_trigger_action(self, item):
        item.action()
        self.notify_item_action(item)

    def _trigger_action(self, item):
        if self.is_enabled() and self._can_be_used_for_action(item):
            if self._scrollable_list != None:
                self._scrollable_list.select_item(item)
            self._current_action_item = item
            self.update()
            self._execute_action_task.restart()
        return

    def _execute_action(self):
        """ Is called by the execute action task and should not be called directly
        use _trigger_action instead """
        if self._current_action_item != None:
            self.do_trigger_action(self._current_action_item)
            self._last_action_item = self._current_action_item
            self._current_action_item = None
            self.update()
        return

    @property
    def selected_item(self):
        return self._scrollable_list.selected_item if self._scrollable_list != None else None

    @property
    def next_item(self):
        item = None
        if self._scrollable_list != None:
            all_items = self._scrollable_list.items
            next_index = self._scrollable_list.selected_item_index + 1
            item = all_items[next_index] if in_range(next_index, 0,
                                                     len(all_items)) else None
        return item

    def _can_be_used_for_action(self, item):
        return item != None and item.supports_action and item != self.last_action_item(
        )

    def _action_target_is_next_item(self):
        return self.selected_item == self.last_action_item(
        ) and self._can_be_used_for_action(self.next_item)

    def _update_action_feedback(self):
        color = 'Browser.Loading'
        if self._current_action_item == None:
            if self._action_target_is_next_item():
                color = 'Browser.LoadNext'
            elif self._can_be_used_for_action(self.selected_item):
                color = 'Browser.Load'
            else:
                color = 'Browser.LoadNotPossible'
        self.action_button.color = color
        return

    def _update_display(self):
        visible_items = self._scrollable_list.visible_items if self._scrollable_list else []
        for index, data_source in enumerate(self._data_sources):
            item = visible_items[index] if index < len(visible_items) else None
            action_in_progress = item and item == self._current_action_item
            display_string = self.item_formatter(index, item,
                                                 action_in_progress)
            data_source.set_display_string(display_string)

        if not visible_items and self._data_sources and self.empty_list_message:
            self._data_sources[0].set_display_string(self.empty_list_message)
        return

    def update(self):
        super(ListComponent, self).update()
        if self.is_enabled():
            self._update_action_feedback()
            self._update_display()
Esempio n. 11
0
class InstrumentScalesComponent(CompoundComponent):
    __subject_events__ = ('scales_changed', )
    presets_toggle_button = ButtonControl(color='DefaultButton.Off',
                                          pressed_color='DefaultButton.On')
    is_absolute = False
    is_diatonic = True
    key_center = 0

    def __init__(self, *a, **k):
        super(InstrumentScalesComponent, self).__init__(*a, **k)
        self._key_center_buttons = []
        self._encoder_touch_button_slots = self.register_slot_manager()
        self._encoder_touch_buttons = []
        self._top_key_center_buttons = None
        self._bottom_key_center_buttons = None
        self._absolute_relative_button = None
        self._diatonic_chromatic_button = None
        table = consts.MUSICAL_MODES
        self._info_sources = map(DisplayDataSource,
                                 ('Scale selection:', '', ''))
        self._line_sources = recursive_map(DisplayDataSource,
                                           (('', '', '', '', '', '', ''),
                                            ('', '', '', '', '', '', '')))
        self._modus_sources = map(
            partial(DisplayDataSource, adjust_string_fn=adjust_string_crop),
            ('', '', '', ''))
        self._presets = self.register_component(
            InstrumentPresetsComponent(is_enabled=False))
        self._presets.selected_mode = 'scale_p4_vertical'
        self._modus_list = self.register_component(
            ListComponent(data_sources=self._modus_sources))
        self._modus_list.scrollable_list.fixed_offset = 1
        self._modus_list.scrollable_list.assign_items([
            Modus(name=table[i], notes=table[i + 1])
            for i in xrange(0, len(consts.MUSICAL_MODES), 2)
        ])
        self._on_selected_modus.subject = self._modus_list.scrollable_list
        self._update_data_sources()

    presets_layer = forward_property('_presets')('layer')

    @property
    def modus(self):
        return self._modus_list.scrollable_list.selected_item.content

    @property
    def available_scales(self):
        return self.modus.scales(KEY_CENTERS)

    @property
    def notes(self):
        return self.modus.scale(self.key_center).notes

    def set_modus_line1(self, display):
        self._set_modus_line(display, 0)

    def set_modus_line2(self, display):
        self._set_modus_line(display, 1)

    def set_modus_line3(self, display):
        self._set_modus_line(display, 2)

    def set_modus_line4(self, display):
        self._set_modus_line(display, 3)

    def _set_modus_line(self, display, index):
        if display:
            display.set_data_sources([self._modus_sources[index]])
            for segment in display.segments:
                segment.separator = ''

    def set_info_line(self, display):
        if display:
            display.set_data_sources(self._info_sources)

    def set_top_display_line(self, display):
        self._set_display_line(display, 0)

    def set_bottom_display_line(self, display):
        self._set_display_line(display, 1)

    def _set_display_line(self, display, line):
        if display:
            display.set_data_sources(self._line_sources[line])

    def set_presets_toggle_button(self, button):
        self.presets_toggle_button.set_control_element(button)
        if button is None:
            self._presets.set_enabled(False)

    @presets_toggle_button.pressed
    def presets_toggle_button(self, button):
        self._presets.set_enabled(True)

    @presets_toggle_button.released
    def presets_toggle_button(self, button):
        self._presets.set_enabled(False)

    def set_top_buttons(self, buttons):
        if buttons:
            buttons.reset()
            self.set_absolute_relative_button(buttons[7])
            self._top_key_center_buttons = buttons[1:7]
            self.set_modus_up_button(buttons[0])
        else:
            self.set_absolute_relative_button(None)
            self._top_key_center_buttons = None
            self.set_modus_up_button(None)
        if self._top_key_center_buttons and self._bottom_key_center_buttons:
            self.set_key_center_buttons(self._top_key_center_buttons +
                                        self._bottom_key_center_buttons)
        else:
            self.set_key_center_buttons(tuple())

    def set_bottom_buttons(self, buttons):
        if buttons:
            buttons.reset()
            self.set_diatonic_chromatic_button(buttons[7])
            self._bottom_key_center_buttons = buttons[1:7]
            self.set_modus_down_button(buttons[0])
        else:
            self.set_diatonic_chromatic_button(None)
            self._bottom_key_center_buttons = None
            self.set_modus_down_button(None)
        if self._top_key_center_buttons and self._bottom_key_center_buttons:
            self.set_key_center_buttons(self._top_key_center_buttons +
                                        self._bottom_key_center_buttons)
        else:
            self.set_key_center_buttons([])

    def set_modus_down_button(self, button):
        self._modus_list.select_next_button.set_control_element(button)

    def set_modus_up_button(self, button):
        self._modus_list.select_prev_button.set_control_element(button)

    def set_encoder_controls(self, encoders):
        self._modus_list.encoders.set_control_element(
            [encoders[0]] if encoders else [])

    def set_key_center_buttons(self, buttons):
        if not (not buttons or len(buttons) == 12):
            raise AssertionError
        buttons = buttons or []
        self._key_center_buttons = buttons
        self._on_key_center_button_value.replace_subjects(buttons)
        self._update_key_center_buttons()

    def set_absolute_relative_button(self, absolute_relative_button):
        self._absolute_relative_button = absolute_relative_button
        self._on_absolute_relative_value.subject = absolute_relative_button
        self._update_absolute_relative_button()

    def set_diatonic_chromatic_button(self, diatonic_chromatic_button):
        self._diatonic_chromatic_button = diatonic_chromatic_button
        self._on_diatonic_chromatic_value.subject = diatonic_chromatic_button
        self._update_diatonic_chromatic_button()

    @subject_slot_group('value')
    def _on_key_center_button_value(self, value, sender):
        if self.is_enabled() and (value or not sender.is_momentary()):
            index = list(self._key_center_buttons).index(sender)
            self.key_center = KEY_CENTERS[index]
            self._update_key_center_buttons()
            self._update_data_sources()
            self.notify_scales_changed()

    @subject_slot('value')
    def _on_absolute_relative_value(self, value):
        if self.is_enabled():
            if value != 0 or not self._absolute_relative_button.is_momentary():
                self.is_absolute = not self.is_absolute
                self._update_absolute_relative_button()
                self._update_data_sources()
                self.notify_scales_changed()

    @subject_slot('value')
    def _on_diatonic_chromatic_value(self, value):
        if self.is_enabled():
            if value != 0 or not self._diatonic_chromatic_button.is_momentary(
            ):
                self.is_diatonic = not self.is_diatonic
                self._update_diatonic_chromatic_button()
                self._update_data_sources()
                self.notify_scales_changed()

    @subject_slot('selected_item')
    def _on_selected_modus(self):
        self._update_data_sources()
        self.notify_scales_changed()

    def update(self):
        super(InstrumentScalesComponent, self).update()
        if self.is_enabled():
            self._update_key_center_buttons()
            self._update_absolute_relative_button()
            self._update_diatonic_chromatic_button()

    def _update_key_center_buttons(self):
        if self.is_enabled():
            for index, button in enumerate(self._key_center_buttons):
                if button:
                    button.set_on_off_values('Scales.Selected',
                                             'Scales.Unselected')
                    button.set_light(self.key_center == KEY_CENTERS[index])

    def _update_absolute_relative_button(self):
        if self.is_enabled() and self._absolute_relative_button != None:
            self._absolute_relative_button.set_on_off_values(
                'Scales.FixedOn', 'Scales.FixedOff')
            self._absolute_relative_button.set_light(self.is_absolute)

    def _update_diatonic_chromatic_button(self):
        if self.is_enabled() and self._diatonic_chromatic_button != None:
            self._diatonic_chromatic_button.set_on_off_values(
                'Scales.Diatonic', 'Scales.Chromatic')
            self._diatonic_chromatic_button.set_light(self.is_diatonic)

    def _update_data_sources(self):
        key_index = list(KEY_CENTERS).index(self.key_center)
        key_sources = self._line_sources[0][:6] + self._line_sources[1][:6]
        key_names = [scale.name for scale in self.available_scales]
        for idx, (source, orig) in enumerate(zip(key_sources, key_names)):
            source.set_display_string('   ' + consts.CHAR_SELECT +
                                      orig if idx == key_index else '    ' +
                                      orig)

        self._line_sources[0][6].set_display_string(
            'Fixed: Y' if self.is_absolute else 'Fixed: N')
        self._line_sources[1][6].set_display_string(
            'In Key' if self.is_diatonic else 'Chromatc')
        self._info_sources[1].set_display_string(
            str(self._modus_list.scrollable_list.selected_item))
Esempio n. 12
0
class ValueComponent(ValueComponentBase):
    """
    Component to control one continuous property with a infinite
    touch-sensitive encoder. You can optionally give it a display and
    a button such that the value will be displayed while its pressed.
    """
    shift_button = ButtonControl()
    encoder_factor = 1.0

    def create_display_component(self, *a, **k):
        return ValueDisplayComponent(
            property_name=self._property_name,
            subject=self._subject,
            display_format=self._display_format,
            view_transform=(lambda x: self.view_transform(x)),
            graphic_transform=(lambda x: self.graphic_transform(x)),
            *a,
            **k)

    def __init__(self,
                 property_name=None,
                 subject=None,
                 display_format='%f',
                 model_transform=None,
                 view_transform=None,
                 graphic_transform=None,
                 encoder_factor=None,
                 *a,
                 **k):
        self._property_name = property_name
        self._subject = subject
        self._display_format = display_format
        super(ValueComponent, self).__init__(*a, **k)
        if model_transform is not None:
            self.model_transform = model_transform
        if view_transform is not None:
            self.view_transform = view_transform
        if graphic_transform is not None:
            self.graphic_transform = graphic_transform
        if encoder_factor is not None:
            self.encoder_factor = encoder_factor
        self._original_encoder_factor = self.encoder_factor

    def model_transform(self, x):
        """
        Tranform a value 'x' from the view domain to the domain as
        stored in the subject.
        """
        return x

    def view_transform(self, x):
        """
        Transform a value 'x' from the model domain to the view domain
        as represented to the user.
        """
        return x

    def graphic_transform(self, x):
        """
        Transform a value 'x' from the model domain to [0..1] range to
        be used in the slider-representation of the value.
        """
        return self.view_transform(x) / self.encoder_factor

    @shift_button.pressed
    def shift_button(self, button):
        self.encoder_factor = self._original_encoder_factor / 10.0

    @shift_button.released
    def shift_button(self, button):
        self.encoder_factor = self._original_encoder_factor

    def _on_value(self, value):
        super(ValueComponent, self)._on_value(value)
        value = self.view_transform(getattr(
            self._subject, self._property_name)) + value * self.encoder_factor
        setattr(self._subject, self._property_name,
                self.model_transform(value))
Esempio n. 13
0
class ScrollComponent(ControlSurfaceComponent, Scrollable):
    """
    A component that handles scrolling behavior over a Scrollable
    with a pair of buttons.
    """
    is_private = True
    scrolling_delay = Defaults.MOMENTARY_DELAY
    scrolling_step_delay = 0.1
    default_scrollable = Scrollable()
    default_pager = Scrollable()
    _scrollable = default_scrollable
    default_scroll_skin = dict(color='Enabled',
                               pressed_color='Pressed',
                               disabled_color=False)
    scroll_up_button = ButtonControl(**default_scroll_skin)
    scroll_down_button = ButtonControl(**default_scroll_skin)

    def __init__(self, scrollable=None, *a, **k):
        super(ScrollComponent, self).__init__(*a, **k)
        self._scroll_task_up = self._make_scroll_task(self._do_scroll_up)
        self._scroll_task_down = self._make_scroll_task(self._do_scroll_down)
        if scrollable != None:
            self.scrollable = scrollable

    def _make_scroll_task(self, scroll_step):
        task = self._tasks.add(
            Task.sequence(
                Task.wait(self.scrolling_delay),
                Task.loop(Task.wait(self.scrolling_step_delay),
                          Task.run(scroll_step))))
        task.kill()
        return task

    def _get_scrollable(self):
        return self._scrollable

    def _set_scrollable(self, scrollable):
        self._scrollable = scrollable
        self._update_scroll_buttons()

    scrollable = property(_get_scrollable, _set_scrollable)

    def can_scroll_up(self):
        return self._scrollable.can_scroll_up()

    def can_scroll_down(self):
        return self._scrollable.can_scroll_down()

    def scroll_up(self):
        return self._scrollable.scroll_up()

    def scroll_down(self):
        return self._scrollable.scroll_down()

    def set_scroll_up_button(self, button):
        self.scroll_up_button.set_control_element(button)

    def set_scroll_down_button(self, button):
        self.scroll_down_button.set_control_element(button)

    def _update_scroll_buttons(self):
        self.scroll_up_button.enabled = self.can_scroll_up()
        self.scroll_down_button.enabled = self.can_scroll_down()

    @scroll_up_button.pressed
    def scroll_up_button(self, button):
        self._on_scroll_pressed(button, self._do_scroll_up,
                                self._scroll_task_up)

    @scroll_up_button.released
    def scroll_up_button(self, button):
        self._on_scroll_released(self._scroll_task_up)

    @scroll_down_button.pressed
    def scroll_down_button(self, button):
        self._on_scroll_pressed(button, self._do_scroll_down,
                                self._scroll_task_down)

    @scroll_down_button.released
    def scroll_down_button(self, button):
        self._on_scroll_released(self._scroll_task_down)

    def _do_scroll_up(self):
        self.scroll_up()
        self._update_scroll_buttons()

    def _do_scroll_down(self):
        self.scroll_down()
        self._update_scroll_buttons()

    def update(self):
        super(ScrollComponent, self).update()
        self._update_scroll_buttons()

    def _on_scroll_pressed(self, button, scroll_step, scroll_task):
        if not not self._scroll_task_up.is_killed:
            is_scrolling = not self._scroll_task_down.is_killed
            if not is_scrolling:
                scroll_step()
            button.enabled and scroll_task.restart()
        self._ensure_scroll_one_direction()

    def _on_scroll_released(self, scroll_task):
        scroll_task.kill()
        self._ensure_scroll_one_direction()

    def _ensure_scroll_one_direction(self):
        if self.scroll_up_button.is_pressed and self.scroll_down_button.is_pressed:
            self._scroll_task_up.pause()
            self._scroll_task_down.pause()
        else:
            self._scroll_task_up.resume()
            self._scroll_task_down.resume()
Esempio n. 14
0
class LaunchpadModHandler(FrameworkModHandler):

    Shift_button = ButtonControl()
    Alt_button = ButtonControl()
    _name = 'LaunchpadModHandler'

    def __init__(self, register_component=None, *a, **k):
        self._color_type = 'Push'
        self._grid = None
        super(LaunchpadModHandler,
              self).__init__(register_component=register_component, *a, **k)
        self.nav_box = self.register_component(
            FrameworkNavigationBox(parent=self,
                                   height=16,
                                   width=16,
                                   window_x=8,
                                   window_y=8,
                                   callback=self.set_offset,
                                   register_component=register_component,
                                   song=self._song))
        self.nav_box._on_off_values = ('Mixer.Solo.Off', 'Mixer.Arm.On')
        self._launchmodColors = list(range(128))
        launchmod_colors = [3, 13, 37, 53, 5, 21, 49]
        self._launchmodColors[1:127] = [
            launchmod_colors[x % 7] for x in range(126)
        ]
        self._launchmodColors[127] = 49
        self._shifted = False

    def select_mod(self, mod):
        super(LaunchpadModHandler, self).select_mod(mod)
        #self._script._select_note_mode()
        self.update()
        debug('lp2modhandler select mod: ' + str(mod))

    def _receive_grid(self,
                      x,
                      y,
                      value=-1,
                      identifier=-1,
                      channel=-1,
                      *a,
                      **k):
        # debug('lp2modhandler._receive_grid:', x, y, value, identifier, channel)
        mod = self.active_mod()
        if mod and self._grid_value.subject:
            if mod.legacy:
                x = x - self.x_offset
                y = y - self.y_offset
            if x in range(8) and y in range(8):
                value > -1 and self._grid_value.subject.send_value(
                    x, y, self._launchmodColors[self._colors[value]], True)
                button = self._grid_value.subject.get_button(y, x)
                if button:
                    new_identifier = identifier if identifier > -1 else button._original_identifier
                    new_channel = channel if channel > -1 else button._original_channel
                    button._msg_identifier != new_identifier and button.set_identifier(
                        new_identifier)
                    button._msg_channel != new_channel and button.set_channel(
                        new_channel)
                    button._report_input = True
                    button.suppress_script_forwarding = ((channel, identifier)
                                                         != (-1, -1))

    def _receive_key(self, x, value):
        # debug('lp2modhandler._receive_key:', x, value)
        if not self._keys_value.subject is None:
            self._keys_value.subject.send_value(
                x, 0, self._launchmodColors[self._colors[value]], True)

    def nav_update(self):
        self.nav_box and self.nav_box.update()

    def set_shift_button(self, button):
        self._shift_value.subject = button
        if button:
            button.send_value(127)

    def update(self, *a, **k):
        if not self._shift_value.subject is None:
            self._shift_value.subject.send_value(127)
        mod = self.active_mod()
        if not mod is None:
            mod.restore()
        else:
            if not self._grid_value.subject is None:
                self._grid_value.subject.reset()
            if not self._keys_value.subject is None:
                self._keys_value.subject.reset()
Esempio n. 15
0
class MixerComponent(MixerComponentBase):
    toggle_view_button = ButtonControl()
    sends_volumes_toggle_button = ButtonControl()
    master_select_button = ButtonControl()
    tracks_activate_send_button = ButtonControl()
    crossfader_control_light = ButtonControl()
    tempo_control_light = ButtonControl()
    prehear_volume_light = ButtonControl()
    master_volume_light = ButtonControl()
    cf_control = None
    prehear_control = None
    master_control = None
    sends_mode = 'A'
    controls_mode = 'send'
    switch_sends_button = ButtonControl()
    send_buttons_mode = None
    sends_for_selected_track_only = False
    send_buttons = control_list(ButtonControl, control_count=6)
    send_controls_lights = control_list(ButtonControl, control_count=12)
    track_activate_send_buttons = control_list(ButtonControl, control_count=8)
    empty_button = ButtonControl(color='Color.Off')
    track_activators = {}
    all_track_activators = False
    count_activated_send_tracks = 0

    def __init__(self, *a, **k):
        self.send_controls = [
            EncoderElement(MIDI_CC_TYPE, 8, SEND_CONTROLS[0],
                           Live.MidiMap.MapMode.absolute),
            EncoderElement(MIDI_CC_TYPE, 8, SEND_CONTROLS[1],
                           Live.MidiMap.MapMode.absolute),
            EncoderElement(MIDI_CC_TYPE, 8, SEND_CONTROLS[2],
                           Live.MidiMap.MapMode.absolute),
            EncoderElement(MIDI_CC_TYPE, 8, SEND_CONTROLS[3],
                           Live.MidiMap.MapMode.absolute),
            EncoderElement(MIDI_CC_TYPE, 8, SEND_CONTROLS[4],
                           Live.MidiMap.MapMode.absolute),
            EncoderElement(MIDI_CC_TYPE, 8, SEND_CONTROLS[5],
                           Live.MidiMap.MapMode.absolute),
            EncoderElement(MIDI_CC_TYPE, 8, SEND_CONTROLS[6],
                           Live.MidiMap.MapMode.absolute),
            EncoderElement(MIDI_CC_TYPE, 8, SEND_CONTROLS[7],
                           Live.MidiMap.MapMode.absolute),
            EncoderElement(MIDI_CC_TYPE, 8, SEND_CONTROLS[8],
                           Live.MidiMap.MapMode.absolute),
            EncoderElement(MIDI_CC_TYPE, 8, SEND_CONTROLS[9],
                           Live.MidiMap.MapMode.absolute),
            EncoderElement(MIDI_CC_TYPE, 8, SEND_CONTROLS[10],
                           Live.MidiMap.MapMode.absolute),
            EncoderElement(MIDI_CC_TYPE, 8, SEND_CONTROLS[11],
                           Live.MidiMap.MapMode.absolute)
        ]
        super(MixerComponent, self).__init__(*a, **k)

    def _create_strip(self):
        return ChannelStripComponent()

    def enable_sends_for_selected_track_only(self, enabled):
        self.sends_for_selected_track_only = True if enabled else False

        if self.sends_for_selected_track_only:
            for strip in self._channel_strips:
                strip.sends_off()
            self.update_sends_for_selected_track()
        else:
            for control in self.send_controls:
                control.release_parameter()
            self.update_sends()

    def clear_buttons(self):
        self.send_buttons_mode = None

        for button in self.send_buttons:
            button.color = 'Color.Off'
            button.set_control_element(None)

        for button in self.track_activate_send_buttons:
            button.color = 'Color.Off'
            button.set_control_element(None)

        #self.sends_volumes_toggle_button.color = 'Color.Off'
        #self.sends_volumes_toggle_button.set_control_element(None)
        #self.toggle_view_button.color = 'Color.Off'
        #self.toggle_view_button.set_control_element(None)
        self.switch_sends_button.color = 'Color.Off'
        self.switch_sends_button.set_control_element(None)
        self.master_select_button.color = 'Color.Off'
        self.master_select_button.set_control_element(None)
        self.tracks_activate_send_button.color = 'Color.Off'
        self.tracks_activate_send_button.set_control_element(None)

    def update_sends_for_selected_track(self):
        track = self.song().view.selected_track

        if track == self.song().master_track:
            for control in self.send_controls:
                control.release_parameter()
        else:
            count = 0

            if self.sends_mode == 'B':
                count = 6

            for control in self.send_controls:
                if count < len(track.mixer_device.sends):
                    control.connect_to(track.mixer_device.sends[count])
                else:
                    control.release_parameter()
                count += 1

    def update_sends(self):
        tracks = self.song().tracks
        self.all_track_activators = True

        for strip, button in izip_longest(self._channel_strips,
                                          self.track_activate_send_buttons):
            if strip._track in tracks:
                index = list(tracks).index(strip._track)

                if index in self.track_activators:
                    if self.track_activators[index] == True:
                        if self.controls_mode == 'send':
                            strip.sends_on(self.sends_mode)
                        button.color = 'Color.TrackActivatedSend'
                    else:
                        strip.sends_off()
                        button.color = 'Color.TrackUnactivatedSend'
                        self.all_track_activators = False
                else:
                    self.track_activators[index] = False
                    strip.sends_off()
                    button.color = 'Color.TrackUnactivatedSend'
                    self.all_track_activators = False
            else:
                button.color = 'Color.Off'

        if self.all_track_activators == True:
            self.tracks_activate_send_button.color = 'Color.TracksActivatedSend'
        else:
            self.tracks_activate_send_button.color = 'Color.TracksUnactivatedSend'

    def update_controls_mode(self):
        length = len(self.song().return_tracks)
        return_tracks = self.song().return_tracks

        if self.controls_mode == 'volume':
            self.sends_volumes_toggle_button.color = 'Color.SendsVolumesToggle'

            for strip in self._channel_strips:
                strip.sends_off()

            for i in xrange(12):
                if i < length:
                    self.send_controls_lights[i].color = 'Color.VolumeControls'
                    self.send_controls[i].connect_to(
                        return_tracks[i].mixer_device.volume)
                else:
                    self.send_controls_lights[i].color = 'Color.Off'
        elif self.controls_mode == 'send':
            self.sends_volumes_toggle_button.color = 'Color.Off'

            for control in self.send_controls:
                control.release_parameter()

            for i in xrange(12):
                if i < length:
                    self.send_controls_lights[i].color = 'Color.SendControls'
                else:
                    self.send_controls_lights[i].color = 'Color.Off'
                    self.send_controls[i].release_parameter()
            self.update_sends()

    def enable_volumes(self):
        master_track = self.song().master_track

        if self.is_enabled():
            if self.cf_control != None:
                self.crossfader_control_light.color = "Color.CrossControlOn"
                self.cf_control.connect_to(
                    master_track.mixer_device.crossfader)
            if self.prehear_control != None:
                self.prehear_volume_light.color = "Color.PrehearVolumeOn"
                self.prehear_control.connect_to(
                    master_track.mixer_device.cue_volume)
            if self.master_control != None:
                self.master_volume_light.color = "Color.MasterVolumeOn"
                self.master_control.connect_to(
                    master_track.mixer_device.volume)

    def disable_volumes(self):
        if self.cf_control != None:
            self.crossfader_control_light.color = "Color.CrossControlOff"
            self.cf_control.release_parameter()
        if self.prehear_control != None:
            self.prehear_volume_light.color = "Color.PrehearVolumeOff"
            self.prehear_control.release_parameter()
        if self.master_control != None:
            self.master_volume_light.color = "Color.MasterVolumeOff"
            self.master_control.release_parameter()

    @sends_volumes_toggle_button.pressed_delayed
    def sends_volumes_toggle_button(self, button):
        self.controls_mode = 'volume'
        self.update_controls_mode()
        #self.enable_volumes()

    @sends_volumes_toggle_button.released_delayed
    def sends_volumes_toggle_button(self, button):
        self.controls_mode = 'send'
        self.update_controls_mode()
        #self.disable_volumes()

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

    @track_activate_send_buttons.pressed
    def track_activate_send_buttons(self, button):
        button_index = button.index
        tracks = self.song().tracks
        strip = self._channel_strips[button_index]

        if strip._track in tracks:
            index = list(tracks).index(strip._track)

            if self.track_activators[index] == True:
                if self.count_activated_send_tracks == 0:
                    for i in self.track_activators:
                        self.track_activators[i] = False
                elif self.count_activated_send_tracks > 0:
                    self.track_activators[index] = False
            else:
                if self.count_activated_send_tracks == 0:
                    for i in self.track_activators:
                        self.track_activators[i] = False
                    self.track_activators[index] = True
                elif self.count_activated_send_tracks > 0:
                    self.track_activators[index] = True

            self.count_activated_send_tracks += 1
            self.update_sends()

    @track_activate_send_buttons.released
    def track_activate_send_buttons(self, button):
        button_index = button.index
        tracks = self.song().tracks
        strip = self._channel_strips[button_index]

        if strip._track in tracks:
            index = list(tracks).index(strip._track)

            self.count_activated_send_tracks -= 1

    @tracks_activate_send_button.pressed
    def tracks_activate_send_button(self, button):
        self.all_track_activators = not self.all_track_activators

        for track in self.track_activators:
            self.track_activators[track] = self.all_track_activators

        self.update_sends()

    @send_buttons.pressed
    def send_buttons(self, button):
        if self.sends_mode == 'A':
            index = button.index
        elif self.sends_mode == 'B':
            index = 6 + button.index

        if index < len(self.song().return_tracks):
            if self.send_buttons_mode == 'select':
                self.song().view.selected_track = self.song(
                ).return_tracks[index]
            elif self.send_buttons_mode == 'mute':
                self.song().return_tracks[index].mute = not self.song(
                ).return_tracks[index].mute

            self.on_return_tracks_changed()

    @switch_sends_button.pressed
    def switch_sends_button(self, button):
        length = len(self.song().return_tracks)

        if length > 6:
            if self.sends_mode == 'A':
                self.sends_mode = 'B'
            elif self.sends_mode == 'B':
                self.sends_mode = 'A'

            self.on_return_tracks_changed()

            if self.sends_for_selected_track_only:
                self.update_sends_for_selected_track()
            else:
                self.update_sends()

    @master_select_button.pressed
    def master_select_button(self, button):
        if self.song().view.selected_track != self.song().master_track:
            self.song().view.selected_track = self.song().master_track

    def _reassign_tracks(self):
        self.all_track_activators = False
        for track in self.track_activators:
            self.track_activators[track] = self.all_track_activators

        for strip in self._channel_strips:
            strip.sends_off()

        MixerComponentBase._reassign_tracks(self)
        self.update_sends()

    def on_selected_track_changed(self):
        MixerComponentBase.on_selected_track_changed(self)
        self.on_master_selected_track_changed()
        self.on_return_tracks_changed()

        if self.sends_for_selected_track_only:
            self.update_sends_for_selected_track()

    def on_master_selected_track_changed(self):
        if self.song().view.selected_track != self.song().master_track:
            self.master_select_button.color = 'Color.MasterUnselected'
        else:
            self.master_select_button.color = 'Color.MasterSelected'

    def on_track_list_changed(self):
        MixerComponentBase.on_track_list_changed(self)
        self.on_return_tracks_changed()
        self.update_controls_mode()

        if not self.sends_for_selected_track_only:
            self.update_sends()

    def on_return_tracks_changed(self):
        length = len(self.song().return_tracks)
        return_tracks = self.song().return_tracks

        if length > 6:
            if self.sends_mode == 'A':
                side_len = 6
                i_plus = 0
                send_color = 'Color.SendsA'
            elif self.sends_mode == 'B':
                side_len = length - 6
                i_plus = 6
                send_color = 'Color.SendsB'

            self.switch_sends_button.color = send_color

            for i in xrange(6):
                if i < side_len:
                    self.set_send_button_light(return_tracks[i + i_plus], i)
                else:
                    self.send_buttons[i].color = 'Color.Off'
        else:
            self.sends_mode = 'A'
            self.switch_sends_button.color = 'Color.Off'

            for i in xrange(6):
                if i < length:
                    self.set_send_button_light(return_tracks[i], i)
                else:
                    self.send_buttons[i].color = 'Color.Off'

    def set_send_button_light(self, track, index):
        if self.send_buttons_mode == 'select':
            if self.song().view.selected_track == track:
                self.send_buttons[index].color = 'Color.TrackSelected'
            else:
                self.send_buttons[index].color = 'Color.TrackUnselected'

        elif self.send_buttons_mode == 'mute':
            if track.mute:
                self.send_buttons[index].color = 'Color.SendMuteOn'
            else:
                self.send_buttons[index].color = 'Color.SendMuteOff'

    def set_send_select_buttons(self, buttons):
        if buttons:
            self.send_buttons_mode = 'select'
            self.send_buttons.set_control_element(buttons)
            self.on_return_tracks_changed()

    def set_send_mute_buttons(self, buttons):
        if buttons:
            self.send_buttons_mode = 'mute'
            self.send_buttons.set_control_element(buttons)
            self.on_return_tracks_changed()

    def set_switch_sends_button(self, button):
        if button:
            self.switch_sends_button.set_control_element(button)
            self.on_return_tracks_changed()

    def set_send_controls_lights(self, controls):
        self.send_controls_lights.set_control_element(controls)
        self.on_return_tracks_changed()

    def set_track_select_buttons(self, buttons):
        for strip, button in izip_longest(self._channel_strips, buttons or []):
            if button:
                button.set_on_off_values('Color.TrackSelected',
                                         'Color.TrackUnselected')
            strip.set_select_button(button)

    def set_arm_buttons(self, buttons):
        for strip, button in izip_longest(self._channel_strips, buttons or []):
            if button:
                button.set_on_off_values('Color.ArmSelected',
                                         'Color.ArmUnselected')
            strip.set_arm_button(button)

    def set_solo_buttons(self, buttons):
        for strip, button in izip_longest(self._channel_strips, buttons or []):
            if button:
                button.set_on_off_values('Color.SoloOn', 'Color.SoloOff')
            strip.set_solo_button(button)

    def set_mute_buttons(self, buttons):
        for strip, button in izip_longest(self._channel_strips, buttons or []):
            if button:
                button.set_on_off_values('Color.MuteOn', 'Color.MuteOff')
            strip.set_mute_button(button)

    def set_crossfader_buttons_A(self, buttons):
        for strip, button in izip_longest(self._channel_strips, buttons or []):
            if button:
                button.set_on_off_values('Color.CrossOn', 'Color.CrossOff')
            strip.set_crossfade_toggle_A(button)

    def set_crossfader_buttons_B(self, buttons):
        for strip, button in izip_longest(self._channel_strips, buttons or []):
            if button:
                button.set_on_off_values('Color.CrossOn', 'Color.CrossOff')
            strip.set_crossfade_toggle_B(button)

    def set_master_select_button(self, button):
        if button:
            self.master_select_button.set_control_element(button)
            self.on_master_selected_track_changed()

    def set_track_activate_send_buttons(self, buttons):
        if buttons:
            tracks = self.song().tracks

            for strip, track, button in izip_longest(
                    self._channel_strips, self.track_activate_send_buttons,
                    buttons):
                track.set_control_element(button)
            self.update_sends()

    def set_tracks_activate_send_button(self, button):
        if button:
            tracks = self.song().tracks
            self.all_track_activators = True

            for strip in self._channel_strips:
                if strip._track in tracks:
                    index = list(tracks).index(strip._track)
                    if index in self.track_activators and self.track_activators[
                            index] == False:
                        self.all_track_activators = False

            self.tracks_activate_send_button.set_control_element(button)
            self.update_sends()

    def set_crossfader_control_light(self, button):
        if button:
            self.crossfader_control_light.set_control_element(button)
            self.crossfader_control_light.color = "Color.CrossControlOn"
            self.crossfader_control_light.enabled = True

    def set_tempo_control_light(self, button):
        if button:
            self.tempo_control_light.set_control_element(button)
            self.tempo_control_light.color = "Color.TempoControlOff"
            self.tempo_control_light.enabled = True

    def set_prehear_volume_light(self, button):
        if button:
            self.prehear_volume_light.set_control_element(button)
            self.prehear_volume_light.color = "Color.PrehearVolumeOn"
            self.prehear_volume_light.enabled = True

    def set_master_volume_light(self, button):
        if button:
            self.master_volume_light.set_control_element(button)
            self.master_volume_light.color = "Color.MasterVolumeOn"
            self.master_volume_light.enabled = True

    def set_cf_control(self, control):
        if control:
            self.cf_control = control
            self.cf_control.connect_to(
                self.song().master_track.mixer_device.crossfader)

    def set_prehear_control(self, control):
        if control:
            self.prehear_control = control
            self.prehear_control.connect_to(
                self.song().master_track.mixer_device.cue_volume)

    def set_master_control(self, control):
        if control:
            self.master_control = control
            self.master_control.connect_to(
                self.song().master_track.mixer_device.volume)

    def set_sends_volumes_toggle_button(self, button):
        if button:
            self.sends_volumes_toggle_button.set_control_element(button)
            self.update_controls_mode()

    def set_toggle_view_button(self, button):
        if button:
            self.toggle_view_button.set_control_element(button)
            self.toggle_view_button.color = 'Color.ToggleView'
Esempio n. 16
0
class ClipActionsComponent(ControlSurfaceComponent, Subject):
    u"""
    Component that provides control over the playing clip on a track
    and notifies listeners when this changes.
    """
    delete_button = ButtonControl(**ACTION_BUTTON_COLORS)
    duplicate_button = ButtonControl(**ACTION_BUTTON_COLORS)
    double_button = ButtonControl(**ACTION_BUTTON_COLORS)
    quantize_button = ButtonControl(**ACTION_BUTTON_COLORS)
    quantization_component = None
    __subject_events__ = (u'selected_clip', )

    def __init__(self, target_track_component, *a, **k):
        super(ClipActionsComponent, self).__init__(*a, **k)
        self._target_track_component = target_track_component
        self._on_track_changed.subject = self._target_track_component
        self._use_selected_track = False
        self._selected_clip = None
        self._track = self.song().view.selected_track
        self._on_selection_changed()

    def use_selected_track(self, use_selected_track):
        self._use_selected_track = use_selected_track
        if use_selected_track:
            self._track = self.song().view.selected_track
        else:
            self._track = self._target_track_component.target_track
        self._on_selection_changed()

    @delete_button.pressed
    def delete_button(self, button):
        if self.can_perform_clip_action():
            self._selected_clip.canonical_parent.delete_clip()

    @duplicate_button.pressed
    def duplicate_button(self, button):
        if self.can_perform_clip_action():
            duplicate_clip(self.song(),
                           self._selected_clip.canonical_parent,
                           should_launch=True)

    @double_button.pressed
    def double_button(self, button):
        if self.can_perform_midi_clip_action():
            double_clip(self._selected_clip)

    @quantize_button.pressed
    def quantize_button(self, button):
        if self.can_perform_clip_action() and self.quantization_component:
            self.quantization_component.quantize_clip(self._selected_clip)

    def delete_pitch(self, pitch):
        if self.can_perform_midi_clip_action():
            clip = self._selected_clip
            loop_length = clip.loop_end - clip.loop_start
            clip.remove_notes(clip.loop_start, pitch, loop_length, 1)

    def on_selected_track_changed(self):
        if self._use_selected_track:
            self._track = self.song().view.selected_track
            self._on_selection_changed()

    @subject_slot(u'target_track')
    def _on_track_changed(self):
        if not self._use_selected_track:
            self._track = self._target_track_component.target_track
            self._on_selection_changed()

    @subject_slot(u'playing_slot_index')
    def _on_selection_changed(self):
        self._selected_clip = None
        if self._track in self.song().tracks:
            slot_index = self._track.playing_slot_index
            if slot_index >= 0 and self._track.clip_slots[slot_index].has_clip:
                self._selected_clip = self._track.clip_slots[slot_index].clip
            self._on_selection_changed.subject = self._track
        else:
            self._on_selection_changed.subject = None
        self._update_control_states()

    def _update_control_states(self):
        can_perform_clip_action = self.can_perform_clip_action()
        self.delete_button.enabled = can_perform_clip_action
        self.duplicate_button.enabled = can_perform_clip_action
        self.quantize_button.enabled = can_perform_clip_action
        self.double_button.enabled = self.can_perform_midi_clip_action()
        self.notify_selected_clip()

    def can_perform_clip_action(self):
        return self._selected_clip is not None

    def can_perform_midi_clip_action(self):
        return self._selected_clip is not None and self._selected_clip.is_midi_clip
class MixerComponent(MixerComponentBase):
    next_sends_button = ButtonControl()
    prev_sends_button = ButtonControl()

    def __init__(self, *a, **k):
        super(MixerComponent, self).__init__(*a, **k)
        self._update_send_buttons()

    def _create_strip(self):
        return ChannelStripComponent()

    def set_send_controls(self, controls):
        self._send_controls = controls
        for index, channel_strip in enumerate(self._channel_strips):
            if self.send_index is None:
                channel_strip.set_send_controls([None])
            else:
                send_controls = [
                    controls.get_button(index, i) for i in xrange(2)
                ] if controls else [None]
                skipped_sends = [None for _ in xrange(self.send_index)]
                channel_strip.set_send_controls(skipped_sends + send_controls)

        return

    def set_send_lights(self, lights):
        for index, channel_strip in enumerate(self._channel_strips):
            elements = None
            if lights is not None:
                lights.reset()
                elements = None if self.send_index is None else [
                    lights.get_button(index, i) for i in xrange(2)
                ]
            channel_strip.send_lights.set_control_element(elements)

        return

    def set_pan_lights(self, lights):
        for strip, light in izip_longest(self._channel_strips, lights or []):
            strip.pan_light.set_control_element(light)

    def _get_send_index(self):
        return super(MixerComponent, self)._get_send_index()

    def _set_send_index(self, index):
        if index is not None and index % 2 > 0:
            index -= 1
        super(MixerComponent, self)._set_send_index(index)
        self._update_send_buttons()
        return

    send_index = property(_get_send_index, _set_send_index)

    def _update_send_buttons(self):
        self.next_sends_button.enabled = self.send_index is not None and self.send_index < self.num_sends - 2
        self.prev_sends_button.enabled = self.send_index is not None and self.send_index > 0
        return

    @next_sends_button.pressed
    def next_sends_button(self, button):
        self.send_index = min(self.send_index + 2, self.num_sends - 1)

    @prev_sends_button.pressed
    def prev_sends_button(self, button):
        self.send_index = max(self.send_index - 2, 0)

    def set_track_select_buttons(self, buttons):
        for strip, button in izip_longest(self._channel_strips, buttons or []):
            if button:
                button.set_on_off_values('Mixer.TrackSelected',
                                         'Mixer.TrackUnselected')
            strip.set_select_button(button)

    def set_solo_buttons(self, buttons):
        for strip, button in izip_longest(self._channel_strips, buttons or []):
            if button:
                button.set_on_off_values('Mixer.SoloOn', 'Mixer.SoloOff')
            strip.set_solo_button(button)

    def set_mute_buttons(self, buttons):
        for strip, button in izip_longest(self._channel_strips, buttons or []):
            if button:
                button.set_on_off_values('Mixer.MuteOn', 'Mixer.MuteOff')
            strip.set_mute_button(button)

    def set_arm_buttons(self, buttons):
        for strip, button in izip_longest(self._channel_strips, buttons or []):
            if button:
                button.set_on_off_values('Mixer.ArmSelected',
                                         'Mixer.ArmUnselected')
            strip.set_arm_button(button)
Esempio n. 18
0
class SelectComponent(CompoundComponent):
    """
    This component handles selection of objects.
    """
    select_button = ButtonControl(**SIDE_BUTTON_COLORS)

    def __init__(self, *a, **k):
        super(SelectComponent, self).__init__(*a, **k)
        self._selected_clip = None
        self._selection_display = self.register_component(SelectionDisplayComponent())
        self._selection_display.set_enabled(False)

    selection_display_layer = forward_property('_selection_display')('layer')

    def set_selected_clip(self, clip):
        self._selected_clip = clip
        self._on_playing_position_changed.subject = clip

    def on_select_clip(self, clip_slot):
        if clip_slot != None:
            if self.song().view.highlighted_clip_slot != clip_slot:
                self.song().view.highlighted_clip_slot = clip_slot
            if clip_slot.has_clip:
                clip = clip_slot.clip
                clip_name = clip.name if clip.name != '' else '[unnamed]'
                self.set_selected_clip(clip)
            else:
                clip_name = '[empty slot]'
                self.set_selected_clip(None)
        else:
            clip_name = '[none]'
        self._selection_display.set_display_string('Clip Selection:')
        self._selection_display.set_display_string(clip_name, 1)
        self._do_show_time_remaining()
        self._selection_display.set_enabled(True)

    @subject_slot('playing_position')
    def _on_playing_position_changed(self):
        self._do_show_time_remaining()

    def _do_show_time_remaining(self):
        clip = self._selected_clip
        if clip != None and (clip.is_triggered or clip.is_playing):
            if clip.is_recording:
                label = 'Record Count:'
                length = (clip.playing_position - clip.loop_start) * clip.signature_denominator / clip.signature_numerator
                time = convert_length_to_bars_beats_sixteenths(length)
            else:
                label = 'Time Remaining:'
                length = clip.loop_end - clip.playing_position
                if clip.is_audio_clip and not clip.warping:
                    time = convert_length_to_mins_secs(length)
                else:
                    time = convert_beats_to_mins_secs(length, self.song().tempo)
        else:
            label = ' '
            time = ' '
        self._selection_display.set_display_string(label, 2)
        self._selection_display.set_display_string(time, 3)

    def on_select_scene(self, scene):
        if scene != None:
            if self.song().view.selected_scene != scene:
                self.song().view.selected_scene = scene
            scene_name = scene.name if scene.name != '' else '[unnamed]'
        else:
            scene_name = '[none]'
        self._selection_display.set_display_string('Scene Selection:')
        self._selection_display.set_display_string(scene_name, 1)
        self._selection_display.reset_display_right()
        self._selection_display.set_enabled(True)

    def on_select_track(self, track):
        if track != None:
            track_name = track.name if track.name != '' else '[unnamed]'
        else:
            track_name = '[none]'
        self._selection_display.set_display_string('Track Selection:')
        self._selection_display.set_display_string(track_name, 1)
        self._selection_display.reset_display_right()
        self._selection_display.set_enabled(True)

    def on_select_drum_pad(self, drum_pad):
        if drum_pad != None:
            drum_pad_name = drum_pad.name if drum_pad.name != '' else '[unnamed]'
        else:
            drum_pad_name = '[none]'
        self._selection_display.set_display_string('Pad Selection:')
        self._selection_display.set_display_string(drum_pad_name, 1)
        self._selection_display.reset_display_right()
        self._selection_display.set_enabled(True)

    @select_button.released
    def select_button(self, control):
        self._selection_display.set_enabled(False)
        self._selection_display.reset_display()
        self.set_selected_clip(None)
Esempio n. 19
0
class SelectPlayingClipComponent(ModesComponent):
    action_button = ButtonControl(color='DefaultButton.Alert')

    def __init__(self,
                 playing_clip_above_layer=None,
                 playing_clip_below_layer=None,
                 *a,
                 **k):
        super(SelectPlayingClipComponent, self).__init__(*a, **k)
        self._update_mode_task = self._tasks.add(
            Task.sequence(Task.delay(1), Task.run(self._update_mode)))
        self._update_mode_task.kill()
        self._notification = self.register_component(
            NotificationComponent(notification_time=-1, is_enabled=False))
        self.add_mode('default', None)
        self.add_mode('above', [
            AddLayerMode(self, playing_clip_above_layer), self._notification,
            partial(self._show_notification,
                    MessageBoxText.PLAYING_CLIP_ABOVE_SELECTED_CLIP)
        ])
        self.add_mode('below', [
            AddLayerMode(self, playing_clip_below_layer), self._notification,
            partial(self._show_notification,
                    MessageBoxText.PLAYING_CLIP_BELOW_SELECTED_CLIP)
        ])
        self.selected_mode = 'default'
        self._on_detail_clip_changed.subject = self.song().view
        self._on_playing_slot_index_changed.subject = self.song(
        ).view.selected_track

    notification_layer = forward_property('_notification')('message_box_layer')

    @action_button.pressed
    def action_button(self, button):
        self._go_to_playing_clip()

    @subject_slot('detail_clip')
    def _on_detail_clip_changed(self):
        self._update_mode_task.restart()

    @subject_slot('playing_slot_index')
    def _on_playing_slot_index_changed(self):
        self._update_mode_task.restart()

    def _go_to_playing_clip(self):
        song_view = self.song().view
        playing_clip_slot = self._playing_clip_slot()
        if playing_clip_slot:
            song_view.highlighted_clip_slot = playing_clip_slot
            song_view.detail_clip = playing_clip_slot.clip

    def _show_notification(self, display_text):
        self._notification.show_notification(
            display_text, blink_text=MessageBoxText.SELECTED_CLIP_BLINK)

    def _selected_track_clip_is_playing(self):
        playing_clip_slot = self._playing_clip_slot()
        return playing_clip_slot and not playing_clip_slot.clip != self.song(
        ).view.detail_clip

    def _playing_clip_slot(self):
        track = self.song().view.selected_track
        try:
            playing_slot_index = track.playing_slot_index
            slot = track.clip_slots[
                playing_slot_index] if 0 <= playing_slot_index < len(
                    track.clip_slots) else None
            return slot
        except RuntimeError:
            pass

    def _selected_track_clip_is_above_playing_clip(self):
        song_view = self.song().view
        track = song_view.selected_track
        playing_slot_index = track.playing_slot_index
        selected_index = index_if(
            lambda slot: slot == song_view.highlighted_clip_slot,
            track.clip_slots)
        return playing_slot_index <= selected_index

    def _update_mode(self):
        if not self._selected_track_clip_is_playing():
            if self._selected_track_clip_is_above_playing_clip():
                self.selected_mode = 'above'
            else:
                self.selected_mode = 'below'
        else:
            self.selected_mode = 'default'
Esempio n. 20
0
class SpecialSessionComponent(SessionComponent):
    scene_component_type = SpecialSceneComponent
    delete_button = ButtonControl(color='DefaultButton.Off',
                                  pressed_color='DefaultButton.On')
    quantize_button = ButtonControl(color='DefaultButton.Off',
                                    pressed_color='DefaultButton.On')
    double_button = ButtonControl(color='DefaultButton.Off',
                                  pressed_color='DefaultButton.On')
    duplicate_button = ButtonControl(color='DefaultButton.Off',
                                     pressed_color='DefaultButton.On')

    def __init__(self, *a, **k):
        self._stop_scene_clip_buttons = None
        super(SpecialSessionComponent, self).__init__(*a, **k)

    def set_clip_launch_buttons(self, buttons):
        if buttons:
            buttons.reset()
        super(SpecialSessionComponent, self).set_clip_launch_buttons(buttons)

    def set_stop_track_clip_buttons(self, buttons):
        if buttons:
            buttons.reset()
        super(SpecialSessionComponent,
              self).set_stop_track_clip_buttons(buttons)

    def set_scene_launch_buttons(self, buttons):
        if buttons:
            buttons.reset_state()
        super(SpecialSessionComponent, self).set_scene_launch_buttons(buttons)

    def set_stop_scene_clip_buttons(self, buttons):
        if buttons:
            buttons.reset()
        self._stop_scene_clip_buttons = buttons
        self._on_stop_scene_value.replace_subjects(buttons or [])
        self._update_stop_scene_clip_buttons()

    def set_stop_all_clips_button(self, button):
        if button:
            button.reset()
        super(SpecialSessionComponent, self).set_stop_all_clips_button(button)

    @subject_slot_group('value')
    def _on_stop_scene_value(self, value, button):
        if self.is_enabled():
            if value is not 0 or not button.is_momentary():
                scene_index = list(self._stop_scene_clip_buttons).index(
                    button) + self.scene_offset()
                for track in self.tracks_to_use():
                    if in_range(scene_index, 0, len(track.clip_slots)) and (
                            track.playing_slot_index == scene_index
                            or track.fired_slot_index == scene_index):
                        track.stop_all_clips()

    def update_navigation_buttons(self):
        self._vertical_banking.update()
        self._horizontal_banking.update()

    def _update_stop_clips_led(self, index):
        if self.is_enabled(
        ) and self._stop_track_clip_buttons is not None and index < len(
                self._stop_track_clip_buttons):
            button = self._stop_track_clip_buttons[index]
            if button is not None:
                tracks_to_use = self.tracks_to_use()
                track_index = index + self.track_offset()
                value_to_send = None
                if track_index < len(tracks_to_use):
                    if tracks_to_use[track_index].clip_slots:
                        track = tracks_to_use[track_index]
                        if track.fired_slot_index == -2:
                            value_to_send = self._stop_clip_triggered_value
                        elif track.playing_slot_index >= 0:
                            value_to_send = self._stop_clip_value
                        else:
                            value_to_send = 'Session.StoppedClip'
                if value_to_send is None:
                    button.turn_off()
                elif in_range(value_to_send, 0, 128):
                    button.send_value(value_to_send)
                else:
                    button.set_light(value_to_send)

    def _update_stop_scene_clip_buttons(self):
        if self.is_enabled():
            for index in xrange(self._num_scenes):
                self._update_stop_scene_leds(index)

    def _update_stop_scene_leds(self, index):
        scenes = self.song().scenes
        scene_index = index + self.scene_offset()
        if self.is_enabled(
        ) and self._stop_scene_clip_buttons is not None and index < len(
                self._stop_scene_clip_buttons):
            button = self._stop_scene_clip_buttons[index]
            if button is not None:
                value_to_send = None
                if scene_index < len(
                        scenes) and scenes[scene_index].clip_slots:
                    tracks = self.tracks_to_use()
                    if find_if(
                            lambda x: x.playing_slot_index == scene_index and x
                            .fired_slot_index != -2, tracks):
                        value_to_send = self._stop_clip_value
                    elif find_if(
                            lambda x: x.fired_slot_index == -2 and x.
                            playing_slot_index == scene_index, tracks):
                        value_to_send = self._stop_clip_triggered_value
                    else:
                        value_to_send = 'Session.StoppedClip'
                if value_to_send is None:
                    button.turn_off()
                elif in_range(value_to_send, 0, 128):
                    button.send_value(value_to_send)
                else:
                    button.set_light(value_to_send)

    def _update_stop_all_clips_button(self):
        button = self._stop_all_button
        if button:
            value_to_send = 'Session.StoppedClip'
            tracks = self.tracks_to_use()
            if find_if(
                    lambda x: x.playing_slot_index >= 0 and x.fired_slot_index
                    != -2, tracks):
                value_to_send = self._stop_clip_value
            elif find_if(lambda x: x.fired_slot_index == -2, tracks):
                value_to_send = self._stop_clip_triggered_value
            if value_to_send is None:
                button.turn_off()
            else:
                button.set_light(value_to_send)

    @subject_slot_group('fired_slot_index')
    def _on_fired_slot_index_changed(self, track_index):
        button_index = track_index - self.track_offset()
        self._update_stop_clips_led(button_index)
        self._update_stop_scene_clip_buttons()
        self._update_stop_all_clips_button()

    @subject_slot_group('playing_slot_index')
    def _on_playing_slot_index_changed(self, track_index):
        button_index = track_index - self.track_offset()
        self._update_stop_clips_led(button_index)
        self._update_stop_scene_clip_buttons()
        self._update_stop_all_clips_button()

    def _reassign_scenes(self):
        super(SpecialSessionComponent, self)._reassign_scenes()
        self._update_stop_scene_clip_buttons()

    def update(self):
        super(SpecialSessionComponent, self).update()
        if self._allow_updates:
            self._update_stop_scene_clip_buttons()
class SpecialTransportComponent(ControlSurfaceComponent):
    """ Unlike other SpecialxxxComponents, this doesn't actually extend an existing
    component, it redoes it with skin support and some different features. """
    play_button = SpecialButtonControl(color='Transport.PlayOff', on_color='Transport.PlayOn')
    play_toggle_button = SpecialButtonControl(color='Transport.PlayOff', on_color='Transport.PlayOn')
    continue_button = SpecialButtonControl(color='Transport.ContinueOff', on_color='Transport.ContinueOn')
    stop_button = SpecialButtonControl(color='Transport.StopOff', on_color='Transport.StopOn')
    overdub_button = SpecialButtonControl(color='Transport.OverdubOff', on_color='Transport.OverdubOn')
    record_button = SpecialButtonControl(color='Transport.RecordOff', on_color='Transport.RecordOn')
    metronome_button = SpecialButtonControl(color='Transport.MetronomeOff', on_color='Transport.MetronomeOn')
    loop_button = SpecialButtonControl(color='Transport.LoopOff', on_color='Transport.LoopOn')
    record_quantize_button = SpecialButtonControl(color='Transport.RecordQuantizeOff', on_color='Transport.RecordQuantizeOn')
    prev_cue_button = SpecialButtonControl(color='Transport.CannotJumpToPrevCue', on_color='Transport.CanJumpToPrevCue')
    next_cue_button = SpecialButtonControl(color='Transport.CannotJumpToNextCue', on_color='Transport.CanJumpToNextCue')
    tap_tempo_button = ButtonControl(**dict(color='Transport.TapTempo', pressed_color='Transport.TapTempoPressed', disabled_color='DefaultButton.Disabled'))
    revert_button = ButtonControl(**dict(color='Transport.Revert', pressed_color='Transport.RevertPressed', disabled_color='DefaultButton.Disabled'))

    def __init__(self, name='Transport_Control', *a, **k):
        super(SpecialTransportComponent, self).__init__(name=name, *a, **k)
        self._relative_tempo_control = None
        self._relative_tempo_fine_control = None
        self._ffwd_button = None
        self._rwd_button = None
        self._rwd_task = Task.Task()
        self._ffwd_task = Task.Task()
        self._nudge_down_button = None
        self._nudge_up_button = None
        self._last_record_quantization = RQ.rec_q_sixtenth
        self._on_is_playing_changed.subject = self.song()
        self._on_overdub_changed.subject = self.song()
        self._on_record_mode_changed.subject = self.song()
        self._on_metronome_changed.subject = self.song()
        self._on_loop_changed.subject = self.song()
        self._on_record_quantize_changed.subject = self.song()
        self._on_can_jump_to_prev_cue_changed.subject = self.song()
        self._on_can_jump_to_next_cue_changed.subject = self.song()
        self._end_undo_step_task = self._tasks.add(Task.sequence(Task.wait(1.5), Task.run(self.song().end_undo_step)))
        self._end_undo_step_task.kill()
        return

    def disconnect(self):
        super(SpecialTransportComponent, self).disconnect()
        self._relative_tempo_control = None
        self._relative_tempo_fine_control = None
        self._ffwd_button = None
        self._rwd_button = None
        self._nudge_down_button = None
        self._nudge_up_button = None
        self._rwd_task = None
        self._ffwd_task = None
        self._end_undo_step_task = None
        return

    def set_relative_tempo_control(self, control):
        """ Sets the relative encoder to use for adjusting tempo in 1-BPM increments. """
        self._relative_tempo_control = control
        self._on_relative_tempo_control_value.subject = control

    def set_relative_tempo_fine_control(self, control):
        """ Sets the relative encoder to use for adjusting tempo in .1-BPM increments. """
        self._relative_tempo_fine_control = control
        self._on_relative_tempo_fine_control_value.subject = control

    def set_seek_forward_button(self, ffwd_button):
        """ Sets the button to use for fastforwarding. """
        if self._ffwd_button != ffwd_button:
            self._ffwd_button = ffwd_button
            self._ffwd_value_slot.subject = ffwd_button
            self._ffwd_task.kill()
            self._update_seek_buttons()

    def set_seek_backward_button(self, rwd_button):
        """ Sets the button to use for rewinding. """
        if self._rwd_button != rwd_button:
            self._rwd_button = rwd_button
            self._rwd_value_slot.subject = rwd_button
            self._rwd_task.kill()
            self._update_seek_buttons()

    def set_nudge_down_button(self, button):
        """ Sets the button to use for nudging down. """
        self.song().nudge_down = False
        if self._nudge_down_button != button:
            self._nudge_down_button = button
            self._on_nudge_down_button_value.subject = button
            self._update_nudge_buttons()

    def set_nudge_up_button(self, button):
        """ Sets the button to use for nudging up. """
        self.song().nudge_up = False
        if self._nudge_up_button != button:
            self._nudge_up_button = button
            self._on_nudge_up_button_value.subject = button
            self._update_nudge_buttons()

    @play_button.pressed
    def play_button(self, _):
        self.song().start_playing()

    @play_toggle_button.pressed
    def play_toggle_button(self, _):
        self.song().is_playing = not self.song().is_playing

    @continue_button.pressed
    def continue_button(self, _):
        self.song().continue_playing()

    @stop_button.pressed
    def stop_button(self, _):
        self.song().stop_playing()

    @overdub_button.pressed
    def overdub_button(self, _):
        self.song().overdub = not self.song().overdub

    @record_button.pressed
    def record_button(self, _):
        self.song().record_mode = not self.song().record_mode

    @metronome_button.pressed
    def metronome_button(self, _):
        self.song().metronome = not self.song().metronome

    @loop_button.pressed
    def loop_button(self, _):
        self.song().loop = not self.song().loop

    @record_quantize_button.pressed
    def record_quantize_button(self, _):
        is_on = self.song().midi_recording_quantization != RQ.rec_q_no_q
        if is_on:
            self._last_record_quantization = self.song().midi_recording_quantization
            self.song().midi_recording_quantization = RQ.rec_q_no_q
        else:
            self.song().midi_recording_quantization = self._last_record_quantization

    @prev_cue_button.pressed
    def prev_cue_button(self, _):
        if self.song().can_jump_to_prev_cue:
            self.song().jump_to_prev_cue()

    @next_cue_button.pressed
    def next_cue_button(self, _):
        if self.song().can_jump_to_next_cue:
            self.song().jump_to_next_cue()

    @tap_tempo_button.pressed
    def tap_tempo_button(self, _):
        if not self._end_undo_step_task.is_running:
            self.song().begin_undo_step()
        self._end_undo_step_task.restart()
        self.song().tap_tempo()

    @revert_button.pressed
    def revert_button(self, _):
        self.song().current_song_time = 0

    @subject_slot('value')
    def _on_nudge_down_button_value(self, value):
        if self.is_enabled():
            self.song().nudge_down = value is not 0
            self._nudge_down_button.set_light('Transport.NudgePressed' if value else 'Transport.Nudge')

    @subject_slot('value')
    def _on_nudge_up_button_value(self, value):
        if self.is_enabled():
            self.song().nudge_up = value is not 0
            self._nudge_up_button.set_light('Transport.NudgePressed' if value else 'Transport.Nudge')

    @subject_slot('value')
    def _ffwd_value_slot(self, value):
        if self.is_enabled():
            self._ffwd_value(value)
            self._ffwd_button.set_light('Transport.SeekPressed' if value else 'Transport.SeekIdle')

    def _ffwd_value(self, value):
        if self._ffwd_button.is_momentary():
            self._ffwd_task.kill()
            if value:
                self._ffwd_task = self._tasks.add(partial(self._move_current_song_time, SEEK_SPEED))
        else:
            self.song().current_song_time += 1

    @subject_slot('value')
    def _rwd_value_slot(self, value):
        if self.is_enabled():
            self._rwd_value(value)
            self._rwd_button.set_light('Transport.SeekPressed' if value else 'Transport.SeekIdle')

    def _rwd_value(self, value):
        if self._rwd_button.is_momentary():
            self._rwd_task.kill()
            if value:
                self._rwd_task = self._tasks.add(partial(self._move_current_song_time, -SEEK_SPEED))
        else:
            song = self.song()
            song.current_song_time = max(0.0, song.current_song_time - 1)

    def _move_current_song_time(self, speed, delta):
        song = self.song()
        song.current_song_time = max(0.0, song.current_song_time + speed * delta)
        return Task.RUNNING

    @subject_slot('value')
    def _on_relative_tempo_control_value(self, value):
        self._adjust_tempo(self._relative_tempo_control, value, False)

    @subject_slot('value')
    def _on_relative_tempo_fine_control_value(self, value):
        self._adjust_tempo(self._relative_tempo_fine_control, value, True)

    def _adjust_tempo(self, control, value, is_fine):
        factor = control.get_adjustment_factor(value, 0)
        if is_fine:
            factor *= 0.1
        self.song().tempo = max(20, min(999, self.song().tempo + factor))

    def update(self):
        super(SpecialTransportComponent, self).update()
        self._on_is_playing_changed()
        self._on_overdub_changed()
        self._on_record_mode_changed()
        self._on_metronome_changed()
        self._on_loop_changed()
        self._on_record_quantize_changed()
        self._on_can_jump_to_prev_cue_changed()
        self._on_can_jump_to_next_cue_changed()
        self._update_nudge_buttons()
        self._update_seek_buttons()

    def _update_nudge_buttons(self):
        if self.is_enabled():
            if self._nudge_down_button:
                self._nudge_down_button.set_light('Transport.Nudge')
            if self._nudge_up_button:
                self._nudge_up_button.set_light('Transport.Nudge')

    def _update_seek_buttons(self):
        if self.is_enabled():
            if self._ffwd_button:
                self._ffwd_button.set_light('Transport.SeekIdle')
            if self._rwd_button:
                self._rwd_button.set_light('Transport.SeekIdle')

    @subject_slot('is_playing')
    def _on_is_playing_changed(self):
        self._update_play_button()
        self._update_play_toggle_button()
        self._update_continue_button()
        self._update_stop_button()

    @subject_slot('overdub')
    def _on_overdub_changed(self):
        if self.is_enabled():
            self.overdub_button.is_on = self.song().overdub

    @subject_slot('record_mode')
    def _on_record_mode_changed(self):
        if self.is_enabled():
            self.record_button.is_on = self.song().record_mode

    @subject_slot('metronome')
    def _on_metronome_changed(self):
        if self.is_enabled():
            self.metronome_button.is_on = self.song().metronome

    @subject_slot('loop')
    def _on_loop_changed(self):
        if self.is_enabled():
            self.loop_button.is_on = self.song().loop

    @subject_slot('midi_recording_quantization')
    def _on_record_quantize_changed(self):
        if self.is_enabled():
            self.record_quantize_button.is_on = self.song().midi_recording_quantization != RQ.rec_q_no_q

    @subject_slot('can_jump_to_prev_cue')
    def _on_can_jump_to_prev_cue_changed(self):
        if self.is_enabled():
            self.prev_cue_button.is_on = self.song().can_jump_to_prev_cue

    @subject_slot('can_jump_to_next_cue')
    def _on_can_jump_to_next_cue_changed(self):
        if self.is_enabled():
            self.next_cue_button.is_on = self.song().can_jump_to_next_cue

    def _update_play_button(self):
        if self.is_enabled():
            self.play_button.is_on = self.song().is_playing

    def _update_play_toggle_button(self):
        if self.is_enabled():
            self.play_toggle_button.is_on = self.song().is_playing

    def _update_continue_button(self):
        if self.is_enabled():
            self.continue_button.is_on = self.song().is_playing

    def _update_stop_button(self):
        if self.is_enabled():
            self.stop_button.is_on = not self.song().is_playing
class DeviceComponent(DeviceComponentBase):
    device_nav_left_button = ButtonControl()
    device_nav_right_button = ButtonControl()

    def __init__(self, *a, **k):
        super(DeviceComponent, self).__init__(*a, **k)
        new_banks = {}
        new_bank_names = {}
        self._device_banks = DEVICE_DICT
        self._device_bank_names = BANK_NAME_DICT
        self._device_best_banks = DEVICE_BOB_DICT
        for device_name, current_banks in self._device_banks.iteritems():
            if len(current_banks) > 1:
                raise device_name in self._device_best_banks.keys(
                ) or AssertionError(
                    "Could not find best-of-banks for '%s'" % device_name)
                raise device_name in self._device_bank_names.keys(
                ) or AssertionError(
                    "Could not find bank names for '%s'" % device_name)
                current_banks = self._device_best_banks[
                    device_name] + current_banks
                new_bank_names[device_name] = (
                    BOB_BANK_NAME, ) + self._device_bank_names[device_name]
            new_banks[device_name] = current_banks

        self._device_banks = new_banks
        self._device_bank_names = new_bank_names

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

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

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

    def _is_banking_enabled(self):
        return True

    def _number_of_parameter_banks(self):
        result = 0
        if self._device != None:
            if self._device.class_name in self._device_banks.keys():
                result = len(self._device_banks[self._device.class_name])
            else:
                result = DeviceComponent._number_of_parameter_banks(self)
        return result

    def _parameter_banks(self):
        return parameter_banks(self._device, self._device_banks)

    def _parameter_bank_names(self):
        return parameter_bank_names(self._device, self._device_bank_names)

    def _update_device_bank_buttons(self):
        if self.is_enabled():
            bank_length = len(self._parameter_banks())
            for index, button in enumerate(self._bank_buttons or []):
                if button:
                    value_to_send = False
                    if index == self._bank_index and self._device:
                        value_to_send = 'Device.BankSelected'
                    elif index == 0:
                        value_to_send = 'Device.BestOfBank'
                    elif index in xrange(bank_length):
                        value_to_send = 'Device.Bank'
                    button.set_light(value_to_send)
Esempio n. 23
0
class SessionComponent(SessionComponentBase):
    _clip_horisontal_buttons = None
    _scene_vertical_buttons = None
    scene_play_button = ButtonControl()
    scene_stop_button = ButtonControl()

    def __init__(self, *a, **k):
        super(SessionComponent, self).__init__(*a, **k)

        self._clip_horisontal_buttons = ScrollComponent()
        self._clip_horisontal_buttons.can_scroll_up = self.can_clip_left
        self._clip_horisontal_buttons.can_scroll_down = self.can_clip_right
        self._clip_horisontal_buttons.scroll_up = self.clip_left
        self._clip_horisontal_buttons.scroll_down = self.clip_right

        self._scene_vertical_buttons = ScrollComponent()
        self._scene_vertical_buttons.can_scroll_up = self.can_scene_up
        self._scene_vertical_buttons.can_scroll_down = self.can_scene_down
        self._scene_vertical_buttons.scroll_up = self.scene_up
        self._scene_vertical_buttons.scroll_down = self.scene_down

    @scene_play_button.pressed
    def scene_play_button(self, button):
        tracks = self.song().tracks
        track_offset = self.track_offset()
        scene_offset = self.scene_offset()

        for x in xrange(self._num_tracks):
            if track_offset + x < len(tracks):
                clip = tracks[track_offset + x].clip_slots[scene_offset]
                clip.fire()

    @scene_stop_button.pressed
    def scene_stop_button(self, button):
        tracks = self.song().tracks
        track_offset = self.track_offset()
        scene_offset = self.scene_offset()

        for x in xrange(self._num_tracks):
            if track_offset + x < len(tracks):
                clip = tracks[track_offset + x].clip_slots[scene_offset]
                if clip.has_clip:
                    clip.stop()

    def on_selected_scene_changed(self):
        super(SessionComponent, self).on_selected_scene_changed()
        if self._clip_horisontal_buttons:
            self._clip_horisontal_buttons.update()
        if self._scene_vertical_buttons:
            self._scene_vertical_buttons.update()

    def _reassign_tracks(self):
        if self._clip_horisontal_buttons:
            self._clip_horisontal_buttons.update()
        super(SessionComponent, self)._reassign_tracks()

    def _reassign_scenes(self):
        if self._scene_vertical_buttons:
            self._scene_vertical_buttons.update()
        super(SessionComponent, self)._reassign_scenes()

    def can_clip_left(self):
        selected_track = self.song().view.selected_track
        tracks = self.song().tracks
        return selected_track != tracks[0]

    def can_clip_right(self):
        selected_track = self.song().view.selected_track
        tracks = self.song().tracks
        return selected_track != tracks[(-1)]

    def clip_left(self):
        selected_track = self.song().view.selected_track
        tracks = self.song().tracks

        if selected_track in tracks:
            index = list(tracks).index(selected_track) - 1
        else:
            index = self.track_offset()

        self.song().view.selected_track = tracks[index]

    def clip_right(self):
        selected_track = self.song().view.selected_track
        tracks = self.song().tracks

        if selected_track in tracks:
            index = list(tracks).index(selected_track) + 1
        else:
            index = self.track_offset()

        self.song().view.selected_track = tracks[index]

    def can_scene_up(self):
        return self._get_minimal_scene_offset() > 0

    def can_scene_down(self):
        return len(self.song().scenes) > self._get_minimal_scene_offset() + 1

    def scene_up(self):
        return self.set_offsets(self.track_offset(),
                                max(0,
                                    self.scene_offset() - 1))

    def scene_down(self):
        return self.set_offsets(self.track_offset(), self.scene_offset() + 1)

    def clear_buttons(self):
        return

    def set_clip_left_button(self, button):
        if button:
            self._clip_horisontal_buttons.scroll_up_button.color = 'Color.NavButtonOn'
            self._clip_horisontal_buttons.scroll_up_button.disabled_color = 'Color.NavButtonOff'
            self._clip_horisontal_buttons.scroll_up_button.pressed_color = None
        else:
            self._clip_horisontal_buttons.scroll_up_button.color = 'Color.Off'
            self._clip_horisontal_buttons.scroll_up_button.disabled_color = 'Color.Off'

        self._clip_horisontal_buttons.set_scroll_up_button(button)

    def set_clip_right_button(self, button):
        if button:
            self._clip_horisontal_buttons.scroll_down_button.color = 'Color.NavButtonOn'
            self._clip_horisontal_buttons.scroll_down_button.disabled_color = 'Color.NavButtonOff'
            self._clip_horisontal_buttons.scroll_down_button.pressed_color = None
        else:
            self._clip_horisontal_buttons.scroll_down_button.color = 'Color.Off'
            self._clip_horisontal_buttons.scroll_down_button.disabled_color = 'Color.Off'

        self._clip_horisontal_buttons.set_scroll_down_button(button)

    def set_page_left_button(self, button):
        if button:
            self._horizontal_paginator.scroll_up_button.color = 'Color.NavButtonOn'
            self._horizontal_paginator.scroll_up_button.disabled_color = 'Color.NavButtonOff'
            self._horizontal_paginator.scroll_up_button.pressed_color = None
        else:
            self._horizontal_paginator.scroll_up_button.color = 'Color.Off'
            self._horizontal_paginator.scroll_up_button.disabled_color = 'Color.Off'

        self._horizontal_paginator.set_scroll_up_button(button)

    def set_page_right_button(self, button):
        if button:
            self._horizontal_paginator.scroll_down_button.color = 'Color.NavButtonOn'
            self._horizontal_paginator.scroll_down_button.disabled_color = 'Color.NavButtonOff'
            self._horizontal_paginator.scroll_down_button.pressed_color = None
        else:
            self._horizontal_paginator.scroll_down_button.color = 'Color.Off'
            self._horizontal_paginator.scroll_down_button.disabled_color = 'Color.Off'

        self._horizontal_paginator.set_scroll_down_button(button)

    #def set_track_bank_left_button(self, button):
    #    if button:
    #        self._horizontal_banking.scroll_up_button.color = 'Color.NavButtonOn'
    #        self._horizontal_banking.scroll_up_button.disabled_color = 'Color.NavButtonOff'
    #        self._horizontal_banking.scroll_up_button.pressed_color = None
    #    else:
    #        self._horizontal_banking.scroll_up_button.color = 'Color.Off'
    #        self._horizontal_banking.scroll_up_button.disabled_color = 'Color.Off'

    #    self._horizontal_banking.set_scroll_up_button(button)

    #def set_track_bank_right_button(self, button):
    #    if button:
    #        self._horizontal_banking.scroll_down_button.color = 'Color.NavButtonOn'
    #        self._horizontal_banking.scroll_down_button.disabled_color = 'Color.NavButtonOff'
    #        self._horizontal_banking.scroll_down_button.pressed_color = None
    #    else:
    #        self._horizontal_banking.scroll_down_button.color = 'Color.Off'
    #        self._horizontal_banking.scroll_down_button.disabled_color = 'Color.Off'

    #    self._horizontal_banking.set_scroll_down_button(button)

    def set_scene_play_button(self, button):
        if button:
            self.scene_play_button.set_control_element(button)

    def set_scene_stop_button(self, button):
        if button:
            self.scene_stop_button.set_control_element(button)

    def set_scene_up_button(self, button):
        if button:
            self._scene_vertical_buttons.scroll_up_button.color = 'Color.NavButtonOn'
            self._scene_vertical_buttons.scroll_up_button.disabled_color = 'Color.NavButtonOff'
            self._scene_vertical_buttons.scroll_up_button.pressed_color = None
        else:
            self._scene_vertical_buttons.scroll_up_button.color = 'Color.Off'
            self._scene_vertical_buttons.scroll_up_button.disabled_color = 'Color.Off'

        self._scene_vertical_buttons.set_scroll_up_button(button)

    def set_scene_down_button(self, button):
        if button:
            self._scene_vertical_buttons.scroll_down_button.color = 'Color.NavButtonOn'
            self._scene_vertical_buttons.scroll_down_button.disabled_color = 'Color.NavButtonOff'
            self._scene_vertical_buttons.scroll_down_button.pressed_color = None
        else:
            self._scene_vertical_buttons.scroll_down_button.color = 'Color.Off'
            self._scene_vertical_buttons.scroll_down_button.disabled_color = 'Color.Off'

        self._scene_vertical_buttons.set_scroll_down_button(button)

    def set_offsets(self, track_offset, scene_offset):
        super(SessionComponent, self).set_offsets(track_offset, scene_offset)
        if scene_offset != None:
            scenes = self.song().scenes
            self.song().view.selected_scene = scenes[scene_offset]
class ChannelStripComponent(ChannelStripComponentBase):
    send_lights = control_list(ButtonControl,
                               control_count=3,
                               color='Mixer.Sends',
                               disabled_color='Mixer.NoTrack')
    pan_light = ButtonControl(color='Mixer.Pans',
                              disabled_color='Mixer.NoTrack')

    def __init__(self, parent, mixer):
        self._parent = parent
        self._mixer = mixer
        self._is_return = False
        self._num_sends_to_display = 0
        self._valid_track = False
        self._track = None
        self._index = -1
        super(ChannelStripComponent, self).__init__()

    def log(self, msg):
        self._mixer.log(msg)

    def set_track(self,
                  track,
                  index=-1,
                  num_sends_to_display=0,
                  is_return=False,
                  is_master=False):
        self._valid_track = bool(track)
        super(ChannelStripComponent, self).set_track(track)
        self._track = track
        self._index = index
        self.log('set_track: ' +
                 ('Empty' if track == None else repr3(track.name)) +
                 ', index: ' + str(index))
        if index != -1:
            col = 'Mixer.ArmSelected' if is_return else (
                63 if is_master else 'Mixer.Sends')
            self.send_lights[0].color = col
            self.send_lights[1].color = col
            self.send_lights[2].color = col
        self.log('select: ' + str(self._select_button) + ', ' +
                 str(bool(track)))
        self._set_send_lights(num_sends_to_display)
        self.pan_light.enabled = bool(track)

    def set_num_sends_to_display(self, num_sends_to_display):
        self._set_send_lights(num_sends_to_display)

    def _set_send_lights(self, num_sends_to_display):
        self._num_sends_to_display = num_sends_to_display
        send_num = 0
        for light in self.send_lights:
            if send_num < num_sends_to_display:
                light.enabled = self._valid_track
            else:
                light.enabled = False
            send_num += 1

    def _connect_parameters(self):
        if self._parent._LOG_PARAMS:
            self.log('strip:_connect_parameters: vol: ' +
                     repr3(self._track.name) + ', ' +
                     str(self._track.mixer_device.volume) + ', ' +
                     str(self._track.mixer_device.volume.value))
        i = 0
        for control in self._send_controls:
            if control != None:
                if i < len(self._track.mixer_device.sends):
                    if self._parent._LOG_PARAMS:
                        self.log('strip:_connect_parameters: send: ' + str(i) +
                                 ': ' +
                                 str(self._track.mixer_device.sends[i]) +
                                 ', ' +
                                 str(self._track.mixer_device.sends[i].value))
                    i += 1

        super(ChannelStripComponent, self)._connect_parameters()

    def set_select_button(self, button):
        super(ChannelStripComponent, self).set_select_button(button)
        self._on_select.subject = button

    if g_v2_api:

        @listens('value')
        def _on_select(self, value):
            self.__on_select(value)

    else:

        @subject_slot('value')
        def _on_select(self, value):
            self.__on_select(value)

    def __on_select(self, value):
        if self._mixer._select_track_on_0_val:
            if value == 127:
                self.log('strip:_on_select: ' + str(value) +
                         ', select_track_on_0_val: ' +
                         str(self._mixer._select_track_on_0_val) +
                         ', process: False')
                self._mixer._track_changed = False
                return
            changed = self._mixer._track_changed
        else:
            changed = self._mixer._selected_track != self._parent.song(
            ).view.selected_track
        self._parent.log('strip:_on_select: ' + str(value) +
                         ', select_track_on_0_val: ' +
                         str(self._mixer._select_track_on_0_val) +
                         ', process: True, selected: ' +
                         repr3(self._parent.song().view.selected_track.name) +
                         ', changed: ' + str(changed))
        if self._parent.song().view.selected_track != None and not changed:
            if self._mixer._select_track_on_0_val and value == 0 or not self._mixer._select_track_on_0_val:
                if self._valid_track:
                    self._mixer.same_track_selected()
class DeviceComponent(Framework_DeviceComponentBase, PrEditorMapper,
                      DeviceComponentBase, PrEditorParameterProvider):
    encoder_rings = control_list(ButtonControl,
                                 control_count=32,
                                 enabled=False)
    parameter_lights = control_list(ButtonControl,
                                    control_count=32,
                                    enabled=True,
                                    color='Device.Parameters',
                                    disabled_color='Device.NoDevice')
    device_select = control_list(ButtonControl,
                                 control_count=8,
                                 enabled=False,
                                 color='DefaultButton.On',
                                 disabled_color='DefaultButton.Off')
    device_enable = control_list(ButtonControl,
                                 control_count=8,
                                 enabled=False,
                                 color='DefaultButton.On',
                                 disabled_color='DefaultButton.Off')
    direct_bank = control_list(ButtonControl,
                               control_count=4,
                               enabled=False,
                               color='DefaultButton.Off',
                               disabled_color='DefaultButton.Off')
    prev_device_button = ButtonControl(color='DefaultButton.Off')
    next_device_button = ButtonControl(color='DefaultButton.Off')
    bank_up_button = ButtonControl(color='DefaultButton.Off')
    bank_down_button = ButtonControl(color='DefaultButton.Off')
    device_button = ButtonControl(color='DefaultButton.Off', enabled=True)
    device_lock_button = ButtonControl(color='DefaultButton.Off', enabled=True)
    strip_button = ButtonControl(color='DefaultButton.Off', enabled=True)

    @staticmethod
    def _get_version():
        return '0' + ('p' if g_mapper else '')

    def __init__(self,
                 parent,
                 num_params=8,
                 direct_bank=False,
                 mono=True,
                 shared=False,
                 path=None,
                 delayed_update=False,
                 new_skin=False,
                 *a,
                 **k):
        self._parent = parent
        self._use_new_skin = new_skin
        self._mapper = None
        Framework_DeviceComponentBase.__init__(self, *a, **k)
        self._is_osc = hasattr(self._parent,
                               '_osc_control') and self._parent._osc_control
        if not self._is_osc:
            g_hud = False
        self.log(
            'DeviceComponent(STANDARD): num: ' + str(num_params) + ', path: ' +
            str(path) + ', HUD: ' + str(g_hud) + ', OSC: ' + str(self._is_osc),
            True)
        if g_mapper:
            PrEditorParameterProvider.__init__(self, parent)
            self.log('****** create mapper(STANDARD): path: ' + str(path),
                     True)
            PrEditorMapper.__init__(self, PrEditorParameterProvider, path,
                                    num_params)
        DeviceComponentBase.__init__(self, parent,
                                     PrEditorMapper if g_mapper else None,
                                     False, num_params, direct_bank, mono,
                                     shared, path, delayed_update, new_skin)
        k.pop('feedback_mode', None)
        k.pop('lock_button', None)

        def make_hud_encoder(index, name):
            control = HUDControlElement(self, self._hud_state_control, i)
            control.name = name + '_' + str(i)
            return control

        def make_osc_encoder(index, name, osc_msg):
            control = OSCControlElement(self, self._parent.oscServer, i,
                                        osc_msg)
            control.name = name + '_' + str(i)
            return control

        if g_hud:
            self.log('Creating HUD notification', True)
            self._hud_state_control = NotifyingControlElement()
            self._hud_state_control.name = 'HUD_State'
            self._hud_controls = ButtonMatrixElement(rows=[[
                make_osc_encoder(i, 'HUD_Control', '/hud') for i in xrange(8)
            ]])
            self._hud_cs_controls = ButtonMatrixElement(rows=[[
                make_osc_encoder(i, 'HUD_CS_Control', '/hudcs')
                for i in xrange(8)
            ]])
        self._mono = mono
        for light in self.encoder_rings:
            light.enabled = True

        for light in self.device_select:
            light.enabled = False
            light.color = 'DefaultButton.Off' if self._mono else 'Device.NoDevice'
            light.disabled_color = 'DefaultButton.Off' if self._mono else 'Device.NoDevice'

        for light in self.device_enable:
            light.enabled = False
            light.color = 'DefaultButton.Off' if self._mono else 'Device.NoDevice'
            light.disabled_color = 'DefaultButton.Off' if self._mono else 'Device.NoDevice'

        for light in self.parameter_lights:
            light.enabled = False

    def log(self, msg, force=False):
        if hasattr(self, '_parent'):
            self._parent.log('device: ' + msg, force)

    def msg(self, msg):
        if hasattr(self, '_parent'):
            self._parent.show_message(msg)

    def _shutdown(self):
        if self._mapper:
            PrEditorMapper._shutdown(self)

    def set_num_devices(self, val):
        DeviceComponentBase.set_num_devices(self, val)

    def toggle_skip_racks(self):
        DeviceComponentBase.toggle_skip_racks(self)

    def get_song(self):
        return self.song()

    def enable(self, flag):
        return DeviceComponentBase.enable(self, flag)

    def set_device_lock(self, val, refresh=True, leaving=False):
        DeviceComponentBase.set_device_lock(self, val, refresh, leaving)

    def toggle_device_lock(self):
        DeviceComponentBase.toggle_device_lock(self)

    @direct_bank.pressed
    def direct_bank_button(self, button):
        DeviceComponentBase.do_direct_bank_button(self, button)

    @device_enable.pressed
    def device_enable_button(self, button):
        DeviceComponentBase.do_device_enable_button(self, button)

    @device_select.pressed
    def device_select_button(self, button):
        DeviceComponentBase.dodevice_select_button(self, button)

    @device_lock_button.pressed
    def device_lock_button(self, button):
        DeviceComponentBase.do_device_lock_button(self, button)

    @prev_device_button.pressed
    def prev_device_button(self, button):
        DeviceComponentBase.do_prev_device_button(self, button)

    @next_device_button.pressed
    def next_device_button(self, button):
        DeviceComponentBase.do_next_device_button(self, button)

    @bank_up_button.pressed
    def bank_up_button(self, button):
        DeviceComponentBase.do_bank_up_button(self, button)

    @bank_down_button.pressed
    def bank_down_button(self, button):
        DeviceComponentBase.do_bank_down_button(self, button)

    def set_device_bank(self, bank):
        DeviceComponentBase.set_device_bank(self, bank)

    def _update_direct_bank(self):
        DeviceComponentBase._update_direct_bank(self)

    def _scroll_device_view(self, direction):
        DeviceComponentBase._scroll_device_view(self, direction)

    def select_device(self, index):
        device = self._selected_track.devices[index]
        if self._mapper:
            device, updated = self._mapper._get_real_device(self, device)
        self.get_song().view.select_device(device)
        self.device_num = index

    def calling(self, msg):
        self.log('CALLLLLLLIIIIIINNNNNGGG: ' + msg, True)

    def set_device(self, device):
        super(DeviceComponent, self).set_device(device)
        DeviceComponentBase.set_device(self, device)

    def _notify_device(self, device=None, force=False, clear=False):
        DeviceComponentBase._notify_device(self, device, force, clear)

    def _on_parameters_changed(self):
        super(DeviceComponent, self)._on_parameters_changed()
        self.log('_on_parameters_changed')
        if self._mapper:
            self._mapper._on_parameters_changed(self)

    def _set_current_bank(self):
        DeviceComponentBase._set_current_bank(self)

    def _get_param_details(self):
        if self._mapper:
            self._mapper._get_param_details(self)
            return
        DeviceComponentBase._get_param_details(self)

    def _my_assign_parameters(self, bank_num=-1, is_push=False):
        if self._mapper:
            return self._mapper._my_assign_parameters(self, bank_num, is_push)
        return DeviceComponentBase._my_assign_parameters(
            self, bank_num, is_push)

    def map_controls(self, is_push=False):
        if self._mapper:
            self._mapper.map_controls(self, is_push)
            return
        DeviceComponentBase.map_controls(self, is_push)

    def refresh(self):
        DeviceComponentBase.refresh(self)

    def on_selected_track_changed(self):
        DeviceComponentBase.on_selected_track_changed(self)

    def update_track_state(self):
        DeviceComponentBase.update_track_state(self)

    @subject_slot_group('value')
    def _on_device_enable(self, param):
        DeviceComponentBase.do_on_device_enable(self, param)

    def set_device_on_off(self, index):
        DeviceComponentBase.set_device_on_off(self, index)

    def _update_select_enable_lights(self, num_devices, selected_device):
        DeviceComponentBase._update_select_enable_lights(
            self, num_devices, selected_device)

    def _show_strip_state(self, index, track_present, iso_present, state):
        DeviceComponentBase._update_select_enable_lights(
            self, index, track_present, iso_present, state)

    def _show_device_state(self, index, selected, enabled):
        DeviceComponentBase._show_device_state(self, index, selected, enabled)

    def _show_device_enabled(self, index, flag):
        DeviceComponentBase._show_device_enabled(self, index, flag)

    def _show_device_selected(self, index, flag):
        DeviceComponentBase._show_device_selected(self, index, flag)

    def _get_top_device(self):
        return DeviceComponentBase._get_top_device(self)

    def _update_bank_buttons(self, disable=False):
        DeviceComponentBase._update_bank_buttons(self, disable)

    def set_bank_buttons(self, buttons):
        super(DeviceComponent, self).set_bank_buttons(buttons)
        DeviceComponentBase.set_bank_buttons(self, buttons)

    def _is_banking_enabled(self):
        return True

    def _setup_device_controls(self, active):
        self.log('_setup_device_controls: nop')
        return False

    def _setup_num_device_controls(self, num_devices):
        self.log('_setup_num_device_controls: nop')

    def sub_notify(self, msg, val):
        super(DeviceComponent, self).sub_notify(msg, val)
class ChannelStripComponent(ChannelstripComponentBase):
    volume_reset_button = ButtonControl()
    pan_reset_button = ButtonControl()
    send_a_reset_button = ButtonControl()
    send_b_reset_button = ButtonControl()

    def __init__(self, *a, **k):
        super(ChannelStripComponent, self).__init__(*a, **k)
        self._arm_on_value = 127
        self._arm_off_value = 0
        self._solo_on_value = 127
        self._solo_off_value = 0
        self._mute_on_value = 127
        self._mute_off_value = 0

    def set_mute_values(self, mute_on_value, mute_off_value):
        self._mute_on_value = mute_on_value
        self._mute_off_value = mute_off_value

    def set_solo_values(self, solo_on_value, solo_off_value):
        self._solo_on_value = solo_on_value
        self._solo_off_value = solo_off_value

    def set_arm_values(self, arm_on_value, arm_off_value):
        self._arm_on_value = arm_on_value
        self._arm_off_value = arm_off_value

    def _on_mute_changed(self):
        if self.is_enabled() and self._mute_button is not None:
            self._mute_button.set_light(self._mute_color_value())
        return

    def _on_solo_changed(self):
        if self.is_enabled() and self._solo_button is not None:
            self._solo_button.set_light(self._solo_color_value())
        return

    def _on_arm_changed(self):
        if self.is_enabled() and self._arm_button is not None:
            self._arm_button.set_light(self._arm_color_value())
        return

    def _mute_color_value(self):
        if self._track != None or self.empty_color is None:
            if self._track in chain(self.song().tracks, self.song().return_tracks) and self._track.mute != self._invert_mute_feedback:
                return self._mute_on_value
            return self._mute_off_value
        else:
            return self.empty_color
        return

    def _solo_color_value(self):
        if self._track != None or self.empty_color is None:
            if self._track in chain(self.song().tracks, self.song().return_tracks) and self._track.solo:
                return self._solo_on_value
            return self._solo_off_value
        else:
            return self.empty_color
        return

    def _arm_color_value(self):
        if self._track != None or self.empty_color is None:
            if self._track in self.song().tracks and self._track.can_be_armed and self._track.arm:
                return self._arm_on_value
            return self._arm_off_value
        else:
            return self.empty_color
        return

    def set_track(self, track):
        super(ChannelStripComponent, self).set_track(track)
        if self._track != None:
            self._on_volume_changed.subject = self._track.mixer_device.volume
            self._on_pan_changed.subject = self._track.mixer_device.panning
            sends = self._track.mixer_device.sends
            if len(sends) > 0:
                self._on_send_a_changed.subject = self._track.mixer_device.sends[0]
            if len(sends) > 1:
                self._on_send_b_changed.subject = self._track.mixer_device.sends[1]
        self._update_volume_button_led()
        self._update_pan_button_led()
        self._update_send_a_button_led()
        self._update_send_b_button_led()
        return

    @subject_slot('value')
    def _on_volume_changed(self):
        self._update_volume_button_led()

    @subject_slot('value')
    def _on_pan_changed(self):
        self._update_pan_button_led()

    @subject_slot('value')
    def _on_send_a_changed(self):
        self._update_send_a_button_led()

    @subject_slot('value')
    def _on_send_b_changed(self):
        self._update_send_b_button_led()

    def _update_volume_button_led(self):
        if self._track == None:
            self.volume_reset_button.color = 'Mixer.Disabled'
        else:
            volume = self._track.mixer_device.volume
            if volume.value != volume.default_value:
                self.volume_reset_button.color = 'Mixer.Volume.On'
            else:
                self.volume_reset_button.color = 'Mixer.Volume.Off'
        return

    def _update_pan_button_led(self):
        if self._track == None:
            self.pan_reset_button.color = 'Mixer.Disabled'
        else:
            panning = self._track.mixer_device.panning
            if abs(panning.value) > PAN_VALUE_DEVIATION_TOLERANCE:
                self.pan_reset_button.color = 'Mixer.Pan.On'
            else:
                self.pan_reset_button.color = 'Mixer.Pan.Off'
        return

    def _update_send_a_button_led(self):
        if self._track == None or len(self._track.mixer_device.sends) < 1:
            self.send_a_reset_button.color = 'Mixer.Disabled'
        else:
            send = self._track.mixer_device.sends[0]
            if send.value != send.default_value:
                self.send_a_reset_button.color = 'Sends.Send0.On'
            else:
                self.send_a_reset_button.color = 'Sends.Send0.Off'
        return

    def _update_send_b_button_led(self):
        if self._track == None or len(self._track.mixer_device.sends) < 2:
            self.send_b_reset_button.color = 'Mixer.Disabled'
        else:
            send = self._track.mixer_device.sends[1]
            if send.value != send.default_value:
                self.send_b_reset_button.color = 'Sends.Send1.On'
            else:
                self.send_b_reset_button.color = 'Sends.Send1.Off'
        return

    @volume_reset_button.pressed
    def volume_reset_button(self, button):
        if self._track != None:
            volume = self._track.mixer_device.volume
            if volume.is_enabled:
                volume.value = volume.default_value
        return

    @pan_reset_button.pressed
    def pan_reset_button(self, button):
        if self._track != None:
            panning = self._track.mixer_device.panning
            if panning.is_enabled:
                panning.value = panning.default_value
        return

    @send_a_reset_button.pressed
    def send_a_reset_button(self, button):
        if self._track != None and len(self._track.mixer_device.sends) > 0:
            send = self._track.mixer_device.sends[0]
            if send.is_enabled:
                send.value = send.default_value
        return

    @send_b_reset_button.pressed
    def send_b_reset_button(self, button):
        if self._track != None and len(self._track.mixer_device.sends) > 1:
            send = self._track.mixer_device.sends[1]
            if send.is_enabled:
                send.value = send.default_value
        return
Esempio n. 27
0
class LoopSelectorComponent(ControlSurfaceComponent):
    """
    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()
    __subject_events__ = ('is_following', )

    def __init__(self,
                 clip_creator=None,
                 measure_length=4.0,
                 follow_detail_clip=False,
                 paginator=None,
                 *a,
                 **k):
        super(LoopSelectorComponent, self).__init__(*a, **k)
        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._is_following = False
        self._follow_button = None
        self._select_button = None
        self._short_loop_selector_matrix = None
        self._loop_selector_matrix = None
        self._pressed_pages = []
        self._page_colors = []
        self._measure_length = measure_length
        self._last_playhead_page = -1
        self._follow_task = self._tasks.add(
            Task.sequence(Task.wait(Defaults.MOMENTARY_DELAY),
                          Task.run(partial(self._set_is_following, True))))
        self._follow_task.kill()
        if follow_detail_clip:
            self._on_detail_clip_changed.subject = self.song().view
        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)

    def _get_is_following(self):
        return self._can_follow and self._is_following

    def _set_is_following(self, value):
        self._is_following = value
        self.notify_is_following(value)

    is_following = property(_get_is_following, _set_is_following)

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

    @subject_slot('page_index')
    def _on_page_index_changed(self):
        self._update_page_colors()

    @subject_slot('page_length')
    def _on_page_length_changed(self):
        self._update_page_colors()
        self._update_follow_button()
        self._select_start_page_if_out_of_loop_range()

    def set_follow_button(self, button):
        self._follow_button = button
        self._on_follow_value.subject = button
        self._update_follow_button()

    def set_select_button(self, button):
        self._select_button = button

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

    def set_detail_clip(self, clip):
        if clip != self._sequencer_clip:
            self._is_following = clip != None 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._select_start_page_if_out_of_loop_range()
            self._on_loop_changed()

    def _update_follow_button(self):
        if self.is_enabled() and self._follow_button:
            self._follow_button.set_light(self.is_following)

    def _select_start_page_if_out_of_loop_range(self):
        if self._sequencer_clip:
            page_start = self._paginator.page_index * self._paginator.page_length
            if self._sequencer_clip and (
                    page_start <= self._sequencer_clip.loop_start
                    or page_start >= self._sequencer_clip.loop_end):
                self._paginator.select_page_in_point(
                    self._sequencer_clip.loop_start)
        else:
            self._paginator.select_page_in_point(0)

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

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

    def _on_loop_changed(self):
        if 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._update_page_colors()

    def set_loop_selector_matrix(self, matrix):
        self._loop_selector_matrix = matrix
        self._on_loop_selector_matrix_value.subject = matrix
        if matrix:
            matrix.reset()
        self._update_page_colors()

    def set_short_loop_selector_matrix(self, matrix):
        self._short_loop_selector_matrix = matrix
        self._on_short_loop_selector_matrix_value.subject = matrix
        if matrix:
            matrix.reset()
        self._update_page_colors()

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

    @subject_slot('is_recording')
    def _on_is_recording_changed(self):
        self.is_following = self._is_following or clip_is_new_recording(
            self._sequencer_clip)

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

    @subject_slot('playing_status')
    def _on_playing_status_changed(self):
        self._update_page_and_playhead_leds()

    @subject_slot('session_record')
    def _on_session_record_changed(self):
        self._update_page_and_playhead_leds()

    @subject_slot('is_playing')
    def _on_song_playback_status_changed(self):
        self._update_page_and_playhead_leds()

    def _has_running_clip(self):
        return self._sequencer_clip != None 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:] = ['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] = 'LoopSelector.PlayheadRecord' if self.song(
                            ).session_record else '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(len(self._loop_selector_matrix or []),
                   len(self._short_loop_selector_matrix or []), 1)

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

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

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

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

            def color_for_page(absolute_page):
                if l_start <= absolute_page < l_start + l_length:
                    return 'LoopSelector.InsideLoopStartBar' if absolute_page % pages_per_measure == 0 else 'LoopSelector.InsideLoop'
                else:
                    return '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(
                        'LoopSelector.InsideLoop'):
                    page_colors[button_index] = 'LoopSelector.SelectedPage'

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

    def _update_page_leds(self):
        self._update_page_leds_in_matrix(self._loop_selector_matrix)
        self._update_page_leds_in_matrix(self._short_loop_selector_matrix)

    def _update_page_leds_in_matrix(self, matrix):
        """ update hardware leds to match precomputed map """
        if self.is_enabled() and matrix:
            for button, color in izip(matrix, self._page_colors):
                if button:
                    button.set_light(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()

    @subject_slot('value')
    def _on_short_loop_selector_matrix_value(self, value, x, y, is_momentary):
        page = x + y * self._short_loop_selector_matrix.width()
        if self.is_enabled():
            if value or not is_momentary:
                self._pressed_pages = [page]
                self._try_set_loop()
                self._pressed_pages = []

    @subject_slot('value')
    def _on_loop_selector_matrix_value(self, value, x, y, is_momentary):
        page = x + y * self._loop_selector_matrix.width()
        if self.is_enabled():
            if value or not is_momentary:
                if page not in self._pressed_pages:
                    self._on_press_loop_selector_matrix(page)
        if (not is_momentary or not value) and page in self._pressed_pages:
            self._pressed_pages.remove(page)

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

        self._pressed_pages.append(page)
        absolute_page = page + self.page_offset
        if not self._select_button or not self._select_button.is_pressed():
            if self._sequencer_clip == None and not self.song(
            ).view.highlighted_clip_slot.has_clip:
                create_clip(absolute_page)
            elif self._sequencer_clip != None:
                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 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
        if loop_start >= self._sequencer_clip.loop_end:
            self._sequencer_clip.loop_end = loop_end
            self._sequencer_clip.loop_start = loop_start
            self._sequencer_clip.end_marker = loop_end
            self._sequencer_clip.start_marker = loop_start
        else:
            self._sequencer_clip.loop_start = loop_start
            self._sequencer_clip.loop_end = loop_end
            self._sequencer_clip.start_marker = loop_start
            self._sequencer_clip.end_marker = loop_end
        self._sequencer_clip.view.show_loop()

    @property
    def _can_follow(self):
        return True

    @property
    def _page_length_in_beats(self):
        return clamp(self._paginator.page_length, 0.5,
                     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):
        size = max(
            self._loop_selector_matrix.width() *
            self._loop_selector_matrix.height()
            if self._loop_selector_matrix else 0, 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)

    @subject_slot('value')
    def _on_follow_value(self, value):
        if self.is_enabled() and value:
            if self._can_follow:
                self.is_following = not self.is_following
                self._update_follow_button()