class VUMeter(ControlSurfaceComponent):
    _active_instances = []

    def __init__(self, parent):
        ControlSurfaceComponent.__init__(self)
        VUMeter._active_instances.append(self)
        self.slider = None
        self._parent = parent
        self._vu_meter = None
        self.frames = [0.0] * RMS_FRAMES
        self.increments = CHANNEL_SCALE_INCREMENTS
        self.top = CHANNEL_SCALE_MAX
        self.bottom = CHANNEL_SCALE_MIN
        self.multiplier = self.calculate_multiplier(self.top, self.bottom, self.increments)
        self.current_level = 0

    def disconnect(self):
        VUMeter._active_instances.remove(self)
        if self._vu_meter != None:
            self._vu_meter.remove_output_meter_left_listener(self.observe)
            self._vu_meter = None

    def observe(self):
        new_frame = self.mean_peak()
        self.store_frame(new_frame)
        self.level = self.scale(new_frame)
        if self.level != self.current_level:
            self.current_level = self.level
            self.send_vu_value(self.level)
        else:
            None

    def store_frame(self, frame):
        self.frames.pop(0)
        self.frames.append(frame)

    def rms(self, frames):
        return math.sqrt(sum((frame * frame for frame in frames)) / len(frames))

    def mean_peak(self):
        return (self._vu_meter.output_meter_left + self._vu_meter.output_meter_right) / 2

    def scale(self, value):
        if value > self.top:
            value = self.top
        elif value < self.bottom:
            value = self.bottom
        value = value - self.bottom
        value = value * self.multiplier
        return int(round(value))

    def calculate_multiplier(self, top, bottom, increments):
        return increments / (top - bottom)

    def set_vu_meter(self, track):
        self.set_led_slider(track)
        self._vu_meter = self.song().tracks[track]
        if self._vu_meter != None:
            self._vu_meter.add_output_meter_left_listener(self.observe)
        else:
            None

    def set_led_slider(self, track):
        self.slider = SliderElement(MIDI_CC_TYPE, SLIDER_CHANNEL, track + 1)

    def send_vu_value(self, level):
        if level != None:
            if level < 1:
                None
            else:
                self.slider.send_value(level, True)
        else:
            None

    def update(self):
        pass
