Changeset - a2081b9adfe4
[Not reviewed]
default
0 7 0
Drew Perttula - 11 years ago 2014-05-28 07:37:36
drewp@bigasterisk.com
effecteval now takes dropped subs and makes new effects out of them
Ignore-this: 69423eca1858291f486dd2a202d3732a
7 files changed with 133 insertions and 42 deletions:
0 comments (0 inline, 0 general)
bin/effecteval
Show inline comments
 
@@ -3,14 +3,15 @@ from run_local import log
 
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 @@ class EffectEdit(cyclone.web.RequestHand
 
    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 @@ def uriFromCode(s):
 
        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 @@ class EffectNode(object):
 
        # 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 @@ class SongEffectsEval(PrettyErrorHandler
 
@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 @@ class App(object):
 
            (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)
light9/curvecalc/curve.py
Show inline comments
 
@@ -62,6 +62,8 @@ class Curve(object):
 
    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:
light9/effecteval/effect.html
Show inline comments
 
@@ -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>
light9/effecteval/index.coffee
Show inline comments
 
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)
light9/effecteval/index.html
Show inline comments
 
@@ -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>
light9/namespaces.py
Show inline comments
 
from rdflib import Namespace
 
from rdflib import Namespace, RDF
 

	
 
L9 = Namespace("http://light9.bigasterisk.com/")
 
MUS = Namespace("http://light9.bigasterisk.com/music/")
static/style.css
Show inline comments
 
@@ -3,6 +3,13 @@ body {
 
    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 @@ button a {
 
    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
0 comments (0 inline, 0 general)