コード例 #1
0
class HardwareSettings(SerializableListenableProperties):
    min_led_brightness = MIN_USER_FACING_LED_BRIGHTNESS
    max_led_brightness = 127
    led_brightness = listenable_property.managed(max_led_brightness)
    min_display_brightness = MIN_USER_FACING_DISPLAY_BRIGHTNESS
    max_display_brightness = MAX_DISPLAY_BRIGHTNESS
    display_brightness = listenable_property.managed(max_display_brightness)
コード例 #2
0
class NoteLayout(Subject):
    is_horizontal = listenable_property.managed(True)
    interval = listenable_property.managed(3)

    def __init__(self, song=None, preferences=dict(), *a, **k):
        raise liveobj_valid(song) or AssertionError
        super(NoteLayout, self).__init__(*a, **k)
        self._song = song
        self._scale = self._get_scale_from_name(self._song.scale_name)
        self._preferences = preferences
        self._is_in_key = self._preferences.setdefault('is_in_key', True)
        self._is_fixed = self._preferences.setdefault('is_fixed', False)

    @property
    def notes(self):
        return self.scale.to_root_note(self.root_note).notes

    @listenable_property
    def root_note(self):
        return self._song.root_note

    @root_note.setter
    def root_note(self, root_note):
        self._song.root_note = root_note
        self.notify_root_note(root_note)

    @listenable_property
    def scale(self):
        return self._scale

    @scale.setter
    def scale(self, scale):
        self._scale = scale
        self._song.scale_name = scale.name
        self.notify_scale(self._scale)

    @listenable_property
    def is_in_key(self):
        return self._is_in_key

    @is_in_key.setter
    def is_in_key(self, is_in_key):
        self._is_in_key = is_in_key
        self._preferences['is_in_key'] = self._is_in_key
        self.notify_is_in_key(self._is_in_key)

    @listenable_property
    def is_fixed(self):
        return self._is_fixed

    @is_fixed.setter
    def is_fixed(self, is_fixed):
        self._is_fixed = is_fixed
        self._preferences['is_fixed'] = self._is_fixed
        self.notify_is_fixed(self._is_fixed)

    def _get_scale_from_name(self, name):
        return find_if(lambda scale: scale.name == name,
                       SCALES) or DEFAULT_SCALE
コード例 #3
0
class PadVelocityCurveSettings(SerializableListenableProperties):
    sensitivity = listenable_property.managed(5)
    min_sensitivity = 0
    max_sensitivity = 10
    gain = listenable_property.managed(5)
    min_gain = 0
    max_gain = 10
    dynamics = listenable_property.managed(5)
    min_dynamics = 0
    max_dynamics = 10
コード例 #4
0
class FixedLengthSetting(Subject):
    option_names = LENGTH_OPTION_NAMES
    selected_index = listenable_property.managed(0)
    enabled = listenable_property.managed(False)

    def get_selected_length(self, song):
        index = self.selected_index
        length = 2.0**index
        quant = LENGTH_OPTIONS[index]
        if index > 1:
            length = length * song.signature_numerator / song.signature_denominator
        return (length, quant)
コード例 #5
0
class GridResolution(ControlManager):
    quantization_buttons = control_list(
        RadioButtonControl,
        checked_color='NoteEditor.QuantizationSelected',
        unchecked_color='NoteEditor.QuantizationUnselected',
        control_count=8)
    index = listenable_property.managed(DEFAULT_INDEX)

    def __init__(self, *a, **k):
        (super(GridResolution, self).__init__)(*a, **k)
        self.index = DEFAULT_INDEX
        self.quantization_buttons[self.index].is_checked = True

    @property
    def step_length(self):
        return old_div(QUANTIZATION_LIST[self.index], QUANTIZATION_FACTOR)

    @property
    def clip_grid(self):
        return CLIP_VIEW_GRID_LIST[self.index]

    @property
    def clip_length(self):
        return CLIP_LENGTH_LIST[self.index]

    @quantization_buttons.checked
    def quantization_buttons(self, button):
        self.index = button.index
コード例 #6
0
class TrackStateColorIndicator(EventObject):
    color = listenable_property.managed(b'DefaultButton.On')

    def __init__(self, item_provider=None, track_property=None, property_active_color=None, song=None, *a, **k):
        super(TrackStateColorIndicator, self).__init__(*a, **k)
        self._provider = item_provider
        self._active_color = property_active_color
        self._property = track_property
        self._song = song
        self.__on_items_changed.subject = item_provider
        self.register_slot(MultiSlot(listener=self.__on_property_changed, event_name_list=(
         b'selected_item', track_property), subject=item_provider))
        self._update_color()

    @listens(b'items')
    def __on_items_changed(self):
        self._update_color()

    def __on_property_changed(self):
        self._update_color()

    def _update_color(self):
        selected_mixable = self._provider.selected_item
        use_active_color = liveobj_valid(selected_mixable) and selected_mixable != self._song.master_track and getattr(selected_mixable, self._property)
        self.color = self._active_color if use_active_color else b'DefaultButton.On'
コード例 #7
0
class FirmwareUpdateComponent(Component):
    state = listenable_property.managed('welcome')

    def __init__(self, *a, **k):
        (super(FirmwareUpdateComponent, self).__init__)(a,
                                                        is_enabled=False,
                                                        **k)
        self._firmware = None

    def start(self, firmware):
        logger.info('Start firmware update using %r', firmware.filename)
        self._firmware = firmware
        self.notify_firmware_file()
        self.set_enabled(True)

        def set_state():
            self.state = 'start'

        self._tasks.add(
            task.sequence(task.wait(WELCOME_STATE_TIME), task.run(set_state)))

    def process_firmware_response(self, data):
        entry = find_if(lambda entry: entry['type'] == 'firmware', data)
        if entry:
            self.state = 'success' if entry['success'] else 'failure'

    @listenable_property
    def firmware_file(self):
        if self._firmware is not None:
            return os.path.join(FIRMWARE_PATH, self._firmware.filename)
        return ''

    @property
    def data_file(self):
        return os.path.join(FIRMWARE_PATH, 'FlashData.bin')
コード例 #8
0
ファイル: routing.py プロジェクト: cce/buttons10
class Router(EventObject):
    current_target_index = listenable_property.managed(-1)

    def __init__(self, routing_level = None, routing_direction = None, song = None, *a, **k):
        assert song is not None
        assert routing_level is not None
        assert routing_direction is not None
        super(Router, self).__init__(*a, **k)
        self._song = song
        self._current_target_property = u'%s_routing_%s' % (routing_direction, routing_level)
        self._register_listeners()
        self.current_target_index = self._current_target_index()
        return

    def _register_listeners(self):
        self.register_slot(MultiSlot(subject=self._song.view, event_name_list=(u'selected_track', self._current_target_property), listener=self.__on_current_routing_changed))
        self.register_slot(MultiSlot(subject=self._song.view, event_name_list=(u'selected_track', u'available_%ss' % self._current_target_property), listener=self.__on_routings_changed))

    @listenable_property
    def routing_targets(self):
        return self._get_targets()

    def _current_target_index(self):
        try:
            return self._get_targets().index(self._get_current_target())
        except ValueError:
            return -1

    @property
    def current_target(self):
        return self._get_current_target()

    @current_target.setter
    def current_target(self, new_target):
        self._set_current_target(new_target)

    def __on_current_routing_changed(self):
        self.current_target_index = self._current_target_index()

    def __on_routings_changed(self):
        self.notify_routing_targets()

    def _get_routing_host(self):
        return self._song.view.selected_track

    def _get_targets(self):
        raise NotImplementedError

    def _get_current_target(self):
        return getattr(self._get_routing_host(), self._current_target_property)

    def _set_current_target(self, new_target_id):
        setattr(self._get_routing_host(), self._current_target_property, new_target_id)

    @listenable_property
    def has_input_channel_position(self):
        return False
コード例 #9
0
ファイル: setup_component.py プロジェクト: xnamahx/Push2x
class SetupComponent(ModesComponent):
    category_radio_buttons = control_list(RadioButtonControl, checked_color='Option.Selected', unchecked_color='Option.Unselected')
    make_it_go_boom_button = ButtonControl()
    make_it_go_boom = listenable_property.managed(False)

    def __init__(self, settings=None, pad_curve_sender=None, firmware_switcher=None, *a, **k):
        assert settings is not None
        super(SetupComponent, self).__init__(*a, **k)
        self._settings = settings
        self._pad_curve_sender = pad_curve_sender
        has_option = self.application.has_option
        self.make_it_go_boom_button.enabled = not has_option('_Push2DeveloperMode') and has_option('_MakePush2GoBoom')
        self._general = GeneralSettingsComponent(parent=self, settings=settings.general, hardware_settings=settings.hardware, is_enabled=False)
        self._info = InfoComponent(parent=self, firmware_switcher=firmware_switcher, is_enabled=False)
        self._pad_settings = PadSettingsComponent(parent=self, pad_settings=settings.pad_settings, is_enabled=False)
        self._display_debug = DisplayDebugSettingsComponent(parent=self, settings=settings.display_debug, is_enabled=False)
        self.add_mode('Settings', [self._general, self._pad_settings])
        self.add_mode('Info', [self._info])
        if self.application.has_option('_Push2DeveloperMode'):
            self.add_mode('Display Debug', [self._display_debug])
        self.selected_mode = 'Settings'
        self.category_radio_buttons.control_count = len(self.modes)
        self.category_radio_buttons.checked_index = 0
        return

    @make_it_go_boom_button.pressed
    def make_it_go_boom_button(self, _button):
        self.make_it_go_boom = True

    @property
    def general(self):
        return self._general

    @property
    def info(self):
        return self._info

    @property
    def pad_settings(self):
        return self._pad_settings

    @property
    def display_debug(self):
        return self._display_debug

    @property
    def settings(self):
        return self._settings

    @property
    def velocity_curve(self):
        return self._pad_curve_sender

    @category_radio_buttons.checked
    def category_radio_buttons(self, button):
        self.selected_mode = self.modes[button.index]
コード例 #10
0
class PadVelocityCurveSender(Component):
    SEND_RATE = 0.5
    curve_points = listenable_property.managed([])

    def __init__(self, curve_sysex_element = None, threshold_sysex_element = None, settings = None, chunk_size = None, *a, **k):
        raise curve_sysex_element is not None or AssertionError
        raise threshold_sysex_element is not None or AssertionError
        raise settings is not None or AssertionError
        raise chunk_size is not None or AssertionError
        super(PadVelocityCurveSender, self).__init__(*a, **k)
        self._curve_sysex_element = curve_sysex_element
        self._threshold_sysex_element = threshold_sysex_element
        self._settings = settings
        self._chunk_size = chunk_size
        self._send_task = self._tasks.add(task.sequence(task.wait(self.SEND_RATE), task.run(self._on_send_task_finished))).kill()
        self._settings_changed = False
        self.register_slot(settings, self._on_setting_changed, 'sensitivity')
        self.register_slot(settings, self._on_setting_changed, 'gain')
        self.register_slot(settings, self._on_setting_changed, 'dynamics')
        self._update_curve_model()
        return

    def send(self):
        self._send_velocity_curve()
        self._send_thresholds()
        self._settings_changed = False

    def _send_velocity_curve(self):
        velocities = self._generate_curve()
        velocity_chunks = chunks(velocities, self._chunk_size)
        for index, velocities in enumerate(velocity_chunks):
            self._curve_sysex_element.send_value(index * self._chunk_size, velocities)

    def _send_thresholds(self):
        threshold_values = generate_thresholds(self._settings.sensitivity, self._settings.gain, self._settings.dynamics)
        self._threshold_sysex_element.send_value(*threshold_values)

    def _generate_curve(self):
        return generate_velocity_curve(self._settings.sensitivity, self._settings.gain, self._settings.dynamics)

    def _on_setting_changed(self, _):
        if not self._send_task.is_running:
            self.send()
            self._send_task.restart()
        else:
            self._settings_changed = True
        self._update_curve_model()

    def _update_curve_model(self):
        self.curve_points = self._generate_curve()[:LAST_INDEX_FOR_DISPLAY]

    def _on_send_task_finished(self):
        if self._settings_changed:
            self.send()
コード例 #11
0
class DisplayDebugSettings(SerializableListenableProperties):
    show_row_spaces = listenable_property.managed(False)
    show_row_margins = listenable_property.managed(False)
    show_row_middle = listenable_property.managed(False)
    show_button_spaces = listenable_property.managed(False)
    show_unlit_button = listenable_property.managed(False)
    show_lit_button = listenable_property.managed(False)
コード例 #12
0
class RoutingChannel(RoutingTarget):
    realtime_channel = listenable_property.managed(None)

    def __init__(self, realtime_channel=None, *a, **k):
        super(RoutingChannel, self).__init__(*a, **k)
        self.realtime_channel = realtime_channel
        self._layout_names = {Live.Track.RoutingChannelLayout.mono: b'mono', 
           Live.Track.RoutingChannelLayout.midi: b'midi', 
           Live.Track.RoutingChannelLayout.stereo: b'stereo'}

    @property
    def layout(self):
        return self._layout_names[self._live_target.layout]
コード例 #13
0
class FirmwareUpdateComponent(Component):
    state = listenable_property.managed('welcome')

    def __init__(self, *a, **k):
        super(FirmwareUpdateComponent, self).__init__(is_enabled=False,
                                                      *a,
                                                      **k)
        available_firmware_files = self._collect_firmware_files()
        logger.debug('Available firmware files %r', available_firmware_files)
        self._latest_firmware = max(available_firmware_files, key=first)

    def start(self):
        raise self.state == 'welcome' or AssertionError
        self.set_enabled(True)

        def set_state():
            self.state = 'start'

        self._tasks.add(
            task.sequence(task.wait(WELCOME_STATE_TIME), task.run(set_state)))

    def process_firmware_response(self, data):
        if not self.state == 'start':
            raise AssertionError
            entry = find_if(lambda entry: entry['type'] == 'firmware', data)
            self.state = entry and ('success'
                                    if entry['success'] else 'failure')

    def has_newer_firmware(self, major, minor, build):
        return self.provided_version > FirmwareVersion(major, minor, build)

    def _collect_firmware_files(self):
        return filter(lambda x: x[0] is not None,
                      [(extract_firmware_version(f), f)
                       for f in os.listdir(FIRMWARE_PATH)
                       if fnmatch.fnmatch(f, '*.upgrade')])

    @property
    def provided_version(self):
        return self._latest_firmware[0]

    @property
    def firmware_file(self):
        return os.path.join(FIRMWARE_PATH, self._latest_firmware[1])

    @property
    def data_file(self):
        return os.path.join(FIRMWARE_PATH, 'FlashData.bin')
