Пример #1
0
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])
Пример #5
0
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)))
Пример #6
0
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
Пример #7
0
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