class AudioClipEditComponent(CompoundComponent):
    """
    classdocs
    """

    def __init__(self, *a, **k):
        super(AudioClipEditComponent, self).__init__(*a, **k)
        self._loop_start_slider = SliderElement(MIDI_CC_TYPE, 2, 60)
        self._action_loop_start.subject = self._loop_start_slider
        self._loop_end_slider = SliderElement(MIDI_CC_TYPE, 2, 61)
        self._action_loop_end.subject = self._loop_end_slider
        self._mark_start_slider = SliderElement(MIDI_CC_TYPE, 2, 62)
        self._action_mark_start.subject = self._mark_start_slider
        self._mark_end_slider = SliderElement(MIDI_CC_TYPE, 2, 63)
        self._action_mark_end.subject = self._mark_end_slider
        self._pitch_c_slider = SliderElement(MIDI_CC_TYPE, 2, 64)
        self._pitch_f_slider = SliderElement(MIDI_CC_TYPE, 2, 65)
        self._gain_slider = SliderElement(MIDI_CC_TYPE, 2, 66)
        self._action_pitch_c.subject = self._pitch_c_slider
        self._action_pitch_f.subject = self._pitch_f_slider
        self._action_gain.subject = self._gain_slider
        self._loop_inc_slider = SliderElement(MIDI_CC_TYPE, 2, 67)
        self._action_loop_inc.subject = self._loop_inc_slider
        self._loop_move_button = ButtonElement(False, MIDI_CC_TYPE, 2, 74)
        self._action_mv_loop.subject = self._loop_move_button
        self._loop_set_button = ButtonElement(False, MIDI_CC_TYPE, 2, 70)
        self._action_loop_toggle.subject = self._loop_set_button
        self._warp_set_button = ButtonElement(False, MIDI_CC_TYPE, 2, 71)
        self._action_warp_toggle.subject = self._warp_set_button
        self._zoom_scroll_button = ButtonElement(False, MIDI_CC_TYPE, 2, 73)
        self._action_scroll_mode.subject = self._zoom_scroll_button
        self.selected_clip_slot = None
        self.inc_index = 4
        self.loop_inc = INC_STEPS[self.inc_index]
        self.start_inc = INC_STEPS[self.inc_index]
        self.mv_loop = False
        self._on_pitch_c_changed.subject = None
        self._on_pitch_f_changed.subject = None
        self._on_gain_changed.subject = None
        self._scroll_mode = False
        self.update_selected_clip()

    @subject_slot("value")
    def _action_scroll_mode(self, value):
        if value > 0:
            self._scroll_mode = True
        else:
            self._scroll_mode = False

    @subject_slot("value")
    def _action_warp_toggle(self, value):
        if value > 0:
            if self.selected_clip_slot and self.selected_clip_slot.has_clip:
                clip = self.selected_clip_slot.clip
                if clip.is_audio_clip:
                    clip.warping = not clip.warping
                    self._warp_set_button.send_value(clip.warping and 127 or 0, True)

    @subject_slot("value")
    def _action_loop_toggle(self, value):
        if value > 0:
            if self.selected_clip_slot and self.selected_clip_slot.has_clip:
                clip = self.selected_clip_slot.clip
                clip.looping = not clip.looping
                self._loop_set_button.send_value(clip.looping and 127 or 0, True)

    @subject_slot("value")
    def _action_loop_inc(self, value):
        if not (value == 1 and 1):
            inc = -1
            val = self.inc_index + inc
            self.inc_index = val >= 0 and val < len(INC_STEPS) and val
            self.loop_inc = INC_STEPS[val]
            self.start_inc = INC_STEPS[val]
            self.canonical_parent.timed_message(2, "Loop Adjust: " + INC_DISP[val])
            self.canonical_parent.show_message("Loop Adjust: " + INC_DISP[val])

    @subject_slot("value")
    def _action_mv_loop(self, value):
        if value > 0:
            if self.mv_loop:
                self._loop_move_button.send_value(0, True)
                self.mv_loop = False
            else:
                self._loop_move_button.send_value(127, True)
                self.mv_loop = True

    @subject_slot("value")
    def _action_mark_start(self, value):
        if self._scroll_mode:
            scroll = value == 1 and 3 or 2
            self.application().view.scroll_view(scroll, "Detail/Clip", False)
        elif not (value == 1 and 1):
            inc = -1
            clip = self.selected_clip_slot and self.selected_clip_slot.has_clip and self.selected_clip_slot.clip
            ls = clip.start_marker
            le = clip.end_marker
            ls = max(0, min(le - self.start_inc, ls + inc * self.start_inc))
            clip.start_marker = ls
            bars_to_measure(ls, clip.signature_denominator, clip.signature_numerator)
            self.canonical_parent.timed_message(
                2, "Clip Start: " + bars_to_measure(ls, clip.signature_denominator, clip.signature_numerator)
            )

    @subject_slot("value")
    def _action_mark_end(self, value):
        if self._scroll_mode:
            scroll = value == 1 and 3 or 2
            self.application().view.zoom_view(scroll, "Detail/Clip", False)
        elif not (value == 1 and 1):
            inc = -1
            clip = self.selected_clip_slot and self.selected_clip_slot.has_clip and self.selected_clip_slot.clip
            ls = clip.start_marker
            le = clip.end_marker
            le = max(ls + self.start_inc, le + inc * self.start_inc)
            clip.end_marker = le
            self.canonical_parent.timed_message(
                2, "Clip End: " + bars_to_measure(le, clip.signature_denominator, clip.signature_numerator)
            )

    @subject_slot("value")
    def _action_loop_start(self, value):
        if not (value == 1 and 1):
            inc = -1
            if self.selected_clip_slot and self.selected_clip_slot.has_clip:
                clip = self.selected_clip_slot.clip
                ls = clip.loop_start
                le = clip.loop_end
                if self.mv_loop:
                    diff = le - ls
                    ls = max(0, ls + inc * self.loop_inc)
                    clip.loop_end = inc > 0 and ls + diff
                    clip.end_marker = ls + diff
                    clip.loop_start = ls
                    clip.start_marker = ls
                else:
                    clip.loop_start = ls
                    clip.start_marker = ls
                    clip.loop_end = ls + diff
                    clip.end_marker = ls + diff
                self.canonical_parent.timed_message(2, loop_str(clip))
            else:
                ls = max(0, min(le - self.loop_inc, ls + inc * self.loop_inc))
                clip.loop_start = ls
                self.canonical_parent.timed_message(2, loop_str(clip))

    @subject_slot("value")
    def _action_loop_end(self, value):
        if not (value == 1 and 1):
            inc = -1
            if self.selected_clip_slot and self.selected_clip_slot.has_clip:
                clip = self.selected_clip_slot.clip
                ls = clip.loop_start
                le = clip.loop_end
                le = max(ls + self.loop_inc, le + inc * self.loop_inc)
                clip.loop_end = le
                clip.end_marker = self.mv_loop and le
            self.canonical_parent.timed_message(2, loop_str(clip))

    def update(self):
        pass

    @subject_slot("value")
    def _action_pitch_c(self, value):
        cs = self.selected_clip_slot
        if cs and cs.has_clip and cs.clip.is_audio_clip:
            cs.clip.pitch_coarse = midi_to_pitchc(value)

    @subject_slot("value")
    def _action_pitch_f(self, value):
        cs = self.selected_clip_slot
        if cs and cs.has_clip and cs.clip.is_audio_clip:
            cs.clip.pitch_fine = midi_to_pitchf(value)

    @subject_slot("value")
    def _action_gain(self, value):
        cs = self.selected_clip_slot
        if cs and cs.has_clip and cs.clip.is_audio_clip:
            cs.clip.gain = midi_to_gain(value)

    def _update_clip_name(self):
        cs = self.song().view.highlighted_clip_slot
        if not cs:
            track = self.song().view.selected_track
            self.canonical_parent.send_to_display("Rt Trck: " + track.name, 3)
        elif cs.has_clip:
            self.canonical_parent.send_to_display(cs.clip.is_audio_clip and "A" or "M" + ":" + cs.clip.name, 3)
        else:
            track = cs.canonical_parent
            index = list(track.clip_slots).index(cs)
            scene = self.song().scenes[index]
            self.canonical_parent.send_to_display("E<" + str(scene.name) + "> T:" + track.name, 3)

    @subject_slot("has_clip")
    def _on_has_clip_changed(self):
        self._update_clip_name()

    @subject_slot("name")
    def _on_name_changed(self):
        self._update_clip_name()

    def update_selected_clip(self):
        cs = self.song().view.highlighted_clip_slot
        if cs != self.selected_clip_slot:
            self.selected_clip_slot = cs
            self._update_clip_name()
            if cs and cs.has_clip and cs.clip.is_audio_clip:
                self._on_pitch_c_changed.subject = cs.clip
                self._on_pitch_f_changed.subject = cs.clip
                self._on_gain_changed.subject = cs.clip
                self._on_warp_changed.subject = cs.clip
                self._gain_slider.send_value(gain_to_midi(cs.clip.gain))
                self._pitch_c_slider.send_value(pitchc_to_midi(cs.clip.pitch_coarse))
                self._pitch_f_slider.send_value(pitchf_to_midi(cs.clip.pitch_fine))
                self._warp_set_button.send_value(cs.clip.warping and 127 or 0, True)
            else:
                self._on_pitch_c_changed.subject = None
                self._on_pitch_f_changed.subject = None
                self._on_gain_changed.subject = None
                self._on_warp_changed.subject = None
                self._on_loop_changed.subject = None
            if cs and cs.has_clip:
                self._on_loop_changed.subject = cs.clip
                self._on_name_changed.subject = cs.clip
                self._loop_set_button.send_value(cs.clip.looping and 127 or 0, True)
            else:
                self._on_name_changed.subject = None
                self._on_loop_changed.subject = None
            self._on_has_clip_changed.subject = cs

    def on_selected_track_changed(self):
        self.update_selected_clip()

    def on_selected_scene_changed(self):
        self.update_selected_clip()

    @subject_slot("looping")
    def _on_loop_changed(self):
        cs = self.song().view.highlighted_clip_slot
        if cs and cs.has_clip:
            self._loop_set_button.send_value(cs.clip.looping and 127 or 0, True)

    @subject_slot("warping")
    def _on_warp_changed(self):
        cs = self.song().view.highlighted_clip_slot
        if cs and cs.has_clip and cs.clip.is_audio_clip:
            self._warp_set_button.send_value(cs.clip.warping and 127 or 0, True)

    @subject_slot("pitch_coarse")
    def _on_pitch_c_changed(self):
        cs = self.song().view.highlighted_clip_slot
        if cs and cs.has_clip and cs.clip.is_audio_clip:
            self._pitch_c_slider.send_value(pitchc_to_midi(cs.clip.pitch_coarse))

    @subject_slot("pitch_fine")
    def _on_pitch_f_changed(self):
        cs = self.song().view.highlighted_clip_slot
        if cs and cs.has_clip and cs.clip.is_audio_clip:
            self._pitch_f_slider.send_value(pitchf_to_midi(cs.clip.pitch_fine))

    @subject_slot("gain")
    def _on_gain_changed(self):
        cs = self.song().view.highlighted_clip_slot
        if cs and cs.has_clip and cs.clip.is_audio_clip:
            self._gain_slider.send_value(gain_to_midi(cs.clip.gain))
