Example #1
0
    def __init__(self, parent, args):
        QtCore.QThread.__init__(self, parent)
        self._running = False
        self.args = args
        self.settings = Settings()
        self.net = Networking(self)
        BufferUtils.set_app(self)
        self.scene = Scene(self)
        self.plugins = PluginLoader()
        self.mixer = Mixer(self)
        self.playlist = Playlist(self)
        self.qt_app = parent

        self.scene.warmup()

        self.aubio_connector = None
        if not self.args.noaudio:
            self.aubio_thread = QtCore.QThread()
            self.aubio_thread.start()
            self.aubio_connector = AubioConnector()
            self.aubio_connector.onset_detected.connect(
                self.mixer.onset_detected)
            self.aubio_connector.fft_data.connect(
                self.mixer.audio.update_fft_data)
            self.aubio_connector.pitch_data.connect(
                self.mixer.audio.update_pitch_data)
            self.aubio_connector.moveToThread(self.aubio_thread)

        self.mixer.set_playlist(self.playlist)

        if self.args.preset:
            log.info("Setting constant preset %s" % args.preset)
            self.mixer.set_constant_preset(args.preset)
Example #2
0
class FireMixApp(QtCore.QThread):
    """
    Main logic of FireMix.  Operates the mixer tick loop.
    """
    playlist_changed = QtCore.Signal()

    def __init__(self, parent, args):
        QtCore.QThread.__init__(self, parent)
        self._running = False
        self.args = args
        self.settings = Settings()
        self.net = Networking(self)
        BufferUtils.set_app(self)
        self.scene = Scene(self)
        self.plugins = PluginLoader()
        self.mixer = Mixer(self)
        self.playlist = Playlist(self)
        self.qt_app = parent

        self.scene.warmup()

        self.aubio_connector = None
        if not self.args.noaudio:
            self.aubio_thread = QtCore.QThread()
            self.aubio_thread.start()
            self.aubio_connector = AubioConnector()
            self.aubio_connector.onset_detected.connect(
                self.mixer.onset_detected)
            self.aubio_connector.fft_data.connect(
                self.mixer.audio.update_fft_data)
            self.aubio_connector.pitch_data.connect(
                self.mixer.audio.update_pitch_data)
            self.aubio_connector.moveToThread(self.aubio_thread)

        self.mixer.set_playlist(self.playlist)

        if self.args.preset:
            log.info("Setting constant preset %s" % args.preset)
            self.mixer.set_constant_preset(args.preset)

    def run(self):
        self._running = True
        self.mixer.run()

    def stop(self):
        self._running = False
        self.aubio_thread.quit()
        self.mixer.stop()
        self.playlist.save()
        self.settings.save()
Example #3
0
class FireMixApp(QtCore.QThread):
    """
    Main logic of FireMix.  Operates the mixer tick loop.
    """
    playlist_changed = QtCore.Signal()

    def __init__(self, parent, args):
        QtCore.QThread.__init__(self, parent)
        self._running = False
        self.args = args
        self.settings = Settings()
        self.net = Networking(self)
        BufferUtils.set_app(self)
        self.scene = Scene(self)
        self.plugins = PluginLoader()
        self.mixer = Mixer(self)
        self.playlist = Playlist(self)
        self.qt_app = parent

        self.scene.warmup()

        self.aubio_connector = None
        if not self.args.noaudio:
            self.aubio_thread = QtCore.QThread()
            self.aubio_thread.start()
            self.aubio_connector = AubioConnector()
            self.aubio_connector.onset_detected.connect(self.mixer.onset_detected)
            self.aubio_connector.fft_data.connect(self.mixer.audio.update_fft_data)
            self.aubio_connector.pitch_data.connect(self.mixer.audio.update_pitch_data)
            self.aubio_connector.moveToThread(self.aubio_thread)

        self.mixer.set_playlist(self.playlist)

        if self.args.preset:
            log.info("Setting constant preset %s" % args.preset)
            self.mixer.set_constant_preset(args.preset)

    def run(self):
        self._running = True
        self.mixer.run()

    def stop(self):
        self._running = False
        self.aubio_thread.quit()
        self.mixer.stop()
        self.playlist.save()
        self.settings["last-preset"] = self.playlist.active_preset.name()
        self.settings.save()
