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
 
#!bin/python
 
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")
 
from cycloneerr import PrettyErrorHandler
 

	
 
class EffectEdit(cyclone.web.RequestHandler):
 
    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
 
        self.graph.addHandler(self.updateClient)
 
        
 
    def updateClient(self):
 
@@ -63,12 +82,14 @@ class EffectUpdates(cyclone.websocket.We
 
def uriFromCode(s):
 
    # i thought this was something a graph could do with its namespace manager
 
    if s.startswith('sub:'):
 
        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):
 
    def __init__(self, graph, uri):
 
        self.graph, self.uri = graph, uri
 
        self.graph.addHandler(self.prepare)
 
@@ -85,14 +106,18 @@ class EffectNode(object):
 
        intensityCurve = uriFromCode(m.group(2))
 
        self.curve = Curve()
 

	
 
        # 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):
 
        # consider http://waxeye.org/ for a parser that can be used in py and js
 
        level = self.curve.eval(songTime)
 
        scaledSubs = self.sub * level
 
@@ -124,38 +149,42 @@ class SongEffectsEval(PrettyErrorHandler
 
        # return dmx dict for all effects in the song, already combined
 

	
 
# Or, we could own that loop, like this:
 
@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)
 
    
 
class App(object):
 
    def __init__(self, show):
 
        self.show = show
 
@@ -173,12 +202,13 @@ class App(object):
 
            (r'/effect\.js', StaticCoffee, {'src': 'light9/effecteval/effect.coffee'}),
 
            (r'/index\.js', StaticCoffee, {'src': 'light9/effecteval/index.coffee'}),
 
            (r'/effectUpdates', EffectUpdates),
 
            (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)
 
        log.info("listening on %s" % networking.effectEval.port)
 

	
 
class StaticCoffee(PrettyErrorHandler, cyclone.web.RequestHandler):
light9/curvecalc/curve.py
Show inline comments
 
@@ -59,12 +59,14 @@ class Curve(object):
 
            f.write("%s %r\n" % p)
 
        f.close()
 

	
 
    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:
 
            return self.points[0][1]
 
        if self.points[i][0]>t:
 
            return self.points[i][1]
light9/effecteval/effect.html
Show inline comments
 
<!doctype html>
 
<html>
 
  <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>
 
    <script src="static/websocket.js"></script>
 
    <script src="static/QueryString.js"></script>
 
    <script src="effect.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)
 
  
 
ko.applyBindings(model)
 

	
light9/effecteval/index.html
Show inline comments
 
<!doctype html>
 
<html>
 
  <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>
 
    <script src="static/websocket.js"></script>
 
    <script src="index.js"></script>
 
  </body>
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/")
 
XSD = Namespace("http://www.w3.org/2001/XMLSchema#")
 
DCTERMS = Namespace("http://purl.org/dc/terms/")
static/style.css
Show inline comments
 
body { 
 
    background: black;
 
    color: white;
 
    font-family: sans-serif;
 
}
 

	
 

	
 
a {
 
color: rgb(97, 97, 255);
 

	
 
    }
 

	
 
.songs {
 
    -moz-column-width:205px;
 
    -webkit-column-width:205px;
 
}
 
.songs button {
 
    display: inline-block;
 
@@ -43,10 +50,45 @@ button a {
 
    margin: 14px;
 
}
 
.stalled {
 
    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)