示例#3
0
class VUMeter(ControlSurfaceComponent):
    _active_instances = []

    def __init__(self, parent):
        ControlSurfaceComponent.__init__(self)
        VUMeter._active_instances.append(self)
        self.slider = None
        self._parent = parent
        self._vu_meter = None
        self.frames = [0.0] * RMS_FRAMES
        self.increments = CHANNEL_SCALE_INCREMENTS
        self.top = CHANNEL_SCALE_MAX
        self.bottom = CHANNEL_SCALE_MIN
        self.multiplier = self.calculate_multiplier(self.top, self.bottom,
                                                    self.increments)
        self.current_level = 0

    def disconnect(self):
        VUMeter._active_instances.remove(self)
        if self._vu_meter != None:
            self._vu_meter.remove_output_meter_left_listener(self.observe)
            self._vu_meter = None

    def observe(self):
        new_frame = self.mean_peak()
        self.store_frame(new_frame)
        self.level = self.scale(new_frame)
        if self.level != self.current_level:
            self.current_level = self.level
            self.send_vu_value(self.level)
        else:
            None

    def store_frame(self, frame):
        self.frames.pop(0)
        self.frames.append(frame)

    def rms(self, frames):
        return math.sqrt(
            sum((frame * frame for frame in frames)) / len(frames))

    def mean_peak(self):
        return (self._vu_meter.output_meter_left +
                self._vu_meter.output_meter_right) / 2

    def scale(self, value):
        if value > self.top:
            value = self.top
        elif value < self.bottom:
            value = self.bottom
        value = value - self.bottom
        value = value * self.multiplier
        return int(round(value))

    def calculate_multiplier(self, top, bottom, increments):
        return increments / (top - bottom)

    def set_vu_meter(self, track):
        self.set_led_slider(track)
        self._vu_meter = self.song().tracks[track]
        if self._vu_meter != None:
            self._vu_meter.add_output_meter_left_listener(self.observe)
        else:
            None

    def set_led_slider(self, track):
        self.slider = SliderElement(MIDI_CC_TYPE, SLIDER_CHANNEL, track + 1)

    def send_vu_value(self, level):
        if level != None:
            if level < 1:
                None
            else:
                self.slider.send_value(level, True)
        else:
            None

    def update(self):
        pass
