changeset 1044:a2081b9adfe4

effecteval now takes dropped subs and makes new effects out of them Ignore-this: 69423eca1858291f486dd2a202d3732a
author Drew Perttula <drewp@bigasterisk.com>
date Wed, 28 May 2014 07:37:36 +0000
parents aa45e5379c5a
children c1face79c0e1
files bin/effecteval light9/curvecalc/curve.py light9/effecteval/effect.html light9/effecteval/index.coffee light9/effecteval/index.html light9/namespaces.py static/style.css
diffstat 7 files changed, 133 insertions(+), 42 deletions(-) [+]
line wrap: on
line diff
--- a/bin/effecteval	Wed May 28 05:57:08 2014 +0000
+++ b/bin/effecteval	Wed May 28 07:37:36 2014 +0000
@@ -3,14 +3,15 @@
 from twisted.internet import reactor, task
 from twisted.internet.defer import inlineCallbacks
 import cyclone.web, cyclone.websocket, cyclone.httpclient
-import sys, optparse, logging, subprocess, json, re, time
-from rdflib import URIRef, RDF
+import sys, optparse, logging, subprocess, json, re, time, traceback
+from rdflib import URIRef, RDF, Literal
 
 sys.path.append(".")
 from light9 import networking, showconfig, Submaster, dmxclient
 from light9.rdfdb.syncedgraph import SyncedGraph
 from light9.curvecalc.curve import Curve
-from light9.namespaces import L9, DCTERMS
+from light9.namespaces import L9, DCTERMS, RDF
+from light9.rdfdb.patch import Patch
 
 sys.path.append("/my/proj/homeauto/lib")
 sys.path.append("/home/drewp/projects/homeauto/lib")
@@ -20,6 +21,24 @@
     def get(self):
         self.write(open("light9/effecteval/effect.html").read())
 
+class SongEffects(PrettyErrorHandler, cyclone.web.RequestHandler):
+    def post(self):
+        song = URIRef(self.get_argument('uri'))
+        drop = URIRef(self.get_argument('drop'))
+        ctx = song
+        now = time.time()
+        effect = song + "/effect/e-%f" % now
+        curve = song + "/curve/c-%f" % now
+        self.settings.graph.patch(Patch(addQuads=[
+            (song, L9['effect'], effect, ctx),
+            (effect, RDF.type, L9['Effect'], ctx),
+            (effect, L9['code'],
+             Literal('out = sub(%s, intensity=%s)' % (drop.n3(), curve.n3())),
+             ctx),
+            (curve, RDF.type, L9['Curve'], ctx),
+            (curve, L9['points'], Literal('0 0'), ctx),
+            ]))
+        
 class SongEffectsUpdates(cyclone.websocket.WebSocketHandler):
     def connectionMade(self, *args, **kwargs):
         self.graph = self.settings.graph
@@ -66,6 +85,8 @@
         return URIRef('http://light9.bigasterisk.com/show/dance2014/sub/' + s[4:])
     if s.startswith('song1:'):
         return URIRef('http://ex/effect/song1/' + s[6:])
+    if (s[0], s[-1]) == ('<', '>'):
+        return URIRef(s[1:-1])
     raise NotImplementedError
         
 class EffectNode(object):
@@ -88,8 +109,12 @@
         # read from disk ok? how do we know to reread? start with
         # mtime. the mtime check could be done occasionally so on
         # average we read at most one curve's mtime per effectLoop.       
-        
-        self.curve.set_from_string(self.graph.value(intensityCurve, L9['points']))
+
+        pts = self.graph.value(intensityCurve, L9['points'])
+        if pts is None:
+            log.info("curve %r has no points" % intensityCurve)
+        else:
+            self.curve.set_from_string(pts)
 
         
     def eval(self, songTime):
@@ -127,32 +152,36 @@
 @inlineCallbacks
 def effectLoop(graph):
     t1 = time.time()
