class ModifierComponent(CompoundComponent): __shift_down = False __select_down = False __delete_down = False __duplicate_down = False __browse_down = False __macro_down = False __quantize_setting = 5 def __init__(self, session, *a, **k): super(ModifierComponent, self).__init__(*a, **k) self.__session = session self.__delete_button = StateButton(True, MIDI_CC_TYPE, 0, 95, name='Clear_Button') self.__do_delete.subject = self.__delete_button self.__duplicate_button = StateButton(True, MIDI_CC_TYPE, 0, 96, name='Duplicate_Button') self.__do_duplicate.subject = self.__duplicate_button self._select_button = StateButton(True, MIDI_CC_TYPE, 0, 80, name='Select_Button') self.__do_select_button.subject = self._select_button self.__lock_button = StateButton(True, MIDI_CC_TYPE, 0, 47, name='Lock_Button') self.__do_lock.subject = self.__lock_button self.__macro_button = StateButton(True, MIDI_CC_TYPE, 0, 90, name='Macro_Button') self.__do_macro.subject = self.__macro_button self._left_button = StateButton(True, MIDI_CC_TYPE, 0, 107, name='Metro_Button') self._right_button = StateButton(True, MIDI_CC_TYPE, 0, 104, name='Loop_Button') self._rec_button = StateButton(True, MIDI_CC_TYPE, 0, 109, name='Record_Button') self._do_left_button.subject = self._left_button self._do_right_button.subject = self._right_button self._do_rec_button.subject = self._rec_button self._listen_overdub.subject = self.song() self._listen_loop.subject = self.song() self._listen_metronome.subject = self.song() self.__action_listener = None self.__shift_listener = None return @subject_slot('overdub') def _listen_overdub(self): if self.__shift_down: self._rec_button.set_display_value( self.song().overdub and 127 or 0, True) @subject_slot('loop') def _listen_loop(self): if self.__shift_down: self._right_button.set_display_value(self.song().loop and 127 or 0, True) @subject_slot('metronome') def _listen_metronome(self): if self.__shift_down: self._left_button.set_display_value( self.song().metronome and 127 or 0, True) def _update_shift_status(self): if self.__shift_down: self._rec_button.set_display_value( self.song().overdub and 127 or 0, True) self._right_button.set_display_value(self.song().loop and 127 or 0, True) self._left_button.set_display_value( self.song().metronome and 127 or 0, True) else: self._rec_button.set_display_value(0, True) self._left_button.set_display_value(0, True) self._right_button.set_display_value(0, True) def set_browse_down(self, value): self.__browse_down = value @subject_slot('value', identify_sender=True) def __do_lock(self, value, sender): if sender.grabbed: return if self.__action_listener and value > 0: newstate = self.__action_listener.notify_edit_toggle( LOCK_BUTTON, self.__shift_down) self.__lock_button.set_value(newstate) @subject_slot('value', identify_sender=True) def __do_macro(self, value, sender): if sender.grabbed: return self.__macro_down = value > 0 self.__macro_button.set_display_value(value, True) def set_edit_state(self, **args): if 'lock' in args: self.__lock_button.set_value(args['lock'] and 127 or 0) else: self.__lock_button.set_value(0) def set_shiftstatus(self, value): self.__shift_down = value > 0 self._update_shift_status() if self.__shift_listener: self.__shift_listener.notify_shift(self.__shift_down) @subject_slot('value', identify_sender=True) def __do_select_button(self, value, sender): if sender.grabbed: return self._select_button.send_value(value) self.__select_down = value > 0 def register_shift_listener(self, listener): self.__shift_listener = listener @subject_slot('value', identify_sender=True) def __do_delete(self, value, sender): if sender.grabbed: return self.__delete_button.send_value(value) if not self.__shift_down: self.__delete_down = value > 0 else: if value != 0: clip = self.song().view.detail_clip if clip != None: clip.clear_all_envelopes() self.canonical_parent.show_message('Clear Envelopes ' + clip.name) return @subject_slot('value', identify_sender=True) def __do_duplicate(self, value, sender): if sender.grabbed: return self.__duplicate_button.send_value(value) if not self.__shift_down: self.__duplicate_down = value > 0 else: if value != 0: clip = self.song().view.detail_clip if clip != None and clip.is_midi_clip: if clip.length <= 128.0: clip.duplicate_loop() self.canonical_parent.show_message( 'Double Loop : ' + str(int(clip.length / 4)) + ' Bars') self.application().view.focus_view('Detail/Clip') else: self.canonical_parent.show_message( 'Clip is to long to Duplicate') return def set_action_listener(self, listener): self.__action_listener = listener def handle_edit(self, clipslotcomp, value): if value == 0: return if clipslotcomp._clip_slot is not None: if self.__delete_down: self.__handle_delete(clipslotcomp) elif self.__duplicate_down: self.__handle_duplicate(clipslotcomp) elif self.__browse_down: self.__handle_mode_scene_clip(clipslotcomp) elif self.__macro_down: self.__handle_new_action(clipslotcomp) elif self.__select_down: self.__handle_select_action(clipslotcomp) elif self.__shift_down: self.__handle_shift_action(clipslotcomp) else: if self.__browse_down: self.__handle_mode_scene_clip(clipslotcomp) else: if self.__shift_down: self.__handle_shift_action(clipslotcomp) return def __handle_shift_action(self, clipslotcomp): columm, row = clipslotcomp.get_index() if row == 0: self.handle_edit_action(columm) def __handle_select_action(self, clipslotcomp): self.song().view.highlighted_clip_slot = clipslotcomp._clip_slot @subject_slot('value', identify_sender=True) def _do_left_button(self, value, sender): if sender.grabbed: return if self.__shift_down: if value == 0: return self.song().metronome = not self.song().metronome else: self._left_button.set_display_value(value > 0 and 127 or 0, True) if value == 0: return self.canonical_parent.invoke_nav_left() @subject_slot('value', identify_sender=True) def _do_right_button(self, value, sender): if sender.grabbed: return if self.__shift_down: if value == 0: return self.song().loop = not self.song().loop else: self._right_button.set_display_value(value > 0 and 127 or 0, True) if value == 0: return self.canonical_parent.invoke_nav_right() @subject_slot('value', identify_sender=True) def _do_rec_button(self, value, sender): if sender.grabbed: return if self.__shift_down: if value == 0: return self.song().overdub = not self.song().overdub else: self._rec_button.set_display_value(value > 0 and 127 or 0, True) if value > 0: self.canonical_parent.invoke_rec() def handle_edit_action(self, index, scale=None): if not self.__shift_down: return if index == 0: if self.song().can_undo == 1: self.song().undo() self.canonical_parent.show_message(str('UNDO')) else: if index == 1: if self.song().can_redo == 1: self.song().redo() self.canonical_parent.show_message(str('REDO')) else: if index == 2 or index == 3: clip = self.song().view.detail_clip if clip: clip.quantize(QUANT_CONST[self.__quantize_setting], index == 2 and 1.0 or 0.5) self.canonical_parent.show_message( 'Quantize Clip ' + clip.name + ' by ' + QUANT_STRING[self.__quantize_setting]) else: clip = self.song().view.detail_clip if clip and clip.is_midi_clip: track = clip.canonical_parent.canonical_parent drum_device = find_drum_device(track) if not drum_device: self.__transpose_clip(clip, TRANSPOSE[index], scale) def __transpose_clip(self, clip, amount, bn_scale): notes = clip.get_selected_notes() if len(notes) == 0: clip.select_all_notes() notes = clip.get_selected_notes() update_notes = [] for note in notes: pitch, pos, dur, vel, mute = note if bn_scale: basenote, scale = bn_scale pv = scale.transpose_by_scale(basenote, pitch, amount) else: pv = pitch + amount if pv < 0 or pv > 127: pv = pitch update_notes.append((pv, pos, dur, vel, mute)) clip.replace_selected_notes(tuple(update_notes)) def __handle_mode_scene_clip(self, clipslotcomp): if clipslotcomp._clip_slot is None: return clip_slot = clipslotcomp._clip_slot self.song().view.highlighted_clip_slot = clip_slot return def __handle_delete(self, clipslotcomp): if self.__shift_down: pass else: clipslotcomp._do_delete_clip() def __handle_duplicate(self, clipslotcomp): if self.__shift_down: pass else: self.duplicate_clip_slot(clipslotcomp._clip_slot) def __handle_new_action(self, clipslotcomp): song = self.song() clip_slot = clipslotcomp._clip_slot track = clip_slot.canonical_parent if clip_slot.clip == None and track.has_midi_input: try: clip_slot.create_clip(4.0) song.view.detail_clip = clip_slot.clip select_clip_slot(song, clip_slot) self.application().view.focus_view('Detail/Clip') self.canonical_parent.show_message( 'New Midi Clip ' + song.view.highlighted_clip_slot.clip.name) except Live.Base.LimitationError: pass except RuntimeError: pass return def double_clipslot(self, clip_slot): song = self.song() track = clip_slot.canonical_parent if clip_slot.clip is not None and track.has_midi_input: clip = clip_slot.clip if clip.length <= 2048.0: clip.duplicate_loop() self.canonical_parent.show_message('Double Loop : ' + str(int(clip.length / 4)) + ' Bars') song.view.detail_clip = clip self.application().view.focus_view('Detail/Clip') else: self.canonical_parent.show_message( 'Clip is to long to Duplicate') return def duplicate_clip_slot(self, clip_slot): if clip_slot.has_clip: try: track = clip_slot.canonical_parent index = list(track.clip_slots).index(clip_slot) track.duplicate_clip_slot(index) self.canonical_parent.show_message('Duplicate Clip ' + clip_slot.clip.name) select_clip_slot(self.song(), track.clip_slots[index + 1]) except Live.Base.LimitationError: pass except RuntimeError: pass def in_spec_mode(self): return self.__shift_down or self.__delete_down or self.__duplicate_down or self.__browse_down or self.__macro_down or self.__select_down def modifier_mask(self): return (self.__shift_down and MASK_SHIFT or 0) | (self.__browse_down and MASK_BROWSE or 0) | (self.__delete_down and MASK_CLEAR or 0) | ( self.__duplicate_down and MASK_DUPLICATE or 0) | (self.__macro_down and MASK_SPEC or 0) | ( self.__select_down and MASK_SELECT or 0) def is_select_down(self): return self.__select_down def is_browse_down(self): return self.__browse_down def is_shift_down(self): return self.__shift_down def is_delete_down(self): return self.__delete_down def is_duplicate_down(self): return self.__duplicate_down
class Maschine(ControlSurface): """Basic Control Script for All Maschine Modell Mikro, Mikro Mk2, Mk1, Mk2, Studio""" __module__ = __name__ _has_stop_button = False use_shift_pads = False __shift_down = False __delete_down = False __duplicate_down = False __select_down = False __undo_state = 0 __redo_state = 0 __play_button = None __nudge_value = 0.25 / 2 __quantize_setting = 5 def __init__(self, c_instance): super(Maschine, self).__init__(c_instance) with self.component_guard(): register_sender(self) self.init_type() self._diplay_cache = ['', '', '', ''] self._timed_text = None self._suppress_send_midi = True self._modifier = None self._c_ref = c_instance self.display_task = DisplayTask() self._challenge = Live.Application.get_random_int( 0, 400000000) & 2139062143 self._active = False self._midi_pause_count = 0 self.blink_state = 0 self.send_slider_index = 0 self.nav_index = 0 self.arm_selected_track = False self._set_suppress_rebuild_requests(True) self._main_mode_container = ModeHandler(c_instance.note_repeat) self._setup_session() self.__track_buttons = TrackControlComponent( self._session, self, name='Track_Select_Button_Matrix') self.__track_buttons.assign_buttons() self._setup_transport() self._set_global_buttons() self._encoder_section = EncoderView() self._init_maschine() self._init_settings() self.set_pad_translations(PAD_TRANSLATIONS) self._on_selected_track_changed() self.show_message(str('')) self.request_rebuild_midi_map() self._set_suppress_rebuild_requests(False) self._active = True self._display_device_param = False self._session.set_track_offset_listener(self.__update_tracks) self.set_feedback_channels(FEEDBACK_CHANNELS) self._final_init() self._main_mode_container.init_elements() self._suppress_send_midi = False self.apply_preferences() self.init_text_display() return def init_type(self): """ Needs to be overridden by specific version to define certain specialized behavior """ pass def _init_maschine(self): pass def _final_init(self): self._encoder_section.connect() def create_pad_button(self, scene_index, track_index, color_source): pass def create_gated_button(self, identifier, hue): pass def apply_preferences(self): pref_dict = self._pref_dict def store_preferences(self): pass def _init_settings(self): from pickle import loads, dumps from encodings import ascii nop(ascii) preferences = self._c_instance.preferences(self.preferences_name()) self._pref_dict = {} try: self._pref_dict = loads(str(preferences)) except Exception: pass pref_dict = self._pref_dict preferences.set_serializer(lambda: dumps(pref_dict)) def preferences_name(self): return 'Maschine' def _pre_serialize(self): from pickle import dumps from encodings import ascii nop(ascii) preferences = self._c_instance.preferences('Maschine') self.store_preferences() dump = dumps(self._pref_dict) preferences.set_serializer(lambda: dump) def _setup_session(self): self._session = MaschineSessionComponent() self._matrix = [] self._bmatrix = MaschineButtonMatrix(4, name='Button_Matrix') for sceneIndex in range(4): button_row = [] for trackindex in range(4): button = PadColorButton(True, 0, sceneIndex, trackindex, self._main_mode_container) button_row.append(button) self._matrix.append(tuple(button_row)) self._bmatrix.add_row(tuple(button_row)) self._session.set_matrix(self._bmatrix, self._matrix) for button, (trackIndex, sceneIndex) in self._bmatrix.iterbuttons(): if button: scene = self._session.scene(sceneIndex) clip_slot = scene.clip_slot(trackIndex) clip_slot.set_launch_button(button) clip_slot.set_triggered_to_play_value(1) clip_slot.set_triggered_to_record_value(1) clip_slot.set_started_value(1) clip_slot.set_recording_value(1) clip_slot.set_stopped_value(1) for sindex in range(self._session.height()): scene = self._session.scene(sindex) for cindex in range(self._session.width()): clip = scene.clip_slot(cindex) clip.set_modifier(self) clip.set_index((cindex, sindex)) self.set_highlighting_session_component(self._session) def _set_global_buttons(self): self._undo_button = StateButton(True, MIDI_CC_TYPE, BASE_CONTROL_CHANNEL, CC_UNDO_BUTTON) self._do_undo.subject = self._undo_button self._redo_button = StateButton(True, MIDI_CC_TYPE, BASE_CONTROL_CHANNEL, CC_REDO_BUTTON) self._do_redo.subject = self._redo_button self._shift_button = StateButton(True, MIDI_CC_TYPE, BASE_CONTROL_CHANNEL, CC_SHIFT_BUTTON) self._do_shift.subject = self._shift_button self._erase_button = StateButton(True, MIDI_CC_TYPE, BASE_CONTROL_CHANNEL, CC_ERASE_BUTTON) self._do_erase.subject = self._erase_button self._duplicate_button = StateButton(True, MIDI_CC_TYPE, BASE_CONTROL_CHANNEL, CC_DUPLICATE_BUTTON) self._do_duplicate.subject = self._duplicate_button self._selectback_button = StateButton(True, MIDI_CC_TYPE, BASE_CONTROL_CHANNEL, CC_BACKSEL_BUTTON) self._do_selectback.subject = self._selectback_button self._event_button = StateButton(True, MIDI_CC_TYPE, BASE_CONTROL_CHANNEL, CC_EVENTS_BUTTON) self._do_event_button.subject = self._event_button self.__arrange_button = StateButton(True, MIDI_CC_TYPE, BASE_CONTROL_CHANNEL, CC_ARRANGE_BUTTON, name='Arrange_button') self.__arrange_button.view_mode = VM_SESSION self._do_arrange.subject = self.__arrange_button def _setup_transport(self): is_momentary = True transport = TransportComponent() self.__play_button = StateButton(is_momentary, MIDI_CC_TYPE, BASE_CONTROL_CHANNEL, CC_PLAY_BUTTON, name='Play_Button') self.__restart_button = StateButton(True, MIDI_CC_TYPE, BASE_CONTROL_CHANNEL, CC_RESTART_BUTTON) stop_button = StateButton(not is_momentary, MIDI_CC_TYPE, BASE_CONTROL_CHANNEL, CC_STOP_BUTTON, name='Stop_Button') self._rec_button = StateButton(is_momentary, MIDI_CC_TYPE, BASE_CONTROL_CHANNEL, CC_RECORD_BUTTON, name='Record_Button') metrononme_button = StateButton(is_momentary, MIDI_CC_TYPE, BASE_CONTROL_CHANNEL, CC_METRONOME_BUTTON, name='Metronome_Button') self._song_follow_button = StateButton(is_momentary, MIDI_CC_TYPE, BASE_CONTROL_CHANNEL, CC_FOLLOW_BUTTON, name='Follow_Button') self._do_rec_button.subject = self._rec_button if self._has_stop_button: transport.set_play_button(self.__play_button) transport.set_stop_button(stop_button) else: self._hand_play_pressed.subject = self.__play_button self._listen_playing.subject = self.song() self._stopall_button = StateButton(True, MIDI_CC_TYPE, 0, CC_ALL_BUTTON) self._do_stop_all.subject = self._stopall_button self._auto_button = StateButton(True, MIDI_CC_TYPE, 0, CC_AUTO_BUTTON, name='Auto_Button') self._handle_automation_record.subject = self._auto_button self._listen_automation_record.subject = self.song() self._handle_restart_button.subject = self.__restart_button self._handle_follows_button.subject = self._song_follow_button self._follow_song_changed.subject = self.song().view transport.set_metronome_button(metrononme_button) self._listen_overdub.subject = self.song() def toggle_nav_mode(self): self._session.switch_step_advance() self.show_message(' View Navigation in steps of ' + str(self._session.get_step_advance())) def _update_hardware(self): self._session.update() self.update_undo_redo(True) self.__track_buttons.refresh_state() self._encoder_section.refresh_state() def refresh_state(self): ControlSurface.refresh_state(self) self._update_hardware() def _send_midi(self, midi_bytes, **keys): self._c_ref.send_midi(midi_bytes) return True def timed_display(self, text, grid): self.timed_message(grid, text, False) def init_text_display(self): pass def _on_selected_track_changed(self): super(Maschine, self)._on_selected_track_changed() self.set_controlled_track(self.song().view.selected_track) self._on_devices_changed.subject = self.song().view.selected_track @subject_slot('devices') def _on_devices_changed(self): pass def update(self): self.set_feedback_channels(FEEDBACK_CHANNELS) super(Maschine, self).update() def is_monochrome(self): return False def has_separate_pad_mode_buttons(self): return False def get_session(self): return self._session def handle_track_select(self, track): if not track: return if self.is_delete_down(): index = vindexof(self.song().tracks, track) self.song().delete_track(index) else: if self.is_duplicate_down(): index = vindexof(self.song().tracks, track) self.song().duplicate_track(index) else: if self.is_select_down(): if track.is_foldable: track.fold_state = track.fold_state == 0 and 1 or 0 else: if self.is_shift_down(): self.song().view.selected_track = track else: self.song().view.selected_track = track arm_exclusive(self.song(), track) def get_button_matrix(self): return self._bmatrix def deassign_matrix(self): for scene_index in range(4): scene = self._session.scene(scene_index) for track_index in range(4): clip_slot = scene.clip_slot(track_index) clip_slot.set_launch_button(None) return def update_display(self): with self.component_guard(): with self._is_sending_scheduled_messages(): self._task_group.update(0.1) self._main_mode_container.notify(self.blink_state) self.__track_buttons.notify(self.blink_state) self.blink_state = (self.blink_state + 1) % 4 self.display_task.tick() self.update_undo_redo(False) if self.blink_state == 0: self.update_arrange_button() def handle_notify(self, blink_state): pass def __update_tracks(self, track_offset): self.__track_buttons.assign_buttons() self._encoder_section.handle_offset_changed() def update_undo_redo(self, force=False): if force: self.__undo_state = self.song().can_undo self.__redo_state = self.song().can_redo if self.song().can_undo != self.__undo_state: self.__undo_state = self.song().can_undo self._undo_button.send_value(self.__undo_state == 1 and 127 or 0) if self.song().can_redo != self.__redo_state: self.__redo_state = self.song().can_redo self._redo_button.send_value(self.__redo_state == 1 and 127 or 0) def navigate(self, nav_dir, modifier, alt_modifier=False): self._main_mode_container.navigate(nav_dir, modifier, alt_modifier) self._encoder_section.navigate(nav_dir, modifier, alt_modifier) def handle_edit_action(self, grid, scale=None): if not self.is_shift_down(): return if grid == (0, 3): if self.song().can_undo == 1: self.song().undo() self.show_message(str('UNDO')) else: if grid == (1, 3): if self.song().can_redo == 1: self.song().redo() self.show_message(str('REDO')) else: if grid == (2, 3): clip = self.song().view.detail_clip if clip and clip.is_midi_clip: clip.select_all_notes() else: if grid == (3, 3): clip = self.song().view.detail_clip if clip and clip.is_midi_clip: clip.deselect_all_notes() else: if grid == (0, 2) or grid == (1, 2): clip = self.song().view.detail_clip if clip: clip.quantize( QUANT_CONST[self.__quantize_setting], grid[0] == 0 and 1.0 or 0.5) self.show_message( 'Quantize Clip ' + clip.name + ' by ' + QUANT_STRING[self.__quantize_setting]) else: if grid == (2, 2): clip = self.song().view.detail_clip if clip and clip.is_midi_clip: self.application().view.show_view( 'Detail/Clip') nudge_notes_in_clip( clip, clip.get_selected_notes(), self.__nudge_value * -1) else: if grid == (3, 2): clip = self.song().view.detail_clip if clip and clip.is_midi_clip: self.application().view.show_view( 'Detail/Clip') nudge_notes_in_clip( clip, clip.get_selected_notes(), self.__nudge_value) else: if grid == (0, 1): clip = self.song().view.detail_clip if clip and clip.is_midi_clip: clip.replace_selected_notes( tuple([])) else: if grid == (1, 1): clip = self.song().view.detail_clip if clip: clip.has_envelopes and clip.clear_all_envelopes( ) else: if grid[1] == 0: clip = self.song( ).view.detail_clip if clip and clip.is_midi_clip: track = clip.canonical_parent.canonical_parent drum_device = find_drum_device( track) if not drum_device: notes = clip.get_selected_notes( ) transpose_notes_in_clip( clip, notes, TRANSPOSE[grid[0]], None) return @subject_slot('is_playing') def _listen_playing(self): if self.song().is_playing: self.__play_button.set_display_value(127, True) else: self.__play_button.set_display_value(0, True) @subject_slot('value') def _hand_play_pressed(self, value): if value != 0: if self.song().is_playing: if self.is_shift_down(): self.song().start_playing() else: self.song().stop_playing() else: self.song().start_playing() @subject_slot('value') def _do_undo(self, value): if value != 0: if self.use_layered_buttons() and self.is_shift_down(): if self.song().can_redo == 1: self.song().redo() self.show_message(str('REDO')) elif self.song().can_undo == 1: self.song().undo() self.show_message(str('UNDO')) @subject_slot('value') def _do_redo(self, value): if value != 0: if self.song().can_redo == 1: self.song().redo() self.show_message(str('REDO')) def handle_modifier(self, value): self._main_mode_container.handle_modifier(value) self._encoder_section.notify_shift(value) def notify_shift(self, value): self.__shift_down = value self.handle_modifier(self.get_modifier_state()) @subject_slot('value') def _do_shift(self, value): self._shift_button.send_value(value) self.__shift_down = value > 0 self.handle_modifier(self.get_modifier_state()) @subject_slot('value') def _do_erase(self, value): self._erase_button.send_value(value) self.__delete_down = value > 0 @subject_slot('value') def _do_duplicate(self, value): self._duplicate_button.send_value(value) self.__duplicate_down = value > 0 @subject_slot('value') def _do_selectback(self, value): self._selectback_button.send_value(value) self.__select_down = value > 0 @subject_slot('overdub') def _listen_overdub(self): self._rec_button.set_display_value(self.song().overdub and 127 or 0, True) @subject_slot('value') def _do_rec_button(self, value): if self.is_shift_down(): if value != 0: self.song().overdub = not self.song().overdub else: self.invoke_rec() def invoke_rec(self): slot = self.song().view.highlighted_clip_slot if slot == None: return if slot.controls_other_clips: slot.fire() else: if slot.has_clip: track = slot.canonical_parent if track.can_be_armed: arm_exclusive(self.song(), track) self.song().overdub = True slot.fire() else: track = slot.canonical_parent if track.can_be_armed: arm_exclusive(self.song(), track) slot.fire() return @subject_slot('value') def _do_fire_button(self, value): assert self._fire_button != None assert value in range(128) if value != 0: if self.is_shift_down(): self.song().tap_tempo() else: clip_slot = self.song().view.highlighted_clip_slot if clip_slot: clip_slot.fire() return @subject_slot('value') def _do_stop_all(self, value): self._do_stop_all.send_value(value) if value != 0: if self.is_shift_down(): self.song().stop_all_clips(0) else: self.song().stop_all_clips(1) @subject_slot('value') def _do_test(self, value): pass @subject_slot('value') def _do_arrange(self, value): if value != 0: appv = self.application().view if appv.is_view_visible('Arranger'): self.application().view.show_view('Session') else: self.application().view.show_view('Arranger') self.update_arrange_button() def update_arrange_button(self): appv = self.application().view if appv.is_view_visible('Arranger'): if self.__arrange_button.view_mode == VM_SESSION: self.__arrange_button.set_display_value(127, True) self.__arrange_button.view_mode = VM_ARRANGE else: if self.__arrange_button.view_mode == VM_ARRANGE: self.__arrange_button.set_display_value(0, True) self.__arrange_button.view_mode = VM_SESSION @subject_slot('session_automation_record') def _listen_automation_record(self): self._auto_button.set_display_value( self.song().session_automation_record and 127 or 0, True) @subject_slot('value') def _do_event_button(self, value): self._event_button.send_value(value) if value != 0: if self.is_shift_down(): clip = self.song().view.detail_clip if clip and clip.is_midi_clip: clip.select_all_notes() @subject_slot('value') def _handle_follows_button(self, value): if value != 0: self.song().view.follow_song = not self.song().view.follow_song @subject_slot('follow_song') def _follow_song_changed(self): self._song_follow_button.send_value( self.song().view.follow_song and 127 or 0) @subject_slot('value') def _handle_restart_button(self, value): self.__restart_button.send_value(value) if value != 0: self.song().stop_playing() self.song().stop_playing() @subject_slot('value') def _handle_automation_record(self, value): if value > 0: self.song().session_automation_record = not self.song( ).session_automation_record def handle_edit(self, clipslotcomp, value): self._main_mode_container.handle_edit(clipslotcomp, value) def do_midi_launch(self, clipslotcomp, value): if self.is_shift_down(): clipslotcomp._do_launch_clip(value) else: if clipslotcomp._clip_slot.has_clip: clipslotcomp._do_launch_clip(value) else: clipslotcomp._clip_slot.create_clip(DEFAULT_CLIP_INIT_SIZE) clipslotcomp._do_launch_clip(value) def is_select_down(self): return self.__select_down def is_shift_down(self): return self.__shift_down def is_delete_down(self): return self.__delete_down def is_duplicate_down(self): return self.__duplicate_down def get_modifier_state(self): return (self.__shift_down and MODIFIER_SHIFT) | ( self.__select_down and MODIFIER_SELECT) | (self.__delete_down and MODIFIER_DELETE) | ( self.__duplicate_down and MODIFIER_DUPLICATE) def get_track_buttons(self): return self.__track_buttons def in_spec_mode(self): """ If Some Modifier is being held down. This concerns only the grid matrix. In Cases where Shift + Pad Button do not call a function (Maschine Studio only) Nothing should happen just like with the Maschine Software """ return self.__shift_down or self.__delete_down or self.__duplicate_down def use_shift_matrix(self): return False def use_layered_buttons(self): return False def to_color_edit_mode(self, active): pass def clear_display_all(self): self.send_to_display('', 0) self.send_to_display('', 1) self.send_to_display('', 2) self.send_to_display('', 3) def clear_display(self, grid): if self._timed_text: self.send_to_display(self._timed_text, grid) else: self.send_to_display('', grid) def timed_message(self, grid, text, hold=False): if USE_DISPLAY == False: self.show_message(text) else: if not self.display_task.active(grid): self._timed_text = self._diplay_cache[grid] self.display_task.set_func(self.clear_display, grid) self.send_to_display(text, grid) if hold: self.display_task.hold() self.display_task.start() def timed_message_release(self): self.display_task.release() def update_bank_display(self): if USE_DISPLAY: name, bank = self._device._current_bank_details() if self._display_device_param: prms = len(bank) d1 = '' for i in range(4): parm = bank[i] if parm: name = parm.name d1 += name[:6] + (i < 3 and '|' or '') else: d1 += ' ' + (i < 3 and '|' or '') self.send_to_display(d1, 2) d1 = '' for i in range(4): parm = bank[i + 4] if parm: name = parm.name d1 += name[:6] + (i < 3 and '|' or '') else: d1 += ' ' + (i < 3 and '|' or '') self.send_to_display(d1, 4) else: self.timed_message(2, 'Bank: ' + name) def display_parameters(self, paramlist): if USE_DISPLAY == False: return def send_to_display(self, text, grid=0, force=False): if USE_DISPLAY == False: return if not force and self._diplay_cache[grid] == text: return self._diplay_cache[grid] = text if len(text) > 28: text = text[:27] msgsysex = [240, 0, 0, 102, 23, 18, min(grid, 3) * 28] filled = text.ljust(28) for c in filled: msgsysex.append(ord(c)) msgsysex.append(247) self._send_midi(tuple(msgsysex)) def cleanup(self): pass def disconnect(self): self._pre_serialize() self.clear_display_all() for button, (track_index, _) in self._bmatrix.iterbuttons(): if button: button.turn_off() time.sleep(0.2) self._active = False self._suppress_send_midi = True super(Maschine, self).disconnect() return
class MaschineJam(ControlSurface): """Control Script for Maschine JAM Controller""" __module__ = __name__ _midi_count = 0 _arm_exclusive = True _solo_exclusive = True _blink_state = 0 __midi_count = 0 __play_button = None __block_nav = False __matrix_state = None def __init__(self, c_instance): super(MaschineJam, self).__init__(c_instance) with self.component_guard(): self._suppress_send_midi = True register_sender(self) self._challenge = Live.Application.get_random_int(0, 400000000) & 2139062143 self._set_suppress_rebuild_requests(True) self._c_ref = c_instance self.request_rebuild_midi_map() self.__matrix_state = MatrixState(self) self._main_mode_container = JamModes(c_instance.note_repeat) self._set_suppress_rebuild_requests(False) self._active = True self._display_device_param = False self._setup_transport() self._setup_session() self._encoder_modes = EncoderComponent(self._session) self._encoder_modes.connect() self._encoder_modes.set_state_listener(self) self._modifier = ModifierComponent(self._session) self._connect_session() self._main_mode_container.bind_session(self.__matrix_state) self._main_mode_container.bind_modify_component(self._modifier) self._setup_mainjogwheel() self._init_m4l() self._init_settings() self.set_pad_translations(PAD_TRANSLATIONS) self.set_feedback_channels(FEEDBACK_CHANNELS) self._suppress_send_midi = False self._main_mode_container._step_mode.set_mode_elements(self._modifier, self._encoder_modes) self._main_mode_container._drum_step_mode.set_mode_elements(self._modifier) self._final_init() self.apply_preferences() def _init_m4l(self): self._bmatrix.set_user_unbind_listener(self._main_mode_container) def _init_settings(self): from pickle import loads, dumps from encodings import ascii nop(ascii) preferences = self._c_instance.preferences(self.preferences_name()) self._pref_dict = {} try: self._pref_dict = loads(str(preferences)) except Exception: pass pref_dict = self._pref_dict preferences.set_serializer(lambda : dumps(pref_dict)) def store_preferences(self): self._pref_dict['matrix_color_mode'] = self._session.get_color_mode() def apply_preferences(self): pref_dict = self._pref_dict if 'matrix_color_mode' in pref_dict: self._session.set_color_mode(pref_dict['matrix_color_mode']) def preferences_name(self): return 'MaschineJam' def _final_init(self): debug_out('########## LIVE 10 Maschine JAM V 1.3 #############') self._auto_button.set_display_value(self.song().session_automation_record and 127 or 0) self._main_mode_container.init_elements() def _init_map(self): msgsysex = [ 240, 0, 33, 9, 21, 0, 77, 80, 0, 1, 2] for _ in range(80): msgsysex.append(COLOR_BLACK) msgsysex.append(247) self._send_midi(tuple(msgsysex)) def _set_touch_strip_led(self): msgsysex = [ 240, 0, 33, 9, 21, 0, 77, 80, 0, 1, 4] for _ in range(8): msgsysex.append(0) msgsysex.append(247) self._send_midi(tuple(msgsysex)) def handle_sysex(self, midi_bytes): if len(midi_bytes) > 11 and midi_bytes[0:10] == (240, 0, 33, 9, 21, 0, 77, 80, 0, 1): msg, value = midi_bytes[10:12] if msg == 70: self.refresh_state() self._modifier.set_shiftstatus(1) elif msg == 77: self.shiftButton.notify_value(value == 1 and 127 or 0) if not self.shiftButton.is_grabbed: self._modifier.set_shiftstatus(value) def _setup_transport(self): is_momentary = True self.shiftButton = SysExButton(120, name='Shift_Button') self.__play_button = StateButton(is_momentary, MIDI_CC_TYPE, 0, 108, name='Play_Button') self._hand_play_pressed.subject = self.__play_button self._listen_playing.subject = self.song() self._channel_led_left = SliderElement(MIDI_CC_TYPE, 0, 38) self._channel_led_right = SliderElement(MIDI_CC_TYPE, 0, 39) self._channel_led_left.last_raw = 0.0 self._channel_led_left.last_send = 0 self._channel_led_right.last_raw = 0.0 self._channel_led_right.last_send = 0 self._listen_master_left.subject = self.song().master_track self._listen_master_right.subject = self.song().master_track self._auto_button = StateButton(is_momentary, MIDI_CC_TYPE, 0, 98, name='Auto_Button') self._listen_automation_record.subject = self.song() self._handle_automation_record.subject = self._auto_button self._do_direction_up.subject = StateButton(is_momentary, MIDI_CC_TYPE, 0, 40, name='Up_Arrow') self._do_direction_down.subject = StateButton(is_momentary, MIDI_CC_TYPE, 0, 41, name='Down_Arrow') self._do_direction_left.subject = StateButton(is_momentary, MIDI_CC_TYPE, 0, 42, name='Left_Arrow') self._do_direction_right.subject = StateButton(is_momentary, MIDI_CC_TYPE, 0, 43, name='Right_Arrow') @subject_slot('output_meter_left') def _listen_master_left(self): cvl = self.song().master_track.output_meter_left if cvl != self._channel_led_left.last_raw: val = cvl > 0.92 and 127 or int(127 * (cvl * cvl)) if val != self._channel_led_left.last_send: self._channel_led_left.last_raw = cvl self._channel_led_left.last_send = val self._channel_led_left.send_value(val, True) @subject_slot('output_meter_right') def _listen_master_right(self): cvl = self.song().master_track.output_meter_right if cvl != self._channel_led_right.last_raw: val = cvl > 0.92 and 127 or int(127 * (cvl * cvl)) if val != self._channel_led_right.last_send: self._channel_led_right.last_raw = cvl self._channel_led_right.last_send = val self._channel_led_right.send_value(val, True) def is_monochrome(self): return False def _send_midi(self, midi_bytes, **keys): self._c_ref.send_midi(midi_bytes) if self._midi_count > 2: time.sleep(0.001) self._midi_count = 0 self._midi_count += 1 return True def _setup_mainjogwheel(self): self._prev_mode = None return def is_shift_down(self): return self._modifier.is_shift_down() def modifier_mask(self): return self._modifier.modifier_mask() def _setup_session(self): self._session = JamSessionComponent() self._matrix = [] self._bmatrix = JamButtonMatrix(8, name='Button_Matrix') for sceneIndex in range(8): button_row = [] for trackindex in range(8): button = PadColorButton(True, 0, sceneIndex, trackindex, self._main_mode_container) button_row.append(button) self._matrix.append(tuple(button_row)) self._bmatrix.add_row(tuple(button_row)) self._session.set_matrix(self._bmatrix, self._matrix) self.__matrix_state.register_matrix(self._bmatrix) self._bmatrix.prepare_update() for button, (trackIndex, sceneIndex) in self._bmatrix.iterbuttons(): if button: scene = self._session.scene(sceneIndex) clip_slot = scene.clip_slot(trackIndex) clip_slot.set_launch_button(button) clip_slot.set_triggered_to_play_value(1) clip_slot.set_triggered_to_record_value(1) clip_slot.set_started_value(1) clip_slot.set_recording_value(1) clip_slot.set_stopped_value(1) self._session._link() self._bmatrix.commit_update() self.set_highlighting_session_component(self._session) def _connect_session(self): for sindex in range(self._session.height()): scene = self._session.scene(sindex) for cindex in range(self._session.width()): clip = scene.clip_slot(cindex) clip.set_modifier(self._modifier) clip.set_index((cindex, sindex)) def update_display(self): with self.component_guard(): self._main_mode_container.notify(self._blink_state) self._encoder_modes.notify(self._blink_state) self._blink_state = (self._blink_state + 1) % 4 def refresh_state(self): self._bmatrix.prepare_update() ControlSurface.refresh_state(self) self.update_hardware() self._bmatrix.commit_update() def update_hardware(self): self._session.update() self.__play_button.set_display_value(self.song().is_playing and 127 or 0, True) self._main_mode_container.refresh_state() self._encoder_modes.refresh_state() def invoke_nav_left(self): self._encoder_modes.invoke_nav_left() def invoke_nav_right(self): self._encoder_modes.invoke_nav_right() def invoke_rec(self): slot = self.song().view.highlighted_clip_slot if slot == None: return if slot.controls_other_clips: slot.fire() else: if slot.has_clip: track = slot.canonical_parent if track.can_be_armed: arm_exclusive(self.song(), track) self.song().overdub = True slot.fire() else: track = slot.canonical_parent if track.can_be_armed: arm_exclusive(self.song(), track) slot.fire() return @subject_slot('session_automation_record') def _listen_automation_record(self): self._auto_button.set_display_value(self.song().session_automation_record and 127 or 0, True) @subject_slot('value', identify_sender=True) def _handle_automation_record(self, value, sender): if value == 0 or sender.grabbed: return self.song().session_automation_record = not self.song().session_automation_record @subject_slot('is_playing') def _listen_playing(self): if self.song().is_playing: self.__play_button.set_display_value(127, True) else: self.__play_button.set_display_value(0, True) @subject_slot('value', identify_sender=True) def _hand_play_pressed(self, value, sender): if value == 0 or sender.grabbed: return if self.song().is_playing: if self._modifier.is_shift_down(): self.song().start_playing() else: self.song().stop_playing() else: self.song().start_playing() @subject_slot('value', identify_sender=True) def do_undo(self, value, sender): if value == 0 or sender.grabbed: return if self._modifier.is_shift_down(): if self.song().can_redo == 1: self.song().redo() self.show_message(str('REDO')) else: if self.song().can_undo == 1: self.song().undo() self.show_message(str('UNDO')) def notify_state(self, state, value): if state == 'controldown': self.__block_nav = value else: if state == 'step': self._main_mode_container.notify_state(state, value) @subject_slot('value', identify_sender=True) def _do_direction_up(self, value, sender): if value == 0 or sender.grabbed: return if not self.__block_nav: self._main_mode_container.navigate(-1, 1, self._modifier.is_shift_down(), NAV_SRC_BUTTON) else: self._encoder_modes.navigate(-1, 1, self._modifier.is_shift_down(), NAV_SRC_BUTTON) @subject_slot('value', identify_sender=True) def _do_direction_down(self, value, sender): if value == 0 or sender.grabbed: return if not self.__block_nav: self._main_mode_container.navigate(1, 1, self._modifier.is_shift_down(), NAV_SRC_BUTTON) else: self._encoder_modes.navigate(1, -1, self._modifier.is_shift_down(), NAV_SRC_BUTTON) @subject_slot('value', identify_sender=True) def _do_direction_left(self, value, sender): if value == 0 or sender.grabbed: return if not self.__block_nav: encoder_changed = self._main_mode_container.navigate(-1, 0, self._modifier.is_shift_down(), NAV_SRC_BUTTON) if encoder_changed: self._encoder_modes.navigate(-1, 0, self._modifier.is_shift_down(), NAV_SRC_BUTTON) else: self._encoder_modes.navigate(-1, 0, self._modifier.is_shift_down(), NAV_SRC_BUTTON) @subject_slot('value', identify_sender=True) def _do_direction_right(self, value, sender): if value == 0 or sender.grabbed: return if not self.__block_nav: encoder_changed = self._main_mode_container.navigate(1, 0, self._modifier.is_shift_down(), NAV_SRC_BUTTON) if encoder_changed: self._encoder_modes.navigate(1, 0, self._modifier.is_shift_down(), NAV_SRC_BUTTON) else: self._encoder_modes.navigate(1, 0, self._modifier.is_shift_down(), NAV_SRC_BUTTON) @property def selected_mode(self): return self._main_mode_container.selected_mode @selected_mode.setter def selected_mode(self, value): self._main_mode_container.selected_mode = value def get_session(self): return self._session def toggle_mode(self): self._session.set_color_mode() self.refresh_state() def get_button_matrix(self): return self._bmatrix def deassign_matrix(self): for scene_index in range(8): scene = self._session.scene(scene_index) for track_index in range(8): clip_slot = scene.clip_slot(track_index) clip_slot.set_launch_button(None) return def _pre_serialize(self): from pickle import dumps from encodings import ascii nop(ascii) preferences = self._c_instance.preferences('MaschineJam') self.store_preferences() dump = dumps(self._pref_dict) preferences.set_serializer(lambda : dump) def disconnect(self): self._pre_serialize() self._set_touch_strip_led() self._encoder_modes.turn_off_bars() self._init_map() self._channel_led_left.send_value(0, True) self._channel_led_right.send_value(0, True) self._active = False self._suppress_send_midi = True super(MaschineJam, self).disconnect() return