class ActionsComponent(ControlSurfaceComponent): """' Simple component that provides undo/redo, record quantization toggle and clip quantization handling. """ __module__ = __name__ undo_button = ButtonControl(**ACTION_BUTTON_COLORS) redo_button = ButtonControl(color='Misc.Shift', pressed_color='Misc.ShiftOn', disabled_color='DefaultButton.Disabled') quantization_on_button = ToggleButtonControl(untoggled_color='Misc.Shift', toggled_color='Misc.ShiftOn') def __init__(self, *a, **k): self.suppressing_control_notifications = BooleanContext() super(ActionsComponent, self).__init__(*a, **k) self._record_quantization = RecordingQuantization.rec_q_sixtenth self._on_record_quantization_changed_in_live.subject = self.song() self._on_record_quantization_changed_in_live() self._metronome_toggle = ToggleComponent('metronome', self.song()) def control_notifications_enabled(self): return self.is_enabled() and not self.suppressing_control_notifications def quantize_clip(self, clip): assert isinstance(clip, Live.Clip.Clip) clip.quantize(self._record_quantization, 1.0) @undo_button.pressed def undo_button(self, button): if self.song().can_undo: self.song().undo() @redo_button.pressed def redo_button(self, button): if self.song().can_redo: self.song().redo() @quantization_on_button.toggled def quantization_on_button(self, is_toggled, button): self._record_quantization_on = is_toggled self.song( ).midi_recording_quantization = self._record_quantization if self._record_quantization_on else RecordingQuantization.rec_q_no_q @subject_slot('midi_recording_quantization') def _on_record_quantization_changed_in_live(self): quant_value = self.song().midi_recording_quantization quant_on = quant_value != RecordingQuantization.rec_q_no_q if quant_on: self._record_quantization = quant_value self._record_quantization_on = quant_on with self.suppressing_control_notifications(): self.quantization_on_button.is_toggled = quant_on def set_metronome_button(self, button): self._metronome_toggle.set_toggle_button(button) def update(self): super(ActionsComponent, self).update() self._metronome_toggle.update()
class BankToggleComponent(ControlSurfaceComponent): bank_toggle_button = ToggleButtonControl() def __init__(self, *a, **k): super(BankToggleComponent, self).__init__(*a, **k) self._toggle_elements = [] @bank_toggle_button.toggled def bank_toggle_button(self, toggled, button): for e in self._toggle_elements: e.set_toggled(toggled) def create_toggle_element(self, *a, **k): element = ToggleElement(*a, **k) element.toggled = self.bank_toggle_button.is_toggled self._toggle_elements.append(element) return element
class DetailViewCntrlComponent(ControlSurfaceComponent): u""" Component that can toggle the device chain- and clip view of the selected track """ device_clip_toggle_button = ButtonControl(color='DefaultButton.Off') device_nav_left_button = ButtonControl(color='DefaultButton.Off') device_nav_right_button = ButtonControl(color='DefaultButton.Off') detail_toggle_button = ToggleButtonControl() def __init__(self, *a, **k): super(DetailViewCntrlComponent, self).__init__(*a, **k) self._detail_view_visibility_changed.subject = self.application().view self._detail_view_visibility_changed() self._go_to_playing_clip_task = self._tasks.add(Task.sequence(Task.wait(SHOW_PLAYING_CLIP_DELAY), Task.run(self._go_to_playing_clip))) self._go_to_playing_clip_task.kill() self.set_device_clip_toggle_button = self.device_clip_toggle_button.set_control_element self.set_detail_toggle_button = self.detail_toggle_button.set_control_element def set_device_nav_buttons(self, left_button, right_button): self.set_device_nav_left_button(left_button) self.set_device_nav_right_button(right_button) @device_clip_toggle_button.pressed def device_clip_toggle_button(self, button): if not self.application().view.is_view_visible('Detail'): self.application().view.show_view('Detail') if not self.application().view.is_view_visible('Detail/DeviceChain'): self.application().view.show_view('Detail/DeviceChain') else: self.application().view.show_view('Detail/Clip') self._go_to_playing_clip_task.restart() @device_clip_toggle_button.released def device_clip_toggle_button(self, button): self._go_to_playing_clip_task.kill() @device_nav_left_button.pressed def device_nav_left_button(self, value): self._scroll_device_chain(NavDirection.left) @device_nav_right_button.pressed def device_nav_right_button(self, value): self._scroll_device_chain(NavDirection.right) def _scroll_device_chain(self, direction): view = self.application().view if not view.is_view_visible('Detail') or not view.is_view_visible('Detail/DeviceChain'): view.show_view('Detail') view.show_view('Detail/DeviceChain') else: view.scroll_view(direction, 'Detail/DeviceChain', False) def _go_to_playing_clip(self): song = self.song() playing_slot_index = song.view.selected_track.playing_slot_index if playing_slot_index > -1: song.view.selected_scene = song.scenes[playing_slot_index] if song.view.highlighted_clip_slot.has_clip: self.application().view.show_view('Detail/Clip') @detail_toggle_button.toggled def detail_toggle_button(self, is_toggled, button): if is_toggled: self.application().view.show_view('Detail') else: self.application().view.hide_view('Detail') @subject_slot('is_view_visible', 'Detail') def _detail_view_visibility_changed(self): self.detail_toggle_button.is_toggled = self.application().view.is_view_visible('Detail') def show_view(self, view): app_view = self.application().view try: if view == 'Detail/DeviceChain' or 'Detail/Clip': if not app_view.is_view_visible('Detail'): app_view.show_view('Detail') if not app_view.is_view_visible(view): app_view.show_view(view) except RuntimeError: pass
class InstrumentComponent(CompoundComponent, Slideable, Messenger): """ Class that sets up the button matrix as a piano, using different selectable layouts for the notes. """ touch_strip_toggle = ToggleButtonControl() midi_channels = range(5, 13) def __init__(self, *a, **k): super(InstrumentComponent, self).__init__(*a, **k) self._scales = self.register_component(InstrumentScalesComponent()) self._matrix = None self._delete_button = None self._first_note = self.page_length * 3 + self.page_offset self._last_page_length = self.page_length self._delete_button = None self._last_page_offset = self.page_offset self._touch_strip = None self._touch_strip_indication = None self._detail_clip = None self._has_notes = [False] * 128 self._has_notes_pattern = self._get_pattern(0) self._takeover_pads = False self._aftertouch_control = None self._scales_menu = self.register_component( EnablingModesComponent(component=self._scales, toggle_value='DefaultButton.On')) self._slider, self._touch_slider = self.register_components( SlideComponent(self), SlideableTouchStripComponent(self)) self._on_scales_changed.subject = self._scales self._on_scales_mode_changed.subject = self._scales._presets self._update_pattern() def set_detail_clip(self, clip): if clip != self._detail_clip: self._detail_clip = clip self._on_clip_notes_changed.subject = clip self._on_loop_start_changed.subject = clip self._on_loop_end_changed.subject = clip self._on_clip_notes_changed() @subject_slot('notes') def _on_clip_notes_changed(self): if self._detail_clip: self._has_notes = [False] * 128 loop_start = self._detail_clip.loop_start loop_length = self._detail_clip.loop_end - loop_start notes = self._detail_clip.get_notes(loop_start, 0, loop_length, 128) for note in notes: self._has_notes[note[0]] = True self.notify_contents() @subject_slot('loop_start') def _on_loop_start_changed(self): self._on_loop_selection_changed() @subject_slot('loop_end') def _on_loop_end_changed(self): self._on_loop_selection_changed() def _on_loop_selection_changed(self): self._on_clip_notes_changed() def contents(self, index): if self._detail_clip: note = self._has_notes_pattern[index].index return self._has_notes[note] if note is not None else False return False @property def page_length(self): return len(self._scales.notes) if self._scales.is_diatonic else 12 @property def position_count(self): if not self._scales.is_diatonic: return 139 else: offset = self.page_offset octaves = 11 if self._scales.notes[0] < 8 else 10 return offset + len(self._scales.notes) * octaves def _first_scale_note_offset(self): if not self._scales.is_diatonic: return self._scales.notes[0] elif self._scales.notes[0] == 0: return 0 else: return len(self._scales.notes) - index_if(lambda n: n >= 12, self._scales.notes) @property def page_offset(self): return 0 if self._scales.is_absolute else self._first_scale_note_offset( ) def _get_position(self): return self._first_note def _set_position(self, note): self._first_note = note self._update_pattern() self._update_matrix() self.notify_position() position = property(_get_position, _set_position) @property def scales(self): return self._scales @property def scales_menu(self): return self._scales_menu @property def pattern(self): return self._pattern @subject_slot('value') def _on_matrix_value(self, value, x, y, is_momentary): if self._delete_button and self._delete_button.is_pressed(): if value: max_y = self._matrix.width() - 1 pitch = self._get_pattern().note(x, max_y - y).index if pitch and self._detail_clip: self._matrix.get_button(x, y).turn_on() self._do_delete_pitch(pitch) else: self._matrix.get_button(x, y).turn_off() def _do_delete_pitch(self, pitch): clip = self._detail_clip if clip: note_name = pitch_index_to_string(pitch) loop_length = clip.loop_end - clip.loop_start clip.remove_notes(clip.loop_start, pitch, loop_length, 1) self.show_notification(consts.MessageBoxText.DELETE_NOTES % note_name) @subject_slot('value') def _on_delete_value(self, value): self._set_control_pads_from_script(bool(value)) def set_matrix(self, matrix): self._matrix = matrix self._on_matrix_value.subject = matrix if matrix: matrix.reset() self._update_matrix() @touch_strip_toggle.toggled def touch_strip_toggle(self, toggled, button): self._update_touch_strip() self._update_touch_strip_indication() self.show_notification( consts.MessageBoxText.TOUCHSTRIP_MODWHEEL_MODE if toggled else consts.MessageBoxText.TOUCHSTRIP_PITCHBEND_MODE) def set_touch_strip(self, control): self._touch_strip = control self._update_touch_strip() def _update_touch_strip(self): if self._touch_strip: self._touch_strip.behaviour = MODWHEEL_BEHAVIOUR if self.touch_strip_toggle.is_toggled else DEFAULT_BEHAVIOUR def set_touch_strip_indication(self, control): self._touch_strip_indication = control self._update_touch_strip_indication() def _update_touch_strip_indication(self): if self._touch_strip_indication: self._touch_strip_indication.set_mode(TouchStripModes.CUSTOM_FREE) self._touch_strip_indication.send_state([ (TouchStripElement.STATE_FULL if self.touch_strip_toggle.is_toggled else TouchStripElement.STATE_HALF) for _ in xrange(24) ]) def set_note_strip(self, strip): self._touch_slider.set_scroll_strip(strip) def set_octave_strip(self, strip): self._touch_slider.set_page_strip(strip) def set_scales_toggle_button(self, button): raise button is None or button.is_momentary() or AssertionError self._scales_menu.set_toggle_button(button) def set_octave_up_button(self, button): self._slider.set_scroll_page_up_button(button) def set_octave_down_button(self, button): self._slider.set_scroll_page_down_button(button) def set_scale_up_button(self, button): self._slider.set_scroll_up_button(button) def set_scale_down_button(self, button): self._slider.set_scroll_down_button(button) def set_aftertouch_control(self, control): self._aftertouch_control = control self._update_aftertouch() def set_delete_button(self, button): self._delete_button = button self._on_delete_value.subject = button self._set_control_pads_from_script(button and button.is_pressed()) def _align_first_note(self): self._first_note = self.page_offset + ( self._first_note - self._last_page_offset) * float( self.page_length) / float(self._last_page_length) if self._first_note >= self.position_count: self._first_note -= self.page_length self._last_page_length = self.page_length self._last_page_offset = self.page_offset @subject_slot('scales_changed') def _on_scales_changed(self): self._update_scale() @subject_slot('scale_mode') def _on_scales_mode_changed(self): self._update_scale() def _update_scale(self): self._align_first_note() self._update_pattern() self._update_matrix() self.notify_position_count() self.notify_position() self.notify_contents() def update(self): super(InstrumentComponent, self).update() if self.is_enabled(): self._update_matrix() self._update_aftertouch() self._update_touch_strip() self._update_touch_strip_indication() def _update_pattern(self): self._pattern = self._get_pattern() self._has_notes_pattern = self._get_pattern(0) def _update_matrix(self): self._setup_instrument_mode() def _setup_instrument_mode(self): if self.is_enabled() and self._matrix: self._matrix.reset() pattern = self._pattern max_j = self._matrix.width() - 1 for button, (i, j) in ifilter(first, self._matrix.iterbuttons()): profile = 'default' if self._takeover_pads else 'instrument' button.sensitivity_profile = profile note_info = pattern.note(i, max_j - j) if note_info.index != None: button.set_on_off_values('Instrument.NoteAction', 'Instrument.' + note_info.color) button.turn_off() button.set_enabled(self._takeover_pads) button.set_channel(note_info.channel) button.set_identifier(note_info.index) else: button.set_channel(NON_FEEDBACK_CHANNEL) button.set_light('Instrument.' + note_info.color) button.set_enabled(True) def _get_pattern(self, first_note=None): if first_note is None: first_note = int(round(self._first_note)) interval = self._scales._presets.interval notes = self._scales.notes octave = first_note / self.page_length offset = first_note % self.page_length - self._first_scale_note_offset( ) if interval == None: interval = 8 elif not self._scales.is_diatonic: interval = [0, 2, 4, 5, 7, 9, 10, 11][interval] if self._scales._presets.is_horizontal: steps = [1, interval] origin = [offset, 0] else: steps = [interval, 1] origin = [0, offset] return MelodicPattern(steps=steps, scale=notes, origin=origin, base_note=octave * 12, chromatic_mode=not self._scales.is_diatonic) def _update_aftertouch(self): if self.is_enabled() and self._aftertouch_control != None: self._aftertouch_control.send_value(Sysex.MONO_AFTERTOUCH) def _set_control_pads_from_script(self, takeover_pads): """ If takeover_pads is True, the matrix buttons will be controlled from the script. Otherwise they send midi notes to the track. """ if takeover_pads != self._takeover_pads: self._takeover_pads = takeover_pads self._update_matrix()