-    response = json.loads((yield cyclone.httpclient.fetch(
-        networking.musicPlayer.path('time'))).body)
-    if response['song'] is not None:
-        song = URIRef(response['song'])
-        songTime = response['t']
-        # Possibilities to make this shut up about graph copies:
-        # - implement the cheap readonly currentState response
-        # - do multiple little currentState calls (in this code) over just
-        #   the required triples
-        # - use addHandler instead and only fire dmx when there is a data
-        #   change (and also somehow call it when there is a time change)
+    try:
+        response = json.loads((yield cyclone.httpclient.fetch(
+            networking.musicPlayer.path('time'))).body)
+        if response['song'] is not None:
+            song = URIRef(response['song'])
+            songTime = response['t']
+            # Possibilities to make this shut up about graph copies:
+            # - implement the cheap readonly currentState response
+            # - do multiple little currentState calls (in this code) over just
+            #   the required triples
+            # - use addHandler instead and only fire dmx when there is a data
+            #   change (and also somehow call it when there is a time change)
 
-        outSubs = []
-        with graph.currentState(tripleFilter=(song, L9['effect'], None)) as g:
-            for effectUri in g.objects(song, L9['effect']):
-                # these should be built once, not per (frequent) update
-                node = EffectNode(graph, effectUri)
-                outSubs.append(node.eval(songTime))
-            out = Submaster.sub_maxes(*outSubs)
-            # out.get_levels() for a more readable view
-            dmx = out.get_dmx_list()
+            outSubs = []
+            with graph.currentState(tripleFilter=(song, L9['effect'], None)) as g:
+                for effectUri in g.objects(song, L9['effect']):
+                    # these should be built once, not per (frequent) update
+                    node = EffectNode(graph, effectUri)
+                    outSubs.append(node.eval(songTime))
+                out = Submaster.sub_maxes(*outSubs)
+                # out.get_levels() for a more readable view
+                dmx = out.get_dmx_list()
 
-            if log.isEnabledFor(logging.DEBUG):
-                log.debug("send dmx: %r", out.get_levels())
-            yield dmxclient.outputlevels(dmx, twisted=True)
-
+                if log.isEnabledFor(logging.DEBUG):
+                    log.debug("send dmx: %r", out.get_levels())
+                yield dmxclient.outputlevels(dmx, twisted=True)
+    except Exception:
+        traceback.print_exc()
+        time.sleep(1)
+        
     loopTime = time.time() - t1
     log.debug('loopTime %.1f ms', 1000 * loopTime)
     
@@ -176,6 +205,7 @@
             (r'/songEffectsUpdates', SongEffectsUpdates),
             (r'/static/(.*)', SFH, {'path': 'static/'}),
             (r'/effect/eval', EffectEval),
+            (r'/songEffects', SongEffects),
             (r'/songEffects/eval', SongEffectsEval),
         ], debug=True, graph=self.graph)
         reactor.listenTCP(networking.effectEval.port, self.cycloneApp)
--- a/light9/curvecalc/curve.py	Wed May 28 05:57:08 2014 +0000
+++ b/light9/curvecalc/curve.py	Wed May 28 07:37:36 2014 +0000
@@ -62,6 +62,8 @@
     def eval(self, t, allow_muting=True):
         if self.muted and allow_muting:
             return 0
+        if not self.points:
+            raise ValueError("curve has no points")
         i = bisect_left(self.points,(t,None))-1
 
         if i == -1:
--- a/light9/effecteval/effect.html	Wed May 28 05:57:08 2014 +0000
+++ b/light9/effecteval/effect.html	Wed May 28 07:37:36 2014 +0000
@@ -3,11 +3,13 @@
   <head>
     <title>effect</title>
     <meta charset="utf-8" />
+    <link rel="stylesheet" href="static/style.css">
+
   </head>
   <body>
-    <a href="./">Effects</a> / <a data-bind="attr: {href: uri}, text: uri"></a>
+    <a href="./">Effects</a> / <a class="effect" data-bind="attr: {href: uri}, text: uri"></a>
 
-    <div>code: <input type="text" size="100" data-bind="value: code"></input></div>
+    <div>code: <input type="text" size="160" data-bind="value: code"></input></div>
     
     <script src="static/jquery-2.1.1.min.js"></script>
     <script src="static/knockout-3.1.0.js"></script>
--- a/light9/effecteval/index.coffee	Wed May 28 05:57:08 2014 +0000
+++ b/light9/effecteval/index.coffee	Wed May 28 07:37:36 2014 +0000
@@ -1,6 +1,17 @@
 model =
   songs: ko.observableArray([])
 
