Changeset - 2fe4bb539b52
[Not reviewed]
default
0 2 2
Drew Perttula - 7 years ago 2018-06-03 11:31:21
drewp@bigasterisk.com
optimize sequencer's updates when you're editing a curve
Ignore-this: 592a034f9d8db36abf3e8c84622ee7cf
4 files changed with 112 insertions and 88 deletions:
0 comments (0 inline, 0 general)
light9/effect/effecteval.py
Show inline comments
 
@@ -10,6 +10,7 @@ from noise import pnoise1
 
import logging
 
import time
 
from light9.effect.settings import DeviceSettings
 
from light9.effect.scale import scale
 

	
 

	
 
log = logging.getLogger('effecteval')
 
@@ -29,71 +30,16 @@ def lerp(a, b, t):
 
def noise(t):
 
    return pnoise1(t % 1000.0, 2)
 

	
 
def scale(value, strength):
 
    if isinstance(value, Literal):
 
        value = value.toPython()
 

	
 
    if isinstance(value, Decimal):
 
        value = float(value)
 
        
 
    if isinstance(value, basestring):
 
        if value[0] == '#':
 
            if strength == '#ffffff':
 
                return value
 
            r,g,b = hex_to_rgb(value)
 
            if isinstance(strength, Literal):
 
                strength = strength.toPython()
 
            if isinstance(strength, basestring):
 
                sr, sg, sb = [v/255 for v in hex_to_rgb(strength)]
 
            else:
 
                sr = sg = sb = strength
 
            return rgb_to_hex([int(r * sr), int(g * sg), int(b * sb)])
 
    elif isinstance(value, (int, float)):
 
        return value * strength
 

	
 
    raise NotImplementedError("%r,%r" % (value, strength))
 
    
 
