diff --git a/bin/effecteval b/bin/effecteval --- a/bin/effecteval +++ b/bin/effecteval @@ -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) diff --git a/light9/Effects.py b/light9/Effects.py --- a/light9/Effects.py +++ b/light9/Effects.py @@ -1,5 +1,6 @@ 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 diff --git a/light9/curvecalc/subterm.py b/light9/curvecalc/subterm.py --- a/light9/curvecalc/subterm.py +++ b/light9/curvecalc/subterm.py @@ -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 diff --git a/light9/effecteval/effect.coffee b/light9/effecteval/effect.coffee --- a/light9/effecteval/effect.coffee +++ b/light9/effecteval/effect.coffee @@ -1,19 +1,20 @@ 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 diff --git a/light9/effecteval/effect.html b/light9/effecteval/effect.html --- a/light9/effecteval/effect.html +++ b/light9/effecteval/effect.html @@ -7,9 +7,14 @@
- Effects / + Effects / -