Changeset - 512381de45bd
[Not reviewed]
default
0 9 0
Drew Perttula - 11 years ago 2014-06-10 08:48:34
drewp@bigasterisk.com
effectclasss in subserver. multiline code suppport (except for evaulation). add some old effect funcs to the new evaluator
Ignore-this: c9bb7359c851bc7c43fb48c50bf41d79
9 files changed with 180 insertions and 105 deletions:
0 comments (0 inline, 0 general)
bin/effecteval
Show inline comments
 
@@ -4,7 +4,7 @@ from run_local import log
 
from twisted.internet import reactor
 
from twisted.internet.defer import inlineCallbacks, returnValue
 
import cyclone.web, cyclone.websocket, cyclone.httpclient
 
import sys, optparse, logging, subprocess, json, time, traceback
 
import sys, optparse, logging, subprocess, json, time, traceback, itertools
 
from rdflib import URIRef, Literal
 

	
 
sys.path.append(".")
 
@@ -39,24 +39,39 @@ class SongEffects(PrettyErrorHandler, cy
 
        ctx = song
 
        graph = self.settings.graph
 
        effect = graph.sequentialUri(song + "/effect-")
 
        curve = graph.sequentialUri(song + "/curve-")
 
        quads = [
 
            (song, L9['effect'], effect, ctx),
 
            (effect, RDF.type, L9['Effect'], ctx),
 
        ]
 

	
 
        with graph.currentState(
 
                tripleFilter=(dropped, RDFS.label, None)) as g:
 
            droppedSubLabel = g.label(dropped)
 
                tripleFilter=(dropped, None, None)) as g:
 
            droppedTypes = g.objects(dropped, RDF.type)
 
            droppedLabel = g.label(dropped)
 
            droppedCodes = g.objects(dropped, L9['code'])
 

	
 
        if L9['EffectClass'] in droppedTypes:
 
            quads.extend([
 
                (effect, RDFS.label, droppedLabel, ctx),
 
                (effect, RDF.type, dropped, ctx),
 
                ] + [(effect, L9['code'], c, ctx) for c in droppedCodes])
 
        elif L9['Curve'] in droppedTypes:
 
            curve = graph.sequentialUri(song + "/curve-")
 
            cr = CurveResource(graph, curve)
 
            cr.newCurve(ctx, label=Literal('sub %s' % droppedLabel))
 
            cr.saveCurve()
 
            quads.extend([
 
                (song, L9['curve'], curve, ctx),                
 
                (effect, L9['code'],
 
                 Literal('out = %s * %s' % (dropped.n3(), curve.n3())),
 
                 ctx),
 
                ])
 
        else:
 
            raise NotImplementedError(
 
                "don't know how to add an effect from %r (types=%r)" %
 
                (dropped, droppedTypes))
 
            
 
        cr = CurveResource(graph, curve)
 
        cr.newCurve(ctx, label=Literal('sub %s' % droppedSubLabel))
 
        cr.saveCurve()
 
        graph.patch(Patch(addQuads=[
 
            (song, L9['curve'], curve, ctx),
 
            (song, L9['effect'], effect, ctx),
 
            (effect, RDF.type, L9['Effect'], ctx),
 
            (effect, L9['code'],
 
             Literal('out = %s * %s' % (dropped.n3(), curve.n3())),
 
             ctx),
 
            ]))
 
        
 
        graph.patch(Patch(addQuads=quads))
 
        
 
class SongEffectsUpdates(cyclone.websocket.WebSocketHandler):
 
    def connectionMade(self, *args, **kwargs):
 
