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