+model.dragover = (obj, event) ->
+  event.preventDefault()
+  event.originalEvent.dataTransfer.dropEffect = 'copy'
+
+model.drop = (uri, event) ->
+  event.preventDefault()
+  dropped(uri, event.originalEvent.dataTransfer.getData('text/uri-list'))
+
+dropped = (songTargetUri, dropUri) ->
+  $.post('songEffects', {uri: songTargetUri, drop: dropUri})
+
 reconnectingWebSocket "ws://localhost:8070/songEffectsUpdates", (msg) ->
   console.log(msg.songs)
   model.songs(msg.songs)
--- a/light9/effecteval/index.html	Wed May 28 05:57:08 2014 +0000
+++ b/light9/effecteval/index.html	Wed May 28 07:37:36 2014 +0000
@@ -3,17 +3,21 @@
   <head>
     <title>effecteval</title>
     <meta charset="utf-8" />
+    <link rel="stylesheet" href="static/style.css">
   </head>
   <body>
-    All effect instances:
+    <h1>All effect instances</h1>
     <!-- subscribe to a query of all effects and their songs -->
-    <ul data-bind="foreach: songs">
+    <ul data-bind="foreach: songs" class="twoColList">
       <li>
-        <a data-bind="attr: {href: uri}">Song <span data-bind="text: label"></span></a>
+        <a class="song" data-bind="attr: {href: uri}">Song <span data-bind="text: label"></span></a>
+        <ul>
+          <!-- ko foreach: effects -->
+          <li><a class="effect" data-bind="attr: {href: 'effect?'+jQuery.param({uri: $data})}, text: $data"></a></li>
+          <!-- /ko -->
+          <li class="dropTarget" data-bind="event: {dragover: $root.dragover, dragenter: $root.dragover, drop: function(data, event) { $root.drop(uri, event); }}">Add another (drop a sub or effect class)</li>
+        </ul>
       </li>
-      <ul data-bind="foreach: effects">
-        <li><a data-bind="attr: {href: 'effect?'+jQuery.param({uri: $data})}, text: $data"></a></li>
-      </ul>
     </ul>
     <script src="static/jquery-2.1.1.min.js"></script>
     <script src="static/knockout-3.1.0.js"></script>
--- a/light9/namespaces.py	Wed May 28 05:57:08 2014 +0000
+++ b/light9/namespaces.py	Wed May 28 07:37:36 2014 +0000
@@ -1,4 +1,4 @@
-from rdflib import Namespace
+from rdflib import Namespace, RDF
 
 L9 = Namespace("http://light9.bigasterisk.com/")
 MUS = Namespace("http://light9.bigasterisk.com/music/")
--- a/static/style.css	Wed May 28 05:57:08 2014 +0000
+++ b/static/style.css	Wed May 28 07:37:36 2014 +0000
@@ -3,6 +3,13 @@
     color: white;
     font-family: sans-serif;
 }
+
+
+a {
+color: rgb(97, 97, 255);
+
+    }
+
 .songs {
     -moz-column-width:205px;
     -webkit-column-width:205px;
@@ -46,7 +53,42 @@
     opacity: .5;
 }
 .num {
-font-size: 27px;
-color: rgb(233, 122, 122);
-display: inline-block; 
-    }
\ No newline at end of file
+    font-size: 27px;
+    color: rgb(233, 122, 122);
+    display: inline-block; 
+}
+
+.dropTarget {
+    padding: 5px;
+    border: 2px dashed gray;
+    font-style: italic;
+    color: rgb(78, 90, 107);
+}
+.twoColList {
+    -webkit-column-width: 24em;
+}
+.twoColList > li {
+    margin-bottom: 13px;
+}
+
+.song {
+    color: rgb(85, 221, 85);
+}
+.song:before {
+
+    content: "♫";
+    color: black;
+    background: rgb(85, 221, 85);
+    border-radius: 30%;
+}
+
+.effect:before {
+    content: "⛖";
+}
+
+.song:before, .effect:before {
+    margin-right: 3px;
+    text-decoration: none !important;
+    font-size: 140%;
+
+}
\ No newline at end of file