コード例 #14
0
class QuantizationSettingsComponent(Component):
    swing_amount_encoder = EncoderControl()
    quantize_to_encoder = StepEncoderControl()
    quantize_amount_encoder = EncoderControl()
    record_quantization_encoder = StepEncoderControl()
    record_quantization_toggle_button = ToggleButtonControl(
        toggled_color='Recording.FixedLengthRecordingOn',
        untoggled_color='Recording.FixedLengthRecordingOff')
    quantize_amount = listenable_property.managed(1.0)
    quantize_to_index = listenable_property.managed(DEFAULT_QUANTIZATION_INDEX)
    record_quantization_index = listenable_property.managed(
        DEFAULT_QUANTIZATION_INDEX)

    def __init__(self, *a, **k):
        super(QuantizationSettingsComponent, self).__init__(*a, **k)
        self.__on_swing_amount_changed.subject = self.song
        self.__on_record_quantization_changed.subject = self.song
        self.__on_record_quantization_changed()

    @property
    def quantize_to(self):
        return QUANTIZATION_OPTIONS[self.quantize_to_index]

    @listenable_property
    def swing_amount(self):
        return self.song.swing_amount

    @listenable_property
    def record_quantization_enabled(self):
        return self.record_quantization_toggle_button.is_toggled

    @property
    def quantization_option_names(self):
        return QUANTIZATION_NAMES

    @swing_amount_encoder.value
    def swing_amount_encoder(self, value, encoder):
        self.song.swing_amount = clamp(self.song.swing_amount + value * 0.5,
                                       0.0, 0.5)

    @staticmethod
    def _clamp_quantization_index(index):
        return clamp(index, 0, len(QUANTIZATION_OPTIONS) - 1)

    @quantize_to_encoder.value
    def quantize_to_encoder(self, value, encoder):
        self.quantize_to_index = self._clamp_quantization_index(
            self.quantize_to_index + value)

    @quantize_amount_encoder.value
    def quantize_amount_encoder(self, value, encoder):
        self.quantize_amount = clamp(self.quantize_amount + value, 0.0, 1.0)

    @record_quantization_encoder.value
    def record_quantization_encoder(self, value, encoder):
        self.record_quantization_index = self._clamp_quantization_index(
            self.record_quantization_index + value)
        self._update_record_quantization()

    @record_quantization_toggle_button.toggled
    def record_quantization_toggle_button(self, value, button):
        self._update_record_quantization()

    @listens('swing_amount')
    def __on_swing_amount_changed(self):
        self.notify_swing_amount()

    @listens('midi_recording_quantization')
    def __on_record_quantization_changed(self):
        quant_value = self.song.midi_recording_quantization
        quant_on = quant_value != RecordingQuantization.rec_q_no_q
        if quant_value in QUANTIZATION_OPTIONS:
            self.record_quantization_index = QUANTIZATION_OPTIONS.index(
                quant_value)
        self.record_quantization_toggle_button.is_toggled = quant_on
        self.notify_record_quantization_enabled(quant_on)

    def _update_record_quantization(self):
        index = QUANTIZATION_OPTIONS[self.record_quantization_index]
        self.song.midi_recording_quantization = index if self.record_quantization_toggle_button.is_toggled else RecordingQuantization.rec_q_no_q
コード例 #15
0
class SimplerPositions(EventObject):
    __events__ = (u'warp_markers', u'before_update_all', u'after_update_all')
    start = listenable_property.managed(0.0)
    end = listenable_property.managed(0.0)
    start_marker = listenable_property.managed(0.0)
    end_marker = listenable_property.managed(0.0)
    active_start = listenable_property.managed(0.0)
    active_end = listenable_property.managed(0.0)
    loop_start = listenable_property.managed(0.0)
    loop_end = listenable_property.managed(0.0)
    loop_fade_in_samples = listenable_property.managed(0.0)
    env_fade_in = listenable_property.managed(0.0)
    env_fade_out = listenable_property.managed(0.0)
    slices = listenable_property.managed([])
    selected_slice = listenable_property.managed(SlicePoint(0, 0.0))
    use_beat_time = listenable_property.managed(False)

    def __init__(self, simpler=None, *a, **k):
        assert simpler is not None
        super(SimplerPositions, self).__init__(*a, **k)
        self._simpler = simpler
        self.__on_active_start_changed.subject = simpler.view
        self.__on_active_end_changed.subject = simpler.view
        self.__on_loop_start_changed.subject = simpler.view
        self.__on_loop_end_changed.subject = simpler.view
        self.__on_loop_fade_changed.subject = simpler.view
        self.__on_env_fade_in_changed.subject = simpler.view
        self.__on_env_fade_out_changed.subject = simpler.view
        self.__on_selected_slice_changed.subject = simpler.view
        self.post_sample_changed()
        return

    def post_sample_changed(self):
        self.__on_start_marker_changed.subject = self._simpler.sample
        self.__on_end_marker_changed.subject = self._simpler.sample
        self.__on_slices_changed.subject = self._simpler.sample
        self.__on_warping_changed.subject = self._simpler.sample
        self.__on_warp_markers_changed.subject = self._simpler.sample
        self.update_all()

    def _convert_sample_time(self, sample_time):
        u"""
        Converts to beat time, if the sample is warped
        """
        sample = self._simpler.sample
        if liveobj_valid(sample) and sample.warping:
            return sample.sample_to_beat_time(sample_time)
        return sample_time

    @listens('start_marker')
    def __on_start_marker_changed(self):
        if liveobj_valid(self._simpler.sample):
            self.start_marker = self._convert_sample_time(
                self._simpler.sample.start_marker)

    @listens('end_marker')
    def __on_end_marker_changed(self):
        if liveobj_valid(self._simpler.sample):
            self.end_marker = self._convert_sample_time(
                self._simpler.sample.end_marker)

    @listens('sample_start')
    def __on_active_start_changed(self):
        self.active_start = self._convert_sample_time(
            self._simpler.view.sample_start)

    @listens('sample_end')
    def __on_active_end_changed(self):
        self.active_end = self._convert_sample_time(
            self._simpler.view.sample_end)

    @listens('sample_loop_start')
    def __on_loop_start_changed(self):
        self.loop_start = self._convert_sample_time(
            self._simpler.view.sample_loop_start)

    @listens('sample_loop_end')
    def __on_loop_end_changed(self):
        self.loop_end = self._convert_sample_time(
            self._simpler.view.sample_loop_end)

    @listens('sample_loop_fade')
    def __on_loop_fade_changed(self):
        self.loop_fade_in_samples = self._simpler.view.sample_loop_fade

    @listens('sample_env_fade_in')
    def __on_env_fade_in_changed(self):
        if liveobj_valid(self._simpler.sample):
            start_marker = self._simpler.sample.start_marker
            fade_in_end = start_marker + self._simpler.view.sample_env_fade_in
            self.env_fade_in = self._convert_sample_time(
                fade_in_end) - self._convert_sample_time(start_marker)

    @listens('sample_env_fade_out')
    def __on_env_fade_out_changed(self):
        if liveobj_valid(self._simpler.sample):
            end_marker = self._simpler.sample.end_marker
            fade_out_start = end_marker - self._simpler.view.sample_env_fade_out
            self.env_fade_out = self._convert_sample_time(
                end_marker) - self._convert_sample_time(fade_out_start)

    @listens('slices')
    def __on_slices_changed(self):
        if liveobj_valid(self._simpler.sample):
            self.slices = [
                SlicePoint(s, self._convert_sample_time(s))
                for s in self._simpler.sample.slices
            ]

    @listens('selected_slice')
    def __on_selected_slice_changed(self):
        if liveobj_valid(self._simpler.sample):
            t = self._convert_sample_time(self._simpler.view.selected_slice)
            self.selected_slice = SlicePoint(t, t)

    @listens('warping')
    def __on_warping_changed(self):
        self.update_all()

    @listens('warp_markers')
    def __on_warp_markers_changed(self):
        self.update_all()
        self.notify_warp_markers()

    def update_all(self):
        if liveobj_valid(self._simpler.sample):
            self.notify_before_update_all()
            self.start = self._convert_sample_time(0)
            self.end = self._convert_sample_time(self._simpler.sample.length)
            self.__on_start_marker_changed()
            self.__on_end_marker_changed()
            self.__on_active_start_changed()
            self.__on_active_end_changed()
            self.__on_loop_start_changed()
            self.__on_loop_end_changed()
            self.__on_loop_fade_changed()
            self.__on_env_fade_in_changed()
            self.__on_env_fade_out_changed()
            self.__on_slices_changed()
            self.__on_selected_slice_changed()
            self.use_beat_time = self._simpler.sample.warping
            self.notify_after_update_all()
コード例 #16
0
class ScalesComponent(Component):
    __events__ = ('close',)
    navigation_colors = dict(color='Scales.Navigation', disabled_color='Scales.NavigationDisabled')
    up_button = ButtonControl(repeat=True, **navigation_colors)
    down_button = ButtonControl(repeat=True, **navigation_colors)
    right_button = ButtonControl(repeat=True, **navigation_colors)
    left_button = ButtonControl(repeat=True, **navigation_colors)
    root_note_buttons = control_list(RadioButtonControl, control_count=len(ROOT_NOTES), checked_color='Scales.OptionOn', unchecked_color='Scales.OptionOff')
    in_key_toggle_button = ToggleButtonControl(toggled_color='Scales.OptionOn', untoggled_color='Scales.OptionOn')
    fixed_toggle_button = ToggleButtonControl(toggled_color='Scales.OptionOn', untoggled_color='Scales.OptionOff')
    scale_encoders = control_list(StepEncoderControl)
    close_button = ButtonControl(color='Scales.Close')
    horizontal_navigation = listenable_property.managed(False)
    NUM_DISPLAY_ROWS = 4
    NUM_DISPLAY_COLUMNS = int(ceil(float(len(SCALES)) / NUM_DISPLAY_ROWS))

    def __init__(self, note_layout = None, *a, **k):
        raise note_layout is not None or AssertionError
        super(ScalesComponent, self).__init__(*a, **k)
        self._note_layout = note_layout
        self._scale_list = list(SCALES)
        self._scale_name_list = map(lambda m: m.name, self._scale_list)
        self._selected_scale_index = -1
        self._selected_root_note_index = -1
        self.in_key_toggle_button.connect_property(note_layout, 'is_in_key')
        self.fixed_toggle_button.connect_property(note_layout, 'is_fixed')
        self.__on_root_note_changed.subject = self._note_layout
        self.__on_scale_changed.subject = self._note_layout
        self.__on_root_note_changed(note_layout.root_note)
        self.__on_scale_changed(note_layout.scale)

    def _set_selected_scale_index(self, index):
        index = clamp(index, 0, len(self._scale_list) - 1)
        self._note_layout.scale = self._scale_list[index]

    @down_button.pressed
    def down_button(self, button):
        self._update_horizontal_navigation()
        self._set_selected_scale_index(self._selected_scale_index + 1)

    @up_button.pressed
    def up_button(self, button):
        self._update_horizontal_navigation()
        self._set_selected_scale_index(self._selected_scale_index - 1)

    @left_button.pressed
    def left_button(self, button):
        self._update_horizontal_navigation()
        self._set_selected_scale_index(self._selected_scale_index - self.NUM_DISPLAY_ROWS)

    @right_button.pressed
    def right_button(self, button):
        self._update_horizontal_navigation()
        self._set_selected_scale_index(self._selected_scale_index + self.NUM_DISPLAY_ROWS)

    @root_note_buttons.pressed
    def root_note_buttons(self, button):
        self._note_layout.root_note = ROOT_NOTES[button.index]

    @listens('root_note')
    def __on_root_note_changed(self, root_note):
        self._selected_root_note_index = list(ROOT_NOTES).index(root_note)
        self.root_note_buttons.checked_index = self._selected_root_note_index
        self.notify_selected_root_note_index()

    @property
    def root_note_names(self):
        return [ NOTE_NAMES[note] for note in ROOT_NOTES ]

    @listenable_property
    def selected_root_note_index(self):
        return self._selected_root_note_index

    @scale_encoders.value
    def scale_encoders(self, value, encoder):
        self._update_horizontal_navigation()
        self._set_selected_scale_index(self._selected_scale_index + value)

    @property
    def scale_names(self):
        return self._scale_name_list

    @listenable_property
    def selected_scale_index(self):
        return self._selected_scale_index

    @listens('scale')
    def __on_scale_changed(self, scale):
        index = self._scale_list.index(scale) if scale in self._scale_list else -1
        if index != self._selected_scale_index:
            self._selected_scale_index = index
            self.up_button.enabled = index > 0
            self.left_button.enabled = index > 0
            self.down_button.enabled = index < len(self._scale_list) - 1
            self.right_button.enabled = index < len(self._scale_list) - 1
            self.notify_selected_scale_index()

    @close_button.pressed
    def close_button(self, button):
        self.notify_close()

    @property
    def note_layout(self):
        return self._note_layout

    def _update_horizontal_navigation(self):
        self.horizontal_navigation = self.right_button.is_pressed or self.left_button.is_pressed
コード例 #17
0
class GeneralSettings(EventObject):
    workflow = listenable_property.managed(b'scene')
コード例 #18
0
class ProfilingSettings(SerializableListenableProperties):
    show_qml_stats = listenable_property.managed(False)
    show_usb_stats = listenable_property.managed(False)
    show_realtime_ipc_stats = listenable_property.managed(False)
コード例 #19
0
class ClipPositions(EventObject):
    __events__ = (u'is_recording', u'warp_markers', u'before_update_all',
                  u'after_update_all')
    MAX_TIME = 10000000
    MIN_TIME = -10000
    start = listenable_property.managed(0.0)
    end = listenable_property.managed(0.0)
    start_marker = listenable_property.managed(0.0)
    end_marker = listenable_property.managed(0.0)
    loop_start = listenable_property.managed(0.0)
    loop_end = listenable_property.managed(0.0)
    loop_length = listenable_property.managed(0.0)
    use_beat_time = listenable_property.managed(False)

    def __init__(self, clip=None, *a, **k):
        assert clip is not None
        super(ClipPositions, self).__init__(*a, **k)
        self._clip = clip
        self._looping = self._clip.looping
        self.__on_is_recording_changed.subject = clip
        self.__on_looping_changed.subject = clip
        self.__on_start_marker_changed.subject = clip
        self.__on_end_marker_changed.subject = clip
        self.__on_loop_start_changed.subject = clip
        self.__on_loop_end_changed.subject = clip
        self.__on_loop_start_changed()
        self.__on_loop_end_changed()
        if clip.is_audio_clip:
            self.__on_warping_changed.subject = clip
            self.__on_warp_markers_changed.subject = clip
        if clip.is_midi_clip:
            self.__on_notes_changed.subject = clip
            self._update_start_end_note_times()
        self.update_all()

    @property
    def is_warping(self):
        return self._clip.is_audio_clip and self._clip.warping

    def _convert_to_desired_unit(self, beat_time_or_seconds):
        u"""
        This converts the given beat time or seconds unit to the desired unit.
        - If the input unit is beat time, we are warped and don't need to do
          anything.
        - If the input time is seconds, we want to have sample time and need to
          convert.
        - If the clip is a midi clip, we don't need to do anything
        """
        if not self._clip.is_midi_clip and not self.is_warping:
            beat_time_or_seconds = self._clip.seconds_to_sample_time(
                beat_time_or_seconds)
        return beat_time_or_seconds

    @listens(u'start_marker')
    def __on_start_marker_changed(self):
        if not self._process_looping_update():
            self.start_marker = self._convert_to_desired_unit(
                self._clip.start_marker)

    @listens(u'end_marker')
    def __on_end_marker_changed(self):
        if not self._process_looping_update():
            self.end_marker = self._convert_to_desired_unit(
                self._clip.end_marker)

    @listens(u'loop_start')
    def __on_loop_start_changed(self):
        if not self._process_looping_update():
            self.loop_start = self._convert_to_desired_unit(
                self._clip.loop_start)
        self._update_loop_length()

    @listens(u'loop_end')
    def __on_loop_end_changed(self):
        if not self._process_looping_update():
            self.loop_end = self._convert_to_desired_unit(self._clip.loop_end)
        self._update_loop_length()

    @listens(u'is_recording')
    def __on_is_recording_changed(self):
        self._update_start_end()
        self.notify_is_recording()

    @listens(u'warp_markers')
    def __on_warp_markers_changed(self):
        self.update_all()
        self.notify_warp_markers()

    @listens(u'looping')
    def __on_looping_changed(self):
        self.update_all()

    @listens(u'warping')
    def __on_warping_changed(self):
        self.update_all()

    @listens(u'notes')
    def __on_notes_changed(self):
        self._update_start_end_note_times()
        self._update_start_end()

    def _update_start_end_note_times(self):
        all_notes = self._clip.get_notes_extended(from_time=self.MIN_TIME,
                                                  from_pitch=0,
                                                  time_span=self.MAX_TIME,
                                                  pitch_span=128)
        start_times, end_times = list(
            zip(*[(note.start_time, note.start_time + note.duration)
                  for note in all_notes])) if len(all_notes) > 0 else (
                      [self.MAX_TIME], [self.MIN_TIME])
        self.start_of_first_note = min(start_times)
        self.end_of_last_note = max(end_times)

    def _process_looping_update(self):
        u"""
        Changing the looping setting is considered a transaction and will update
        all parameters.
        This method should be called, before updating any position parameter.
        Returns True, in case a looping change has been processed.
        """
        looping = self._clip.looping
        if looping != self._looping:
            self._looping = looping
            self.update_all()
            return True
        return False

    def _update_loop_length(self):
        self.loop_length = self._convert_to_desired_unit(
            self._clip.loop_end) - self._convert_to_desired_unit(
                self._clip.loop_start)

    def _update_start_end(self):
        start = None
        end = None
        if self.is_warping:
            start = self._clip.sample_to_beat_time(0)
            end = self._clip.sample_to_beat_time(self._clip.sample_length)
        elif self._clip.is_audio_clip:
            start = 0
            end = self._clip.sample_length
        else:
            start = self.start_of_first_note
            end = self.end_of_last_note
        self.start = min(
            start,
            self.loop_start if self._clip.looping else self.start_marker)
        self.end = max(end, self.loop_end)

    def update_all(self):
        self.notify_before_update_all()
        self.__on_start_marker_changed()
        self.__on_end_marker_changed()
        self.__on_loop_start_changed()
        self.__on_loop_end_changed()
        self._update_start_end()
        if self._clip.is_audio_clip:
            self.use_beat_time = self._clip.warping
        self.notify_after_update_all()