示例#4
0
class AudioClipEditComponent(CompoundComponent):
    """
    classdocs
    """

    def __init__(self, *a, **k):
        super(AudioClipEditComponent, self).__init__(*a, **k)
        self._loop_start_slider = SliderElement(MIDI_CC_TYPE, 2, 60)
        self._action_loop_start.subject = self._loop_start_slider
        self._loop_end_slider = SliderElement(MIDI_CC_TYPE, 2, 61)
        self._action_loop_end.subject = self._loop_end_slider
        self._mark_start_slider = SliderElement(MIDI_CC_TYPE, 2, 62)
        self._action_mark_start.subject = self._mark_start_slider
        self._mark_end_slider = SliderElement(MIDI_CC_TYPE, 2, 63)
        self._action_mark_end.subject = self._mark_end_slider
        self._pitch_c_slider = SliderElement(MIDI_CC_TYPE, 2, 64)
        self._pitch_f_slider = SliderElement(MIDI_CC_TYPE, 2, 65)
        self._gain_slider = SliderElement(MIDI_CC_TYPE, 2, 66)
        self._action_pitch_c.subject = self._pitch_c_slider
        self._action_pitch_f.subject = self._pitch_f_slider
        self._action_gain.subject = self._gain_slider
        self._loop_inc_slider = SliderElement(MIDI_CC_TYPE, 2, 67)
        self._action_loop_inc.subject = self._loop_inc_slider
        self._loop_move_button = ButtonElement(False, MIDI_CC_TYPE, 2, 74)
        self._action_mv_loop.subject = self._loop_move_button
        self._loop_set_button = ButtonElement(False, MIDI_CC_TYPE, 2, 70)
        self._action_loop_toggle.subject = self._loop_set_button
        self._warp_set_button = ButtonElement(False, MIDI_CC_TYPE, 2, 71)
        self._action_warp_toggle.subject = self._warp_set_button
        self._zoom_scroll_button = ButtonElement(False, MIDI_CC_TYPE, 2, 73)
        self._action_scroll_mode.subject = self._zoom_scroll_button
        self.selected_clip_slot = None
        self.inc_index = 4
        self.loop_inc = INC_STEPS[self.inc_index]
        self.start_inc = INC_STEPS[self.inc_index]
        self.mv_loop = False
        self._on_pitch_c_changed.subject = None
        self._on_pitch_f_changed.subject = None
        self._on_gain_changed.subject = None
        self._scroll_mode = False
        self.update_selected_clip()
        return

    @subject_slot('value')
    def _action_scroll_mode(self, value):
        if value > 0:
            self._scroll_mode = True
        else:
            self._scroll_mode = False

    @subject_slot('value')
    def _action_warp_toggle(self, value):
        if value > 0:
            if self.selected_clip_slot and self.selected_clip_slot.has_clip:
                clip = self.selected_clip_slot.clip
                if clip.is_audio_clip:
                    clip.warping = not clip.warping
                    self._warp_set_button.send_value(clip.warping and 127 or 0, True)

    @subject_slot('value')
    def _action_loop_toggle(self, value):
        if value > 0:
            if self.selected_clip_slot and self.selected_clip_slot.has_clip:
                clip = self.selected_clip_slot.clip
                clip.looping = not clip.looping
                self._loop_set_button.send_value(clip.looping and 127 or 0, True)

    @subject_slot('value')
    def _action_loop_inc(self, value):
        inc = value == 1 and 1 or -1
        val = self.inc_index + inc
        if val >= 0 and val < len(INC_STEPS):
            self.inc_index = val
            self.loop_inc = INC_STEPS[val]
            self.start_inc = INC_STEPS[val]
            self.canonical_parent.timed_message(2, 'Loop Adjust: ' + INC_DISP[val])
            self.canonical_parent.show_message('Loop Adjust: ' + INC_DISP[val])

    @subject_slot('value')
    def _action_mv_loop(self, value):
        if value > 0:
            if self.mv_loop:
                self._loop_move_button.send_value(0, True)
                self.mv_loop = False
            else:
                self._loop_move_button.send_value(127, True)
                self.mv_loop = True

    @subject_slot('value')
    def _action_mark_start(self, value):
        if self._scroll_mode:
            scroll = value == 1 and 3 or 2
            self.application().view.scroll_view(scroll, 'Detail/Clip', False)
        else:
            inc = value == 1 and 1 or -1
            if self.selected_clip_slot and self.selected_clip_slot.has_clip:
                clip = self.selected_clip_slot.clip
                ls = clip.start_marker
                le = clip.end_marker
                ls = max(0, min(le - self.start_inc, ls + inc * self.start_inc))
                clip.start_marker = ls
                bars_to_measure(ls, clip.signature_denominator, clip.signature_numerator)
                self.canonical_parent.timed_message(2, 'Clip Start: ' + bars_to_measure(ls, clip.signature_denominator, clip.signature_numerator))

    @subject_slot('value')
    def _action_mark_end(self, value):
        if self._scroll_mode:
            scroll = value == 1 and 3 or 2
            self.application().view.zoom_view(scroll, 'Detail/Clip', False)
        else:
            inc = value == 1 and 1 or -1
            if self.selected_clip_slot and self.selected_clip_slot.has_clip:
                clip = self.selected_clip_slot.clip
                ls = clip.start_marker
                le = clip.end_marker
                le = max(ls + self.start_inc, le + inc * self.start_inc)
                clip.end_marker = le
                self.canonical_parent.timed_message(2, 'Clip End: ' + bars_to_measure(le, clip.signature_denominator, clip.signature_numerator))

    @subject_slot('value')
    def _action_loop_start(self, value):
        inc = value == 1 and 1 or -1
        if self.selected_clip_slot and self.selected_clip_slot.has_clip:
            clip = self.selected_clip_slot.clip
            ls = clip.loop_start
            le = clip.loop_end
            if self.mv_loop:
                diff = le - ls
                ls = max(0, ls + inc * self.loop_inc)
                if inc > 0:
                    clip.loop_end = ls + diff
                    clip.end_marker = ls + diff
                    clip.loop_start = ls
                    clip.start_marker = ls
                else:
                    clip.loop_start = ls
                    clip.start_marker = ls
                    clip.loop_end = ls + diff
                    clip.end_marker = ls + diff
                self.canonical_parent.timed_message(2, loop_str(clip))
            else:
                ls = max(0, min(le - self.loop_inc, ls + inc * self.loop_inc))
                clip.loop_start = ls
                self.canonical_parent.timed_message(2, loop_str(clip))

    @subject_slot('value')
    def _action_loop_end(self, value):
        inc = value == 1 and 1 or -1
        if self.selected_clip_slot and self.selected_clip_slot.has_clip:
            clip = self.selected_clip_slot.clip
            ls = clip.loop_start
            le = clip.loop_end
            le = max(ls + self.loop_inc, le + inc * self.loop_inc)
            clip.loop_end = le
            if self.mv_loop:
                clip.end_marker = le
            self.canonical_parent.timed_message(2, loop_str(clip))

    def update(self):
        pass

    @subject_slot('value')
    def _action_pitch_c(self, value):
        cs = self.selected_clip_slot
        if cs and cs.has_clip and cs.clip.is_audio_clip:
            cs.clip.pitch_coarse = midi_to_pitchc(value)

    @subject_slot('value')
    def _action_pitch_f(self, value):
        cs = self.selected_clip_slot
        if cs and cs.has_clip and cs.clip.is_audio_clip:
            cs.clip.pitch_fine = midi_to_pitchf(value)

    @subject_slot('value')
    def _action_gain(self, value):
        cs = self.selected_clip_slot
        if cs and cs.has_clip and cs.clip.is_audio_clip:
            cs.clip.gain = midi_to_gain(value)

    def _update_clip_name(self):
        cs = self.song().view.highlighted_clip_slot
        if not cs:
            track = self.song().view.selected_track
            self.canonical_parent.send_to_display('Rt Trck: ' + track.name, 3)
        else:
            if cs.has_clip:
                self.canonical_parent.send_to_display((cs.clip.is_audio_clip and 'A' or 'M') + ':' + cs.clip.name, 3)
            else:
                track = cs.canonical_parent
                index = list(track.clip_slots).index(cs)
                scene = self.song().scenes[index]
                self.canonical_parent.send_to_display('E<' + str(scene.name) + '> T:' + track.name, 3)

    @subject_slot('has_clip')
    def _on_has_clip_changed(self):
        self._update_clip_name()

    @subject_slot('name')
    def _on_name_changed(self):
        self._update_clip_name()

    def update_selected_clip(self):
        cs = self.song().view.highlighted_clip_slot
        if cs != self.selected_clip_slot:
            self.selected_clip_slot = cs
            self._update_clip_name()
            if cs and cs.has_clip and cs.clip.is_audio_clip:
                self._on_pitch_c_changed.subject = cs.clip
                self._on_pitch_f_changed.subject = cs.clip
                self._on_gain_changed.subject = cs.clip
                self._on_warp_changed.subject = cs.clip
                self._gain_slider.send_value(gain_to_midi(cs.clip.gain))
                self._pitch_c_slider.send_value(pitchc_to_midi(cs.clip.pitch_coarse))
                self._pitch_f_slider.send_value(pitchf_to_midi(cs.clip.pitch_fine))
                self._warp_set_button.send_value(cs.clip.warping and 127 or 0, True)
            else:
                self._on_pitch_c_changed.subject = None
                self._on_pitch_f_changed.subject = None
                self._on_gain_changed.subject = None
                self._on_warp_changed.subject = None
                self._on_loop_changed.subject = None
            if cs and cs.has_clip:
                self._on_loop_changed.subject = cs.clip
                self._on_name_changed.subject = cs.clip
                self._loop_set_button.send_value(cs.clip.looping and 127 or 0, True)
            else:
                self._on_name_changed.subject = None
                self._on_loop_changed.subject = None
            self._on_has_clip_changed.subject = cs
        return

    def on_selected_track_changed(self):
        self.update_selected_clip()

    def on_selected_scene_changed(self):
        self.update_selected_clip()

    @subject_slot('looping')
    def _on_loop_changed(self):
        cs = self.song().view.highlighted_clip_slot
        if cs and cs.has_clip:
            self._loop_set_button.send_value(cs.clip.looping and 127 or 0, True)

    @subject_slot('warping')
    def _on_warp_changed(self):
        cs = self.song().view.highlighted_clip_slot
        if cs and cs.has_clip and cs.clip.is_audio_clip:
            self._warp_set_button.send_value(cs.clip.warping and 127 or 0, True)

    @subject_slot('pitch_coarse')
    def _on_pitch_c_changed(self):
        cs = self.song().view.highlighted_clip_slot
        if cs and cs.has_clip and cs.clip.is_audio_clip:
            self._pitch_c_slider.send_value(pitchc_to_midi(cs.clip.pitch_coarse))

    @subject_slot('pitch_fine')
    def _on_pitch_f_changed(self):
        cs = self.song().view.highlighted_clip_slot
        if cs and cs.has_clip and cs.clip.is_audio_clip:
            self._pitch_f_slider.send_value(pitchf_to_midi(cs.clip.pitch_fine))

    @subject_slot('gain')
    def _on_gain_changed(self):
        cs = self.song().view.highlighted_clip_slot
        if cs and cs.has_clip and cs.clip.is_audio_clip:
            self._gain_slider.send_value(gain_to_midi(cs.clip.gain))