Example #4
0
    def __init__(self, parent, args):
        QtCore.QThread.__init__(self, parent)
        self._running = False
        self.args = args
        self.settings = Settings()
        self.net = Networking(self)
        BufferUtils.set_app(self)
        self.scene = Scene(self)
        self.plugins = PluginLoader()
        self.mixer = Mixer(self)
        self.playlist = Playlist(self)
        self.qt_app = parent

        self.scene.warmup()

        self.aubio_connector = None
        if not self.args.noaudio:
            self.aubio_thread = QtCore.QThread()
            self.aubio_thread.start()
            self.aubio_connector = AubioConnector()
            self.aubio_connector.onset_detected.connect(self.mixer.onset_detected)
            self.aubio_connector.fft_data.connect(self.mixer.audio.update_fft_data)
            self.aubio_connector.pitch_data.connect(self.mixer.audio.update_pitch_data)
            self.aubio_connector.moveToThread(self.aubio_thread)

        self.mixer.set_playlist(self.playlist)

        if self.args.preset:
            log.info("Setting constant preset %s" % args.preset)
            self.mixer.set_constant_preset(args.preset)
Example #5
0
    def __init__(self, args, parent=None):
        self._running = False
        self.args = args
        self.settings = Settings()
        self.net = Networking(self)
        BufferUtils.set_app(self)
        self.scene = Scene(self)
        self.plugins = PluginLoader()
        self.mixer = Mixer(self)

        # Create the default layer.
        default_playlist = Playlist(self, self.args.playlist, 'last_playlist')
        default_layer = Layer(self, 'default')
        default_layer.set_playlist(default_playlist)
        self.mixer.add_layer(default_layer)

        if self.args.speech_layer:
            speech_playlist = Playlist(self, self.args.speech_playlist,
                                       'last_speech_playlist')
            speech_layer = Layer(self, 'speech')
            speech_layer.set_playlist(speech_playlist)
            self.mixer.add_layer(speech_layer)

        self.scene.warmup()

        self.aubio_connector = None
        if not self.args.noaudio:
            self.aubio_connector = AubioConnector()
            self.aubio_connector.onset_detected.connect(
                self.mixer.onset_detected)

        self.osc_server = None
        if not self.args.noosc:
            self.osc_server = OscServer(self.args.osc_port,
                                        self.args.mixxx_osc_port, self.mixer)
            self.osc_server.start()

        if self.args.preset:
            log.info("Setting constant preset %s" % args.preset)
            self.mixer.set_constant_preset(args.preset)

        QtCore.QThread.__init__(self, parent)
Example #6
0
    def __init__(self, args, parent=None):
        self._running = False
        self.args = args
        self.settings = Settings()
        self.net = Networking(self)
        self.scene = Scene(SceneLoader(self))
        self.plugins = PluginLoader()
        self.mixer = Mixer(self)
        self.playlist = Playlist(self)

        self.aubio_connector = None
        if self.args.audio:
            self.aubio_connector = AubioConnector()
            self.aubio_connector.onset_detected.connect(self.mixer.onset_detected)

        self.mixer.set_playlist(self.playlist)

        if self.args.preset:
            log.info("Setting constant preset %s" % args.preset)
            self.mixer.set_constant_preset(args.preset)

        QtCore.QThread.__init__(self, parent)
