Skip to content

Raw preset development

Jon Evans edited this page May 21, 2013 · 2 revisions

Introduction

If you haven't already mastered basic preset development, you may wish to start there. I will assume you are familiar with that section already.

Raw presets are designed for per-pixel manipulation. This concept can be much more efficient for some preset designs that apply logic on a per-pixel basis, since implementing them using tickers would quickly become unwieldy.

Raw presets are children of the RawPreset class, which itself is a child of the Preset class. Because raw presets are rendered differently than basic presets by the mixer, you cannot use tickers in raw presets.

As with basic presets, raw presets can override the setup() and parameter_changed() methods to run initialization code if necessary.

Raw presets should also override the draw() method, which is called by the mixer each frame. draw() can call the setp() method to set pixels to colors. When implementing draw(), take care to minimize the size and scope of loops if possible in order to get the best performance. Looping over all pixels is possible, but on a typical computer, adding in too many function calls or math operations to this loop can cause performance problems or stuttering. If you find that your preset is not running as quickly as you'd like, see if any of the operations you perform in loops can be extracted from the loop and cached in your preset class (for example, a preset that uses a pixel's (x, y) location to calculate its distance from a certain point could cache that distance, because the pixel locations do not change during runtime). Note that calls to the Scene object such as get_pixel_location() are already cached, so you do not need to implement your own caching on the return values from those calls.

Example Raw Preset: Twinkle

Let's dissect the code to one of the example presets: Twinkle

import colorsys
import random

from lib.raw_preset import RawPreset
from lib.colors import float_to_uint8
from lib.color_fade import ColorFade
from lib.parameters import FloatParameter, HSVParameter

class Twinkle(RawPreset):
    """Random pixels fade in and out"""

    _fading_up = []
    _fading_down = []
    _time = {}
    _fader = None

    def setup(self):
        random.seed()
        self.add_parameter(FloatParameter('birth-rate', 0.15))
        self.add_parameter(FloatParameter('fade-up-time', 0.25))
        self.add_parameter(FloatParameter('fade-down-time', 2.5))
        self.add_parameter(HSVParameter('on-color', (0.15, 1.0, 1.0)))
        self.add_parameter(HSVParameter('off-color', (0.0, 0.0, 0.0)))
        self._setup_colors()

    def parameter_changed(self, parameter):
        if str(parameter) == 'on-color':
            self._setup_colors()

    def _setup_colors(self):
        self._up_target_rgb = float_to_uint8(colorsys.hsv_to_rgb(*self.parameter('on-color').get()))
        self._down_target_rgb = float_to_uint8(colorsys.hsv_to_rgb(*self.parameter('off-color').get()))
        self._fader = ColorFade('hsv', [self.parameter('off-color').get(), self.parameter('on-color').get()])

    def reset(self):
        self._fading_up = []
        self._fading_down = []
        self._time = {}

    def draw(self, dt):

        # Birth
        if random.random() > (1.0 - self.parameter('birth-rate').get()):
            address = ( random.randint(0, self._max_strand - 1),
                        random.randint(0, self._max_fixture - 1),
                        random.randint(0, self._max_pixel - 1))
            if address not in self._fading_up:
                self._fading_up.append(address)
                self._time[address] = dt

        # Growth
        for address in self._fading_up:
            color = self._get_next_color(address, dt)
            if color == self._up_target_rgb:
                self._fading_up.remove(address)
                self._fading_down.append(address)
                self._time[address] = dt
            self.setp(address, color)

        # Decay
        for address in self._fading_down:
            color = self._get_next_color(address, dt, down=True)
            if color == self._down_target_rgb:
                self._fading_down.remove(address)
            self.setp(address, color)

    def _get_next_color(self, address, dt, down=False):
        time_target = float(self.parameter('fade-up-time').get()) if down else float(self.parameter('fade-down-time').get())
        progress = (dt - self._time[address]) / time_target

        if progress > 1.0:
            progress = 1.0
        elif dt == self._time[address]:
            progress = 0.0

        if down:
            progress = 1.0 - progress

        return self._fader.get_color(progress)