コード例 #20
0
ファイル: convert.py プロジェクト: xnamahx/Push2x
class ConvertComponent(Component):
    __events__ = (u'cancel', u'success')
    action_buttons = control_list(ButtonControl, color='Option.Unselected', pressed_color='Option.Selected')
    cancel_button = ButtonControl(color='Option.Unselected', pressed_color='Option.Selected')
    source_color_index = listenable_property.managed(UNCOLORED_INDEX)
    source_name = listenable_property.managed(unicode(''))

    def __init__(self, tracks_provider=None, conversions_provider=possible_conversions, decorator_factory=None, *a, **k):
        assert tracks_provider is not None
        assert callable(conversions_provider)
        super(ConvertComponent, self).__init__(*a, **k)
        self._tracks_provider = tracks_provider
        self._conversions_provider = conversions_provider
        self._decorator_factory = decorator_factory
        self._category = NullConvertCategory()
        self._update_possible_conversions()
        return

    @listenable_property
    def available_conversions(self):
        return map(lambda x: x.label, self._category.actions)

    def on_enabled_changed(self):
        super(ConvertComponent, self).on_enabled_changed()
        self._update_possible_conversions()

    def _update_possible_conversions(self):
        self.disconnect_disconnectable(self._category)
        track = self._tracks_provider.selected_item
        self._category = self.register_disconnectable(self._conversions_provider(track, self._decorator_factory))
        self.__on_action_invalidated.subject = self._category
        self.__on_action_source_color_index_changed.subject = self._category.color_source
        self.__on_action_source_name_changed.subject = self._category.name_source
        self.__on_action_source_color_index_changed()
        self.__on_action_source_name_changed()
        self.action_buttons.control_count = len(self._category.actions)
        self.notify_available_conversions()

    @listens('color_index')
    def __on_action_source_color_index_changed(self):
        color_source = self.__on_action_source_color_index_changed.subject
        self.source_color_index = color_source.color_index if color_source and color_source.color_index is not None else UNCOLORED_INDEX
        return

    @listens('name')
    def __on_action_source_name_changed(self):
        name_source = self.__on_action_source_name_changed.subject
        self.source_name = name_source.name if name_source else unicode()

    @action_buttons.released
    def action_buttons(self, button):
        if self._do_conversion(button.index):
            self.notify_cancel()

    def _do_conversion(self, action_index):
        self._update_possible_conversions()
        if action_index < len(self._category.actions):
            action = self._category.actions[action_index]
            if action.needs_deferred_invocation:
                self._tasks.add(task.sequence(task.delay(1), task.run(lambda : self._do_conversion_deferred(action))))
                return False
            self._invoke_conversion(action)
        return True

    def _do_conversion_deferred(self, action):
        self._invoke_conversion(action)
        self.notify_cancel()

    def _invoke_conversion(self, action):
        self._category.convert(self.song, action)
        self.notify_success(self._category.internal_name)

    @cancel_button.released
    def cancel_button(self, button):
        self.notify_cancel()

    @listens('action_invalidated')
    def __on_action_invalidated(self):
        self.notify_cancel()
コード例 #21
0
class TransportState(CompoundComponent):
    count_in_duration = listenable_property.managed(0)

    def __init__(self, song=None, *a, **kw):
        super(TransportState, self).__init__(*a, **kw)
        self._song = song
        self.__on_is_playing_changed.subject = song
        self._count_in_time_real_time_data = self.register_component(
            RealTimeDataComponent(channel_type='count-in'))
        self.__on_count_in_duration_changed.subject = song
        self.__on_is_counting_in_changed.subject = song
        self.__on_signature_numerator_changed.subject = song
        self.__on_signature_denominator_changed.subject = song
        self.__on_count_in_channel_changed.subject = self._count_in_time_real_time_data
        self._update_count_in_duration()

    @listenable_property
    def count_in_real_time_channel_id(self):
        return self._count_in_time_real_time_data.channel_id

    @listenable_property
    def is_counting_in(self):
        return self._song.is_counting_in

    @listenable_property
    def signature_numerator(self):
        return self._song.signature_numerator

    @listenable_property
    def signature_denominator(self):
        return self._song.signature_denominator

    def _update_count_in_duration(self):
        self.count_in_duration = COUNT_IN_DURATION_IN_BARS[
            self._song.count_in_duration]

    @listens('count_in_duration')
    def __on_count_in_duration_changed(self):
        if not self.is_counting_in:
            self._update_count_in_duration()

    @listens('is_counting_in')
    def __on_is_counting_in_changed(self):
        self._count_in_time_real_time_data.set_data(
            self._song if self.is_counting_in else None)
        self.notify_is_counting_in()
        self._update_count_in_duration()
        return

    @listens('signature_numerator')
    def __on_signature_numerator_changed(self):
        self.notify_signature_numerator()

    @listens('signature_denominator')
    def __on_signature_denominator_changed(self):
        self.notify_signature_denominator()

    @listenable_property
    def is_playing(self):
        return self._song.is_playing

    @listens('is_playing')
    def __on_is_playing_changed(self):
        self.notify_is_playing()

    @listens('channel_id')
    def __on_count_in_channel_changed(self):
        self.notify_count_in_real_time_channel_id()
コード例 #22
0
class RoutingControlComponent(ModesComponent):
    monitor_state_encoder = ListValueEncoderControl(num_steps=10)
    input_output_choice_encoder = ListValueEncoderControl(num_steps=10)
    routing_type_encoder = ListValueEncoderControl(num_steps=10)
    routing_channel_encoders = control_list(ListValueEncoderControl, control_count=4, num_steps=10)
    routing_channel_position_encoder = ListIndexEncoderControl(num_steps=10)
    can_route = listenable_property.managed(False)

    @depends(real_time_mapper=None, register_real_time_data=const(nop))
    def __init__(self, real_time_mapper=None, register_real_time_data=None, *a, **k):
        super(RoutingControlComponent, self).__init__(*a, **k)
        self.__on_current_monitoring_state_changed.subject = self.song.view
        self._real_time_channel_assigner = RoutingMeterRealTimeChannelAssigner(real_time_mapper=real_time_mapper, register_real_time_data=register_real_time_data, is_enabled=False)
        self._update_monitoring_state_task = self._tasks.add(task.run(self._update_monitoring_state))
        input_type_router = self.register_disconnectable(InputTypeRouter(song=self.song))
        output_type_router = self.register_disconnectable(OutputTypeRouter(song=self.song))
        input_channel_router = self.register_disconnectable(InputChannelRouter(song=self.song))
        output_channel_router = self.register_disconnectable(OutputChannelRouter(song=self.song))
        input_channel_and_position_router = self.register_disconnectable(InputChannelAndPositionRouter(input_channel_router, input_type_router))
        self._active_type_router = input_type_router
        self._active_channel_router = input_channel_and_position_router
        self._can_route = can_set_input_routing
        self._update_can_route()
        self._routing_type_list, self._routing_channel_list = self.register_disconnectables([
         RoutingTypeList(parent_task_group=self._tasks, router=self._active_type_router),
         RoutingChannelList(parent_task_group=self._tasks, rt_channel_assigner=self._real_time_channel_assigner, router=self._active_channel_router)])
        self.__on_input_channel_position_index_changed.subject = input_channel_and_position_router
        self._routing_channel_position_list = None
        self._update_routing_channel_position_list()
        self.add_mode('input', [
         SetAttributeMode(self, '_can_route', can_set_input_routing),
         partial(self._set_active_routers, input_type_router, input_channel_and_position_router),
         self._real_time_channel_assigner])
        self.add_mode('output', [
         SetAttributeMode(self, '_can_route', lambda *a: True),
         partial(self._set_active_routers, output_type_router, output_channel_router),
         self._real_time_channel_assigner])
        self.selected_mode = 'input'
        self.__on_selected_track_changed.subject = self.song.view
        self.__on_selected_track_changed()
        self._connect_monitoring_state_encoder()
        self.input_output_choice_encoder.connect_static_list(self, 'selected_mode', list_values=[
         'input', 'output'])
        self.__on_selected_mode_changed.subject = self
        self.__on_tracks_changed.subject = self.song
        self.__on_return_tracks_changed.subject = self.song
        self._update_track_listeners()
        return

    @listenable_property
    def can_monitor(self):
        track = self.song.view.selected_track
        return hasattr(track, 'current_monitoring_state') and not track.is_frozen and track.can_be_armed

    @listenable_property
    def monitoring_state_index(self):
        if self.can_monitor:
            return self.song.view.selected_track.current_monitoring_state
        return

    @listenable_property
    def is_choosing_output(self):
        return self.selected_mode == 'output'

    @listenable_property
    def routing_type_list(self):
        return self._routing_type_list

    @listenable_property
    def routing_channel_list(self):
        return self._routing_channel_list

    @listenable_property
    def routing_channel_position_list(self):
        return self._routing_channel_position_list

    @listens('tracks')
    def __on_tracks_changed(self):
        self._update_track_listeners()

    @listens('return_tracks')
    def __on_return_tracks_changed(self):
        self._update_track_listeners()

    @listens('selected_mode')
    def __on_selected_mode_changed(self, _):
        self.notify_is_choosing_output()

    @listens('selected_track.current_monitoring_state')
    def __on_current_monitoring_state_changed(self):
        self.notify_monitoring_state_index()

    @listens('selected_track')
    def __on_selected_track_changed(self):
        self._update_monitoring_state()
        self._update_can_route()
        self._update_routing_type_list()
        self._update_routing_channel_list()
        self._update_routing_channel_position_list()
        self._reconnect_selected_track_slots()

    @listens_group('output_routing_type')
    def __on_any_output_routing_type_changed(self, *_a):
        self._update_monitoring_state_task.restart()

    @listens('is_frozen')
    def __on_is_frozen_changed(self):
        self._update_can_monitor()
        self._update_can_route()

    @listens('input_channel_position_index')
    def __on_input_channel_position_index_changed(self):
        self._update_routing_channel_list()

    @listens('has_input_channel_position')
    def __on_has_input_channel_position_changed(self, *a):
        self._update_routing_channel_position_list()
        self._connect_input_channel_position_encoder()

    @listens('input_routing_type')
    def __on_input_routing_type_changed(self):
        self._update_can_monitor()

    def _update_can_route(self):
        track = self.song.view.selected_track
        self.can_route = self._can_route(track, self.song) and track != self.song.master_track
        self._enable_encoders(self.can_route)

    def _enable_encoders(self, enabled):
        self.routing_type_encoder.enabled = enabled
        self.routing_channel_position_encoder.enabled = enabled
        for encoder in self.routing_channel_encoders:
            encoder.enabled = enabled

    def _set_active_routers(self, type_router, channel_router):
        self._active_type_router = type_router
        self._active_channel_router = channel_router
        self._update_can_route()
        self._update_routing_type_list()
        self._update_routing_channel_list()
        self._update_routing_channel_position_list()
        self._connect_input_channel_position_encoder()
        self.__on_has_input_channel_position_changed.subject = channel_router

    def _connect_input_channel_position_encoder(self):
        if self._active_channel_router.has_input_channel_position:
            self.routing_channel_position_encoder.connect_list_property(self._active_channel_router, current_index_property_name='input_channel_position_index', max_index=len(self._active_channel_router.input_channel_positions) - 1)
            self.routing_channel_position_encoder.enabled = self.can_route
        else:
            self.routing_channel_position_encoder.enabled = False
            self.routing_channel_position_encoder.disconnect_property()

    def _update_routing_type_list(self):
        self.unregister_disconnectable(self._routing_type_list)
        self._routing_type_list.disconnect()
        self._routing_type_list = self.register_disconnectable(RoutingTypeList(parent_task_group=self._tasks, router=self._active_type_router))
        self.notify_routing_type_list()
        self.routing_type_encoder.connect_list_property(self._routing_type_list, current_value_property_name='selected_target', list_property_name='targets')

    def _update_routing_channel_list(self):
        self.unregister_disconnectable(self._routing_channel_list)
        self._routing_channel_list.disconnect()
        self._routing_channel_list = self.register_disconnectable(RoutingChannelList(parent_task_group=self._tasks, rt_channel_assigner=self._real_time_channel_assigner, router=self._active_channel_router))
        self.notify_routing_channel_list()
        for encoder in self.routing_channel_encoders:
            encoder.connect_list_property(self._routing_channel_list, current_value_property_name='selected_target', list_property_name='targets')

    def _update_routing_channel_position_list(self):
        if self._routing_channel_position_list is not None:
            self.unregister_disconnectable(self._routing_channel_position_list)
            self._routing_channel_position_list.disconnect()
        if self._active_channel_router.has_input_channel_position:
            self._routing_channel_position_list = self.register_disconnectable(RoutingChannelPositionList(self._active_channel_router))
        else:
            self._routing_channel_position_list = None
        self.notify_routing_channel_position_list()
        return

    def _connect_monitoring_state_encoder(self):
        self.monitor_state_encoder.connect_static_list(self.song.view.selected_track, 'current_monitoring_state', list_values=[
         Live.Track.Track.monitoring_states.IN,
         Live.Track.Track.monitoring_states.AUTO,
         Live.Track.Track.monitoring_states.OFF])

    def _update_monitoring_state(self):
        self._update_monitoring_state_task.kill()
        self._connect_monitoring_state_encoder()
        self._update_can_monitor()

    def _update_can_monitor(self):
        if self.monitor_state_encoder.enabled != self.can_monitor:
            self.monitor_state_encoder.enabled = self.can_monitor
            self.notify_can_monitor()

    def _update_track_listeners(self):
        self.__on_any_output_routing_type_changed.replace_subjects(list(self.song.tracks) + list(self.song.return_tracks))
        self.__on_any_output_routing_type_changed()

    def _reconnect_selected_track_slots(self):
        selected_track = self.song.view.selected_track
        self.__on_is_frozen_changed.subject = selected_track
        self.__on_input_routing_type_changed.subject = selected_track