Example #7
0
    def __init__(self, app):
        super(Mixer, self).__init__()
        self._app = app
        self._net = app.net
        self.playlist = None
        self._scene = app.scene
        self._tick_rate = self._app.settings.get('mixer')['tick-rate']
        self._in_transition = False
        self._start_transition = False
        self._transition_scrubbing = False
        self._transition_duration = self._app.settings.get(
            'mixer')['transition-duration']
        self._transition_slop = self._app.settings.get(
            'mixer')['transition-slop']
        self._render_thread = None
        self._duration = self._app.settings.get('mixer')['preset-duration']
        self._elapsed = 0.0
        self.running = False
        self._max_pixels = 0
        self._tick_time_data = dict()
        self._num_frames = 0
        self._last_frame_time = 0.0
        self._start_time = 0.0
        self._stop_time = 0.0
        self._strand_keys = list()
        self._enable_profiling = self._app.args.profile
        self._paused = self._app.settings.get('mixer').get('paused', False)
        self._frozen = False
        self._last_onset_time = 0.0
        self._onset_holdoff = self._app.settings.get('mixer')['onset-holdoff']
        self._onset = False
        self._reset_onset = False
        self.global_dimmer = 1.0
        self.global_speed = 1.0
        self._render_in_progress = False
        self._last_tick_time = 0.0
        self._fps_time = 0.0
        self._fps_frames = 0
        self._fps = 0.0
        self.last_time = time.time()
        self.transition_progress = 0.0
        self.useColorCorrections = True

        # TODO: bring back noaudio
        #if not self._app.args.noaudio:
        # TODO: harmonize on threading.Thread
        self._audio_thread = QtCore.QThread()
        self._audio_thread.start()
        self.aubio_connector = AubioConnector()
        self.audio = Audio(self)
        self.audio.onset.connect(self.onset_detected)
        self.aubio_connector.onset_detected.connect(self.audio.trigger_onset)
        self.aubio_connector.fft_data.connect(self.audio.fft_data_from_network)
        self.aubio_connector.pitch_data.connect(self.audio.update_pitch_data)
        self.aubio_connector.moveToThread(self._audio_thread)

        log.info("Warming up BufferUtils cache...")
        BufferUtils.init()
        log.info("Completed BufferUtils cache warmup")

        # Load transitions
        self.set_transition_mode(self._app.settings.get('mixer')['transition'])

        log.info("Initializing preset rendering buffer")
        fh = self._scene.fixture_hierarchy()
        for strand in fh:
            self._strand_keys.append(strand)

        (maxs, maxp) = self._scene.get_matrix_extents()

        self._buffer_a = BufferUtils.create_buffer()
        self._buffer_b = BufferUtils.create_buffer()
        self._output_buffer = BufferUtils.create_buffer()
        self._max_pixels = maxp
