Changeset - 4a7594476905
[Not reviewed]
default
0 4 0
Drew Perttula - 9 years ago 2016-06-09 08:50:47
drewp@bigasterisk.com
hack up KC so it edits effect strengths instead
Ignore-this: 65706b0434cb635d1cf9a7e92a867df9
4 files changed with 106 insertions and 104 deletions:
0 comments (0 inline, 0 general)
bin/effectsequencer
Show inline comments
 
@@ -9,15 +9,13 @@ from light9.greplin_cyclone import Stats
 
from light9.rdfdb.syncedgraph import SyncedGraph
 
from light9 import networking, showconfig
 
from greplin import scales
 
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):
 
    def __init__(self, show, session):
 
        self.show = show
 
        self.session = session
 
@@ -32,19 +30,16 @@ class App(object):
 
                                       scales.PmfStat('evals'),
 
                                       scales.PmfStat('sendOutput'),
 
                                       scales.IntStat('errors'),
 
                                       )
 
    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),
 
        ],
 
                                                  debug=True,
 
                                                  graph=self.graph,
bin/keyboardcomposer
Show inline comments
 
@@ -4,26 +4,25 @@ from __future__ import division, nested_
 
from run_local import log
 
import cgi, time, logging
 
from optparse import OptionParser
 
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
 
from light9.namespaces import L9
 
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
 

	
 
nudge_keys = {
 
    'up'   : list('qwertyui'),
 
    'down' : list('asdfghjk')
 
@@ -58,101 +57,102 @@ class SubScale(tk.Scale, Fadable):
 

	
 
class SubmasterBox(tk.Frame):
 
    """
 
    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",
 
            bg='black', fg='white', pady=0)
 
        levellabel.pack(side=tk.TOP)
 
        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)
 

	
 
    def cleanup(self):
 
        self.slider_var.trace_vdelete('w', self._slider_var_trace)
 

	
 
    def slider_changed(self, *args):
 
        self.scale.draw_indicator_colors()
 

	
 
        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):
 
        """in our per-session graph, we maintain SubSetting objects like this:
 

	
 
           ?session :subSetting [a :SubSetting; :sub ?s; :level ?l]
 
        """
 
        # 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']))
 
                finally:
 
                    self.pauseTrace = False
 

	
 
    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,
 
                 hw_sliders=True):
 
        tk.Frame.__init__(self, root, bg='black')
 
        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
 

	
 
        self.current_row = 0 # should come from session graph
 

	
 
@@ -205,45 +205,42 @@ class KeyboardComposer(tk.Frame, SubClie
 
            b.cleanup()
 
        self.subbox.clear()
 
        self.slider_table.clear()
 

	
 
        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
 

	
 
    def toggle_slider_connectedness(self):
 
        self.use_hw_sliders = not self.use_hw_sliders
 
        if self.use_hw_sliders:
 
@@ -347,13 +344,13 @@ class KeyboardComposer(tk.Frame, SubClie
 
            except KeyError:
 
                # unfilled bottom row has holes (plus rows with incomplete
 
                # groups
 
                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):
 
        try:
 
            subbox = self.slider_table[(self.current_row, number)]
 
        except KeyError:
 
@@ -431,56 +428,37 @@ class KeyboardComposer(tk.Frame, SubClie
 
        this sub displays in this row"""
 
        group = row.subGroup
 
        self.graph.patchObject(
 
            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'
 

	
 
    def unhighlight_row(self, row):
 
        row = self.rows[row]
 
        row['bg'] = 'black'
 

	
 
    def get_levels(self):
 
        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)
 

	
 
        log.debug("new sub for reference: %r", sub)
 

	
 
@@ -615,19 +593,10 @@ if __name__ == "__main__":
 

	
 
    session = clientsession.getUri('keyboardcomposer', opts)
 

	
 
    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)
 
    prof.run(reactor.run, profile=None)
light9/effect/sequencer.py
Show inline comments
 
@@ -2,39 +2,46 @@
 
copies from effectloop.py, which this should replace
 
'''
 

	
 
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 = []
 
        for curve in g.objects(uri, L9['curve']):
 
            if g.value(curve, L9['attr']) != L9['strength']:
 
                continue
 
            for point in g.objects(curve, L9['point']):
 
                self.points.append((
 
                    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]
 

	
 
    def evalCurve(self, t):
 
        i = bisect.bisect_left(self.points, (t, None)) - 1
 
@@ -49,27 +56,58 @@ class Note(object):
 
        p1, p2 = self.points[i], self.points[i + 1]
 
        frac = (t - p1[0]) / (p2[0] - p1[0])
 
        y = p1[1] + (p2[1] - p1[1]) * frac
 
        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
 
        self.sendToCollector = sendToCollector
 
        self.music = MusicTime(period=.2, pollCurvecalc=False)
 

	
 
@@ -86,13 +124,13 @@ class Sequencer(object):
 
                self.notes[song].append(Note(g, note))
 
        
 
    def update(self):
 
        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']
 

	
 
        settings = []
 
        
light9/subclient.py
Show inline comments
 
from light9 import dmxclient
 
from light9.effect.sequencer import sendToCollector
 

	
 
class SubClient:
 
    def __init__(self):
 
        """assumed that your init saves self.graph"""
 
        pass # we may later need init code for network setup
 

	
 
@@ -16,9 +16,9 @@ class SubClient:
 
        """This function assumes that we are an instance of a Tk object
 
        (or at least that we have an 'after' method)"""
 
        self.graph.addHandler(self.send_levels)
 
        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)
0 comments (0 inline, 0 general)