changeset 1391:4a7594476905

hack up KC so it edits effect strengths instead Ignore-this: 65706b0434cb635d1cf9a7e92a867df9
author Drew Perttula <drewp@bigasterisk.com>
date Thu, 09 Jun 2016 08:50:47 +0000
parents 6326a26e548c
children 361dbf72618f
files bin/effectsequencer bin/keyboardcomposer light9/effect/sequencer.py light9/subclient.py
diffstat 4 files changed, 106 insertions(+), 104 deletions(-) [+]
line wrap: on
line diff
--- a/bin/effectsequencer	Thu Jun 09 07:07:55 2016 +0000
+++ b/bin/effectsequencer	Thu Jun 09 08:50:47 2016 +0000
@@ -12,9 +12,7 @@
 import optparse, sys, logging
 import cyclone.web
 from rdflib import URIRef
-from light9.effect.sequencer import Sequencer
-import treq
-import json
+from light9.effect.sequencer import Sequencer, sendToCollector
 from light9.rdfdb import clientsession
 
 class App(object):
@@ -35,13 +33,10 @@
                                        )
     def launch(self, *args):
         print 'launch'
-        def sendToCollector(settings):
-            return treq.put(networking.collector.path('attrs'),
-                            data=json.dumps({'settings': settings,
-                                             'client': 'effectSequencer',
-                                             'clientSession': self.session}))
-
-        seq = Sequencer(self.graph, sendToCollector)
+        self.seq = Sequencer(
+            self.graph,
+            lambda settings: sendToCollector('effectSequencer', self.session,
+                                             settings))
 
         self.cycloneApp = cyclone.web.Application(handlers=[
             (r'/stats', StatsForCyclone),
--- a/bin/keyboardcomposer	Thu Jun 09 07:07:55 2016 +0000
+++ b/bin/keyboardcomposer	Thu Jun 09 08:50:47 2016 +0000
@@ -7,13 +7,11 @@
 import webcolors, colorsys
 from louie import dispatcher
 from twisted.internet import reactor, tksupport
-from twisted.web import server, resource
+from twisted.web import resource
 from rdflib import URIRef, Literal
 import Tix as tk
 
 from light9.Fadable import Fadable
-from light9.Submaster import Submasters, sub_maxes, PersistentSubmaster
-from light9.Patch import get_channel_uri
 from light9.subclient import SubClient
 from light9 import showconfig, networking, prof
 from light9.uihelpers import toplevelat
@@ -21,6 +19,7 @@
 from light9.tkdnd import initTkdnd, dragSourceRegister, dropTargetRegister
 from light9.rdfdb import clientsession
 from light9.rdfdb.syncedgraph import SyncedGraph
+from light9.effect.sequencer import EffectEval
 
 from bcf2000 import BCF2000
 
@@ -61,24 +60,25 @@
     this object owns the level of the submaster (the rdf graph is the
     real authority)
     """
-    def __init__(self, master, sub, session, col, row):
+    def __init__(self, master, graph, sub, session, col, row):
+        self.graph = graph
         self.sub = sub
         self.session = session
         self.col, self.row = col, row
-        bg = sub.graph.value(sub.uri, L9.color, default='#000000')
+        bg = self.graph.value(sub, L9.color, default='#000000')
         rgb = webcolors.hex_to_rgb(bg)
         hsv = colorsys.rgb_to_hsv(*[x/255 for x in rgb])
         darkBg = webcolors.rgb_to_hex(tuple([x * 255 for x in colorsys.hsv_to_rgb(
             hsv[0], hsv[1], .2)]))
         tk.Frame.__init__(self, master, bd=1, relief='raised', bg=bg)
-        self.name = sub.name
+        self.name = self.graph.label(sub)
         self.slider_var = tk.DoubleVar()
         self.pauseTrace = False
         self.scale = SubScale(self, variable=self.slider_var, width=20)
 
         self.namelabel = tk.Label(self, font="Arial 7", bg=darkBg,
             fg='white', pady=0)
-        self.sub.graph.addHandler(self.updateName)
+        self.graph.addHandler(self.updateName)
 
         self.namelabel.pack(side=tk.TOP)
         levellabel = tk.Label(self, textvariable=self.slider_var, font="Arial 6",
@@ -87,11 +87,11 @@
         self.scale.pack(side=tk.BOTTOM, expand=1, fill=tk.BOTH)
 
         for w in [self, self.namelabel, levellabel]:
-            dragSourceRegister(w, 'copy', 'text/uri-list', sub.uri)
+            dragSourceRegister(w, 'copy', 'text/uri-list', sub)
 
         self._slider_var_trace = self.slider_var.trace('w', self.slider_changed)
 
-        sub.graph.addHandler(self.updateLevelFromGraph)
+        self.graph.addHandler(self.updateLevelFromGraph)
 
         # initial position
         # stil need? dispatcher.send("send_to_hw", sub=sub.uri, hwCol=col + 1)
@@ -104,10 +104,10 @@
 
         if self.pauseTrace:
             return
-        self.updateGraphWithLevel(self.sub.uri, self.slider_var.get())
+        self.updateGraphWithLevel(self.sub, self.slider_var.get())
 
         # needs fixing: plan is to use dispatcher or a method call to tell a hardware-mapping object who changed, and then it can make io if that's a current hw slider
-        dispatcher.send("send_to_hw", sub=self.sub.uri, hwCol=self.col + 1,
+        dispatcher.send("send_to_hw", sub=self.sub, hwCol=self.col + 1,
                         boxRow=self.row)
 
     def updateGraphWithLevel(self, uri, level):
@@ -117,20 +117,20 @@
         """
         # move to syncedgraph patchMapping
 
-        self.sub.graph.patchMapping(context=self.session,
-                                    subject=self.session,
-                                    predicate=L9['subSetting'],
-                                    nodeClass=L9['SubSetting'],
-                                    keyPred=L9['sub'], newKey=uri,
-                                    valuePred=L9['level'], newValue=Literal(level))
+        self.graph.patchMapping(context=self.session,
+                                subject=self.session,
+                                predicate=L9['subSetting'],
+                                nodeClass=L9['SubSetting'],
+                                keyPred=L9['sub'], newKey=uri,
+                                valuePred=L9['level'], newValue=Literal(level))
 
     def updateLevelFromGraph(self):
         """read rdf level, write it to subbox.slider_var"""
         # move this to syncedgraph readMapping
-        graph = self.sub.graph
+        graph = self.graph
 
         for setting in graph.objects(self.session, L9['subSetting']):
-            if graph.value(setting, L9['sub']) == self.sub.uri:
+            if graph.value(setting, L9['sub']) == self.sub:
                 self.pauseTrace = True # don't bounce this update back to server
                 try:
                     self.slider_var.set(graph.value(setting, L9['level']))
@@ -140,7 +140,7 @@
     def updateName(self):
         def shortUri(u):
             return '.../' + u.split('/')[-1]
-        self.namelabel.config(text=self.sub.graph.label(self.sub.uri) or shortUri(self.sub.uri))
+        self.namelabel.config(text=self.graph.label(self.sub) or shortUri(self.sub))
 
 class KeyboardComposer(tk.Frame, SubClient):
     def __init__(self, root, graph, session,
@@ -149,7 +149,7 @@
         SubClient.__init__(self)
         self.graph = graph
         self.session = session
-        self.submasters = Submasters(graph)
+
         self.subbox = {} # sub uri : SubmasterBox
         self.slider_table = {} # coords : SubmasterBox
         self.rows = [] # this holds Tk Frames for each row
@@ -208,39 +208,36 @@
 
         self.tk_focusFollowsMouse()
 
-        self.submasters.findSubs() # trigger graph load, but we read
-                                   # from get_all_subs, below
-        
         rowcount = -1
         col = 0
         last_group = None
 
-        # there are unlikely to be any subs at startup because we
-        # probably haven't been called back with the graph data yet
+        withgroups = []
+        for effect in self.graph.objects(L9['live'], L9['controls']):
+            withgroups.append((
+                self.graph.value(effect, L9['group']),
+                self.graph.value(effect, L9['order']),
+                self.graph.label(effect),
+                effect))
+        withgroups.sort()
 
-        withgroups = sorted(
-            (self.graph.value(sub.uri, L9['group']),
-             self.graph.value(sub.uri, L9['order']),
-             self.graph.label(sub), # todo: split numbers into ints so they sort right
-             sub.uri, 
-             sub)
-            for sub in self.submasters.get_all_subs())
         log.info("withgroups %s", withgroups)
 
-        for group, order, sortLabel, _sortUri, sub in withgroups:
-            group = self.graph.value(sub.uri, L9['group'])
-
+        self.effectEval = {}
+        for group, order, sortLabel, effect in withgroups:
             if col == 0 or group != last_group:
                 row = self.make_row(group)
                 rowcount += 1
                 col = 0
 
-            subbox = SubmasterBox(row, sub, self.session, col, rowcount)
+            subbox = SubmasterBox(row, self.graph, effect, self.session, col, rowcount)
             subbox.place(relx=col / 8, rely=0, relwidth=1 / 8, relheight=1)
-            self.subbox[sub.uri] = self.slider_table[(rowcount, col)] = subbox
+            self.subbox[effect] = self.slider_table[(rowcount, col)] = subbox
 
             self.setup_key_nudgers(subbox.scale)
 
+            self.effectEval[effect] = EffectEval(self.graph, effect)
+
             col = (col + 1) % 8
             last_group = group
 
@@ -350,7 +347,7 @@
                 self.sliders.valueOut("button-upper%d" % col, False)
                 self.sliders.valueOut("slider%d" % col, 0)
                 continue
-            self.send_to_hw(sub=subbox.sub.uri, hwCol=col,
+            self.send_to_hw(sub=subbox.sub, hwCol=col,
                             boxRow=self.current_row)
 
     def got_nudger(self, number, direction, full=0):
@@ -434,15 +431,6 @@
             context=self.session,
             subject=sub, predicate=L9['group'], newObject=group)
 
-    def subs_in_row(self, row):
-        """uris of the submasters displayed in this row. untested"""
-        rowNum = self.rows.index(row) + 1
-        ret = set()
-        for coords, subbox in self.slider_table.items():
-            if coords[0] == rowNum:
-                ret.add(subbox.sub.uri)
-        return ret
-            
     def highlight_row(self, row):
         row = self.rows[row]
         row['bg'] = 'red'
@@ -455,29 +443,19 @@
         return dict([(uri, box.slider_var.get())
             for uri, box in self.subbox.items()])
 
-    def get_levels_as_sub(self, graph=None):
-        """this has to depend on the graph to get called back enough"""
-        if graph is None:
-            graph = self.graph
-        # but it was using all synced values accidentally
+    def get_output_settings(self):
+        outputSettings = []
         for setting in graph.objects(self.session, L9['subSetting']):
-            level = graph.value(setting, L9['level'])
-            if level != 0:
-                sub = graph.value(setting, L9['sub'])
-                for ll in graph.objects(sub, L9['lightLevel']):
-                    graph.value(ll, L9['channel'])
-                    graph.value(ll, L9['level'])
+            effect = graph.value(setting, L9['sub'])
+            strength = graph.value(setting, L9['level'])
+            outputSettings.extend(
+                self.effectEval[effect].outputFromEffect(
+                    [(L9['strength'], strength)]))
 
-        # this is the older code, which uses some local objects
-        # that are (supposedly) synced to the graph. It's a waste,
-        # since I think the previous graph code just fetched all this
-        # same data
-        scaledsubs = [self.submasters.get_sub_by_uri(sub) * level
-            for sub, level in self.get_levels().items() if level > 0.0]
-        maxes = sub_maxes(*scaledsubs)
-        return maxes
+        return outputSettings
 
     def save_current_stage(self, subname):
+        raise NotImplementedError
         log.info("saving current levels as %s", subname)
         with self.graph.currentState() as current:
             sub = self.get_levels_as_sub(graph=current)
@@ -618,15 +596,6 @@
     graph.initiallySynced.addCallback(
         lambda _: launch(opts, root, graph, session))
 
-    if 0: # needs fixing, or maybe it's obsolete because other progs can just patch the rdf graph
-        import twisted.internet
-        try:
-            reactor.listenTCP(networking.keyboardComposer.port,
-                              server.Site(LevelServerHttp(kc.name_to_subbox)))
-        except twisted.internet.error.CannotListenError, e:
-            log.warn("Can't (and won't!) start level server:")
-            log.warn(e)
-
     root.protocol('WM_DELETE_WINDOW', reactor.stop)
 
     tksupport.install(root,ms=10)
--- a/light9/effect/sequencer.py	Thu Jun 09 07:07:55 2016 +0000
+++ b/light9/effect/sequencer.py	Thu Jun 09 08:50:47 2016 +0000
@@ -5,19 +5,29 @@
 from __future__ import division
 from rdflib import URIRef, Literal
 from twisted.internet import reactor
-from twisted.internet.defer import inlineCallbacks, returnValue, succeed
-from webcolors import hex_to_rgb, rgb_to_hex
-import time, json, logging, traceback, bisect
+from webcolors import rgb_to_hex
+import json, logging, bisect
+import treq
+from light9 import networking
 
-from light9.namespaces import L9, RDF, RDFS
+from light9.namespaces import L9, RDF
+
 from light9.vidref.musictime import MusicTime
 
 log = logging.getLogger('sequencer')
 
+def sendToCollector(client, session, settings):
+    return treq.put(networking.collector.path('attrs'),
+                    data=json.dumps({'settings': settings,
+                                     'client': client,
+                                     'clientSession': session}))
+
+
 class Note(object):
     def __init__(self, graph, uri):
         g = self.graph = graph
         self.uri = uri
+        self.effectEval = EffectEval(graph, g.value(uri, L9['effectClass']))
         floatVal = lambda s, p: float(g.value(s, p).toPython())
         originTime = floatVal(uri, L9['originTime'])
         self.points = []
@@ -29,9 +39,6 @@
                     originTime + floatVal(point, L9['time']),
                     floatVal(point, L9['value'])))
             self.points.sort()
-
-        for ds in g.objects(g.value(uri, L9['effectClass']), L9['deviceSetting']):
-            self.setting = (g.value(ds, L9['device']), g.value(ds, L9['attr']))
         
     def activeAt(self, t):
         return self.points[0][0] <= t <= self.points[-1][0]
@@ -52,21 +59,52 @@
         return y
         
     def outputSettings(self, t):
+        """
+        list of (device, attr, value)
+        """
+        effectSettings = [(L9['strength'], self.evalCurve(t))]
+        return self.effectEval.outputFromEffect(self.effect, effectSettings)
+                
 
-        c = int(255 * self.evalCurve(t))
+class EffectEval(object):
+    """
+    runs one effect's code to turn effect attr settings into output
+    device settings
+    """
+    def __init__(self, graph, effect):
+        self.graph = graph
+        self.effect = effect
+        
+        #for ds in g.objects(g.value(uri, L9['effectClass']), L9['deviceSetting']):
+        #    self.setting = (g.value(ds, L9['device']), g.value(ds, L9['attr']))
+
+    def outputFromEffect(self, effectSettings):
+        """
+        From effect attr settings, like strength=0.75, to output device
+        settings like light1/bright=0.72;light2/bright=0.78. This runs
+        the effect code.
+        """
+        attr, value = effectSettings[0]
+        value = float(value)
+        assert attr == L9['strength']
+        c = int(255 * value)
         color = [0, 0, 0]
-        if self.setting[1] == L9['red']: # throwaway
+        if self.effect == L9['RedStrip']: # throwaway
             color[0] = c
-        elif self.setting[1] == L9['blue']:
+        elif self.effect == L9['BlueStrip']:
             color[2] = c
-        
+        elif self.effect == URIRef('http://light9.bigasterisk.com/effect/WorkLight'):
+            color[1] = c
+        elif self.effect == URIRef('http://light9.bigasterisk.com/effect/Curtain'):
+            color[0] = color[2] = 70/255 * c
+
         return [
             # device, attr, lev
-            (self.setting[0],
+            (URIRef('http://light9.bigasterisk.com/device/colorStrip'),
              URIRef("http://light9.bigasterisk.com/color"),
              Literal(rgb_to_hex(color)))
             ]
-            
+        
 class Sequencer(object):
     def __init__(self, graph, sendToCollector):
         self.graph = graph
@@ -89,7 +127,7 @@
         reactor.callLater(1/30, self.update)
 
         musicState = self.music.getLatest()
-        song = URIRef(musicState['song']) if 'song' in musicState else None
+        song = URIRef(musicState['song']) if musicState.get('song') else None
         if 't' not in musicState:
             return
         t = musicState['t']
--- a/light9/subclient.py	Thu Jun 09 07:07:55 2016 +0000
+++ b/light9/subclient.py	Thu Jun 09 08:50:47 2016 +0000
@@ -1,4 +1,4 @@
-from light9 import dmxclient
+from light9.effect.sequencer import sendToCollector
 
 class SubClient:
     def __init__(self):
@@ -19,6 +19,6 @@
         self.after(delay, self.send_levels_loop, delay)
 
     def _send_sub(self):
-        maxes = self.get_levels_as_sub()
-        levels = maxes.get_dmx_list()
-        dmxclient.outputlevels(levels)
+        outputSettings = self.get_output_settings()
+        print outputSettings
+        sendToCollector('subclient', self.session, outputSettings)