Example #8
0
class Mixer(QtCore.QObject):
    """
    Mixer is the brains of FireMix.  It handles the playback of presets
    and the generation of the final command stream to send to the output
    device(s).
    """
    transition_starting = QtCore.pyqtSignal()

    def __init__(self, app):
        super(Mixer, self).__init__()
        self._app = app
        self._net = app.net
        self.playlist = None
        self._scene = app.scene
        self._tick_rate = self._app.settings.get('mixer')['tick-rate']
        self._in_transition = False
        self._start_transition = False
        self._transition_scrubbing = False
        self._transition_duration = self._app.settings.get(
            'mixer')['transition-duration']
        self._transition_slop = self._app.settings.get(
            'mixer')['transition-slop']
        self._render_thread = None
        self._duration = self._app.settings.get('mixer')['preset-duration']
        self._elapsed = 0.0
        self.running = False
        self._max_pixels = 0
        self._tick_time_data = dict()
        self._num_frames = 0
        self._last_frame_time = 0.0
        self._start_time = 0.0
        self._stop_time = 0.0
        self._strand_keys = list()
        self._enable_profiling = self._app.args.profile
        self._paused = self._app.settings.get('mixer').get('paused', False)
        self._frozen = False
        self._last_onset_time = 0.0
        self._onset_holdoff = self._app.settings.get('mixer')['onset-holdoff']
        self._onset = False
        self._reset_onset = False
        self.global_dimmer = 1.0
        self.global_speed = 1.0
        self._render_in_progress = False
        self._last_tick_time = 0.0
        self._fps_time = 0.0
        self._fps_frames = 0
        self._fps = 0.0
        self.last_time = time.time()
        self.transition_progress = 0.0
        self.useColorCorrections = True

        # TODO: bring back noaudio
        #if not self._app.args.noaudio:
        # TODO: harmonize on threading.Thread
        self._audio_thread = QtCore.QThread()
        self._audio_thread.start()
        self.aubio_connector = AubioConnector()
        self.audio = Audio(self)
        self.audio.onset.connect(self.onset_detected)
        self.aubio_connector.onset_detected.connect(self.audio.trigger_onset)
        self.aubio_connector.fft_data.connect(self.audio.fft_data_from_network)
        self.aubio_connector.pitch_data.connect(self.audio.update_pitch_data)
        self.aubio_connector.moveToThread(self._audio_thread)

        log.info("Warming up BufferUtils cache...")
        BufferUtils.init()
        log.info("Completed BufferUtils cache warmup")

        # Load transitions
        self.set_transition_mode(self._app.settings.get('mixer')['transition'])

        log.info("Initializing preset rendering buffer")
        fh = self._scene.fixture_hierarchy()
        for strand in fh:
            self._strand_keys.append(strand)

        (maxs, maxp) = self._scene.get_matrix_extents()

        self._buffer_a = BufferUtils.create_buffer()
        self._buffer_b = BufferUtils.create_buffer()
        self._output_buffer = BufferUtils.create_buffer()
        self._max_pixels = maxp

    @QtCore.pyqtSlot()
    def start(self):
        assert self._render_thread is None, "Cannot start render thread more than once"
        self._tick_rate = self._app.settings.get('mixer')['tick-rate']
        self._last_tick_time = old_div(1.0, self._tick_rate)
        self._elapsed = 0.0
        self._num_frames = 0
        self._start_time = self._last_frame_time = time.time()
        self.reset_output_buffers()
        self.running = True

        self._render_thread = threading.Thread(target=self._render_loop,
                                               name="Firemix-render-thread")
        self._render_thread.start()

    def _render_loop(self):
        delay = 0.0
        while self.running:
            time.sleep(delay)
            if self._frozen:
                delay = old_div(1.0, self._tick_rate)
            else:
                start = time.time()
                self._render_in_progress = True
                self.tick(self._last_tick_time)
                self._render_in_progress = False
                dt = (time.time() - start)
                delay = max(0, (old_div(1.0, self._tick_rate)) - dt)
                if not self._paused:
                    self._elapsed += dt + delay

    @QtCore.pyqtSlot()
    def restart(self):
        self.stop()
        self.start()

    @QtCore.pyqtSlot()
    def stop(self):
        self.running = False
        self._stop_time = time.time()
        if self._render_thread is not None:
            self._render_thread.join()
            self._render_thread = None

        # TODO: Should we restart the audio thread on mixer restart?
        #self._audio_thread.quit()
        #self._audio_thread = None

    def pause(self, pause=True):
        self._paused = pause
        self._app.settings.get('mixer')['paused'] = pause

    def is_paused(self):
        return self._paused

    def fps(self):
        if self.running and self._num_frames > self._fps_frames:
            delta_t = time.time() - self._fps_time
            if delta_t > 1.0:
                self._fps = old_div((self._num_frames - self._fps_frames),
                                    delta_t)
                self._fps_frames = self._num_frames
                self._fps_time = time.time()
            return self._fps
        else:
            self._fps_frames = 0
            self._fps_time = time.time()
            return 0.0

    @QtCore.pyqtSlot()
    def onset_detected(self):
        t = time.time()
        if (t - self._last_onset_time) > self._onset_holdoff:
            self._last_onset_time = t
            self._onset = True

    def get_transition_by_name(self, name):
        if not name or name == "Cut":
            return None

        if name == "Random":
            self.build_random_transition_list()
            return self.get_next_transition()

        tl = [
            c for c in self._app.plugins.get('Transition')
            if str(c(None)) == name
        ]

        if len(tl) == 1:
            return tl[0](self._app)
        else:
            log.error("Transition %s is not loaded!" % name)
            return None

    def set_transition_mode(self, name):
        #if not self._in_transition:
        #    self._transition = self.get_transition_by_name(name)
        self._transition = self.get_transition_by_name(name)
        if self._transition is not None:
            self._transition.reset()
        return True

    def build_random_transition_list(self):
        self._transition_list = [
            c for c in self._app.plugins.get('Transition')
        ]
        random.shuffle(self._transition_list)

    def get_next_transition(self):
        if len(self._transition_list) == 0:
            self.build_random_transition_list()
        self._transition = self._transition_list.pop()(self._app)
        self._transition.setup()

    def freeze(self, freeze=True):
        self._frozen = freeze

    def is_frozen(self):
        return self._frozen

    def set_preset_duration(self, duration):
        if duration >= 0.0:
            self._duration = duration
            return True
        else:
            log.warn("Pattern duration must be positive or zero.")
            return False

    def get_preset_duration(self):
        return self._duration

    def set_transition_duration(self, duration):
        if duration >= 0.0:
            self._transition_duration = duration
            return True
        else:
            log.warn("Transition duration must be positive or zero.")
            return False

    def get_transition_duration(self):
        return self._transition_duration

    def set_constant_preset(self, classname):
        self._app.playlist.clear_playlist()
        self._app.playlist.add_preset(classname, classname)
        self._paused = True

    def set_playlist(self, playlist):
        """
        Assigns a Playlist object to the mixer
        """
        self.playlist = playlist

    def get_tick_rate(self):
        return self._tick_rate

    def is_onset(self):
        """
        Called by presets; resets after tick if called during tick
        """
        if self._onset:
            self._reset_onset = True
            return True
        return False

    def __next__(self):
        #TODO: Fix this after the Playlist merge
        if len(self.playlist) == 0:
            return

        self.start_transition(self.playlist.next_preset)

    def prev(self):
        # Disabling this because it's not very useful and complicates implementation.
        pass

    def start_transition(self, next=None):
        """
        Starts a transition.  If a name is given for Next, it will be the endpoint of the transition
        """
        if next is not None:
            self.playlist.set_next_preset_by_name(next)

        self._in_transition = True
        self._start_transition = True
        self._elapsed = 0.0
        self.transition_starting.emit()
        next_preset = self.playlist.get_next_preset()
        log.info("Starting transition to pattern '%s'" % (next_preset.name()))

    def cancel_transition(self):
        self._start_transition = False
        self._transition_scrubbing = False
        if self._in_transition:
            self._in_transition = False
            self.transition_progress = 0

    def scrub_transition(self, scrub_ratio):
        if not self.is_paused():
            return

        self._in_transition = True

        if not self._transition_scrubbing:
            self._start_transition = True
            self._transition_scrubbing = True

        self.transition_progress = scrub_ratio

    def cancel_scrub(self):
        self._transition_scrubbing = False

    def tick(self, dt):
        self._num_frames += 1

        dt *= self.global_speed

        if len(self.playlist) > 0:

            active_preset = self.playlist.get_active_preset()
            next_preset = self.playlist.get_next_preset()

            if active_preset is None:
                return

            try:
                active_preset.tick(dt)
            except:
                log.error("Exception raised in preset %s" %
                          active_preset.name())
                self.playlist.disable_presets_by_class(
                    active_preset.__class__.__name__)
                raise

            # Handle transition by rendering both the active and the next
            # preset, and blending them together
            if self._in_transition and next_preset and (next_preset !=
                                                        active_preset):
                if self._start_transition:
                    self._start_transition = False
                    if self._app.settings.get(
                            'mixer')['transition'] == "Random":
                        self.get_next_transition()
                    if self._transition:
                        self._transition.reset()
                    next_preset._reset()

                if self._transition_duration > 0.0 and self._transition is not None:
                    if not self._paused and not self._transition_scrubbing:
                        self.transition_progress = clip(
                            0.0,
                            old_div(self._elapsed, self._transition_duration),
                            1.0)
                else:
                    if not self._transition_scrubbing:
                        self.transition_progress = 1.0

                next_preset.tick(dt)

            # If the scene tree is available, we can do efficient mixing of presets.
            # If not, a tree would need to be constructed on-the-fly.
            # TODO: Support mixing without a scene tree available

            if self._in_transition:
                self.render_presets(active_preset,
                                    next_preset,
                                    self._transition,
                                    self.transition_progress,
                                    check_for_nan=self._enable_profiling)
            else:
                self.render_presets(active_preset,
                                    check_for_nan=self._enable_profiling)

            # Mod hue by 1 (to allow wrap-around) and clamp lightness and
            # saturation to [0, 1].
            np.mod(self._output_buffer['hue'], 1.0, self._output_buffer['hue'])
            np.clip(self._output_buffer['light'], 0.0, 1.0,
                    self._output_buffer['light'])
            np.clip(self._output_buffer['sat'], 0.0, 1.0,
                    self._output_buffer['sat'])

            # Write this buffer to enabled clients.
            if self._net is not None:
                self._net.write_buffer(self._output_buffer)

            if (not self._paused and (self._elapsed >= self._duration)
                    and active_preset.can_transition()
                    and not self._in_transition):

                if (self._elapsed >=
                    (self._duration + self._transition_slop)) or self._onset:
                    if len(self.playlist) > 1:
                        self.start_transition()
                    self._elapsed = 0.0

            elif self._in_transition:
                if not self._transition_scrubbing and (self.transition_progress
                                                       >= 1.0):
                    self._in_transition = False
                    # Reset the elapsed time counter so the preset runs for the
                    # full duration after the transition
                    self._elapsed = 0.0
                    self.playlist.advance()

        if self._reset_onset:
            self._onset = False
            self._reset_onset = False

        if self._enable_profiling:
            tick_time = (time.time() - self._last_frame_time)
            self._last_frame_time = time.time()
            if tick_time > 0.0:
                index = int((old_div(1.0, tick_time)))
                self._tick_time_data[index] = self._tick_time_data.get(
                    index, 0) + 1

    def scene(self):
        return self._scene

    def render_presets(self,
                       first_preset,
                       second_preset=None,
                       transition=None,
                       transition_progress=0.0,
                       check_for_nan=False):
        """
        Generates the final output buffer from either a single preset or two
        presets and a Transition.
        """

        first_preset.render(self._buffer_a)
        if check_for_nan:
            self._validate_buffer(self._buffer_a)

        if transition is None:
            # Single preset
            self._output_buffer = self._buffer_a
            return

        # Two presets
        second_preset.render(self._buffer_b)
        if check_for_nan:
            self._validate_buffer(self._buffer_b)

        transition.render(self._buffer_a, self._buffer_b, transition_progress,
                          self._output_buffer)
        if check_for_nan:
            self._validate_buffer(self._output_buffer)

    def reset_output_buffers(self):
        """
        Clears the output buffers
        """
        self._buffer_a = BufferUtils.create_buffer()
        self._buffer_b = BufferUtils.create_buffer()
        self._output_buffer = BufferUtils.create_buffer()

    def _validate_buffer(self, buf):
        np.isnan(struct_flat(buf), self._nan_check_buf)
        if np.any(self._nan_check_buf):
            raise ValueError