changeset 1773:2fe4bb539b52

optimize sequencer's updates when you're editing a curve Ignore-this: 592a034f9d8db36abf3e8c84622ee7cf
author Drew Perttula <drewp@bigasterisk.com>
date Sun, 03 Jun 2018 11:31:21 +0000
parents 66a55cb17cbf
children 74ec008aee56
files light9/effect/effecteval.py light9/effect/scale.py light9/effect/sequencer.py light9/effect/simple_outputs.py
diffstat 4 files changed, 112 insertions(+), 88 deletions(-) [+]
line wrap: on
line diff
--- a/light9/effect/effecteval.py	Sun Jun 03 11:19:12 2018 +0000
+++ b/light9/effect/effecteval.py	Sun Jun 03 11:31:21 2018 +0000
@@ -10,6 +10,7 @@
 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 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 @@
 
         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 @@
 
         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 {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/light9/effect/scale.py	Sun Jun 03 11:31:21 2018 +0000
@@ -0,0 +1,30 @@
+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))
+    
--- a/light9/effect/sequencer.py	Sun Jun 03 11:19:12 2018 +0000
+++ b/light9/effect/sequencer.py	Sun Jun 03 11:31:21 2018 +0000
@@ -18,6 +18,7 @@
 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 @@
 log = logging.getLogger('sequencer')
 stats = scales.collection('/sequencer/',
                           scales.PmfStat('update'),
+                          scales.PmfStat('compile'),
                           scales.DoubleStat('recentFps'),
 )
 
@@ -77,11 +79,11 @@
 
 
 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 @@
         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 @@
         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()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/light9/effect/simple_outputs.py	Sun Jun 03 11:31:21 2018 +0000
@@ -0,0 +1,51 @@
+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
+