コード例 #23
0
class InputChannelAndPositionRouter(EventObject):
    u"""
    Adapts an InputChannelRouter (and InputTypeRouter).
    
    For non-track input types, the input channel interface is passed through
    unaltered, so this looks exactly like the wrapped InputChannelRouter.
    
    For track types, the list of input channels is compressed by combining
    groups of three (pre-fx, post-fx and post mixer - called "positions")
    items into a single item in the routing_targets list. The position
    is then selected with input_channel_position_index.
    """
    has_input_channel_position = listenable_property.managed(False)

    def __init__(self, input_channel_router=None, input_type_router=None, *a, **k):
        super(InputChannelAndPositionRouter, self).__init__(*a, **k)
        self._input_type_router = input_type_router
        self._input_channel_router = input_channel_router
        self._input_channel_postfixes = []
        self._update_channel_grouping()
        if self.has_input_channel_position:
            self._last_input_channel_position_index = self.input_channel_position_index
        else:
            self._last_input_channel_position_index = None
        self.__on_routing_targets_changed.subject = input_channel_router
        self.__on_current_target_index_changed.subject = input_channel_router
        self.__on_input_type_changed.subject = input_type_router
        return

    @listens('current_target_index')
    def __on_input_type_changed(self, _):
        self._update_channel_grouping()
        self.notify_routing_targets()
        self.notify_input_channel_position_index()

    @listens('routing_targets')
    def __on_routing_targets_changed(self):
        self._update_channel_grouping()
        self.notify_routing_targets()

    @listens('current_target_index')
    def __on_current_target_index_changed(self, _):
        if self.has_input_channel_position and self._last_input_channel_position_index != self.input_channel_position_index:
            self.notify_input_channel_position_index()
            self._last_input_channel_position_index = self.input_channel_position_index
        self.notify_current_target_index()

    @listenable_property
    def routing_targets(self):
        u"""
        Input channels of wrapped InputChannelRouter if has_input_channel_position
        is false.
        Input channels of from wrapped InputChannelRouter that are in the "position"
        input_channel_position if has_input_channel_position is true
        """
        complete_list = self._input_channel_router.routing_targets
        if self.has_input_channel_position:
            slice_size = len(self.live_position_postfixes)
            index_in_complete_list = self._input_channel_router.current_target_index
            return complete_list[index_in_complete_list % slice_size::slice_size]
        return complete_list

    @listenable_property
    def current_target_index(self):
        u"""
        Index in routing_targets of the current_target
        """
        index_in_complete_list = self._input_channel_router.current_target_index
        if self.has_input_channel_position:
            slice_size = len(self.live_position_postfixes)
            return index_in_complete_list // slice_size
        return index_in_complete_list

    @listenable_property
    def current_target(self):
        u"""
        Currently selected target
        """
        return self._input_channel_router.current_target

    @current_target.setter
    def current_target(self, new_target):
        self._input_channel_router.current_target = new_target

    @listenable_property
    def input_channel_positions(self):
        u"""
        List of strings naming the input channel positions.
        Only use if has_input_channel_position is true.
        """
        return self.live_position_postfixes

    @property
    def live_position_postfixes(self):
        u"""
        List of postfixes found in the names of Live's routing channels with position.
        Only use if has_input_channel_position is true.
        """
        assert self.has_input_channel_position
        return self._input_channel_postfixes

    @listenable_property
    def input_channel_position_index(self):
        u"""
        Index into input_channel_positions of current channel position.
        Only use if has_input_channel_position is true.
        """
        assert self.has_input_channel_position
        slice_size = len(self.live_position_postfixes)
        return self._input_channel_router.current_target_index % slice_size

    @input_channel_position_index.setter
    def input_channel_position_index(self, new_index):
        assert self.has_input_channel_position
        complete_list = self._input_channel_router.routing_targets
        index_in_complete_list = self._input_channel_router.current_target_index
        slice_size = len(self.live_position_postfixes)
        self._input_channel_router.current_target = complete_list[index_in_complete_list // slice_size * slice_size + new_index]

    @property
    def input_type_name(self):
        u"""
        Name of the input type.
        Only use if has_input_channel_position is true.
        """
        assert self.has_input_channel_position
        current_target = self._input_type_router.current_target
        if current_target:
            return getattr(current_target.attached_object, 'name', '')
        return ''

    def _update_channel_grouping(self):
        attached_object = getattr(self._input_type_router.current_target, 'attached_object', None)
        original_channels = self._input_channel_router.routing_targets
        if can_combine_targets(original_channels[:len(AUDIO_CHANNEL_POSITION_POSTFIXES)], AUDIO_CHANNEL_POSITION_POSTFIXES):
            postfixes = AUDIO_CHANNEL_POSITION_POSTFIXES
        else:
            postfixes = MIDI_CHANNEL_POSITION_POSTFIXES
        has_positions = liveobj_valid(attached_object) and targets_can_be_grouped(original_channels, postfixes)
        self._input_channel_postfixes = postfixes if has_positions else []
        self.has_input_channel_position = has_positions
        self.notify_input_channel_positions()
        return
コード例 #24
0
class RoutingMeterRealTimeChannelAssigner(Component):
    list_index_to_pool_index_mapping = listenable_property.managed({})

    def __init__(self, real_time_mapper=None, register_real_time_data=nop, sliding_window_size=None, *a, **k):
        assert real_time_mapper is not None
        super(RoutingMeterRealTimeChannelAssigner, self).__init__(*a, **k)
        if sliding_window_size is None:
            sliding_window_size = real_time_mapper.METER_POOLSIZE
        assert sliding_window_size > 0
        self._half_window_size = sliding_window_size // 2
        self._routing_channels = []
        self._selected_index = -1
        self.real_time_channels = [ RealTimeDataComponent(parent=self, channel_type='meter', real_time_mapper=real_time_mapper, register_real_time_data=register_real_time_data) for _ in xrange(sliding_window_size)
                                  ]
        return

    def disconnect(self):
        super(RoutingMeterRealTimeChannelAssigner, self).disconnect()
        self._routing_channels = []

    @property
    def selected_index(self):
        return self._selected_index

    @selected_index.setter
    def selected_index(self, index):
        self._selected_index = index
        self._update_attachments()

    @property
    def routing_channels(self):
        return self._routing_channels

    @routing_channels.setter
    def routing_channels(self, channels):
        self._routing_channels = channels
        for rt in self.real_time_channels:
            rt.set_data(None)

        self.list_index_to_pool_index_mapping = {}
        self._update_attachments()
        return

    def _update_attachments(self):
        visible_routing_channels = set(self._visible_routing_channels())
        attached_routing_channels = set(self._attached_routing_channels())
        to_be_detached = attached_routing_channels - visible_routing_channels
        to_be_attached = visible_routing_channels - attached_routing_channels
        for routing_channel in to_be_detached:
            rt_assignment = find_if(lambda rt: rt.attached_object == routing_channel, self.real_time_channels)
            rt_assignment.set_data(None)

        for routing_channel in to_be_attached:
            free_channel = find_if(lambda rt: rt.attached_object is None, self.real_time_channels)
            free_channel.set_data(routing_channel)

        self._update_list_index_to_pool_index_mapping()
        return

    def _visible_routing_channels(self):
        window_start = max(0, self._selected_index - self._half_window_size)
        window_end = self._selected_index + self._half_window_size + 1
        return self._routing_channels[window_start:window_end]

    def _attached_routing_channels(self):
        return filter(liveobj_valid, imap(lambda real_time_assignment: real_time_assignment.attached_object, self.real_time_channels))

    def _update_list_index_to_pool_index_mapping(self):
        new_mapping = {}
        for index, rt_assignment in enumerate(self.real_time_channels):
            if liveobj_valid(rt_assignment.attached_object):
                list_index = self._routing_channels.index(rt_assignment.attached_object)
                new_mapping[list_index] = index

        self.list_index_to_pool_index_mapping = new_mapping
コード例 #25
0
class BrowserComponent(Component, Messenger):
    __events__ = (u'loaded', u'close')
    NUM_ITEMS_PER_COLUMN = 6
    NUM_VISIBLE_BROWSER_LISTS = 7
    NUM_COLUMNS_IN_EXPANDED_LIST = 3
    EXPAND_LIST_TIME = 1.5
    REVEAL_PREVIEW_LIST_TIME = 0.2
    MIN_TIME = 0.6
    MAX_TIME = 1.4
    MIN_TIME_TEXT_LENGTH = 30
    MAX_TIME_TEXT_LENGTH = 70
    up_button = ButtonControl(repeat=True)
    down_button = ButtonControl(repeat=True)
    right_button = ButtonControl(repeat=True, **NAVIGATION_COLORS)
    left_button = ButtonControl(repeat=True, **NAVIGATION_COLORS)
    back_button = ButtonControl(**NAVIGATION_COLORS)
    open_button = ButtonControl(**NAVIGATION_COLORS)
    load_button = ButtonControl(**NAVIGATION_COLORS)
    close_button = ButtonControl()
    prehear_button = ToggleButtonControl(toggled_color=u'Browser.Option', untoggled_color=u'Browser.OptionDisabled')
    scroll_encoders = control_list(StepEncoderControl, num_steps=10, control_count=NUM_VISIBLE_BROWSER_LISTS)
    scroll_focused_encoder = StepEncoderControl(num_steps=10)
    scrolling = listenable_property.managed(False)
    horizontal_navigation = listenable_property.managed(False)
    list_offset = listenable_property.managed(0)
    can_enter = listenable_property.managed(False)
    can_exit = listenable_property.managed(False)
    context_color_index = listenable_property.managed(-1)
    context_text = listenable_property.managed(u'')

    @depends(commit_model_changes=None, selection=None)
    def __init__(self, preferences = dict(), commit_model_changes = None, selection = None, main_modes_ref = None, *a, **k):
        assert commit_model_changes is not None
        super(BrowserComponent, self).__init__(*a, **k)
        self._lists = []
        self._browser = Live.Application.get_application().browser
        self._current_hotswap_target = self._browser.hotswap_target
        self._updating_root_items = BooleanContext()
        self._focused_list_index = 0
        self._commit_model_changes = commit_model_changes
        self._preferences = preferences
        self._expanded = False
        self._unexpand_with_scroll_encoder = False
        self._delay_preview_list = BooleanContext()
        self._selection = selection
        self._main_modes_ref = main_modes_ref if main_modes_ref is not None else nop
        self._load_neighbour_overlay = LoadNeighbourOverlayComponent(parent=self, is_enabled=False)
        self._content_filter_type = None
        self._content_hotswap_target = None
        self._preview_list_task = self._tasks.add(task.sequence(task.wait(self.REVEAL_PREVIEW_LIST_TIME), task.run(self._replace_preview_list_by_task))).kill()
        self._update_root_items()
        self._update_navigation_buttons()
        self._update_context()
        self.prehear_button.is_toggled = preferences.setdefault(u'browser_prehear', True)
        self._on_selected_track_color_index_changed.subject = self.song.view
        self._on_selected_track_name_changed.subject = self.song.view
        self._on_detail_clip_name_changed.subject = self.song.view
        self._on_hotswap_target_changed.subject = self._browser
        self._on_load_next.subject = self._load_neighbour_overlay
        self._on_load_previous.subject = self._load_neighbour_overlay
        self._on_focused_item_changed.subject = self
        self.register_slot(self, self.notify_focused_item, u'focused_list_index')

        def auto_unexpand():
            self.expanded = False
            self._update_list_offset()

        self._unexpand_task = self._tasks.add(task.sequence(task.wait(self.EXPAND_LIST_TIME), task.run(auto_unexpand))).kill()

    @up_button.pressed
    def up_button(self, button):
        with self._delay_preview_list():
            self.focused_list.select_index_with_offset(-1)
        self._update_auto_expand()
        self._update_scrolling()
        self._update_horizontal_navigation()

    @up_button.released
    def up_button(self, button):
        self._finish_preview_list_task()
        self._update_scrolling()

    @down_button.pressed
    def down_button(self, button):
        with self._delay_preview_list():
            self.focused_list.select_index_with_offset(1)
        self._update_auto_expand()
        self._update_scrolling()
        self._update_horizontal_navigation()

    @down_button.released
    def down_button(self, button):
        self._finish_preview_list_task()
        self._update_scrolling()

    @right_button.pressed
    def right_button(self, button):
        if self._expanded and self._can_auto_expand() and self._focused_list_index > 0:
            self.focused_list.select_index_with_offset(self.NUM_ITEMS_PER_COLUMN)
            self._update_scrolling()
            self.horizontal_navigation = True
        elif not self._enter_selected_item():
            self._update_auto_expand()

    @right_button.released
    def right_button(self, button):
        self._update_scrolling()

    @left_button.pressed
    def left_button(self, button):
        if self._expanded and self._focused_list_index > 0 and self.focused_list.selected_index >= self.NUM_ITEMS_PER_COLUMN:
            self.focused_list.select_index_with_offset(-self.NUM_ITEMS_PER_COLUMN)
            self._update_scrolling()
            self.horizontal_navigation = True
        else:
            self._exit_selected_item()

    @left_button.released
    def left_button(self, button):
        self._update_scrolling()

    @open_button.pressed
    def open_button(self, button):
        self._enter_selected_item()

    @back_button.pressed
    def back_button(self, button):
        self._exit_selected_item()

    @scroll_encoders.touched
    def scroll_encoders(self, encoder):
        list_index = self._get_list_index_for_encoder(encoder)
        if list_index is not None:
            try:
                if self._focus_list_with_index(list_index, crop=False):
                    self._unexpand_with_scroll_encoder = True
                    self._prehear_selected_item()
                if self.focused_list.selected_item.is_loadable and encoder.index == self.scroll_encoders.control_count - 1:
                    self._update_list_offset()
                self._on_encoder_touched()
            except CannotFocusListError:
                pass

    @scroll_encoders.released
    def scroll_encoders(self, encoders):
        self._on_encoder_released()

    @scroll_encoders.value
    def scroll_encoders(self, value, encoder):
        list_index = self._get_list_index_for_encoder(encoder)
        if list_index is not None:
            try:
                if self._focus_list_with_index(list_index):
                    self._unexpand_with_scroll_encoder = True
                self._on_encoder_value(value)
            except CannotFocusListError:
                pass

    @scroll_focused_encoder.value
    def scroll_focused_encoder(self, value, encoder):
        self._on_encoder_value(value)

    @scroll_focused_encoder.touched
    def scroll_focused_encoder(self, encoder):
        self._on_encoder_touched()

    @scroll_focused_encoder.released
    def scroll_focused_encoder(self, encoder):
        self._on_encoder_released()

    def _on_encoder_value(self, value):
        with self._delay_preview_list():
            self.focused_list.select_index_with_offset(value)
        first_visible_list_focused = self.focused_list_index == self.list_offset
        if self.expanded and first_visible_list_focused:
            self.expanded = False
            self._unexpand_with_scroll_encoder = True
        elif not first_visible_list_focused and not self.expanded and self._can_auto_expand():
            self._update_auto_expand()
            self._unexpand_with_scroll_encoder = True
        self._update_scrolling()
        self._update_horizontal_navigation()

    def _on_encoder_touched(self):
        self._unexpand_task.kill()
        self._update_scrolling()
        self._update_horizontal_navigation()

    def _on_encoder_released(self):
        any_encoder_touched = any(map(lambda e: e.is_touched, self.scroll_encoders)) or self.scroll_focused_encoder.is_touched
        if not any_encoder_touched and self._unexpand_with_scroll_encoder:
            self._unexpand_task.restart()
        self._update_scrolling()

    def _get_list_index_for_encoder(self, encoder):
        if self.expanded:
            if encoder.index == 0:
                return self.list_offset
            return self.list_offset + 1
        index = self.list_offset + encoder.index
        if self.focused_list_index + 1 == index and self.should_widen_focused_item:
            index = self.focused_list_index
        if 0 <= index < len(self._lists):
            return index
        else:
            return None

    @load_button.pressed
    def load_button(self, button):
        self._load_selected_item()

    @prehear_button.toggled
    def prehear_button(self, toggled, button):
        if toggled:
            self._prehear_selected_item()
        else:
            self._browser.stop_preview()
        self._preferences[u'browser_prehear'] = toggled
        self.notify_prehear_enabled()

    @close_button.pressed
    def close_button(self, button):
        self.notify_close()

    @listenable_property
    def lists(self):
        return self._lists

    @listenable_property
    def focused_list_index(self):
        return self._focused_list_index

    @listenable_property
    def prehear_enabled(self):
        return self.prehear_button.is_toggled

    @property
    def focused_list(self):
        return self._lists[self._focused_list_index]

    @listenable_property
    def focused_item(self):
        return self.focused_list.selected_item

    @listenable_property
    def expanded(self):
        return self._expanded

    @property
    def load_neighbour_overlay(self):
        return self._load_neighbour_overlay

    @listenable_property
    def should_widen_focused_item(self):
        return self.focused_item.is_loadable and not self.focused_item.is_device

    @property
    def context_display_type(self):
        return u'custom_button'

    def disconnect(self):
        super(BrowserComponent, self).disconnect()
        self._lists = []
        self._commit_model_changes = None

    @expanded.setter
    def expanded(self, expanded):
        if self._expanded != expanded:
            self._expanded = expanded
            self._unexpand_with_scroll_encoder = False
            self._update_navigation_buttons()
            if len(self._lists) > self._focused_list_index + 1:
                self._lists[self._focused_list_index + 1].limit = self.num_preview_items
            self.notify_expanded()

    @listens(u'selected_track.color_index')
    def _on_selected_track_color_index_changed(self):
        if self.is_enabled():
            self._update_context()
            self._update_navigation_buttons()

    @listens(u'selected_track.name')
    def _on_selected_track_name_changed(self):
        if self.is_enabled():
            self._update_context()

    @listens(u'detail_clip.name')
    def _on_detail_clip_name_changed(self):
        if self.is_enabled():
            self._update_context()

    @listens(u'hotswap_target')
    def _on_hotswap_target_changed(self):
        if self.is_enabled():
            if not self._switched_to_empty_pad():
                self._update_root_items()
                self._update_context()
                self._update_list_offset()
                self._update_load_neighbour_overlay_visibility()
            else:
                self._load_neighbour_overlay.set_enabled(False)
        self._current_hotswap_target = self._browser.hotswap_target

    @listens(u'focused_item')
    def _on_focused_item_changed(self):
        self.notify_should_widen_focused_item()

    @property
    def browse_for_audio_clip(self):
        main_modes = self._main_modes_ref()
        if main_modes is None:
            return False
        has_midi_support = self.song.view.selected_track.has_midi_input
        return not has_midi_support and u'clip' in main_modes.active_modes

    def _switched_to_empty_pad(self):
        hotswap_target = self._browser.hotswap_target
        is_browsing_drumpad = isinstance(hotswap_target, Live.DrumPad.DrumPad)
        was_browsing_pad = isinstance(self._current_hotswap_target, Live.DrumPad.DrumPad)
        return is_browsing_drumpad and was_browsing_pad and len(hotswap_target.chains) == 0

    def _focus_list_with_index(self, index, crop = True):
        u"""
        Focus the list with the given index.
        Raises CannotFocusListError if the operation fails.
        Returns True if a new list was focused and False if it was already focused.
        """
        if self._focused_list_index != index:
            if self._finish_preview_list_task():
                if index >= len(self._lists):
                    raise CannotFocusListError()
            assert 0 <= index < len(self._lists)
            self._on_focused_selection_changed.subject = None
            if self._focused_list_index > index and crop:
                for l in self._lists[self._focused_list_index:]:
                    l.selected_index = -1

            self._focused_list_index = index
            self.focused_list.limit = -1
            if self.focused_list.selected_index == -1:
                self.focused_list.selected_index = 0
            self.notify_focused_list_index()
            self._on_focused_selection_changed.subject = self.focused_list
            if crop:
                self._crop_browser_lists(self._focused_list_index + 2)
            if self._focused_list_index == len(self._lists) - 1:
                self._replace_preview_list()
            self._load_neighbour_overlay.set_enabled(False)
            self._update_navigation_buttons()
            return True
        return False

    @listens(u'selected_index')
    def _on_focused_selection_changed(self):
        if self._delay_preview_list and not self.focused_item.is_loadable:
            self._preview_list_task.restart()
        else:
            self._replace_preview_list()
        self._update_navigation_buttons()
        self._prehear_selected_item()
        self._load_neighbour_overlay.set_enabled(False)
        self.notify_focused_item()

    def _get_actual_item(self, item):
        contained_item = getattr(item, u'contained_item', None)
        if contained_item is not None:
            return contained_item
        return item

    def _previous_can_be_loaded(self):
        return self.focused_list.selected_index > 0 and self.focused_list.items[self.focused_list.selected_index - 1].is_loadable

    def _next_can_be_loaded(self):
        items = self.focused_list.items
        return self.focused_list.selected_index < len(items) - 1 and items[self.focused_list.selected_index + 1].is_loadable

    @listens(u'load_next')
    def _on_load_next(self):
        self.focused_list.selected_index += 1
        self._load_selected_item()

    @listens(u'load_previous')
    def _on_load_previous(self):
        self.focused_list.selected_index -= 1
        self._load_selected_item()

    def _update_load_neighbour_overlay_visibility(self):
        self._load_neighbour_overlay.set_enabled(liveobj_valid(self._browser.hotswap_target) and (self._next_can_be_loaded() or self._previous_can_be_loaded()) and not self.focused_list.selected_item.is_device)

    def _load_selected_item(self):
        focused_list = self.focused_list
        self._update_load_neighbour_overlay_visibility()
        self._update_navigation_buttons()
        item = self._get_actual_item(focused_list.selected_item)
        self._load_item(item)
        self.notify_loaded()

    def _show_load_notification(self, item):
        notification_text = self._make_notification_text(item)
        text_length = len(notification_text)
        notification_time = self.MIN_TIME
        if text_length > self.MIN_TIME_TEXT_LENGTH:
            if text_length > self.MAX_TIME_TEXT_LENGTH:
                notification_time = self.MAX_TIME
            else:
                notification_time = self.MIN_TIME + (self.MAX_TIME - self.MIN_TIME) * old_div(float(text_length - self.MIN_TIME_TEXT_LENGTH), self.MAX_TIME_TEXT_LENGTH - self.MIN_TIME_TEXT_LENGTH)
        self.show_notification(notification_text, notification_time=notification_time)
        self._commit_model_changes()

    def _make_notification_text(self, browser_item):
        return u'Loading %s' % browser_item.name

    def _load_item(self, item):
        self._show_load_notification(item)
        if liveobj_valid(self._browser.hotswap_target):
            if isinstance(item, PluginPresetBrowserItem):
                self._browser.hotswap_target.selected_preset_index = item.preset_index
            else:
                self._browser.load_item(item)
                self._content_hotswap_target = self._browser.hotswap_target
        else:
            with self._insert_right_of_selected():
                self._browser.load_item(item)

    @contextmanager
    def _insert_right_of_selected(self):
        DeviceInsertMode = Live.Track.DeviceInsertMode
        device_to_select = get_selection_for_new_device(self._selection)
        if device_to_select:
            self._selection.selected_object = device_to_select
        selected_track_view = self.song.view.selected_track.view
        selected_track_view.device_insert_mode = DeviceInsertMode.selected_right
        yield
        selected_track_view.device_insert_mode = DeviceInsertMode.default

    def _prehear_selected_item(self):
        if self.prehear_button.is_toggled and not self._updating_root_items:
            self._browser.stop_preview()
            item = self._get_actual_item(self.focused_list.selected_item)
            if item and item.is_loadable and isinstance(item, Live.Browser.BrowserItem):
                self._browser.preview_item(item)

    def _stop_prehear(self):
        if self.prehear_button.is_toggled and not self._updating_root_items:
            self._browser.stop_preview()

    def _update_navigation_buttons(self):
        focused_list = self.focused_list
        self.up_button.enabled = focused_list.selected_index > 0
        self.down_button.enabled = focused_list.selected_index < len(focused_list.items) - 1
        selected_item_loadable = self.focused_list.selected_item.is_loadable
        can_exit = self._focused_list_index > 0
        assume_can_enter = self._preview_list_task.is_running and not selected_item_loadable
        can_enter = self._focused_list_index < len(self._lists) - 1 or assume_can_enter
        self.back_button.enabled = can_exit
        self.open_button.enabled = can_enter
        self.load_button.enabled = selected_item_loadable
        self._load_neighbour_overlay.can_load_previous = self._previous_can_be_loaded()
        self._load_neighbour_overlay.can_load_next = self._next_can_be_loaded()
        context_button_color = IndexedColor.from_live_index(self.context_color_index, DISPLAY_BUTTON_SHADE_LEVEL) if self.context_color_index > -1 else u'Browser.Navigation'
        self.load_button.color = context_button_color
        self.close_button.color = context_button_color
        self._load_neighbour_overlay.load_next_button.color = context_button_color
        self._load_neighbour_overlay.load_previous_button.color = context_button_color
        if not self._expanded:
            self.left_button.enabled = self.back_button.enabled
            self.right_button.enabled = can_enter or self._can_auto_expand()
        else:
            num_columns = int(ceil(old_div(float(len(self.focused_list.items)), self.NUM_ITEMS_PER_COLUMN)))
            last_column_start_index = (num_columns - 1) * self.NUM_ITEMS_PER_COLUMN
            self.left_button.enabled = self._focused_list_index > 0
            self.right_button.enabled = can_enter or self.focused_list.selected_index < last_column_start_index
        self.can_enter = can_enter
        self.can_exit = can_exit

    def _update_scrolling(self):
        self.scrolling = self.up_button.is_pressed or self.down_button.is_pressed or self.scroll_focused_encoder.is_touched or any(map(lambda e: e.is_touched, self.scroll_encoders)) or self.right_button.is_pressed and self._expanded or self.left_button.is_pressed and self._expanded

    def _update_horizontal_navigation(self):
        self.horizontal_navigation = self.right_button.is_pressed or self.left_button.is_pressed

    def _update_context(self):
        selected_track = self.song.view.selected_track
        clip = self.song.view.detail_clip
        if self.browse_for_audio_clip and clip:
            self.context_text = clip.name
        elif liveobj_valid(self._browser.hotswap_target):
            self.context_text = self._browser.hotswap_target.name
        else:
            self.context_text = selected_track.name
        selected_track_color_index = selected_track.color_index
        self.context_color_index = selected_track_color_index if selected_track_color_index is not None else -1

    def _enter_selected_item(self):
        item_entered = False
        self._finish_preview_list_task()
        new_index = self._focused_list_index + 1
        if 0 <= new_index < len(self._lists):
            self._focus_list_with_index(new_index)
            self._unexpand_task.kill()
            self._update_list_offset()
            self._update_auto_expand()
            self._prehear_selected_item()
            item_entered = True
        return item_entered

    def _exit_selected_item(self):
        item_exited = False
        try:
            self._focus_list_with_index(self._focused_list_index - 1)
            self._update_list_offset()
            self._update_auto_expand()
            self._stop_prehear()
            item_exited = True
        except CannotFocusListError:
            pass

        return item_exited

    def _can_auto_expand(self):
        return len(self.focused_list.items) > self.NUM_ITEMS_PER_COLUMN * 2 and self.focused_list.selected_item.is_loadable and getattr(self.focused_list.selected_item, u'contained_item', None) == None

    def _update_auto_expand(self):
        self.expanded = self._can_auto_expand()
        self._update_list_offset()

    def _update_list_offset(self):
        if self.expanded:
            self.list_offset = max(0, self.focused_list_index - 1)
        else:
            offset = len(self._lists)
            if self.focused_list.selected_item.is_loadable:
                offset += 1
            self.list_offset = max(0, offset - self.NUM_VISIBLE_BROWSER_LISTS)

    def _replace_preview_list_by_task(self):
        self._replace_preview_list()
        self._update_navigation_buttons()

    def _finish_preview_list_task(self):
        if self._preview_list_task.is_running:
            self._replace_preview_list_by_task()
            return True
        return False

    def _replace_preview_list(self):
        self._preview_list_task.kill()
        self._crop_browser_lists(self._focused_list_index + 1)
        selected_item = self.focused_list.selected_item
        children_iterator = selected_item.iter_children
        if len(children_iterator) > 0:
            enable_wrapping = getattr(selected_item, u'enable_wrapping', True) and self.focused_list.items_wrapped
            self._append_browser_list(children_iterator=children_iterator, limit=self.num_preview_items, enable_wrapping=enable_wrapping)

    def _append_browser_list(self, children_iterator, limit = -1, enable_wrapping = True):
        l = BrowserList(item_iterator=children_iterator, item_wrapper=self._wrap_item if enable_wrapping else nop, limit=limit)
        l.items_wrapped = enable_wrapping
        self._lists.append(l)
        self.register_disconnectable(l)
        self.notify_lists()

    def _crop_browser_lists(self, length):
        num_items_to_crop = len(self._lists) - length
        for _ in range(num_items_to_crop):
            l = self._lists.pop()
            self.unregister_disconnectable(l)

        if num_items_to_crop > 0:
            self.notify_lists()

    def _make_root_browser_items(self):
        filter_type = self._browser.filter_type
        hotswap_target = self._browser.hotswap_target
        if liveobj_valid(hotswap_target):
            filter_type = filter_type_for_hotswap_target(hotswap_target, default=filter_type)
        return make_root_browser_items(self._browser, filter_type)

    def _content_cache_is_valid(self):
        return self._content_filter_type == self._browser.filter_type and not liveobj_changed(self._content_hotswap_target, self._browser.hotswap_target)

    def _invalidate_content_cache(self):
        self._content_hotswap_target = None
        self._content_filter_type = None

    def _update_content_cache(self):
        self._content_filter_type = self._browser.filter_type
        self._content_hotswap_target = self._browser.hotswap_target

    def _update_root_items(self):
        if not self._content_cache_is_valid():
            self._update_content_cache()
            with self._updating_root_items():
                self._on_focused_selection_changed.subject = None
                self._crop_browser_lists(0)
                self._append_browser_list(children_iterator=self._make_root_browser_items())
                self._focused_list_index = 0
                self.focused_list.selected_index = 0
                self._select_hotswap_target()
                self._on_focused_selection_changed.subject = self.focused_list
                self._on_focused_selection_changed()

    def _select_hotswap_target(self, list_index = 0):
        if list_index < len(self._lists):
            l = self._lists[list_index]
            l.access_all = True
            children = l.items
            i = index_if(lambda i: i.is_selected, children)
            if i < len(children):
                self._focused_list_index = list_index
                l.selected_index = i
                self._replace_preview_list()
                self._select_hotswap_target(list_index + 1)

    @property
    def num_preview_items(self):
        if self._expanded:
            return self.NUM_ITEMS_PER_COLUMN * self.NUM_COLUMNS_IN_EXPANDED_LIST
        return 6

    def update(self):
        super(BrowserComponent, self).update()
        self._invalidate_content_cache()
        if self.is_enabled():
            self._update_root_items()
            self._update_context()
            self._update_list_offset()
            self._update_load_neighbour_overlay_visibility()
            self._update_navigation_buttons()
            self.expanded = False
            self._update_list_offset()
        else:
            self._stop_prehear()
            self.list_offset = 0

    def _wrap_item(self, item):
        if item.is_device:
            return self._wrap_device_item(item)
        if self._is_hotswap_target_plugin(item):
            return self._wrap_hotswapped_plugin_item(item)
        return item

    def _wrap_device_item(self, item):
        u"""
        Create virtual folder around items that can be loaded AND have children, to avoid
        having two actions on an item (open and load).
        """
        wrapped_loadable = WrappedLoadableBrowserItem(name=item.name, is_loadable=True, contained_item=item)
        return FolderBrowserItem(name=item.name, is_loadable=True, is_device=True, contained_item=item, wrapped_loadable=wrapped_loadable, icon=u'browser_arrowcontent.svg')

    def _is_hotswap_target_plugin(self, item):
        return isinstance(self._browser.hotswap_target, Live.PluginDevice.PluginDevice) and isinstance(item, Live.Browser.BrowserItem) and self._browser.relation_to_hotswap_target(item) == Live.Browser.Relation.equal

    def _wrap_hotswapped_plugin_item(self, item):
        return PluginBrowserItem(name=item.name, vst_device=self._browser.hotswap_target)
コード例 #26
0
class ClipPositions(EventObject):
    __events__ = ('is_recording', 'warp_markers', 'before_update_all', 'after_update_all')
    MAX_TIME = 10000000
    MIN_TIME = -10000
    start = listenable_property.managed(0.0)
    end = listenable_property.managed(0.0)
    start_marker = listenable_property.managed(0.0)
    end_marker = listenable_property.managed(0.0)
    loop_start = listenable_property.managed(0.0)
    loop_end = listenable_property.managed(0.0)
    loop_length = listenable_property.managed(0.0)
    use_beat_time = listenable_property.managed(False)

    def __init__(self, clip=None, *a, **k):
        (super(ClipPositions, self).__init__)(*a, **k)
        self._clip = clip
        self._looping = self._clip.looping
        self._ClipPositions__on_is_recording_changed.subject = clip
        self._ClipPositions__on_looping_changed.subject = clip
        self._ClipPositions__on_start_marker_changed.subject = clip
        self._ClipPositions__on_end_marker_changed.subject = clip
        self._ClipPositions__on_loop_start_changed.subject = clip
        self._ClipPositions__on_loop_end_changed.subject = clip
        self._ClipPositions__on_loop_start_changed()
        self._ClipPositions__on_loop_end_changed()
        if clip.is_audio_clip:
            self._ClipPositions__on_warping_changed.subject = clip
            self._ClipPositions__on_warp_markers_changed.subject = clip
        if clip.is_midi_clip:
            self._ClipPositions__on_notes_changed.subject = clip
            self._update_start_end_note_times()
        self.update_all()

    @property
    def is_warping(self):
        return self._clip.is_audio_clip and self._clip.warping

    def _convert_to_desired_unit(self, beat_time_or_seconds):
        if not self._clip.is_midi_clip:
            if not self.is_warping:
                beat_time_or_seconds = self._clip.seconds_to_sample_time(beat_time_or_seconds)
            return beat_time_or_seconds

    @listens('start_marker')
    def __on_start_marker_changed(self):
        if not self._process_looping_update():
            self.start_marker = self._convert_to_desired_unit(self._clip.start_marker)

    @listens('end_marker')
    def __on_end_marker_changed(self):
        if not self._process_looping_update():
            self.end_marker = self._convert_to_desired_unit(self._clip.end_marker)

    @listens('loop_start')
    def __on_loop_start_changed(self):
        if not self._process_looping_update():
            self.loop_start = self._convert_to_desired_unit(self._clip.loop_start)
        self._update_loop_length()

    @listens('loop_end')
    def __on_loop_end_changed(self):
        if not self._process_looping_update():
            self.loop_end = self._convert_to_desired_unit(self._clip.loop_end)
        self._update_loop_length()

    @listens('is_recording')
    def __on_is_recording_changed(self):
        self._update_start_end()
        self.notify_is_recording()

    @listens('warp_markers')
    def __on_warp_markers_changed(self):
        self.update_all()
        self.notify_warp_markers()

    @listens('looping')
    def __on_looping_changed(self):
        self.update_all()

    @listens('warping')
    def __on_warping_changed(self):
        self.update_all()

    @listens('notes')
    def __on_notes_changed(self):
        self._update_start_end_note_times()
        self._update_start_end()

    def _update_start_end_note_times(self):
        all_notes = self._clip.get_notes_extended(from_time=(self.MIN_TIME),
          from_pitch=0,
          time_span=(self.MAX_TIME),
          pitch_span=128)
        start_times, end_times = list(zip(*[(note.start_time, note.start_time + note.duration) for note in all_notes])) if len(all_notes) > 0 else (
         [
          self.MAX_TIME], [self.MIN_TIME])
        self.start_of_first_note = min(start_times)
        self.end_of_last_note = max(end_times)

    def _process_looping_update(self):
        looping = self._clip.looping
        if looping != self._looping:
            self._looping = looping
            self.update_all()
            return True
        return False

    def _update_loop_length(self):
        self.loop_length = self._convert_to_desired_unit(self._clip.loop_end) - self._convert_to_desired_unit(self._clip.loop_start)

    def _update_start_end(self):
        start = None
        end = None
        if self.is_warping:
            start = self._clip.sample_to_beat_time(0)
            end = self._clip.sample_to_beat_time(self._clip.sample_length)
        elif self._clip.is_audio_clip:
            start = 0
            end = self._clip.sample_length
        else:
            start = self.start_of_first_note
            end = self.end_of_last_note
        self.start = min(start, self.loop_start if self._clip.looping else self.start_marker)
        self.end = max(end, self.loop_end)

    def update_all(self):
        self.notify_before_update_all()
        self._ClipPositions__on_start_marker_changed()
        self._ClipPositions__on_end_marker_changed()
        self._ClipPositions__on_loop_start_changed()
        self._ClipPositions__on_loop_end_changed()
        self._update_start_end()
        if self._clip.is_audio_clip:
            self.use_beat_time = self._clip.warping
        self.notify_after_update_all()
コード例 #27
0
class ScalesComponent(Component):
    navigation_colors = dict(color=b'Scales.Navigation',
                             disabled_color=b'Scales.NavigationDisabled')
    up_button = ButtonControl(repeat=True, **navigation_colors)
    down_button = ButtonControl(repeat=True, **navigation_colors)
    right_button = ButtonControl(repeat=True, **navigation_colors)
    left_button = ButtonControl(repeat=True, **navigation_colors)
    root_note_buttons = control_list(RadioButtonControl,
                                     control_count=len(ROOT_NOTES),
                                     checked_color=b'Scales.OptionOn',
                                     unchecked_color=b'Scales.OptionOff')
    in_key_toggle_button = ToggleButtonControl(
        toggled_color=b'Scales.OptionOn', untoggled_color=b'Scales.OptionOn')
    fixed_toggle_button = ToggleButtonControl(
        toggled_color=b'Scales.OptionOn', untoggled_color=b'Scales.OptionOff')
    scale_encoders = control_list(StepEncoderControl)
    layout_encoder = StepEncoderControl()
    direction_encoder = StepEncoderControl()
    horizontal_navigation = listenable_property.managed(False)
    NUM_DISPLAY_ROWS = 4
    NUM_DISPLAY_COLUMNS = int(ceil(float(len(SCALES)) / NUM_DISPLAY_ROWS))

    def __init__(self, note_layout=None, *a, **k):
        assert note_layout is not None
        super(ScalesComponent, self).__init__(*a, **k)
        self._note_layout = note_layout
        self._scale_list = list(SCALES)
        self._scale_name_list = map(lambda m: m.name, self._scale_list)
        self._selected_scale_index = -1
        self._selected_root_note_index = -1
        self._layouts = (Layout(b'4ths',
                                3), Layout(b'3rds',
                                           2), Layout(b'Sequential', None))
        self._selected_layout_index = 0
        self.in_key_toggle_button.connect_property(note_layout, b'is_in_key')
        self.fixed_toggle_button.connect_property(note_layout, b'is_fixed')
        self.__on_root_note_changed.subject = self._note_layout
        self.__on_scale_changed.subject = self._note_layout
        self.__on_interval_changed.subject = self._note_layout
        self.__on_root_note_changed(note_layout.root_note)
        self.__on_scale_changed(note_layout.scale)
        self.__on_interval_changed(self._note_layout.interval)
        return

    def _set_selected_scale_index(self, index):
        index = clamp(index, 0, len(self._scale_list) - 1)
        self._note_layout.scale = self._scale_list[index]

    @down_button.pressed
    def down_button(self, button):
        self._update_horizontal_navigation()
        self._set_selected_scale_index(self._selected_scale_index + 1)

    @up_button.pressed
    def up_button(self, button):
        self._update_horizontal_navigation()
        self._set_selected_scale_index(self._selected_scale_index - 1)

    @left_button.pressed
    def left_button(self, button):
        self._update_horizontal_navigation()
        self._set_selected_scale_index(self._selected_scale_index -
                                       self.NUM_DISPLAY_ROWS)

    @right_button.pressed
    def right_button(self, button):
        self._update_horizontal_navigation()
        self._set_selected_scale_index(self._selected_scale_index +
                                       self.NUM_DISPLAY_ROWS)

    @root_note_buttons.pressed
    def root_note_buttons(self, button):
        self._note_layout.root_note = ROOT_NOTES[button.index]

    @listens(b'root_note')
    def __on_root_note_changed(self, root_note):
        self._selected_root_note_index = list(ROOT_NOTES).index(root_note)
        self.root_note_buttons.checked_index = self._selected_root_note_index
        self.notify_selected_root_note_index()

    @property
    def root_note_names(self):
        return [NOTE_NAMES[note] for note in ROOT_NOTES]

    @listenable_property
    def selected_root_note_index(self):
        return self._selected_root_note_index

    @scale_encoders.value
    def scale_encoders(self, value, encoder):
        self._update_horizontal_navigation()
        self._set_selected_scale_index(self._selected_scale_index + value)

    @property
    def scale_names(self):
        return self._scale_name_list

    @listenable_property
    def selected_scale_index(self):
        return self._selected_scale_index

    @listens(b'scale')
    def __on_scale_changed(self, scale):
        index = self._scale_list.index(
            scale) if scale in self._scale_list else -1
        if index != self._selected_scale_index:
            self._selected_scale_index = index
            self.up_button.enabled = index > 0
            self.left_button.enabled = index > 0
            self.down_button.enabled = index < len(self._scale_list) - 1
            self.right_button.enabled = index < len(self._scale_list) - 1
            self.notify_selected_scale_index()

    @layout_encoder.value
    def layout_encoder(self, value, encoder):
        index = clamp(self._selected_layout_index + value, 0,
                      len(self._layouts) - 1)
        self.selected_layout_index = index

    @property
    def layout_names(self):
        return [layout.name for layout in self._layouts]

    @listenable_property
    def selected_layout_index(self):
        return self._selected_layout_index

    @selected_layout_index.setter
    def selected_layout_index(self, index):
        if index != self._selected_layout_index:
            self._selected_layout_index = index
            interval = self._layouts[index].interval
            self._note_layout.interval = interval
            self.notify_selected_layout_index()

    @direction_encoder.value
    def direction_encoder(self, value, encoder):
        self._note_layout.is_horizontal = value < 0

    @property
    def note_layout(self):
        return self._note_layout

    def _update_horizontal_navigation(self):
        self.horizontal_navigation = self.right_button.is_pressed or self.left_button.is_pressed

    @listens(b'interval')
    def __on_interval_changed(self, interval):
        index = index_if(lambda layout: layout.interval == interval,
                         self._layouts)
        self.selected_layout_index = index
コード例 #28
0
class WaveformNavigation(EventObject):
    """ Class for managing a visible area of a waveform """
    visible_region = listenable_property.managed(Region(0, 1))
    visible_region_in_samples = listenable_property.managed(Region(0, 1))
    animate_visible_region = listenable_property.managed(False)
    focus_marker = listenable_property.managed(FocusMarker('', 0))
    show_focus = listenable_property.managed(False)
    ZOOM_SENSITIVITY = 1.5
    MIN_VISIBLE_SAMPLES = 49
    WAVEFORM_WIDTH_IN_PX = 933
    MARGIN_IN_PX = 121
    RELATIVE_FOCUS_MARGIN = float(MARGIN_IN_PX) / WAVEFORM_WIDTH_IN_PX
    UNSNAPPING_THRESHOLD = 0.6
    CHANGE_OBJECT_TIME = 0.1

    def __init__(self, *a, **k):
        super(WaveformNavigation, self).__init__(*a, **k)
        self._waveform_region = Region(0, 1)
        self.waveform_roi = self.make_region_of_interest(
            getter=lambda: self._waveform_region, with_margin=False)
        self.focused_object_roi = self.make_region_of_interest(
            getter=self._make_region_for_focused_object, with_margin=False)
        self._focused_identifier = None
        self._touched_identifiers = set()
        self._changed_identifiers = set()
        self._has_tasks = False
        self._target_roi = self.waveform_roi
        self._source_roi = self.waveform_roi
        self._request_select_region = False
        self._unsnapping_value = 0
        self._locked_roi = None
        self._last_action = None
        return

    def disconnect(self):
        super(WaveformNavigation, self).disconnect()
        if self._has_tasks:
            self._tasks.kill()
            self._tasks.clear()

    def get_object_identifier(self, obj):
        raise NotImplementedError

    def get_zoom_object(self):
        raise NotImplementedError

    def get_region_in_samples(self, region):
        return region

    def get_min_visible_length(self):
        return self.MIN_VISIBLE_SAMPLES

    @listenable_property
    def waveform_region(self):
        return self._waveform_region

    @waveform_region.setter
    def waveform_region(self, region):
        if region != self._waveform_region:
            self._waveform_region = region
            self._request_select_region = True
            self.set_visible_region(self._waveform_region)
            self.notify_waveform_region()

    def make_region_of_interest(self,
                                start_identifier=None,
                                end_identifier=None,
                                getter=None,
                                with_margin=True):
        return RegionOfInterest(
            start_identifier,
            end_identifier,
            getter,
            add_margin=self._add_margin_to_region if with_margin else nop)

    @lazy_attribute
    def regions_of_interest(self):
        """
        The region of interests, that can be zoomed into. By default, the waveform
        navigation zooms only between the full waveform and the focused objects
        position. Override additional_regions_of_interest to add more regions to it.
        """
        rois = {
            'waveform': self.waveform_roi,
            'focused_object': self.focused_object_roi
        }
        rois.update(self.additional_regions_of_interest)
        return rois

    @lazy_attribute
    def additional_regions_of_interest(self):
        return {}

    def get_name_for_roi(self, roi):
        """
        Returns the name for the given roi or None, if it doesn't have one
        """
        item = find_if(lambda i: i[1] == roi,
                       self.regions_of_interest.iteritems())
        if item is not None:
            return item[0]
        else:
            return

    @lazy_attribute
    def focusable_object_descriptions(self):
        """
        Describes focusable objects and how they zoom into regions.
        Returns a dictionary of identifier to ObjectDescriptions.
        """
        return {}

    def get_object_description(self, identifier):
        return self.focusable_object_descriptions.get(identifier, None)

    @property
    def visible_proportion(self):
        """ Returns the proportion between the visible length and the sample length """
        return self.visible_region.length / float(self._waveform_region.length)

    def set_visible_region(self,
                           region,
                           source_action=None,
                           force_animate=False):
        """
        Set the current visible region in the current unit and samples.
        The region is animated, if source action changes.
        Animation is enforced, if force_animate is True.
        """
        self.animate_visible_region = force_animate or source_action != self._last_action
        self.visible_region = region.clamp_to_region(self._waveform_region)
        self.visible_region_in_samples = self.get_region_in_samples(
            self.visible_region)
        self._last_action = source_action

    def set_visible_length(self, length):
        """
        Extends or reduces the visible end to show the given length.
        If the end of the waveform is reached, the visible start is adapted.
        """
        start = self.visible_region.start
        end = min(start + length, self.waveform_region.end)
        start = end - length
        self.set_visible_region(Region(start, end))

    def zoom(self, value):
        """ Zooms in or out of the waveform start
            value should be between -1.0 and 1.0, where 1.0 will zoom in as much as
            possible and -1.0 will zoom out completely.
        """
        animate = self._request_select_region
        if self._request_select_region or self._process_unsnapping(value):
            self._select_region(value > 0)
        source = self._source_roi.region_with_margin
        target = self._target_roi.region_with_margin
        easing_degree = calc_easing_degree_for_proportion(
            float(target.length) / float(source.length))
        focused_region, focus_marker, margin_type = self._get_zoom_info_for_focused_object(
        )
        t = inverse_interpolate_region(
            source,
            target,
            self.visible_region,
            easing_degree,
            prefer_end=margin_type == MarginType.START)
        t = clamp(t + value * self.ZOOM_SENSITIVITY, 0.0, 1.0)
        region = interpolate_region(source, target, t, easing_degree)
        region = self._add_margin_to_zoomed_region(region, focused_region,
                                                   margin_type)
        self.set_visible_region(region,
                                force_animate=animate,
                                source_action='zoom')
        self.focus_marker = focus_marker
        self.show_focus = True
        self.try_hide_focus_delayed()
        self._try_lock_region()

    def _get_zoom_info_for_focused_object(self):
        """
        Returns a tuple of the region for the focused object and weather a
        margin should be added to the zoom region.
        """
        identifier = self._focused_identifier
        roi = self._get_roi_for_object_identifier(identifier)
        margin_type = MarginType.NONE
        region = None
        focus_marker = None
        if roi is not None:
            margin = self.waveform_region.length * self.RELATIVE_FOCUS_MARGIN
            region = roi.region
            is_start = roi.start_identifier == identifier
            if is_start and region.start < margin:
                margin_type = MarginType.START
            elif not is_start and region.end > self.waveform_region.end - margin:
                margin_type = MarginType.END
            obj_description = self.focusable_object_descriptions.get(
                identifier, None)
            if obj_description is not None:
                focus_marker = FocusMarker(
                    obj_description.focus_name, region.end
                    if roi.end_identifier == identifier else region.start)
        return (region, focus_marker, margin_type)

    def _add_margin_to_zoomed_region(self, zoom_region, focused_region,
                                     margin_type):
        """
        Adds a margin to a zoom region, so that the focused object is shown with a margin
        as soon as possible. This makes switching between zooming an focusing an object
        seamless, as focusing will always add the margin as well.
        """
        if focused_region is not None and margin_type != MarginType.NONE:
            position = focused_region.start if margin_type == MarginType.START else focused_region.end
            if zoom_region.start <= position <= zoom_region.end:
                if margin_type == MarginType.START:
                    zoom_region = self._add_margin_to_zoomed_region_start(
                        zoom_region, position)
                else:
                    zoom_region = self._add_margin_to_zoomed_region_end(
                        zoom_region, position)
            else:
                logger.warn(
                    "Focused object not visible. Couldn't add margin to zoomed region. %d not in %r"
                    % (position, zoom_region))
        return zoom_region

    def _add_margin_to_zoomed_region_start(self, region, focused_position):
        p = focused_position - self.waveform_region.start
        samples_per_pixel = p / self.MARGIN_IN_PX
        length = self.WAVEFORM_WIDTH_IN_PX * samples_per_pixel
        if self.waveform_region.start + length < region.end:
            region = Region(self.waveform_region.start, region.end)
        else:
            p = region.end - focused_position
            samples_per_pixel = p / (self.WAVEFORM_WIDTH_IN_PX -
                                     self.MARGIN_IN_PX)
            length = self.WAVEFORM_WIDTH_IN_PX * samples_per_pixel
            start = region.end - length
            if start < region.start:
                region = Region(start, region.end)
        return region

    def _add_margin_to_zoomed_region_end(self, region, focused_position):
        p = self.waveform_region.end - focused_position
        samples_per_pixel = p / self.MARGIN_IN_PX
        length = self.WAVEFORM_WIDTH_IN_PX * samples_per_pixel
        if self.waveform_region.end - length > region.start:
            region = Region(region.start, self.waveform_region.end)
        else:
            p = focused_position - region.start
            samples_per_pixel = p / (self.WAVEFORM_WIDTH_IN_PX -
                                     self.MARGIN_IN_PX)
            length = self.WAVEFORM_WIDTH_IN_PX * samples_per_pixel
            end = region.start + length
            if end > region.end:
                region = Region(region.start, end)
        return region

    def _process_unsnapping(self, value):
        """
        Process unsnapping for the given normalized value.
        Returns true if the region should be unsnapped.
        """
        if self.is_snapped:
            self._unsnapping_value += value
            return abs(self._unsnapping_value) >= self.UNSNAPPING_THRESHOLD
        return False

    def _try_lock_region(self):
        if self.visible_region == self._waveform_region:
            self._locked_roi = None
        elif self.visible_region == self._target_roi.region_with_margin:
            self._locked_roi = self._target_roi
        elif self.visible_region == self._source_roi.region_with_margin:
            self._locked_roi = self._source_roi
        else:
            self._locked_roi = None
        return

    @property
    def is_snapped(self):
        return self.visible_region == self._target_roi.region_with_margin or self.visible_region == self._source_roi.region_with_margin

    def focus_object(self, obj):
        if obj != self.get_zoom_object():
            identifier = self.get_object_identifier(obj)
            zoom_identifier = self.get_object_identifier(
                self.get_zoom_object())
            touched_identifiers = self._touched_identifiers - set(
                [zoom_identifier])
            objects_to_show = self._changed_identifiers & touched_identifiers
            if identifier in self.focusable_object_descriptions:
                if len(objects_to_show) > 1:
                    logger.debug('Focus all objects %r' % objects_to_show)
                    self._focused_identifier = identifier
                    self._show_all_objects(objects_to_show)
                else:
                    logger.debug('Focus object %r' % identifier)
                    animate = len(
                        touched_identifiers) <= 1 and self.object_changed(
                            self._focused_identifier, identifier)
                    self._focused_identifier = identifier
                    self._focus_object_by_identifier(identifier,
                                                     animate=animate)
                return True
        return False

    def object_changed(self, identifier1, identifier2):
        return identifier1 != identifier2

    def _get_roi_for_object_identifier(self, identifier):
        return find_if(lambda roi: roi.bound_by(identifier),
                       self.regions_of_interest.values())

    def _get_position_for_identifier(self, identifier):
        roi = self._get_roi_for_object_identifier(identifier)
        if roi is not None:
            if roi.start_identifier == identifier:
                return roi.region.start
            return roi.region.end
        else:
            return

    def _zoom_out_or_move_region(self, source_region, target_region):
        """
        Zooms out the source region, if it's contained in the target region
        or moves it left or right depending on where they overlap.
        """
        new_region = None
        if source_region.inside(target_region):
            new_region = target_region
        elif target_region.start < source_region.start:
            new_region = Region(
                target_region.start,
                max(target_region.start + source_region.length,
                    target_region.end))
        elif target_region.end > source_region.end:
            new_region = Region(
                min(target_region.end - source_region.length,
                    target_region.start), target_region.end)
        return new_region

    def _show_all_objects(self, identifiers):
        start = self.waveform_region.end
        end = self.waveform_region.start
        positions = imap(self._get_position_for_identifier, identifiers)
        for position in ifilter(None, positions):
            start = min(start, position)
            end = max(end, position)

        margin = self.visible_region.length * self.RELATIVE_FOCUS_MARGIN
        visible_region_without_margin = Region(
            self.visible_region.start + margin,
            self.visible_region.end - margin)
        object_region = Region(start, end)
        new_region = self._zoom_out_or_move_region(
            visible_region_without_margin, object_region)
        if new_region is not None:
            self.set_visible_region(self._add_margin_to_region(new_region),
                                    source_action='show_objects %r' %
                                    identifiers)
            self._request_select_region = True
            self._locked_roi = None
        self.focus_marker = FocusMarker('', 0)
        return

    def _focus_object_by_identifier(self, identifier, animate=False):
        """ Focuses the object in the waveform and brings it into the visible range.
            The visible length is preserved. The position is aligned to the left or right
            of the visible range, with a certain margin defined by RELATIVE_FOCUS_MARGIN.
            If the objects region boundary is already in the visible range, the visible
            position is not changing.
            :identifier: the object identifier to focus
            :animate: should be set to True if, if it should animate to the new position
        """
        roi = self._get_roi_for_object_identifier(identifier)
        region = roi.region
        if self._locked_roi is not None and self._locked_roi.bound_by(
                identifier):
            if region.start < self.waveform_region.start:
                start = self.waveform_region.start
                new_visible_region = Region(start,
                                            start + self.visible_region.length)
            elif region.end > self.waveform_region.end:
                end = self.waveform_region.end
                new_visible_region = Region(end - self.visible_region.length,
                                            end)
            else:
                new_visible_region = self._add_margin_to_region(region)
            self.set_visible_region(new_visible_region, force_animate=animate)
        else:
            visible_length = self.visible_region.length
            visible_margin = visible_length * self.RELATIVE_FOCUS_MARGIN
            waveform_start, waveform_end = self._waveform_region
            if roi.end_identifier == identifier:
                start = min(region.start - visible_margin,
                            self.visible_region.start)
                right = max(region.end + visible_margin,
                            start + visible_length)
                left = right - visible_length
            else:
                end = max(region.end + visible_margin, self.visible_region.end)
                left = min(region.start - visible_margin, end - visible_length)
                right = left + visible_length
            self.set_visible_region(Region(
                clamp(left, waveform_start, waveform_end - visible_length),
                clamp(right, waveform_start + visible_length, waveform_end)),
                                    force_animate=animate)
            self._request_select_region = True
        self.focus_marker = FocusMarker(
            self.focusable_object_descriptions[identifier].focus_name,
            region.end if roi.end_identifier == identifier else region.start)
        return

    def touch_object(self, obj):
        is_zoom_object = obj == self.get_zoom_object()
        if is_zoom_object and self.is_snapped:
            self._request_select_region = True
        self._touched_identifiers.add(self.get_object_identifier(obj))
        if self.focus_object(obj) or is_zoom_object:
            self.show_focus = True

    def release_object(self, obj):
        identifier = self.get_object_identifier(obj)
        self._remove_changed_object(identifier)
        if identifier in self._touched_identifiers:
            self._touched_identifiers.remove(identifier)
            self.try_hide_focus()

    def _remove_changed_object(self, identifier):
        if identifier in self._changed_identifiers:
            self._changed_identifiers.remove(identifier)

    def _remove_changed_object_delayed(self, identifier):
        tasks = self._tasks
        if tasks is not None:
            tasks.add(
                task.sequence(
                    task.wait(self.CHANGE_OBJECT_TIME),
                    task.run(partial(self._remove_changed_object,
                                     identifier))))
        return

    def change_object(self, obj):
        identifier = self.get_object_identifier(obj)
        self._changed_identifiers.add(identifier)
        self._remove_changed_object_delayed(identifier)
        if self.focus_object(obj) or obj == self.get_zoom_object():
            self.show_focus = True
            self.try_hide_focus_delayed()

    def focus_region_of_interest(self, roi_identifier, focused_object):
        roi = self.regions_of_interest[roi_identifier]
        visible_region = roi.region_with_margin
        self.set_visible_region(visible_region)
        self.focus_object(focused_object)
        if visible_region != self._waveform_region:
            self._locked_roi = roi

    def try_hide_focus(self):
        """ Hides the focus, if the focused object is not longer touched """
        if self._should_hide_focus():
            self.show_focus = False

    def try_hide_focus_delayed(self):
        """ Hides the focus after some time, if the focused object is not
            longer touched
        """
        if self._hide_focus_task and self._should_hide_focus():
            self._hide_focus_task.restart()

    def _should_hide_focus(self):
        zoom_identifier = self.get_object_identifier(self.get_zoom_object())
        return zoom_identifier not in self._touched_identifiers and self._focused_identifier not in self._touched_identifiers

    def reset_focus_and_animation(self):
        self.show_focus = False
        self.animate_visible_region = False
        self._touched_identifiers = set()
        self._changed_identifiers = set()

    def copy_state(self, navigation):
        """
        Tries to replicate the state of the given waveform navigation.
        The waveform regions need to be identical for this to make sense.
        The focused identifier and all region of interests should be available
        in both navigations, or the result will be undefined.
        """
        if self._waveform_region == navigation.waveform_region:
            self.set_visible_region(navigation.visible_region)
            self._focused_identifier = navigation._focused_identifier
            source_roi_name = navigation.get_name_for_roi(
                navigation._source_roi)
            target_roi_name = navigation.get_name_for_roi(
                navigation._target_roi)
            locked_roi_name = navigation.get_name_for_roi(
                navigation._locked_roi)
            self._source_roi = self.regions_of_interest.get(
                source_roi_name, None)
            self._target_roi = self.regions_of_interest.get(
                target_roi_name, None)
            self._locked_roi = self.regions_of_interest.get(
                locked_roi_name, None)
        return

    @lazy_attribute
    @depends(parent_task_group=const(None))
    def _tasks(self, parent_task_group=None):
        if parent_task_group is not None:
            tasks = parent_task_group.add(task.TaskGroup())
            self._has_tasks = True
            return tasks
        else:
            return

    @lazy_attribute
    def _hide_focus_task(self):
        tasks = self._tasks
        if tasks is not None:
            return tasks.add(
                task.sequence(task.wait(EncoderControl.TOUCH_TIME),
                              task.run(self.try_hide_focus)))
        else:
            return

    def _add_margin_to_region(self, region):
        start, end = region
        margin = self.RELATIVE_FOCUS_MARGIN
        start1 = (margin * start + end * margin - start) / (2 * margin - 1)
        start1 = self._waveform_region.clamp_position(start1)
        end1 = (end - margin * start1) / (1 - margin)
        end2 = (margin * start + end * margin - end) / (2 * margin - 1)
        end2 = self._waveform_region.clamp_position(end2)
        start2 = (start - margin * end2) / (1 - margin)
        return Region(max(start1, start2), min(end1, end2))

    def _make_region_from_position_identifier(self, identifier):
        roi = self._get_roi_for_object_identifier(identifier)
        align_right = roi.end_identifier == identifier
        region = roi.region
        position = region.end if align_right else region.start
        length = self.get_min_visible_length()
        margin = self.RELATIVE_FOCUS_MARGIN * length
        if align_right:
            right = self._waveform_region.clamp_position(position + margin)
            left = self._waveform_region.clamp_position(right - length)
        else:
            left = self._waveform_region.clamp_position(position - margin)
            right = self._waveform_region.clamp_position(left + length)
        return Region(left, right)

    def _make_region_for_focused_object(self):
        if self._focused_identifier is not None:
            return self._make_region_from_position_identifier(
                self._focused_identifier)
        else:
            return Region(0, 0)

    def _get_roi_for_focused_identifier(self):
        if self._focused_identifier is not None:
            return map(
                self.regions_of_interest.get,
                self.get_object_description(self._focused_identifier).regions)
        else:
            return []

    def _get_unique_regions_of_interest(self):
        """
        Eliminates duplicates of the current regions and returns the remaining getters
        sorted by the length of the regions.
        """
        rois = OrderedDict()
        for roi in self._get_roi_for_focused_identifier():
            rois[roi.region_with_margin] = roi

        items = sorted(rois.items(), key=lambda (r, _): r.length, reverse=True)
        return map(lambda item: item[1], items)

    def _select_region_around_visible_region(self):
        regions_of_interest = self._get_unique_regions_of_interest()
        source_roi = find_if(
            lambda roi: self.visible_region.inside(roi.region_with_margin),
            reversed(regions_of_interest[:-1]))
        if source_roi is not None:
            self._set_source_and_target_roi(
                source_roi,
                regions_of_interest[regions_of_interest.index(source_roi) + 1])
        return

    def _select_reached_region(self, zoom_in):
        rois = self._get_unique_regions_of_interest()
        i = index_if(lambda roi: self.visible_region == roi.region_with_margin,
                     rois)
        if i != len(rois):
            if zoom_in:
                if i < len(rois) - 1:
                    self._set_source_and_target_roi(rois[i], rois[i + 1])
            elif i > 0:
                self._set_source_and_target_roi(rois[i - 1], rois[i])
            return True
        return False

    def _select_region(self, zoom_in):
        if not self._select_reached_region(zoom_in):
            self._select_region_around_visible_region()
        self._request_select_region = False
        self._unsnapping_value = 0

    def _set_source_and_target_roi(self, source_roi, target_roi):
        self._source_roi = source_roi
        self._target_roi = target_roi
        if logger.isEnabledFor(logging.DEBUG):
            self._report_current_source_and_target_roi()

    def _report_current_source_and_target_roi(self):
        source_roi_name = ''
        target_roi_name = ''
        for name, roi in self.regions_of_interest.iteritems():
            if roi == self._source_roi:
                source_roi_name = name
            elif roi == self._target_roi:
                target_roi_name = name

        logger.debug('Zooming between roi "%s" and "%s"' %
                     (source_roi_name, target_roi_name))
コード例 #29
0
class LoopSelectorComponent(Component, Messenger):
    u"""
    Component that uses a button matrix to display the timeline of a
    clip. It allows you to select the loop of the clip and a page
    within it of a given Paginator object.
    """
    next_page_button = ButtonControl()
    prev_page_button = ButtonControl()
    delete_button = ButtonControl()
    select_button = ButtonControl()
    loop_selector_matrix = control_matrix(PadControl, sensitivity_profile=u'loop', mode=PlayableControl.Mode.listenable)
    short_loop_selector_matrix = control_matrix(ButtonControl)
    is_following = listenable_property.managed(False)

    def __init__(self, clip_creator = None, measure_length = 4.0, follow_detail_clip = False, paginator = None, default_size = None, *a, **k):
        super(LoopSelectorComponent, self).__init__(*a, **k)
        assert default_size is not None
        self._clip_creator = clip_creator
        self._sequencer_clip = None
        self._paginator = Paginator()
        self._loop_start = 0
        self._loop_end = 0
        self._loop_length = 0
        self._default_size = default_size
        self._pressed_pages = []
        self._page_colors = []
        self._measure_length = measure_length
        self._last_playhead_page = -1

        def set_is_following_true():
            self.is_following = True

        self._follow_task = self._tasks.add(task.sequence(task.wait(defaults.MOMENTARY_DELAY), task.run(set_is_following_true)))
        self._follow_task.kill()
        self.set_step_duplicator(None)
        self._notification_reference = partial(nop, None)
        self.is_deleting = False
        if follow_detail_clip:
            self._on_detail_clip_changed.subject = self.song.view
            self._on_detail_clip_changed()
        self._on_session_record_changed.subject = self.song
        self._on_song_playback_status_changed.subject = self.song
        if paginator is not None:
            self.set_paginator(paginator)
        return

    def set_paginator(self, paginator):
        self._paginator = paginator or Paginator()
        self._on_page_index_changed.subject = paginator
        self._on_page_length_changed.subject = paginator
        self._update_page_colors()

    @listens(u'page_index')
    def _on_page_index_changed(self):
        self._update_page_colors()

    @listens(u'page_length')
    def _on_page_length_changed(self):
        self._update_page_colors()
        self._select_start_page()

    def set_step_duplicator(self, duplicator):
        self._step_duplicator = duplicator or NullStepDuplicator()
        self._step_duplicator.set_clip(self._sequencer_clip)

    @listens(u'detail_clip')
    def _on_detail_clip_changed(self):
        self.set_detail_clip(self.song.view.detail_clip)

    def set_detail_clip(self, clip):
        if liveobj_changed(clip, self._sequencer_clip):
            self.is_following = liveobj_valid(clip) and (self.is_following or clip_is_new_recording(clip))
            self._on_playing_position_changed.subject = clip
            self._on_playing_status_changed.subject = clip
            self._on_loop_start_changed.subject = clip
            self._on_loop_end_changed.subject = clip
            self._on_is_recording_changed.subject = clip
            self._sequencer_clip = clip
            self._step_duplicator.set_clip(clip)
            self._on_loop_changed()

    def _select_start_page(self):
        if liveobj_valid(self._sequencer_clip):
            page_start = self._paginator.page_index * self._paginator.page_length
            to_select = page_start
            if page_start <= self._sequencer_clip.loop_start:
                to_select = self._sequencer_clip.loop_start
            elif page_start >= self._sequencer_clip.loop_end:
                to_select = max(self._sequencer_clip.loop_end - self._paginator.page_length, self._sequencer_clip.loop_start)
            self._paginator.select_page_in_point(to_select)

    @listens(u'loop_start')
    def _on_loop_start_changed(self):
        self._on_loop_changed()

    @listens(u'loop_end')
    def _on_loop_end_changed(self):
        self._on_loop_changed()

    def _on_loop_changed(self):
        if liveobj_valid(self._sequencer_clip):
            self._loop_start = self._sequencer_clip.loop_start
            self._loop_end = self._sequencer_clip.loop_end
            self._loop_length = self._loop_end - self._loop_start
        else:
            self._loop_start = 0
            self._loop_end = 0
            self._loop_length = 0
        self._select_start_page()
        self._update_page_colors()

    def set_loop_selector_matrix(self, matrix):
        self.loop_selector_matrix.set_control_element(matrix)
        self._update_page_colors()

    def set_short_loop_selector_matrix(self, matrix):
        self.short_loop_selector_matrix.set_control_element(matrix)
        self._update_page_colors()

    def update(self):
        super(LoopSelectorComponent, self).update()
        self._update_page_and_playhead_leds()

    @listens(u'is_recording')
    def _on_is_recording_changed(self):
        self.is_following = self.is_following or clip_is_new_recording(self._sequencer_clip)

    @listens(u'playing_position')
    def _on_playing_position_changed(self):
        self._update_page_and_playhead_leds()
        self._update_page_selection()

    @listens(u'playing_status')
    def _on_playing_status_changed(self):
        self._update_page_and_playhead_leds()

    @listens(u'session_record')
    def _on_session_record_changed(self):
        self._update_page_and_playhead_leds()

    @listens(u'is_playing')
    def _on_song_playback_status_changed(self):
        self._update_page_and_playhead_leds()

    def _has_running_clip(self):
        return liveobj_valid(self._sequencer_clip) and (self._sequencer_clip.is_playing or self._sequencer_clip.is_recording)

    def _update_page_selection(self):
        if self.is_enabled() and self.is_following and self._has_running_clip():
            position = self._sequencer_clip.playing_position
            self._paginator.select_page_in_point(position)

    def _update_page_and_playhead_leds(self):

        @contextmanager
        def save_page_color(page_colors, page):
            old_page_value = page_colors[page]
            yield
            page_colors[page] = old_page_value

        @contextmanager
        def replace_and_restore_tail_colors(page_colors, page):
            if clip_is_new_recording(self._sequencer_clip):
                old_tail_values = page_colors[page + 1:]
                page_colors[page + 1:] = [u'LoopSelector.OutsideLoop'] * len(old_tail_values)
            yield
            if clip_is_new_recording(self._sequencer_clip):
                page_colors[page + 1:] = old_tail_values

        if self.is_enabled() and self._has_running_clip():
            position = self._sequencer_clip.playing_position
            visible_page = int(position / self._page_length_in_beats) - self.page_offset
            page_colors = self._page_colors
            if 0 <= visible_page < len(page_colors):
                with save_page_color(page_colors, visible_page):
                    if self.song.is_playing:
                        page_colors[visible_page] = u'LoopSelector.PlayheadRecord' if self.song.session_record else u'LoopSelector.Playhead'
                    with replace_and_restore_tail_colors(page_colors, visible_page):
                        self._update_page_leds()
            else:
                self._update_page_leds()
            self._last_playhead_page = visible_page
        else:
            self._update_page_leds()

    def _get_size(self):
        return max(self.loop_selector_matrix.control_count, self.short_loop_selector_matrix.control_count, self._default_size)

    def _get_loop_in_pages(self):
        page_length = self._page_length_in_beats
        loop_start = int(self._loop_start / page_length)
        loop_end = int(self._loop_end / page_length)
        loop_length = loop_end - loop_start + int(self._loop_end % page_length != 0)
        return (loop_start, loop_length)

    def _selected_pages_range(self):
        size = self._get_size()
        page_length = self._page_length_in_beats
        seq_page_length = max(self._paginator.page_length / page_length, 1)
        seq_page_start = int(self._paginator.page_index * self._paginator.page_length / page_length)
        seq_page_end = int(min(seq_page_start + seq_page_length, self.page_offset + size))
        return (seq_page_start, seq_page_end)

    def _update_page_colors(self):
        u"""
        Update the offline array mapping the timeline of the clip to buttons.
        """
        page_length = self._page_length_in_beats
        size = self._get_size()

        def calculate_page_colors():
            l_start, l_length = self._get_loop_in_pages()
            page_offset = self.page_offset
            pages_per_measure = int(self._one_measure_in_beats / page_length)

            def color_for_page(absolute_page):
                if l_start <= absolute_page < l_start + l_length:
                    if absolute_page % pages_per_measure == 0:
                        return u'LoopSelector.InsideLoopStartBar'
                    return u'LoopSelector.InsideLoop'
                else:
                    return u'LoopSelector.OutsideLoop'

            return map(color_for_page, xrange(page_offset, page_offset + size))

        def mark_selected_pages(page_colors):
            for page_index in xrange(*self._selected_pages_range()):
                button_index = page_index - self.page_offset
                if page_colors[button_index].startswith(u'LoopSelector.InsideLoop'):
                    page_colors[button_index] = u'LoopSelector.SelectedPage'

        page_colors = calculate_page_colors()
        mark_selected_pages(page_colors)
        self._page_colors = page_colors
        self._update_page_and_playhead_leds()

    def _update_page_leds(self):
        self._update_page_leds_in_matrix(self.loop_selector_matrix)
        self._update_page_leds_in_matrix(self.short_loop_selector_matrix)

    def _update_page_leds_in_matrix(self, matrix):
        u""" update hardware leds to match precomputed map """
        if self.is_enabled() and matrix:
            for button, color in izip(matrix, self._page_colors):
                button.color = color

    def _jump_to_page(self, next_page):
        start, length = self._get_loop_in_pages()
        if next_page >= start + length:
            next_page = start
        elif next_page < start:
            next_page = start + length - 1
        self._paginator.select_page_in_point(next_page * self._page_length_in_beats)

    @next_page_button.pressed
    def next_page_button(self, button):
        if self.is_following:
            self.is_following = False
        else:
            _, end = self._selected_pages_range()
            self._jump_to_page(end)
            self._follow_task.restart()

    @next_page_button.released
    def next_page_button(self, button):
        self._follow_task.kill()

    @prev_page_button.pressed
    def prev_page_button(self, button):
        if self.is_following:
            self.is_following = False
        else:
            begin, end = self._selected_pages_range()
            self._jump_to_page(begin - (end - begin))
            self._follow_task.restart()

    @prev_page_button.released
    def prev_page_button(self, button):
        self._follow_task.kill()

    @short_loop_selector_matrix.pressed
    def short_loop_selector_matrix(self, button):
        if self.is_enabled():
            page = self._get_corresponding_page(button, self.short_loop_selector_matrix)
            self._pressed_pages = [page]
            self._try_set_loop()
            self._pressed_pages = []

    @loop_selector_matrix.pressed
    def loop_selector_matrix(self, button):
        if self.is_enabled():
            page = self._get_corresponding_page(button, self.loop_selector_matrix)
            if page not in self._pressed_pages:
                self._on_press_loop_selector_matrix(page)

    @loop_selector_matrix.released
    def loop_selector_matrix(self, button):
        page = self._get_corresponding_page(button, self.loop_selector_matrix)
        if page in self._pressed_pages:
            self._pressed_pages.remove(page)

    def _get_corresponding_page(self, button, matrix):
        y, x = button.coordinate
        return x + y * matrix.width

    def _quantize_page_index(self, page_index, quant):
        page_length = self._page_length_in_beats
        return quant * float(int(page_length * page_index / quant))

    def _clear_page(self, page):
        page_start, page_end = self._selected_pages_time_range(page)
        notes = self._sequencer_clip.get_notes(page_start, 0, page_end, 128)
        if len(notes) > 0:
            self._sequencer_clip.remove_notes(page_start, 0, page_end - page_start, 128)
            self._notification_reference = self.show_notification(MessageBoxText.PAGE_CLEARED)
        else:
            self._notification_reference = self.show_notification(MessageBoxText.CANNOT_CLEAR_EMPTY_PAGE)

    def _selected_pages_time_range(self, page):
        page_start = 0
        page_end = 0
        page_length = self._page_length_in_beats
        if self._loop_length > page_length:
            range_start, range_end = self._selected_pages_range()
            page_start = range_start * page_length
            page_end = range_end * page_length
        else:
            page_start = page * page_length
            page_end = page_start + page_length
        return (page_start, page_end)

    def _add_page_to_duplicator(self, page):
        page_start, page_end = self._selected_pages_time_range(page)
        self._step_duplicator.add_step(page_start, page_end, nudge_offset=0, is_page=True)

    def _on_press_loop_selector_matrix(self, page):

        def create_clip(pages):
            measure = self._one_measure_in_beats
            length = self._quantize_page_index(pages, measure) + measure
            create_clip_in_selected_slot(self._clip_creator, self.song, length)

        def handle_page_press_on_clip(page):
            l_start, l_length = self._get_loop_in_pages()
            page_in_loop = l_start <= page < l_start + l_length
            buttons_pressed = len(self._pressed_pages)
            if buttons_pressed == 1 and page_in_loop:
                self._try_select_page(page)
            elif buttons_pressed > 1 or not page_in_loop:
                self._try_set_loop()
            if self._step_duplicator.is_duplicating:
                self._add_page_to_duplicator(page)
            if self.delete_button.is_pressed:
                self._clear_page(page)

        self._pressed_pages.append(page)
        absolute_page = page + self.page_offset
        if not self.select_button.is_pressed:
            if not liveobj_valid(self._sequencer_clip) and not self.song.view.highlighted_clip_slot.has_clip:
                create_clip(absolute_page)
            elif liveobj_valid(self._sequencer_clip):
                handle_page_press_on_clip(absolute_page)
        elif not self.is_following:
            self._try_select_page(absolute_page)

    def _try_select_page(self, page):
        step_time = page * self._page_length_in_beats
        if self._paginator.select_page_in_point(step_time):
            self.is_following = False
            return True
        return False

    def _try_set_loop(self):
        did_set_loop = False
        if liveobj_valid(self._sequencer_clip):
            if not clip_is_new_recording(self._sequencer_clip):
                lowest_page = min(self._pressed_pages) + self.page_offset
                if self._try_select_page(lowest_page):
                    self._set_loop_in_live()
                    did_set_loop = True
            if did_set_loop:
                self.is_following = True
        return did_set_loop

    def _set_loop_in_live(self):
        quant = self._page_length_in_beats
        start_page = min(self._pressed_pages) + self.page_offset
        end_page = max(self._pressed_pages) + self.page_offset
        loop_start = self._quantize_page_index(start_page, quant)
        loop_end = self._quantize_page_index(end_page, quant) + quant
        set_loop(self._sequencer_clip, loop_start, loop_end)
        self._sequencer_clip.view.show_loop()

    @property
    def _page_length_in_beats(self):
        return clamp(self._paginator.page_length, 0.25, self._one_measure_in_beats)

    @property
    def _one_measure_in_beats(self):
        return self._measure_length * self.song.signature_numerator / self.song.signature_denominator

    @property
    def page_offset(self):

        def zero_if_none(n):
            if n is None:
                return 0
            else:
                return n

        width = zero_if_none(self.loop_selector_matrix.width)
        height = zero_if_none(self.loop_selector_matrix.height)
        size = max(width * height, 1)
        page_index = self._paginator.page_index
        page_length = self._paginator.page_length
        selected_page_index = int(page_index * page_length / self._page_length_in_beats)
        return size * int(selected_page_index / size)
コード例 #30
0
class GeneralSettings(Subject):
    workflow = listenable_property.managed('scene')