class EffectEval(object):
 
    """
 
    runs one effect's code to turn effect attr settings into output
 
    device settings. No state; suitable for reload().
 
    """
 
    def __init__(self, graph, effect, sharedEffectOutputs):
 
    def __init__(self, graph, effect, simpleOutputs):
 
        self.graph = graph
 
        self.effect = effect 
 

	
 
        # effect : [(dev, attr, value, isScaled)]
 
        self.effectOutputs = sharedEffectOutputs
 

	
 
        if not self.effectOutputs:
 
            self.graph.addHandler(self.updateEffectsFromGraph)
 
        self.effect = effect
 
        self.simpleOutputs = simpleOutputs
 

	
 
    def updateEffectsFromGraph(self):
 
        for effect in self.graph.subjects(RDF.type, L9['Effect']):
 
            settings = []
 
            for setting in self.graph.objects(effect, L9['setting']):
 
                settingValues = dict(self.graph.predicate_objects(setting))
 
                try:
 
                    d = settingValues.get(L9['device'], None)
 
                    a = settingValues.get(L9['deviceAttr'], None)
 
                    v = settingValues.get(L9['value'], None)
 
                    sv = settingValues.get(L9['scaledValue'], None)
 
                    if not (bool(v) ^ bool(sv)):
 
                        raise NotImplementedError('no value for %s' % setting)
 
                    if d is None:
 
                        raise TypeError('no device on %s' % effect)
 
                    if a is None:
 
                        raise TypeError('no attr on %s' % effect)
 
                except Exception:
 
                    traceback.print_exc()
 
                    continue
 

	
 
                settings.append((d, a, v if v is not None else sv, bool(sv)))
 

	
 
            if settings:
 
                self.effectOutputs[effect] = settings
 
            # also have to read eff :effectAttr [ :tint x; :tintStrength y ]
 
        
 
    def outputFromEffect(self, effectSettings, songTime, noteTime):
 
        """
 
        From effect attr settings, like strength=0.75, to output device
 
@@ -109,8 +55,8 @@ class EffectEval(object):
 

	
 
        out = {} # (dev, attr): value
 

	
 
        out.update(self.simpleOutput(strength,
 
                                     effectSettings.get(L9['colorScale'], None)))
 
        out.update(self.simpleOutputs.values(
 
            self.effect, strength, effectSettings.get(L9['colorScale'], None)))
 

	
 
        if self.effect.startswith(L9['effect/']):
 
            tail = 'effect_' + self.effect[len(L9['effect/']):]
 
@@ -123,20 +69,7 @@ class EffectEval(object):
 

	
 
        outList = [(d, a, v) for (d, a), v in out.iteritems()]
 
        return DeviceSettings(self.graph, outList)
 
                            
 
    def simpleOutput(self, strength, colorScale):
 
        out = {}
 
        for dev, devAttr, value, isScaled in self.effectOutputs.get(self.effect, []):
 
            if isScaled:
 
                value = scale(value, strength)
 
            if colorScale is not None and devAttr == L9['color']:
 
                value = scale(value, colorScale)
 
            out[(dev, devAttr)] = value
 
        return out
 
        
 

	
 

	
 
    
 
                  
 

	
 
def effect_Curtain(effectSettings, strength, songTime, noteTime):
 
    return {
light9/effect/scale.py
Show inline comments
 
new file 100644
 
from __future__ import division
 
from rdflib import Literal
 
from decimal import Decimal
 
from webcolors import rgb_to_hex, hex_to_rgb
 

	
 

	
 
def scale(value, strength):
 
    if isinstance(value, Literal):
 
        value = value.toPython()
 

	
 
    if isinstance(value, Decimal):
 
        value = float(value)
 
        
 
    if isinstance(value, basestring):
 
        if value[0] == '#':
 
            if strength == '#ffffff':
 
                return value
 
            r,g,b = hex_to_rgb(value)
 
            if isinstance(strength, Literal):
 
                strength = strength.toPython()
 
            if isinstance(strength, basestring):
 
                sr, sg, sb = [v/255 for v in hex_to_rgb(strength)]
 
            else:
 
                sr = sg = sb = strength
 
            return rgb_to_hex([int(r * sr), int(g * sg), int(b * sb)])
 
    elif isinstance(value, (int, float)):
 
        return value * strength
 

	
 
    raise NotImplementedError("%r,%r" % (value, strength))
 
    
light9/effect/sequencer.py
Show inline comments
 
@@ -18,6 +18,7 @@ from light9.namespaces import L9, RDF
 
from light9.vidref.musictime import MusicTime
 
from light9.effect import effecteval
 
from light9.effect.settings import DeviceSettings
 
from light9.effect.simple_outputs import SimpleOutputs
 

	
 
from greplin import scales
 
from txzmq import ZmqEndpoint, ZmqFactory, ZmqPushConnection
 
@@ -25,6 +26,7 @@ from txzmq import ZmqEndpoint, ZmqFactor
 
log = logging.getLogger('sequencer')
 
stats = scales.collection('/sequencer/',
 
                          scales.PmfStat('update'),
 
                          scales.PmfStat('compile'),
 
                          scales.DoubleStat('recentFps'),
 
)
 

	
 
@@ -77,11 +79,11 @@ def sendToCollector(client, session, set
 

	
 

	
 
class Note(object):
 
    def __init__(self, graph, uri, effectevalModule, sharedEffectOutputs):
 
    def __init__(self, graph, uri, effectevalModule, simpleOutputs):
 
        g = self.graph = graph
 
        self.uri = uri
 
        self.effectEval = effectevalModule.EffectEval(
 
            graph, g.value(uri, L9['effectClass']), sharedEffectOutputs)
 
            graph, g.value(uri, L9['effectClass']), simpleOutputs)
 
        self.baseEffectSettings = {}  # {effectAttr: value}
 
        for s in g.objects(uri, L9['setting']):
 
            settingValues = dict(g.predicate_objects(s))
 
@@ -92,16 +94,22 @@ class Note(object):
 
        originTime = floatVal(uri, L9['originTime'])
 
        self.points = []
 
        for curve in g.objects(uri, L9['curve']):
 
            po = list(g.predicate_objects(curve))
 
            if dict(po).get(L9['attr'], None) != L9['strength']:
 
                continue
 
            for point in [row[1] for row in po if row[0] == L9['point']]:
 
                po2 = dict(g.predicate_objects(point))
 
                self.points.append((
 
                    originTime + float(po2[L9['time']]),
 
                    float(po2[L9['value']])))
 
            self.points.sort()
 
        
 
            self.points.extend(
 
                self.getCurvePoints(curve, L9['strength'], originTime))
 
        self.points.sort()
 

	
 
    def getCurvePoints(self, curve, attr, originTime):
 
        points = []
 
        po = list(self.graph.predicate_objects(curve))
 
        if dict(po).get(L9['attr'], None) != attr:
 
            return []
 
        for point in [row[1] for row in po if row[0] == L9['point']]:
 
            po2 = dict(self.graph.predicate_objects(point))
 
            points.append((
 
                originTime + float(po2[L9['time']]),
 
                float(po2[L9['value']])))
 
        return points
 
            
 
    def activeAt(self, t):
 
        return self.points[0][0] <= t <= self.points[-1][0]
 

	
 
@@ -164,24 +172,26 @@ class Sequencer(object):
 
        self.lastStatLog = 0
 
        self._compileGraphCall = None
 
        self.notes = {} # song: [notes]
 
        self.simpleOutputs = SimpleOutputs(self.graph)
 
        self.graph.addHandler(self.compileGraph)
 
        self.update()
 

	
 
        self.codeWatcher = CodeWatcher(
 
            onChange=lambda: self.graph.addHandler(self.compileGraph))
 

	
 
    @stats.compile.time()
 
    def compileGraph(self):
 
        """rebuild our data from the graph"""
 
        log.info('compileGraph start')
 
        t1 = time.time()
 
        g = self.graph
 

	
 
        sharedEffectOutputs = {}
 
        for song in g.subjects(RDF.type, L9['Song']):
 
            # ideally, wrap this (or smaller) in a sub-handler to run less on each patch
 
            self.notes[song] = []
 
            for note in g.objects(song, L9['note']):
 
                self.notes[song].append(Note(g, note, effecteval, sharedEffectOutputs))
 
                self.notes[song].append(Note(g, note, effecteval,
 
                                             self.simpleOutputs))
 
        log.info('compileGraph done %.2f ms', 1000 * (time.time() - t1))
 

	
 
    @stats.update.time()
light9/effect/simple_outputs.py
Show inline comments
 
new file 100644
 
from __future__ import division
 
import traceback
 
from light9.namespaces import L9, RDF
 
from light9.effect.scale import scale
 

	
 
class SimpleOutputs(object):
 
    def __init__(self, graph):
 
        self.graph = graph
 
        
 
        # effect : [(dev, attr, value, isScaled)]
 
        self.effectOutputs = {}
 

	
 
        self.graph.addHandler(self.updateEffectsFromGraph)
 
        
 
    def updateEffectsFromGraph(self):
 
        for effect in self.graph.subjects(RDF.type, L9['Effect']):
 
            settings = []
 
            for setting in self.graph.objects(effect, L9['setting']):
 
                settingValues = dict(self.graph.predicate_objects(setting))
 
                try:
 
                    d = settingValues.get(L9['device'], None)
 
                    a = settingValues.get(L9['deviceAttr'], None)
 
                    v = settingValues.get(L9['value'], None)
 
                    sv = settingValues.get(L9['scaledValue'], None)
 
                    if not (bool(v) ^ bool(sv)):
 
                        raise NotImplementedError('no value for %s' % setting)
 
                    if d is None:
 
                        raise TypeError('no device on %s' % effect)
 
                    if a is None:
 
                        raise TypeError('no attr on %s' % effect)
 
                except Exception:
 
                    traceback.print_exc()
 
                    continue
 

	
 
                settings.append((d, a, v if v is not None else sv, bool(sv)))
 

	
 
            if settings:
 
                self.effectOutputs[effect] = settings
 
            # also have to read eff :effectAttr [ :tint x; :tintStrength y ]
 
        
 

	
 
    def values(self, effect, strength, colorScale):
 
        out = {}
 
        for dev, devAttr, value, isScaled in self.effectOutputs.get(effect, []):
 
            if isScaled:
 
                value = scale(value, strength)
 
            if colorScale is not None and devAttr == L9['color']:
 
                value = scale(value, colorScale)
 
            out[(dev, devAttr)] = value
 
        return out
 
        
0 comments (0 inline, 0 general)