@@ -89,7 +104,8 @@ class EffectUpdates(cyclone.websocket.We
 
    def updateClient(self):
 
        # todo: if client has dropped, abort and don't get any more
 
        # graph updates
 
        self.sendMessage({'code': self.graph.value(self.uri, L9['code'])})
 
        self.sendMessage({'codeLines':
 
                          list(self.graph.objects(self.uri, L9['code']))})
 
        
 
    def connectionLost(self, reason):
 
        log.info("websocket closed")
 
@@ -98,18 +114,41 @@ class EffectUpdates(cyclone.websocket.We
 
        log.info("got message %s" % message)
 
        # write a patch back to the graph
 

	
 
def replaceObjects(graph, c, s, p, newObjs):
 
    patch = graph.getObjectPatch(
 
        context=c,
 
        subject=s,
 
        predicate=p,
 
        newObject=newObjs[0])
 

	
 
    moreAdds = []
 
    for line in newObjs[1:]:
 
        moreAdds.append((s, p, line, c))
 
    fullPatch = Patch(delQuads=patch.delQuads,
 
                      addQuads=patch.addQuads + moreAdds)
 
    graph.patch(fullPatch)
 

	
 
        
 
class Code(PrettyErrorHandler, cyclone.web.RequestHandler):
 
    def put(self):
 
        effect = URIRef(self.get_argument('uri'))
 
        code = Literal(self.get_argument('code'))
 
        codeLines = []
 
        for i in itertools.count(0):
 
            k = 'codeLines[%s][text]' % i
 
            v = self.get_argument(k, None)
 
            if v is not None:
 
                codeLines.append(Literal(v))
 
            else:
 
                break
 
        if not codeLines:
 
            log.info("no codelines recevied on PUT /code")
 
            return
 
        with self.settings.graph.currentState(
 
                tripleFilter=(None, L9['effect'], effect)) as g:
 
            song = g.subjects(L9['effect'], effect).next()
 
        self.settings.graph.patchObject(
 
            context=song,
 
            subject=effect,
 
            predicate=L9['code'],
 
            newObject=code)
 
            
 
        replaceObjects(self.settings.graph, song, effect, L9['code'], codeLines)
 
            
 
        # right here we could tell if the code has a python error and return it
 
        self.send_error(202)
 
        
light9/Effects.py
Show inline comments
 
from __future__ import division
 
from random import Random
 
import random as random_mod
 
import math
 
import logging, colorsys
 
import light9.Submaster as Submaster
 
from chase import chase as chase_logic
 
@@ -9,12 +10,18 @@ from light9 import Patch
 
from light9.namespaces import L9
 
log = logging.getLogger()
 

	
 
registered = []
 
def register(f):
 
    registered.append(f)
 
    return f
 

	
 
@register
 
def chase(t, ontime=0.5, offset=0.2, onval=1.0, 
 
          offval=0.0, names=None, combiner=max, random=False):
 
    """names is list of URIs. returns a submaster that chases through
 
    the inputs"""
 
    if random:
 
        r = Random(random)
 
        r = random_mod.Random(random)
 
        names = names[:]
 
        r.shuffle(names)
 

	
 
@@ -31,6 +38,7 @@ def chase(t, ontime=0.5, offset=0.2, onv
 

	
 
    return Submaster.Submaster(name="chase" ,levels=lev)
 

	
 
@register
 
def hsv(h, s, v, light='all', centerScale=.5):
 
    r,g,b = colorsys.hsv_to_rgb(h % 1.0, s, v)
 
    lev = {}
 
@@ -41,7 +49,8 @@ def hsv(h, s, v, light='all', centerScal
 
    if light in ['center', 'all']:
 
        lev[88], lev[89], lev[90] = r*centerScale,g*centerScale,b*centerScale
 
    return Submaster.Submaster(name='hsv', levels=lev)
 
    
 

	
 
@register
 
def stack(t, names=None, fade=0):
 
    """names is list of URIs. returns a submaster that stacks the the inputs
 

	
 
@@ -65,6 +74,10 @@ def stack(t, names=None, fade=0):
 
    
 
    return Submaster.Submaster(name="stack", levels=lev)
 

	
 
@register
 
def smoove(x):
 
    return -2 * (x ** 3) + 3 * (x ** 2)
 
    
 
def configExprGlobals():
 
    graph = showconfig.getGraph()
 
    ret = {}
 
@@ -75,7 +88,35 @@ def configExprGlobals():
 
        ret[shortName] = list(graph.items(chans))
 
        print "%r is a chase" % shortName
 

	
 
    ret['chase'] = chase
 
    ret['stack'] = stack
 
    ret['hsv'] = hsv
 
    for f in registered:
 
        ret[f.__name__] = f
 

	
 
    ret['nsin'] = lambda x: (math.sin(x * (2 * math.pi)) + 1) / 2
 
    ret['ncos'] = lambda x: (math.cos(x * (2 * math.pi)) + 1) / 2
 

	
 
    _smooth_random_items = [random_mod.random() for x in range(100)]
 

	
 
    # suffix '2' to keep backcompat with the versions that magically knew time
 
    def smooth_random2(t, speed=1):
 
        """1 = new stuff each second, <1 is slower, fade-ier"""
 
        x = (t * speed) % len(_smooth_random_items)
 
        x1 = int(x)
 
        x2 = (int(x) + 1) % len(_smooth_random_items)
 
        y1 = _smooth_random_items[x1]
 
        y2 = _smooth_random_items[x2]
 
        return y1 + (y2 - y1) * ((x - x1))
 

	
 
    def notch_random2(t, speed=1):
 
        """1 = new stuff each second, <1 is slower, notch-ier"""
 
        x = (t * speed) % len(_smooth_random_items)
 
        x1 = int(x)
 
        y1 = _smooth_random_items[x1]
 
        return y1
 

	
 
    ret['noise2'] = smooth_random2
 
    ret['notch2'] = notch_random2
 

	
 

	
 

	
 
    
 
    return ret
light9/curvecalc/subterm.py
Show inline comments
 
@@ -13,7 +13,6 @@ class Expr(object):
 
    e.g. chases"""
 
    def __init__(self):
 
        self.effectGlobals = light9.Effects.configExprGlobals()
 
        self._smooth_random_items = [random.random() for x in range(100)]
 
    
 
    def exprGlobals(self, startDict, t):
 
        """globals dict for use by expressions"""
 
@@ -23,50 +22,27 @@ class Expr(object):
 
        # add in functions from Effects
 
        glo.update(self.effectGlobals)
 

	
 
        glo['nsin'] = lambda x: (math.sin(x * (2 * math.pi)) + 1) / 2
 
        glo['ncos'] = lambda x: (math.cos(x * (2 * math.pi)) + 1) / 2
 
        def chan(name):
 
            return Submaster.Submaster(
 
                name=name,
 
                levels={get_dmx_channel(name) : 1.0})
 
        glo['chan'] = chan
 
        glo['within'] = lambda a, b: a < t < b
 
        glo['bef'] = lambda x: t < x
 

	
 

	
 
        def smoove(x):
 
            return -2 * (x ** 3) + 3 * (x ** 2)
 
        glo['smoove'] = smoove
 

	
 
        def aft(t, x, smooth=0):
 
            left = x - smooth / 2
 
            right = x + smooth / 2
 
            if left < t < right:
 
                return smoove((t - left) / (right - left))
 
                return light9.Effects.smoove((t - left) / (right - left))
 
            return t > x
 
        glo['aft'] = lambda x, smooth=0: aft(t, x, smooth)
 

	
 
        def chan(name):
 
            return Submaster.Submaster(
 
                name=name,
 
                levels={get_dmx_channel(name) : 1.0})
 
        glo['chan'] = chan
 

	
 
        def smooth_random(speed=1):
 
            """1 = new stuff each second, <1 is slower, fade-ier"""
 
            x = (t * speed) % len(self._smooth_random_items)
 
            x1 = int(x)
 
            x2 = (int(x) + 1) % len(self._smooth_random_items)
 
            y1 = self._smooth_random_items[x1]
 
            y2 = self._smooth_random_items[x2]
 
            return y1 + (y2 - y1) * ((x - x1))
 

	
 
        def notch_random(speed=1):
 
            """1 = new stuff each second, <1 is slower, notch-ier"""
 
            x = (t * speed) % len(self._smooth_random_items)
 
            x1 = int(x)
 
            y1 = self._smooth_random_items[x1]
 
            return y1
 
            
 
        glo['noise'] = smooth_random
 
        glo['notch'] = notch_random
 

	
 
        glo['smooth_random'] = lambda speed=1: glo['smooth_random2'](t, speed)
 
        glo['notch_random'] = lambda speed=1: glo['notch_random2'](t, speed)
 
        
 
        glo['noise'] = glo['smooth_random']
 
        glo['notch'] = glo['notch_random']
 

	
 
        return glo
 

	
light9/effecteval/effect.coffee
Show inline comments
 
qs = new QueryString()
 
model =
 
  uri: ko.observable(qs.value('uri'))
 
  code: ko.observable()
 
  toSave: 
 
    uri: ko.observable(qs.value('uri'))
 
    codeLines: ko.observableArray([])
 
  
 
socket = reconnectingWebSocket "ws://localhost:8070/effectUpdates" + window.location.search, (msg) ->
 
  console.log('effectData ' + JSON.stringify(msg))
 
  # there's a shorter unpack thing
 
    
 
  model.code(msg.code)
 
  
 
writeBack = ko.computed ->
 
  model.toSave.codeLines(msg.codeLines.map((x) -> {text: ko.observable(x)})) if msg.codeLines?
 

	
 
model.saveCode = ->
 
  $.ajax
 
    type: 'PUT'
 
    url: 'code'
 
    data: {uri: model.uri(), code: model.code()}
 
    data: ko.toJS(model.toSave)
 

	
 
writeBack = ko.computed(model.saveCode)
 
  
 
ko.applyBindings(model)
 
  
 
\ No newline at end of file
light9/effecteval/effect.html
Show inline comments
 
@@ -7,9 +7,14 @@
 

	
 
  </head>
 
  <body>
 
    <a href="./">Effects</a> / <a class="effect" data-bind="attr: {href: uri}, text: uri"></a>
 
    <a href="./">Effects</a> / <a class="effect" data-bind="attr: {href: toSave.uri}, text: toSave.uri"></a>
 

	
 
    <div>code: <input type="text" size="160" data-bind="value: code"></input></div>
 
    <div data-bind="foreach: toSave.codeLines">
 
      <div>
 
        code:
 
        <input type="text" size="160" data-bind="value: text"></input>
 
      </div>
 
    </div>
 
    
 
    <script src="static/jquery-2.1.1.min.js"></script>
 
    <script src="static/knockout-3.1.0.js"></script>
light9/effecteval/effect.py
Show inline comments
 
@@ -2,7 +2,7 @@ import re, logging
 
from rdflib import URIRef
 
from light9.namespaces import L9, RDF
 
from light9.curvecalc.curve import CurveResource
 
from light9 import Submaster
 
from light9 import Submaster, Effects
 
log = logging.getLogger('effect')
 

	
 
def uriFromCode(s):
 
@@ -23,6 +23,7 @@ class CodeLine(object):
 
        self.graph, self.code = graph, code
 

	
 
        self.outName, self.expr, self.resources = self._asPython()
 
        self.pyResources = self._resourcesAsPython(self.resources)
 

	
 
    def _asPython(self):
 
        """
 
@@ -55,32 +56,14 @@ class CodeLine(object):
 
        # this result could vary with graph changes (rare)
 
        return self.graph.contains((uri, RDF.type, L9['Curve']))
 
        
 
class EffectNode(object):
 
    def __init__(self, graph, uri):
 
        self.graph, self.uri = graph, uri
 
        # this is not expiring at the right time, when an effect goes away
 
        self.graph.addHandler(self.prepare)
 

	
 
    def prepare(self):
 
        log.info("prepare effect %s", self.uri)
 
        # maybe there can be multiple lines of code as multiple
 
        # objects here, and we sort them by dependencies
 
        codeStr = self.graph.value(self.uri, L9['code'])
 
        if codeStr is None:
 
            raise ValueError("effect %s has no code" % self.uri)
 

	
 
        self.code = CodeLine(self.graph, codeStr)
 

	
 
        self.resourceMap = self.resourcesAsPython()
 
        
 
    def resourcesAsPython(self):
 
    def _resourcesAsPython(self, resources):
 
        """
 
        mapping of the local names for uris in the code to high-level
 
        objects (Submaster, Curve)
 
        """
 
        out = {}
 
        subs = Submaster.get_global_submasters(self.graph)
 
        for localVar, uri in self.code.resources.items():
 
        for localVar, uri in resources.items():
 
            for rdfClass in self.graph.objects(uri, RDF.type):
 
                if rdfClass == L9['Curve']:
 
                    cr = CurveResource(self.graph, uri)
 
@@ -92,21 +75,39 @@ class EffectNode(object):
 
                    out[localVar] = uri
 

	
 
        return out
 
        
 

	
 
class EffectNode(object):
 
    def __init__(self, graph, uri):
 
        self.graph, self.uri = graph, uri
 
        # this is not expiring at the right time, when an effect goes away
 
        self.graph.addHandler(self.prepare)
 

	
 
    def prepare(self):
 
        log.info("prepare effect %s", self.uri)
 
        # maybe there can be multiple lines of code as multiple
 
        # objects here, and we sort them by dependencies
 
        codeStrs = list(self.graph.objects(self.uri, L9['code']))
 
        if not codeStrs:
 
            raise ValueError("effect %s has no code" % self.uri)
 

	
 
        self.codes = [CodeLine(self.graph, s) for s in codeStrs]
 

	
 
        self.otherFuncs = Effects.configExprGlobals()
 
                
 
    def eval(self, songTime):
 
        ns = {'t': songTime}
 
        ns.update(self.otherFuncs)
 

	
 
        ns.update(dict(
 
            curve=lambda c, t: c.eval(t),
 
            ))
 
        # loop over lines in order, merging in outputs 
 
        # merge in named outputs from previous lines
 
        
 
        ns.update(self.resourceMap)
 
        return eval(self.code.expr, ns)
 

	
 

	
 
class GraphAwareFunction(object):
 
    def __init__(self, graph):
 
        self.graph = graph
 
        for c in self.codes:
 
            codeNs = ns.copy()
 
            codeNs.update(c.pyResources)
 
            if c.outName == 'out':
 
                out = eval(c.expr, codeNs)
 
        return out
 

	
light9/effecteval/effectloop.py
Show inline comments
 
@@ -79,7 +79,8 @@ class EffectLoop(object):
 
                        if now > self.lastErrorLog + 5:
 
                            log.error("effect %s: %s" % (e.uri, exc))
 
                            log.error("  expr: %s", e.code.expr)
 
                            log.error("  resources: %r", e.resourcesAsPython())
 
                            log.error("  resources: %r",
 
                                      getattr(e, 'resourceMap', '?'))
 
                            self.lastErrorLog = now
 
                out = Submaster.sub_maxes(*outSubs)
 

	
light9/subserver/effects.coffee
Show inline comments
 
class Model
 
  constructor: ->
 
    @classes = ko.observable([])
 
    
 
    @chases = ko.observable([])
 
    @moreExprs = [
 
      {label: "rainbow", expr: "hsv(t*1,1,1)"},
 
@@ -22,12 +24,11 @@ class Model
 
    ]
 
  
 

	
 

	
 
model = new Model()
 

	
 

	
 
reconnectingWebSocket "ws://localhost:8052/effectsUpdates", (msg) ->
 
  model.chases(msg.chases) if msg.chases?
 
  model.classes(msg.classes) if msg.classes?
 

	
 
# this sort of works to stop clicks in <input> from following the
 
# submaster hyperlink, but it may make certain clicks act wrong
light9/subserver/effects.jade
Show inline comments
 
@@ -19,6 +19,16 @@ html
 
        ul(data-bind="foreach: $parent.subtermExprs($data)")
 
          li: a.resource(data-bind="attr: {href: $root.subtermLink($parent.label, $data)}, text: $data")
 

	
 

	
 
    div(data-bind="foreach: classes")
 
      div.resource.effectClass
 
        h2
 
          | Effect class
 
          | 
 
          a(data-bind="attr: {href: uri}, text: label")
 
        div
 
          code(data-bind="text: code")
 
        
 
    #status
 
      
 
    script(src="static/jquery-2.1.1.min.js")
0 comments (0 inline, 0 general)