class ChannelStripComponent(ChannelStripComponentBase): empty_color = 'Mixer.EmptyTrack' track_color_control = SendValueControl() static_color_control = SendValueControl() def __init__(self, *a, **k): super(ChannelStripComponent, self).__init__(*a, **k) self._static_color_value = 0 self._track_color_value = 0 def set_static_color_value(self, value): if value is not None: self._static_color_value = value self._update_static_color_control() return def set_track(self, track): super(ChannelStripComponent, self).set_track(track) self.__on_track_color_changed.subject = track if liveobj_valid( track) else None self.__on_track_color_changed() self._update_static_color_control() return @listens('color') def __on_track_color_changed(self): self._track_color_value = get_midi_color_value_for_track(self._track) self._track_color_changed() def _track_color_changed(self): self.track_color_control.value = self._track_color_value def _update_static_color_control(self): self.static_color_control.value = self._static_color_value if liveobj_valid( self._track) else 0
class ScrollingDeviceNavigationComponent(DeviceNavigationComponent): prev_device_button = ButtonControl(color=b'Action.Off', pressed_color=b'Action.On') next_device_button = ButtonControl(color=b'Action.Off', pressed_color=b'Action.On') num_devices_control = SendValueControl() device_index_control = SendValueControl() device_name_display = TextDisplayControl() def __init__(self, *a, **k): super(ScrollingDeviceNavigationComponent, self).__init__(item_provider=ScrollableDeviceChain(), *a, **k) @prev_device_button.pressed def prev_device_button(self, value): self.item_provider.scroll_left() @next_device_button.pressed def next_device_button(self, value): self.item_provider.scroll_right() def _on_selection_changed(self): selected_item = self.item_provider.selected_item self._select_item(selected_item) self._update_device_index_control() def _on_items_changed(self): self.num_devices_control.value = len(self.item_provider.items) self._update_device_index_control() def _update_device(self): device = self._device_component.device() live_device = getattr(device, b'proxied_object', device) self._update_item_provider( live_device if liveobj_valid(live_device) else None) self.__on_appointed_device_name_changed.subject = device self.__on_appointed_device_name_changed() return @listens(b'name') def __on_appointed_device_name_changed(self): self._update_device_name_display() def _update_device_index_control(self): selected_index = self.item_provider.selected_index if selected_index is not None: self.device_index_control.value = selected_index return def _update_device_name_display(self): device = self._device_component.device() self.device_name_display[0] = device.name if liveobj_valid( device) else b'No Device'
class SceneComponent(SceneComponentBase): clip_slot_component_type = ClipSlotComponent scene_name_display = TextDisplayControl(segments=('', )) scene_color_control = ButtonControl() scene_selection_control = SendValueControl() force_launch_button = ButtonControl(color='Session.SceneOff', pressed_color='Session.SceneOn') _default_scene_color = 'DefaultButton.Off' def __init__(self, *a, **k): (super(SceneComponent, self).__init__)(*a, **k) @force_launch_button.pressed def force_launch_button(self, value): self._on_launch_button_pressed() @force_launch_button.released def force_launch_button(self, value): self._on_launch_button_released() def set_scene(self, scene): super(SceneComponent, self).set_scene(scene) self._SceneComponent__on_scene_name_changed.subject = self._scene self._SceneComponent__on_scene_name_changed() self._SceneComponent__on_scene_color_changed.subject = self._scene self._SceneComponent__on_scene_color_changed() self._SceneComponent__on_selected_scene_changed.subject = self.song.view self._SceneComponent__on_selected_scene_changed() @listens('name') def __on_scene_name_changed(self): self._update_scene_name_display() @listens('color_index') def __on_scene_color_changed(self): self._update_scene_color_control() @listens('selected_scene') def __on_selected_scene_changed(self): self.scene_selection_control.value = int(self.song.view.selected_scene == self._scene) def _update_scene_color_control(self): color = 'DefaultButton.Off' scene = self._scene if liveobj_valid(scene): color = scene.color_index + LIVE_COLOR_TABLE_INDEX_OFFSET if scene.color_index != None else self._default_scene_color self.scene_color_control.color = color def _update_scene_name_display(self): scene = self._scene self.scene_name_display[0] = scene.name if liveobj_valid(scene) else '' def _update_launch_button(self): value_to_send = self._no_scene_color if liveobj_valid(self._scene): value_to_send = self._scene_color if self._scene.is_triggered: value_to_send = self._triggered_color self.launch_button.color = value_to_send
class FocusFollowComponent(Component): focus_follow_control = SendValueControl() def __init__(self, *a, **k): super(FocusFollowComponent, self).__init__(*a, **k) self._track = None self.__on_selected_track_changed.subject = self.song.view self.__on_selected_track_changed() return @listens('selected_track') def __on_selected_track_changed(self): track = self.song.view.selected_track self._track = track if track.has_midi_input else None self.update() return @listens_group('chains') def __on_chains_changed(self, _): self.update() @listens_group('devices') def __on_devices_changed(self, _): self.update() def update(self): super(FocusFollowComponent, self).update() self._update_listeners() self._update_komplete_kontrol_instance() def _update_listeners(self): devices = list(find_instrument_devices(self._track)) racks = filter(lambda d: d.can_have_chains, devices) chains = list(chain([self._track], *[d.chains for d in racks])) self.__on_chains_changed.replace_subjects(racks) self.__on_devices_changed.replace_subjects(chains) def _update_komplete_kontrol_instance(self): instance = find_instrument_meeting_requirement( lambda d: isinstance(d, Live.PluginDevice.PluginDevice) and d.name. startswith(KK_NAME_PREFIX), self._track) param_name = '' if liveobj_valid(instance): param_name = instance.get_parameter_names(end=1)[0] self.focus_follow_control.value = tuple([ord(n) for n in param_name])
class ChannelStripComponent(ChannelStripComponentBase): track_arm_display = SendValueControl() def __init__(self, *a, **k): (super(ChannelStripComponent, self).__init__)(*a, **k) self._meter_display_callback = None def set_meter_display_callback(self, callback): self._meter_display_callback = callback def set_track(self, track): super(ChannelStripComponent, self).set_track(track) self._on_implicit_arm_changed.subject = self._track if (liveobj_valid(self._track)) and (self._track.can_be_armed) else None def select_track(self): if liveobj_valid(self._track): if self.song.view.selected_track != self._track: self.song.view.selected_track = self._track def mute_track(self): if liveobj_valid(self._track): if self._track != self.song.master_track: self._track.mute = not self._track.mute def solo_track(self): if liveobj_valid(self._track): if self._track != self.song.master_track: solo_exclusive = self.song.exclusive_solo new_value = not self._track.solo respect_multi_selection = self._track.is_part_of_selection for track in chain(self.song.tracks, self.song.return_tracks): self.update_solo_state(solo_exclusive, new_value, respect_multi_selection, track) def update(self): super(ChannelStripComponent, self).update() self._update_output_listeners() def _update_output_listeners(self): has_track = liveobj_valid(self._track) with_audio = self._track if has_track and (self._track.has_audio_output) else None self._on_output_meter_left_changed.subject = with_audio self._on_output_meter_right_changed.subject = with_audio self._on_has_audio_output_changed.subject = self._track if has_track else None if with_audio: self._on_output_meter_left_changed() elif self._meter_display_callback: self._meter_display_callback((0, 0)) def _on_arm_changed(self): self.track_arm_display.value = int(self._track.can_be_armed and (self._track.arm or self._track.implicit_arm)) if liveobj_valid(self._track) else 0 @listens('implicit_arm') def _on_implicit_arm_changed(self): self._on_arm_changed() @listens('has_audio_output') def _on_has_audio_output_changed(self): self._update_output_listeners() @listens('output_meter_left') def _on_output_meter_left_changed(self): self._update_meter_display() @listens('output_meter_right') def _on_output_meter_right_changed(self): self._update_meter_display() def _update_meter_display(self): if self._meter_display_callback: self._meter_display_callback(( int(self._track.output_meter_left * 127), int(self._track.output_meter_right * 127)))
class ChannelStripComponent(ChannelStripComponentBase): track_type_control = SendValueControl() oled_display_style_control = SendValueControl() arm_color_control = ButtonControl() mute_color_control = ButtonControl() solo_color_control = ButtonControl() output_meter_left_control = SendValueControl() output_meter_right_control = SendValueControl() track_color_control = ButtonControl() physical_track_color_control = ButtonControl() volume_touch_control = ButtonControl() solo_mute_button = ButtonControl() crossfade_assign_control = SendReceiveValueControl() assign_a_button = ButtonControl() assign_b_button = ButtonControl() assign_a_color_control = ButtonControl() assign_b_color_control = ButtonControl() volume_value_display = TextDisplayControl() pan_value_display = TextDisplayControl() send_value_displays = control_list(TextDisplayControl, MAX_NUM_SENDS) mpc_mute_button = ButtonControl() def __init__(self, *a, **k): self._oled_display_track_name_data_source = DisplayDataSource() self._oled_display_volume_value_data_source = DisplayDataSource() self._track_name_or_volume_value_display = None self._drum_group_finder = None super(ChannelStripComponent, self).__init__(*a, **k) self.__on_selected_track_changed.subject = self.song.view self.__on_selected_track_changed() self._drum_group_finder = self.register_disconnectable(PercussionInstrumentFinder(device_parent=self.track)) def set_track(self, track): super(ChannelStripComponent, self).set_track(track) self._drum_group_finder.device_parent = track self.__on_drum_group_found.subject = self._drum_group_finder self.__on_drum_group_found() self._update_listeners() self._update_controls() def set_volume_control(self, control): super(ChannelStripComponent, self).set_volume_control(control) self.volume_touch_control.set_control_element(control.touch_element if control else None) def set_track_name_display(self, display): if display: display.set_data_sources([self.track_name_data_source()]) def set_track_name_or_volume_value_display(self, display): self._track_name_or_volume_value_display = display self._update_track_name_or_volume_value_display() def set_send_value_displays(self, displays): self.send_value_displays.set_control_element(displays) @volume_touch_control.pressed def volume_touch_control(self, _): self._update_track_name_or_volume_value_display() @volume_touch_control.released def volume_touch_control(self, _): self._update_track_name_or_volume_value_display() @crossfade_assign_control.value def crossfade_assign_control(self, value, _): value_to_set = force_to_live_crossfade_assign_value(value) if value_to_set < len(LIVE_CROSSFADE_ASSIGN_VALUES) and self._track_has_visible_crossfade_assignment_buttons(): self.track.mixer_device.crossfade_assign = value_to_set @assign_a_button.pressed def assign_a_button(self, _): self._toggle_crossfade_assign(force_to_live_crossfade_assign_value(CROSSFADE_ASSIGN_A)) @assign_b_button.pressed def assign_b_button(self, _): self._toggle_crossfade_assign(force_to_live_crossfade_assign_value(CROSSFADE_ASSIGN_B)) @mpc_mute_button.pressed def mpc_mute_button(self, _): track = self.track if liveobj_valid(track) and track != self.song.master_track: track.mute = not track.mute def _on_select_button_pressed_delayed(self, _): if self.track.is_foldable: self.track.fold_state = not self.track.fold_state @listens(u'has_audio_output') def __on_has_audio_output_changed(self): self._update_output_meter_listeners() self._update_track_type_control() self._update_oled_display_style_control() self._update_crossfade_assignment_control() self._update_crossfade_assign_color_controls() def _update_output_meter_listeners(self): track = self.track subject = track if liveobj_valid(track) and track.has_audio_output else None self.__on_output_meter_left_changed.subject = subject self.__on_output_meter_right_changed.subject = subject if liveobj_valid(subject): self.__on_output_meter_left_changed() self.__on_output_meter_right_changed() else: self._reset_output_meter_controls() def _on_arm_changed(self): super(ChannelStripComponent, self)._on_arm_changed() self._update_arm_color_control() def _on_mute_changed(self): self._update_mute_color_controls() def _on_solo_changed(self): super(ChannelStripComponent, self)._on_solo_changed() self._update_solo_color_control() def _on_cf_assign_changed(self): super(ChannelStripComponent, self)._on_cf_assign_changed() self._update_crossfade_assignment_control() self._update_crossfade_assign_color_controls() def _on_sends_changed(self): super(ChannelStripComponent, self)._on_sends_changed() self._update_listeners() self._update_controls() @listens(u'output_meter_left') def __on_output_meter_left_changed(self): self.output_meter_left_control.value = meter_value_to_midi_value(self.track.output_meter_left) @listens(u'output_meter_right') def __on_output_meter_right_changed(self): self.output_meter_right_control.value = meter_value_to_midi_value(self.track.output_meter_right) @listens(u'color') def __on_track_color_changed(self): self._update_track_color_control() @listens(u'value') def __on_volume_changed(self): track = self.track value_string = format_volume_value_string(str(track.mixer_device.volume) if liveobj_valid(track) and track.has_audio_output else u'') self._oled_display_volume_value_data_source.set_display_string(value_string) self.volume_value_display[0] = value_string @listens(u'value') def __on_pan_changed(self): track = self.track self.pan_value_display[0] = str(track.mixer_device.panning) if liveobj_valid(track) and track.has_audio_output else u'' @listens_group(u'value') def __on_send_value_changed(self, send_index): self._update_send_value_display(send_index) @listens(u'selected_track') def __on_selected_track_changed(self): self._update_select_button() self._update_track_color_control() @listens(u'muted_via_solo') def __on_muted_via_solo_changed(self): self.solo_mute_button.color = u'DefaultButton.On' if liveobj_valid(self.track) and self.track != self.song.master_track and self.track.muted_via_solo else u'DefaultButton.Off' @listens(u'instrument') def __on_drum_group_found(self): self._update_track_type_control() def _update_listeners(self): track = self.track self.__on_has_audio_output_changed.subject = track self.__on_has_audio_output_changed() self.__on_track_color_changed.subject = track self.__on_track_color_changed() self.__on_volume_changed.subject = track.mixer_device.volume if liveobj_valid(track) else None self.__on_volume_changed() self.__on_muted_via_solo_changed.subject = track self.__on_muted_via_solo_changed() self.__on_pan_changed.subject = track.mixer_device.panning if liveobj_valid(track) else None self.__on_pan_changed() track = self.track self.__on_send_value_changed.replace_subjects(track.mixer_device.sends if liveobj_valid(track) else [], count()) def _update_controls(self): self._update_track_type_control() self._update_oled_display_style_control() for send_index in range(MAX_NUM_SENDS): self._update_send_value_display(send_index) def _update_track_type_control(self): track_type = NO_TRACK track = self.track if liveobj_valid(track): if track == self.song.master_track: track_type = MASTER_TRACK elif track in self.song.return_tracks: track_type = RETURN_TRACK elif track.is_foldable: track_type = GROUP_TRACK elif track.has_midi_input: if self._drum_group_finder is not None and liveobj_valid(self._drum_group_finder.drum_group): track_type = DRUM_TRACK elif track.has_audio_output: track_type = MELODIC_TRACK else: track_type = EMPTY_MIDI_TRACK elif track.has_audio_output: track_type = AUDIO_TRACK self.track_type_control.value = track_type def _update_crossfade_assignment_control(self): self.crossfade_assign_control.value = LIVE_CROSSFADE_ASSIGN_VALUES[self.track.mixer_device.crossfade_assign] if self._track_has_visible_crossfade_assignment_buttons() else CROSSFADE_ASSIGN_OFF def _update_crossfade_assign_color_controls(self): off_color = u'DefaultButton.Off' track = self.track assign_a_control_color = off_color assign_b_control_color = off_color if self._track_has_visible_crossfade_assignment_buttons(): mixer_device = track.mixer_device assign_a_control_color = u'Mixer.CrossfadeAssignA' if mixer_device.crossfade_assign == force_to_live_crossfade_assign_value(CROSSFADE_ASSIGN_A) else off_color assign_b_control_color = u'Mixer.CrossfadeAssignB' if mixer_device.crossfade_assign == force_to_live_crossfade_assign_value(CROSSFADE_ASSIGN_B) else off_color self.assign_a_color_control.color = assign_a_control_color self.assign_b_color_control.color = assign_b_control_color def _update_track_name_data_source(self): super(ChannelStripComponent, self)._update_track_name_data_source() self._oled_display_track_name_data_source.set_display_string(self._track.name if liveobj_valid(self._track) else u' - ') def _update_arm_color_control(self): color = u'Mixer.ArmOff' track = self.track if liveobj_valid(track) and track in self.song.tracks and track.can_be_armed and track.arm: color = u'Mixer.ArmOn' self.arm_color_control.color = color def _update_mute_color_controls(self): mute_color_control_color = u'Mixer.MuteOff' mute_button_color = u'Mixer.MuteOn' track = self.track if liveobj_valid(track) and (track == self.song.master_track or not track.mute): mute_color_control_color = u'Mixer.MuteOn' mute_button_color = u'Mixer.MuteOff' self.mute_color_control.color = mute_color_control_color self.mpc_mute_button.color = mute_color_control_color if self._mute_button: self._mute_button.set_light(mute_button_color) def _update_solo_color_control(self): color = u'Mixer.SoloOff' track = self.track if liveobj_valid(track) and track != self.song.master_track and track.solo: color = u'Mixer.SoloOn' self.solo_color_control.color = color def _update_track_color_control(self): color_to_send = u'DefaultButton.Off' selected_color_to_send = None track = self.track if liveobj_valid(track) and track.color_index != None: color_to_send = track.color_index + LIVE_COLOR_TABLE_INDEX_OFFSET if track == self.song.view.selected_track: selected_color_to_send = u'DefaultButton.On' self.track_color_control.color = color_to_send self.physical_track_color_control.color = selected_color_to_send or color_to_send def _update_oled_display_style_control(self): value_to_send = OLED_DISPLAY_OFF track = self.track if liveobj_valid(track) and track.has_audio_output: value_to_send = OLED_DISPLAY_UNIPOLAR self.oled_display_style_control.value = value_to_send def _update_track_name_or_volume_value_display(self): if self._track_name_or_volume_value_display: self._track_name_or_volume_value_display.set_data_sources([self._oled_display_volume_value_data_source if self.volume_touch_control.is_pressed else self._oled_display_track_name_data_source]) def _update_send_value_display(self, index): if index < MAX_NUM_SENDS: value_to_send = u'' track = self.track if liveobj_valid(track): sends = track.mixer_device.sends if index < len(sends): value_to_send = str(sends[index]) self.send_value_displays[index][0] = value_to_send def _reset_output_meter_controls(self): self.output_meter_left_control.value = 0 self.output_meter_right_control.value = 0 def _track_has_visible_crossfade_assignment_buttons(self): track = self.track return liveobj_valid(track) and track != self.song.master_track and track.has_audio_output def _toggle_crossfade_assign(self, value): track = self.track if self._track_has_visible_crossfade_assignment_buttons(): mixer_device = track.mixer_device mixer_device.crossfade_assign = force_to_live_crossfade_assign_value(CROSSFADE_ASSIGN_OFF) if mixer_device.crossfade_assign == value else value
class MixerComponent(MixerComponentBase): num_sends_control = SendValueControl() master_button = ButtonControl() def __init__(self, *a, **k): super(MixerComponent, self).__init__(*a, **k) self._last_selected_track = None self._last_track_offset = None self.__on_offsets_changed.subject = self._provider self.__on_offsets_changed(self._provider.track_offset, self._provider.scene_offset) return def __getattr__(self, name): if name.startswith(b'set_') and name.endswith(b's'): return partial(self._set_channel_strip_controls, name[4:-1]) raise AttributeError def on_num_sends_changed(self): self.num_sends_control.value = clamp(self.num_sends, 0, MAX_NUM_SENDS) @property def max_track_offset(self): return max( 0, len(self._provider.tracks_to_use()) - self._provider.num_tracks) def _on_selected_track_changed(self): selected_track = self.song.view.selected_track button_color = b'DefaultButton.On' if selected_track != self.song.master_track: self._last_selected_track = selected_track button_color = b'DefaultButton.Off' self.master_button.color = button_color @listens(b'offset') def __on_offsets_changed(self, track_offset, _): max_track_offset = self.max_track_offset if max_track_offset == 0 or track_offset < max_track_offset: self._last_track_offset = track_offset def set_send_controls(self, controls): self._send_controls = controls for strip, row in izip_longest(self._channel_strips, controls.rows() if controls else []): strip.set_send_controls(row) def set_send_value_displays(self, displays): for strip, row in izip_longest(self._channel_strips, displays.rows() if displays else []): strip.set_send_value_displays(row) def set_selected_track_mute_button(self, button): self._selected_strip.mpc_mute_button.set_control_element(button) set_selected_track_arm_button = forward_property(b'_selected_strip')( b'set_arm_button') set_selected_track_solo_button = forward_property(b'_selected_strip')( b'set_solo_button') def set_track_type_controls(self, controls): for strip, control in izip_longest(self._channel_strips, controls or []): strip.track_type_control.set_control_element(control) def _set_channel_strip_controls(self, name, controls): for strip, control in izip_longest(self._channel_strips, controls or []): set_method = getattr(strip, (b'set_{}').format(name), None) if not set_method: set_method = getattr(strip, name, None).set_control_element set_method(control) return def set_solo_mute_buttons(self, buttons): for strip, button in izip_longest(self._channel_strips, buttons or []): strip.solo_mute_button.set_control_element(button) @master_button.pressed def master_button_value(self, _button): master_track = self.song.master_track if self.song.view.selected_track != master_track: self.song.view.selected_track = master_track else: self.song.view.selected_track = self._last_selected_track if liveobj_valid( self._last_selected_track) else self.song.tracks[0] if self._provider.track_offset < self.max_track_offset: self._provider.track_offset = self.max_track_offset else: self._provider.track_offset = self._last_track_offset
class PrintToClipComponent(Component): print_to_clip_control = InputControl() print_to_clip_enabler = SendValueControl() def __init__(self, *a, **k): (super(PrintToClipComponent, self).__init__)(*a, **k) self._clip_data = {} self._last_packet_id = -1 self._reset_last_packet_id_task = self._tasks.add(task.sequence(task.wait(RESET_PACKET_ID_TASK_DELAY), task.run(self._reset_last_packet_id))) self._reset_last_packet_id_task.kill() self._PrintToClipComponent__on_selected_track_changed.subject = self.song.view self._PrintToClipComponent__on_selected_track_changed() @print_to_clip_control.value def print_to_clip_control(self, data_bytes, _): self._reset_last_packet_id_task.restart() packet_id = sum_multi_byte_value((data_bytes[PACKET_ID_SLICE]), bits_per_byte=4) if packet_id != 0: if packet_id - 1 != self._last_packet_id: self.show_message(PACKET_ERROR_MESSAGE) return num_bytes = len(data_bytes) transfer_type = data_bytes[MESSAGE_TYPE_INDEX] if transfer_type == MessageType.begin: self._clip_data = {'notes': []} elif transfer_type == MessageType.data and num_bytes >= MIN_DATA_PACKET_LENGTH: self._handle_data_packet(data_bytes) elif transfer_type == MessageType.end: self._print_data_to_clip() self._last_packet_id = packet_id def _handle_data_packet(self, data_bytes): payload = data_bytes[PAYLOAD_START_INDEX:] if len(payload) == BYTES_PER_GROUP_OFFSET: self._clip_data['length'] = to_absolute_beat_time(payload) else: group_offset = to_absolute_beat_time(payload[:BYTES_PER_GROUP_OFFSET]) payload = payload[BYTES_PER_GROUP_OFFSET:] payload_length = len(payload) if payload_length % BYTES_PER_NOTE == 0: self._clip_data['notes'].extend([create_note(payload[i:i + BYTES_PER_NOTE], group_offset) for i in range(0, payload_length, BYTES_PER_NOTE)]) def _reset_last_packet_id(self): self._last_packet_id = -1 def _print_data_to_clip(self): if 'length' in self._clip_data: clip = self._create_clip(self._clip_data['length']) if liveobj_valid(clip): self._wrap_trailing_notes() note_data = sorted((self._clip_data['notes']), key=(itemgetter(1))) notes = tuple((Live.Clip.MidiNoteSpecification(pitch=(note[Note.pitch]), start_time=(note[Note.start]), duration=(note[Note.length]), velocity=(note[Note.velocity]), mute=(note[Note.mute])) for note in note_data)) clip.add_new_notes(notes) def _create_clip(self, length): song = self.song view = song.view track = view.selected_track try: scene_index = list(song.scenes).index(view.selected_scene) scene_count = len(song.scenes) while track.clip_slots[scene_index].has_clip: scene_index += 1 if scene_index == scene_count: song.create_scene(scene_count) slot = track.clip_slots[scene_index] slot.create_clip(length) return slot.clip except Live.Base.LimitationError: self.show_message(LIMITATION_ERROR_MESSAGE) return def _wrap_trailing_notes(self): for note in self._clip_data['notes'][:]: note_end_position = note[Note.start] + note[Note.length] if note_end_position > self._clip_data['length']: wrapped_note_length = note_end_position - self._clip_data['length'] + WRAPPED_NOTE_OFFSET self._clip_data['notes'].append(( note[Note.pitch], -WRAPPED_NOTE_OFFSET, wrapped_note_length, note[Note.velocity], note[Note.mute])) @listens('selected_track') def __on_selected_track_changed(self): can_print = self.song.view.selected_track.has_midi_input self.print_to_clip_control.enabled = can_print self.print_to_clip_enabler.value = int(can_print)
class PrintToClipComponent(Component): u""" Component that handles the print to clip functionality (whereby we receive SysEx messages that represent note data to write to a MIDI clip in Live) and workflow for Novation products. The print to clip SysEx API is as follows. CONTENT TRANSFER SYSEX MESSAGE FORMAT -------------------------------------------------------------------------------------- | BYTE(S) | DESCRIPTION -------------------------------------------------------------------------------------- | Message Type (0) | 0x01 - Begin transfer | | | | Indicates the start of a transfer. | | | | 0x02 - Data packet | | | | Packet of data as part of the transfer. | | | | 0x03 - End transfer | | | | Indicates the end of a transfer. | | -------------------------------------------------------------------------------------- | Packet ID (1 - 8) | 8 bytes indicating the packet ID as an integer. This will | | be 0 for the Begin Transfer Message Type and increase by 1 | | for each subsequent packet. | | | | This is used to validate that no packets were lost in the | | transfer. | | -------------------------------------------------------------------------------------- | Content Type (9) | Not used. | | -------------------------------------------------------------------------------------- | Content Index (10) | Not used. | | -------------------------------------------------------------------------------------- | Payload (11 - ?) | Multiple bytes that depend on the Message Type. The only | | payload we care about is that of data packets. The contents | | of those is described in the next table. | | -------------------------------------------------------------------------------------- NOTES: A content transfer complete with note data requires at least 4 SysEx messages: - Begin transfer - Data packet containing note data - Data packet containing the end time of the clip to create - End transfer A content transfer without note data requires 3 messages and will create an empty clip: - Begin transfer - Data packet containing the end time of the clip to create - End transfer DATA PACKET FORMAT FOR PRINT TO CLIP -------------------------------------------------------------------------------------- | BYTE(S) | DESCRIPTION -------------------------------------------------------------------------------------- | Group Offset (0 - 2) | Specifies either the starting offset for all of the notes | | that follow or the absolute length of the clip to create. | | | | In the case of the latter, this will be the only bytes | | we deal with. | | -------------------------------------------------------------------------------------- | Start Time (3 - 4) | The start time of the note relative to the Group Offset. | | -------------------------------------------------------------------------------------- | Length (5 - 6) | The length of the note. | | -------------------------------------------------------------------------------------- | Pitch (7) | The pitch of the note. | | -------------------------------------------------------------------------------------- | Velocity (8) | The velocity of the note. | | -------------------------------------------------------------------------------------- NOTES: - Timing information is in ms and based on a tempo of 120 BPM. - Aside from the Group Offset, all other bytes can be repeated any number of times. """ print_to_clip_control = InputControl() print_to_clip_enabler = SendValueControl() def __init__(self, *a, **k): super(PrintToClipComponent, self).__init__(*a, **k) self._clip_data = {} self._last_packet_id = -1 self._reset_last_packet_id_task = self._tasks.add( task.sequence(task.wait(RESET_PACKET_ID_TASK_DELAY), task.run(self._reset_last_packet_id))) self._reset_last_packet_id_task.kill() self.__on_selected_track_changed.subject = self.song.view self.__on_selected_track_changed() @print_to_clip_control.value def print_to_clip_control(self, data_bytes, _): self._reset_last_packet_id_task.restart() packet_id = sum_multi_byte_value(data_bytes[PACKET_ID_SLICE], bits_per_byte=4) if packet_id != 0 and packet_id - 1 != self._last_packet_id: self.show_message(PACKET_ERROR_MESSAGE) return num_bytes = len(data_bytes) transfer_type = data_bytes[MESSAGE_TYPE_INDEX] if transfer_type == MessageType.begin: self._clip_data = {u'notes': []} elif transfer_type == MessageType.data and num_bytes >= MIN_DATA_PACKET_LENGTH: self._handle_data_packet(data_bytes) elif transfer_type == MessageType.end: self._print_data_to_clip() self._last_packet_id = packet_id def _handle_data_packet(self, data_bytes): payload = data_bytes[PAYLOAD_START_INDEX:] if len(payload) == BYTES_PER_GROUP_OFFSET: self._clip_data[u'length'] = to_absolute_beat_time(payload) else: group_offset = to_absolute_beat_time( payload[:BYTES_PER_GROUP_OFFSET]) payload = payload[BYTES_PER_GROUP_OFFSET:] payload_length = len(payload) if payload_length % BYTES_PER_NOTE == 0: self._clip_data[u'notes'].extend([ create_note(payload[i:i + BYTES_PER_NOTE], group_offset) for i in range(0, payload_length, BYTES_PER_NOTE) ]) def _reset_last_packet_id(self): self._last_packet_id = -1 def _print_data_to_clip(self): if u'length' in self._clip_data: clip = self._create_clip(self._clip_data[u'length']) if liveobj_valid(clip): self._wrap_trailing_notes() note_data = sorted(self._clip_data[u'notes'], key=itemgetter(1)) notes = tuple((Live.Clip.MidiNoteSpecification( pitch=note[Note.pitch], start_time=note[Note.start], duration=note[Note.length], velocity=note[Note.velocity], mute=note[Note.mute]) for note in note_data)) clip.add_new_notes(notes) def _create_clip(self, length): song = self.song view = song.view track = view.selected_track try: scene_index = list(song.scenes).index(view.selected_scene) scene_count = len(song.scenes) while track.clip_slots[scene_index].has_clip: scene_index += 1 if scene_index == scene_count: song.create_scene(scene_count) slot = track.clip_slots[scene_index] slot.create_clip(length) return slot.clip except Live.Base.LimitationError: self.show_message(LIMITATION_ERROR_MESSAGE) return None def _wrap_trailing_notes(self): for note in self._clip_data[u'notes'][:]: note_end_position = note[Note.start] + note[Note.length] if note_end_position > self._clip_data[u'length']: wrapped_note_length = note_end_position - self._clip_data[ u'length'] + WRAPPED_NOTE_OFFSET self._clip_data[u'notes'].append( (note[Note.pitch], -WRAPPED_NOTE_OFFSET, wrapped_note_length, note[Note.velocity], note[Note.mute])) @listens(u'selected_track') def __on_selected_track_changed(self): can_print = self.song.view.selected_track.has_midi_input self.print_to_clip_control.enabled = can_print self.print_to_clip_enabler.value = int(can_print)
class ChannelStripComponent(ChannelStripComponentBase): track_type_display = SendValueControl() track_selection_display = SendValueControl() track_mute_display = SendValueControl() track_solo_display = SendValueControl() track_muted_via_solo_display = SendValueControl() def __init__(self, *a, **k): super(ChannelStripComponent, self).__init__(*a, **k) self._track_volume_data_source = DisplayDataSource() self._track_panning_data_source = DisplayDataSource() self.__on_selected_track_changed.subject = self.song.view @property def track_volume_data_source(self): return self._track_volume_data_source @property def track_panning_data_source(self): return self._track_panning_data_source def set_track(self, track): super(ChannelStripComponent, self).set_track(track) track = track if liveobj_valid(track) else None mixer = track.mixer_device if track else None self.__on_muted_via_solo_changed.subject = track self.__on_volume_value_changed.subject = mixer.volume if mixer else None self.__on_panning_value_changed.subject = mixer.panning if mixer else None self._update_track_volume_data_source() self._update_track_panning_data_source() self._update_track_type_display() self.__on_muted_via_solo_changed() return def _on_mute_changed(self): super(ChannelStripComponent, self)._on_mute_changed() self.track_mute_display.value = int(self._track.mute) if liveobj_valid(self._track) and self._track != self.song.master_track else 0 def _on_solo_changed(self): super(ChannelStripComponent, self)._on_solo_changed() self.track_solo_display.value = int(self._track.solo) if liveobj_valid(self._track) and self._track != self.song.master_track else 0 @listens('selected_track') def __on_selected_track_changed(self): selected_track = self.song.view.selected_track self.track_selection_display.value = int(self._track == selected_track) if liveobj_valid(self._track) else 0 @listens('muted_via_solo') def __on_muted_via_solo_changed(self): self.track_muted_via_solo_display.value = int(self._track.muted_via_solo) if liveobj_valid(self._track) and self._track != self.song.master_track else 0 @listens('value') def __on_volume_value_changed(self): self._update_track_volume_data_source() @listens('value') def __on_panning_value_changed(self): self._update_track_panning_data_source() def _update_track_volume_data_source(self): volume_string = '' if liveobj_valid(self._track): volume_string = str(self._track.mixer_device.volume) found_string = ('').join(volume_pattern.findall(volume_string)) if found_string: volume_string = '%s dB' % round(float(found_string), 1) self._track_volume_data_source.set_display_string(volume_string) def _update_track_panning_data_source(self): pan_string = '' if liveobj_valid(self._track): pan_string = str(self._track.mixer_device.panning) self._track_panning_data_source.set_display_string(pan_string) def _update_track_type_display(self): value_to_send = EMPTY_TRACK_TYPE_VALUE if liveobj_valid(self._track): value_to_send = DEFAULT_TRACK_TYPE_VALUE if self._track == self.song.master_track: value_to_send = MASTER_TRACK_TYPE_VALUE self.track_type_display.value = value_to_send