class MaschineJam(ControlSurface):
    """Control Script for Maschine JAM Controller"""
    __module__ = __name__
    _midi_count = 0
    _arm_exclusive = True
    _solo_exclusive = True
    _blink_state = 0
    __midi_count = 0
    __play_button = None
    __block_nav = False
    __matrix_state = None

    def __init__(self, c_instance):
        super(MaschineJam, self).__init__(c_instance)
        with self.component_guard():
            self._suppress_send_midi = True
            register_sender(self)
            self._challenge = Live.Application.get_random_int(0, 400000000) & 2139062143
            self._set_suppress_rebuild_requests(True)
            self._c_ref = c_instance
            self.request_rebuild_midi_map()
            self.__matrix_state = MatrixState(self)
            self._main_mode_container = JamModes(c_instance.note_repeat)
            self._set_suppress_rebuild_requests(False)
            self._active = True
            self._display_device_param = False
            self._setup_transport()
            self._setup_session()
            self._encoder_modes = EncoderComponent(self._session)
            self._encoder_modes.connect()
            self._encoder_modes.set_state_listener(self)
            self._modifier = ModifierComponent(self._session)
            self._connect_session()
            self._main_mode_container.bind_session(self.__matrix_state)
            self._main_mode_container.bind_modify_component(self._modifier)
            self._setup_mainjogwheel()
            self._init_m4l()
            self._init_settings()
            self.set_pad_translations(PAD_TRANSLATIONS)
            self.set_feedback_channels(FEEDBACK_CHANNELS)
            self._suppress_send_midi = False
            self._main_mode_container._step_mode.set_mode_elements(self._modifier, self._encoder_modes)
            self._main_mode_container._drum_step_mode.set_mode_elements(self._modifier)
            self._final_init()
            self.apply_preferences()

    def _init_m4l(self):
        self._bmatrix.set_user_unbind_listener(self._main_mode_container)

    def _init_settings(self):
        from pickle import loads, dumps
        from encodings import ascii
        nop(ascii)
        preferences = self._c_instance.preferences(self.preferences_name())
        self._pref_dict = {}
        try:
            self._pref_dict = loads(str(preferences))
        except Exception:
            pass

        pref_dict = self._pref_dict
        preferences.set_serializer(lambda : dumps(pref_dict))

    def store_preferences(self):
        self._pref_dict['matrix_color_mode'] = self._session.get_color_mode()

    def apply_preferences(self):
        pref_dict = self._pref_dict
        if 'matrix_color_mode' in pref_dict:
            self._session.set_color_mode(pref_dict['matrix_color_mode'])

    def preferences_name(self):
        return 'MaschineJam'

    def _final_init(self):
        debug_out('########## LIVE 10 Maschine JAM V 1.3 #############')
        self._auto_button.set_display_value(self.song().session_automation_record and 127 or 0)
        self._main_mode_container.init_elements()

    def _init_map(self):
        msgsysex = [
         240, 0, 33, 9, 21, 0, 77, 80, 0, 1, 2]
        for _ in range(80):
            msgsysex.append(COLOR_BLACK)

        msgsysex.append(247)
        self._send_midi(tuple(msgsysex))

    def _set_touch_strip_led(self):
        msgsysex = [
         240, 0, 33, 9, 21, 0, 77, 80, 0, 1, 4]
        for _ in range(8):
            msgsysex.append(0)

        msgsysex.append(247)
        self._send_midi(tuple(msgsysex))

    def handle_sysex(self, midi_bytes):
        if len(midi_bytes) > 11 and midi_bytes[0:10] == (240, 0, 33, 9, 21, 0, 77,
                                                         80, 0, 1):
            msg, value = midi_bytes[10:12]
            if msg == 70:
                self.refresh_state()
                self._modifier.set_shiftstatus(1)
            elif msg == 77:
                self.shiftButton.notify_value(value == 1 and 127 or 0)
                if not self.shiftButton.is_grabbed:
                    self._modifier.set_shiftstatus(value)

    def _setup_transport(self):
        is_momentary = True
        self.shiftButton = SysExButton(120, name='Shift_Button')
        self.__play_button = StateButton(is_momentary, MIDI_CC_TYPE, 0, 108, name='Play_Button')
        self._hand_play_pressed.subject = self.__play_button
        self._listen_playing.subject = self.song()
        self._channel_led_left = SliderElement(MIDI_CC_TYPE, 0, 38)
        self._channel_led_right = SliderElement(MIDI_CC_TYPE, 0, 39)
        self._channel_led_left.last_raw = 0.0
        self._channel_led_left.last_send = 0
        self._channel_led_right.last_raw = 0.0
        self._channel_led_right.last_send = 0
        self._listen_master_left.subject = self.song().master_track
        self._listen_master_right.subject = self.song().master_track
        self._auto_button = StateButton(is_momentary, MIDI_CC_TYPE, 0, 98, name='Auto_Button')
        self._listen_automation_record.subject = self.song()
        self._handle_automation_record.subject = self._auto_button
        self._do_direction_up.subject = StateButton(is_momentary, MIDI_CC_TYPE, 0, 40, name='Up_Arrow')
        self._do_direction_down.subject = StateButton(is_momentary, MIDI_CC_TYPE, 0, 41, name='Down_Arrow')
        self._do_direction_left.subject = StateButton(is_momentary, MIDI_CC_TYPE, 0, 42, name='Left_Arrow')
        self._do_direction_right.subject = StateButton(is_momentary, MIDI_CC_TYPE, 0, 43, name='Right_Arrow')

    @subject_slot('output_meter_left')
    def _listen_master_left(self):
        cvl = self.song().master_track.output_meter_left
        if cvl != self._channel_led_left.last_raw:
            val = cvl > 0.92 and 127 or int(127 * (cvl * cvl))
            if val != self._channel_led_left.last_send:
                self._channel_led_left.last_raw = cvl
                self._channel_led_left.last_send = val
                self._channel_led_left.send_value(val, True)

    @subject_slot('output_meter_right')
    def _listen_master_right(self):
        cvl = self.song().master_track.output_meter_right
        if cvl != self._channel_led_right.last_raw:
            val = cvl > 0.92 and 127 or int(127 * (cvl * cvl))
            if val != self._channel_led_right.last_send:
                self._channel_led_right.last_raw = cvl
                self._channel_led_right.last_send = val
                self._channel_led_right.send_value(val, True)

    def is_monochrome(self):
        return False

    def _send_midi(self, midi_bytes, **keys):
        self._c_ref.send_midi(midi_bytes)
        if self._midi_count > 2:
            time.sleep(0.001)
            self._midi_count = 0
        self._midi_count += 1
        return True

    def _setup_mainjogwheel(self):
        self._prev_mode = None
        return

    def is_shift_down(self):
        return self._modifier.is_shift_down()

    def modifier_mask(self):
        return self._modifier.modifier_mask()

    def _setup_session(self):
        self._session = JamSessionComponent()
        self._matrix = []
        self._bmatrix = JamButtonMatrix(8, name='Button_Matrix')
        for sceneIndex in range(8):
            button_row = []
            for trackindex in range(8):
                button = PadColorButton(True, 0, sceneIndex, trackindex, self._main_mode_container)
                button_row.append(button)

            self._matrix.append(tuple(button_row))
            self._bmatrix.add_row(tuple(button_row))

        self._session.set_matrix(self._bmatrix, self._matrix)
        self.__matrix_state.register_matrix(self._bmatrix)
        self._bmatrix.prepare_update()
        for button, (trackIndex, sceneIndex) in self._bmatrix.iterbuttons():
            if button:
                scene = self._session.scene(sceneIndex)
                clip_slot = scene.clip_slot(trackIndex)
                clip_slot.set_launch_button(button)
                clip_slot.set_triggered_to_play_value(1)
                clip_slot.set_triggered_to_record_value(1)
                clip_slot.set_started_value(1)
                clip_slot.set_recording_value(1)
                clip_slot.set_stopped_value(1)

        self._session._link()
        self._bmatrix.commit_update()
        self.set_highlighting_session_component(self._session)

    def _connect_session(self):
        for sindex in range(self._session.height()):
            scene = self._session.scene(sindex)
            for cindex in range(self._session.width()):
                clip = scene.clip_slot(cindex)
                clip.set_modifier(self._modifier)
                clip.set_index((cindex, sindex))

    def update_display(self):
        with self.component_guard():
            self._main_mode_container.notify(self._blink_state)
            self._encoder_modes.notify(self._blink_state)
            self._blink_state = (self._blink_state + 1) % 4

    def refresh_state(self):
        self._bmatrix.prepare_update()
        ControlSurface.refresh_state(self)
        self.update_hardware()
        self._bmatrix.commit_update()

    def update_hardware(self):
        self._session.update()
        self.__play_button.set_display_value(self.song().is_playing and 127 or 0, True)
        self._main_mode_container.refresh_state()
        self._encoder_modes.refresh_state()

    def invoke_nav_left(self):
        self._encoder_modes.invoke_nav_left()

    def invoke_nav_right(self):
        self._encoder_modes.invoke_nav_right()

    def invoke_rec(self):
        slot = self.song().view.highlighted_clip_slot
        if slot == None:
            return
        if slot.controls_other_clips:
            slot.fire()
        else:
            if slot.has_clip:
                track = slot.canonical_parent
                if track.can_be_armed:
                    arm_exclusive(self.song(), track)
                self.song().overdub = True
                slot.fire()
            else:
                track = slot.canonical_parent
                if track.can_be_armed:
                    arm_exclusive(self.song(), track)
                    slot.fire()
        return

    @subject_slot('session_automation_record')
    def _listen_automation_record(self):
        self._auto_button.set_display_value(self.song().session_automation_record and 127 or 0, True)

    @subject_slot('value', identify_sender=True)
    def _handle_automation_record(self, value, sender):
        if value == 0 or sender.grabbed:
            return
        self.song().session_automation_record = not self.song().session_automation_record

    @subject_slot('is_playing')
    def _listen_playing(self):
        if self.song().is_playing:
            self.__play_button.set_display_value(127, True)
        else:
            self.__play_button.set_display_value(0, True)

    @subject_slot('value', identify_sender=True)
    def _hand_play_pressed(self, value, sender):
        if value == 0 or sender.grabbed:
            return
        if self.song().is_playing:
            if self._modifier.is_shift_down():
                self.song().start_playing()
            else:
                self.song().stop_playing()
        else:
            self.song().start_playing()

    @subject_slot('value', identify_sender=True)
    def do_undo(self, value, sender):
        if value == 0 or sender.grabbed:
            return
        if self._modifier.is_shift_down():
            if self.song().can_redo == 1:
                self.song().redo()
                self.show_message(str('REDO'))
        else:
            if self.song().can_undo == 1:
                self.song().undo()
                self.show_message(str('UNDO'))

    def notify_state(self, state, value):
        if state == 'controldown':
            self.__block_nav = value
        else:
            if state == 'step':
                self._main_mode_container.notify_state(state, value)

    @subject_slot('value', identify_sender=True)
    def _do_direction_up(self, value, sender):
        if value == 0 or sender.grabbed:
            return
        if not self.__block_nav:
            self._main_mode_container.navigate(-1, 1, self._modifier.is_shift_down(), NAV_SRC_BUTTON)
        else:
            self._encoder_modes.navigate(-1, 1, self._modifier.is_shift_down(), NAV_SRC_BUTTON)

    @subject_slot('value', identify_sender=True)
    def _do_direction_down(self, value, sender):
        if value == 0 or sender.grabbed:
            return
        if not self.__block_nav:
            self._main_mode_container.navigate(1, 1, self._modifier.is_shift_down(), NAV_SRC_BUTTON)
        else:
            self._encoder_modes.navigate(1, -1, self._modifier.is_shift_down(), NAV_SRC_BUTTON)

    @subject_slot('value', identify_sender=True)
    def _do_direction_left(self, value, sender):
        if value == 0 or sender.grabbed:
            return
        if not self.__block_nav:
            encoder_changed = self._main_mode_container.navigate(-1, 0, self._modifier.is_shift_down(), NAV_SRC_BUTTON)
            if encoder_changed:
                self._encoder_modes.navigate(-1, 0, self._modifier.is_shift_down(), NAV_SRC_BUTTON)
        else:
            self._encoder_modes.navigate(-1, 0, self._modifier.is_shift_down(), NAV_SRC_BUTTON)

    @subject_slot('value', identify_sender=True)
    def _do_direction_right(self, value, sender):
        if value == 0 or sender.grabbed:
            return
        if not self.__block_nav:
            encoder_changed = self._main_mode_container.navigate(1, 0, self._modifier.is_shift_down(), NAV_SRC_BUTTON)
            if encoder_changed:
                self._encoder_modes.navigate(1, 0, self._modifier.is_shift_down(), NAV_SRC_BUTTON)
        else:
            self._encoder_modes.navigate(1, 0, self._modifier.is_shift_down(), NAV_SRC_BUTTON)

    @property
    def selected_mode(self):
        return self._main_mode_container.selected_mode

    @selected_mode.setter
    def selected_mode(self, value):
        self._main_mode_container.selected_mode = value

    def get_session(self):
        return self._session

    def toggle_mode(self):
        self._session.set_color_mode()
        self.refresh_state()

    def get_button_matrix(self):
        return self._bmatrix

    def deassign_matrix(self):
        for scene_index in range(8):
            scene = self._session.scene(scene_index)
            for track_index in range(8):
                clip_slot = scene.clip_slot(track_index)
                clip_slot.set_launch_button(None)

        return

    def _pre_serialize(self):
        from pickle import dumps
        from encodings import ascii
        nop(ascii)
        preferences = self._c_instance.preferences('MaschineJam')
        self.store_preferences()
        dump = dumps(self._pref_dict)
        preferences.set_serializer(lambda : dump)

    def disconnect(self):
        self._pre_serialize()
        self._set_touch_strip_led()
        self._encoder_modes.turn_off_bars()
        self._init_map()
        self._channel_led_left.send_value(0, True)
        self._channel_led_right.send_value(0, True)
        self._active = False
        self._suppress_send_midi = True
        super(MaschineJam, self).disconnect()
        return