Changeset - 299d49de85a8
[Not reviewed]
default
0 4 0
Drew Perttula - 7 years ago 2018-06-04 07:05:42
drewp@bigasterisk.com
effectSequencer info display
Ignore-this: 88d2b52511206f38b90b2658bc63ec9c
4 files changed with 130 insertions and 154 deletions:
0 comments (0 inline, 0 general)
bin/keyboardcomposer
Show inline comments
 
@@ -415,102 +415,102 @@ class KeyboardComposer(tk.Frame, SubClie
 
        if abs(v - self.sliders.lastValue.get(chan, -5)) <= 1:
 
            return
 
        self.sliders.valueOut(chan, v)
 

	
 
    def make_row(self, group):
 
        """group is a URI or None"""
 
        row = tk.Frame(self, bd=2, bg='black')
 
        row.subGroup = group
 
        def onDrop(ev):
 
            self.change_group(sub=URIRef(ev.data), row=row)
 
            return "link"
 
        
 
        dropTargetRegister(row, onDrop=onDrop, typeList=['*'],
 
                           hoverStyle=dict(background="#555500"))
 
        
 
        row.pack(expand=1, fill=tk.BOTH)
 
        self.setup_key_nudgers(row)
 
        self.rows.append(row)
 
        return row
 

	
 
    def change_group(self, sub, row):
 
        """update this sub's group, and maybe other sub groups as needed, so
 
        this sub displays in this row"""
 
        group = row.subGroup
 
        self.graph.patchObject(
 
            context=self.session,
 
            subject=sub, predicate=L9['group'], newObject=group)
 

	
 
    def highlight_row(self, row):
 
        row = self.rows[row]
 
        row['bg'] = 'red'
 

	
 
    def unhighlight_row(self, row):
 
        row = self.rows[row]
 
        row['bg'] = 'black'
 

	
 
    def get_levels(self):
 
        return dict([(uri, box.slider_var.get())
 
            for uri, box in self.subbox.items()])
 

	
 
    def get_output_settings(self, _graph=None):
 
        _graph = _graph or graph
 
        outputSettings = []
 
        for setting in _graph.objects(self.session, L9['subSetting']):
 
            effect = _graph.value(setting, L9['sub'])
 
            strength = _graph.value(setting, L9['level'])
 
            if strength:
 
                now = time.time()
 
                outputSettings.append(
 
                    self.effectEval[effect].outputFromEffect(
 
                        [(L9['strength'], strength)],
 
                        songTime=now,
 
                        # should be counting from when you bumped up from 0
 
                        noteTime=now))
 
                out, report = self.effectEval[effect].outputFromEffect(
 
                    [(L9['strength'], strength)],
 
                    songTime=now,
 
                    # should be counting from when you bumped up from 0
 
                    noteTime=now)
 
                outputSettings.append(out)
 

	
 
        return DeviceSettings.fromList(_graph, outputSettings)
 

	
 
    def save_current_stage(self, subname):
 
        log.info("saving current levels as %s", subname)
 
        with graph.currentState() as g:
 
            ds = self.get_output_settings(_graph=g)
 
        effect = L9['effect/%s' % subname]
 
        ctx = URIRef(showconfig.showUri() + '/effect/' + subname)
 
        stmts = ds.statements(effect, ctx, effect + '/', set())
 
        stmts.extend([
 
            (effect, RDF.type, L9['Effect'], ctx),
 
            (effect, RDFS.label, Literal(subname), ctx),
 
            (effect, L9['publishAttr'], L9['strength'], ctx),
 
            ])
 

	
 
        graph.suggestPrefixes(ctx, {'eff': effect + '/'})
 
        graph.patch(Patch(addQuads=stmts, delQuads=[]))
 
              
 
        self.sub_name.delete(0, tk.END)
 

	
 
    def send_frequent_updates(self):
 
        """called when we get a fade -- send events as quickly as possible"""
 
        if time.time() <= self.stop_frequent_update_time:
 
            self.send_levels()
 
            self.after(10, self.send_frequent_updates)
 

	
 
    def alltozero(self):
 
        for uri, subbox in self.subbox.items():
 
            if subbox.scale.scale_var.get() != 0:
 
                subbox.scale.fade(value=0.0, length=0)
 

	
 
# move to web lib
 
def postArgGetter(request):
 
    """return a function that takes arg names and returns string
 
    values. Supports args encoded in the url or in postdata. No
 
    support for repeated args."""
 
    # this is something nevow normally does for me
 
    request.content.seek(0)
 
    fields = cgi.FieldStorage(request.content, request.received_headers,
 
                              environ={'REQUEST_METHOD': 'POST'})
 
    def getArg(n):
 
        try:
 
            return request.args[n][0]
 
        except KeyError:
 
            return fields[n].value
 
    return getArg
 

	
light9/effect/effecteval.py
Show inline comments
 
@@ -6,114 +6,115 @@ from colorsys import hsv_to_rgb
 
from decimal import Decimal
 
import math
 
import traceback
 
from noise import pnoise1
 
import logging
 
import time
 
from light9.effect.settings import DeviceSettings
 
from light9.effect.scale import scale
 

	
 

	
 
log = logging.getLogger('effecteval')
 

	
 
def literalColor(rnorm, gnorm, bnorm):
 
    return Literal(rgb_to_hex([int(rnorm * 255), int(gnorm * 255), int(bnorm * 255)]))
 

	
 
def literalColorHsv(h, s, v):
 
    return literalColor(*hsv_to_rgb(h, s, v))
 
    
 
def nsin(x): return (math.sin(x * (2 * math.pi)) + 1) / 2
 
def ncos(x): return (math.cos(x * (2 * math.pi)) + 1) / 2
 
def nsquare(t, on=.5):
 
    return (t % 1.0) < on
 
def lerp(a, b, t):
 
    return a + (b - a) * t
 
def noise(t):
 
    return pnoise1(t % 1000.0, 2)
 

	
 
class EffectEval(object):
 
    """
 
    runs one effect's code to turn effect attr settings into output
 
    device settings. No state; suitable for reload().
 
    """
 
    def __init__(self, graph, effect, simpleOutputs):
 
        self.graph = graph
 
        self.effect = effect
 
        self.simpleOutputs = simpleOutputs
 

	
 
    def outputFromEffect(self, effectSettings, songTime, noteTime):
 
        """
 
        From effect attr settings, like strength=0.75, to output device
 
        settings like light1/bright=0.72;light2/bright=0.78. This runs
 
        the effect code.
 
        """
 
        # both callers need to apply note overrides
 
        effectSettings = dict(effectSettings) # we should make everything into nice float and Color objects too
 

	
 
        strength = float(effectSettings[L9['strength']])
 
        if strength <= 0:
 
            return DeviceSettings(self.graph, [])
 
            return DeviceSettings(self.graph, []), {'zero': True}
 

	
 
        report = {}
 
        out = {} # (dev, attr): value
 

	
 
        out.update(self.simpleOutputs.values(
 
            self.effect, strength, effectSettings.get(L9['colorScale'], None)))
 

	
 
        if self.effect.startswith(L9['effect/']):
 
            tail = 'effect_' + self.effect[len(L9['effect/']):]
 
            try:
 
                func = globals()[tail]
 
            except KeyError:
 
                pass
 
                report['error'] = 'effect code not found for %s' % self.effect
 
            else:
 
                out.update(func(effectSettings, strength, songTime, noteTime))
 

	
 
        outList = [(d, a, v) for (d, a), v in out.iteritems()]
 
        return DeviceSettings(self.graph, outList)
 
        return DeviceSettings(self.graph, outList), report
 
                  
 

	
 
def effect_Curtain(effectSettings, strength, songTime, noteTime):
 
    return {
 
        (L9['device/lowPattern%s' % n], L9['color']):
 
        literalColor(strength, strength, strength)
 
        for n in range(301,308+1)
 
        }
 
    
 
def effect_animRainbow(effectSettings, strength, songTime, noteTime):
 
    out = {}
 
    tint = effectSettings.get(L9['tint'], '#ffffff')
 
    tintStrength = float(effectSettings.get(L9['tintStrength'], 0))
 
    tr, tg, tb = hex_to_rgb(tint)
 
    for n in range(1, 5+1):
 
        scl = strength * nsin(songTime + n * .3)**3
 
        col = literalColor(
 
            scl * lerp(nsin(songTime + n * .2), tr/255, tintStrength),
 
            scl * lerp(nsin(songTime + n * .2 + .3), tg/255, tintStrength),
 
            scl * lerp(nsin(songTime + n * .3 + .6), tb/255, tintStrength))
 

	
 
        dev = L9['device/aura%s' % n]
 
        out.update({
 
            (dev, L9['color']): col,
 
            (dev, L9['zoom']): .9,
 
            })
 
        ang = songTime * 4
 
        out.update({
 
        (dev, L9['rx']): lerp(.27, .7, (n-1)/4) + .2 * math.sin(ang+n),
 
        (dev, L9['ry']): lerp(.46, .52, (n-1)/4) + .5 * math.cos(ang+n),
 
            })
 
    return out
 

	
 
def effect_auraSparkles(effectSettings, strength, songTime, noteTime):
 
    out = {}
 
    tint = effectSettings.get(L9['tint'], '#ffffff')
 
    tintStrength = float(effectSettings.get(L9['tintStrength'], 0))
 
    print effectSettings
 
    tr, tg, tb = hex_to_rgb(tint)
 
    for n in range(1, 5+1):
 
        scl = strength * ((int(songTime * 10) % n) < 1)
 
        col = scale('#ffffff', scl)
 
        print n, 'scl', col
 

	
 
        dev = L9['device/aura%s' % n]
 
        out.update({
 
            (dev, L9['color']): col,
 
            (dev, L9['zoom']): .95,
light9/effect/sequencer.html
Show inline comments
 
<!doctype html>
 
<html>
 
  <head>
 
    <title>effect sequencer</title>
 
    <meta charset="utf-8" />
 
    <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 
    <link rel="import" href="/lib/polymer/polymer.html">
 
    <link rel="import" href="/lib/iron-ajax/iron-ajax.html">
 
    <link rel="import" href="../rdfdb-synced-graph.html">
 
    <link rel="import" href="../resource-display.html">
 
    <script src="/node_modules/n3/n3-browser.js"></script> 
 
    <script src="/lib/async/dist/async.js"></script>
 
    <script src="/lib/underscore/underscore-min.js"></script>
 

	
 
    <link rel="stylesheet"  href="/style.css">
 
    <style>
 
     td { white-space: nowrap; }
 
    </style>
 
  </head>
 
  <body>
 

	
 
    <dom-module id="light9-collector-device">
 
    <dom-module id="light9-sequencer-ui">
 
      <template>
 
        <style>
 
         :host {
 
             display: block;
 
             break-inside: avoid-column;
 
         }
 
         h3 {
 
             margin-top: 12px;
 
             margin-bottom: 0;
 
         td {
 
             white-space: nowrap;
 
             padding: 0 20px;
 
         }
 
         td.nonzero {
 
             background: #310202;
 
             color: #e25757;
 
         tr.active { background: #151515; }
 
         .inactive > * { opacity: .5; }
 
         .effectSetting {
 
             display: inline-block;
 
             background: #1b1e21;
 
             margin: 1px 3px;
 
         }
 
         td.full {
 
             background: #2b0000;
 
             color: red;
 
             font-weight: bold;
 
         .chart {
 
             height: 40px;
 
             background: #222;
 
             display: inline-flex;
 
             align-items: flex-end;
 
         }
 
         .chart > div {
 
             background: #a4a54f;
 
             width: 8px;
 
             margin: 0 1px;
 
         }
 
        </style>
 
        <h3><resource-display graph="{{graph}}" uri="{{uri}}"></resource-display></h3>
 
        <table class="borders">
 
        <rdfdb-synced-graph graph="{{graph}}"></rdfdb-synced-graph>
 

	
 
        <h1>Sequencer <a href="stats">[stats]</a></h1>
 

	
 
        <p>fps={{report.recentFps}}</p>
 
        
 
        <p>Recent frame times (best to worst): 
 
        <div class="chart">
 
         <template is="dom-repeat" items="{{report.recentDeltasStyle}}">
 
           <div style$="{{item}}"></div>
 
         </template>
 
        </div>
 
        </p>
 
        
 
        <h2>Song</h2>
 

	
 
        <resource-display graph="{{graph}}" uri="{{report.song}}"></resource-display>
 
        t={{report.roundT}}
 
        
 
        <h3>Notes</h3>
 

	
 
        <table>
 
          <tr>
 
            <th>output attr</th>
 
            <th>value</th>
 
            <th>output chan</th>
 
            <th>Note</th>
 
            <th>Effect class</th>
 
            <th>Effect settings</th>
 
            <th>Devices affected</th>
 
          </tr>
 
          <template is="dom-repeat" items="{{attrs}}">
 
            <tr>
 
              <td>{{item.attr}}</td>
 
              <td class$="{{item.valClass}}">{{item.val}} →</td>
 
              <td>{{item.chan}}</td>
 
          <template is="dom-repeat" items="{{report.songNotes}}">
 

	
 
            <tr class$="{{item.rowClass}}">
 
              <td>
 
                <resource-display graph="{{graph}}" uri="{{item.note}}"></resource-display>
 
              </td>
 
              <td>
 
                <resource-display graph="{{graph}}" uri="{{item.effectClass}}"></resource-display>
 
              </td>  
 
              <td>
 
                <template is="dom-repeat" items="{{item.effectSettingsPairs}}">
 
                  <span class="effectSetting">
 
                    <resource-display graph="{{graph}}" uri="{{item.effectAttr}}"></resource-display>: {{item.value}}
 
                  </span>
 
                </template>
 
              </td>
 
              <td>
 
                {{item.devicesAffected}}
 
              </td>
 
            </tr>
 
          </template>
 
        </table>
 

	
 
      </template>
 
      <script>
 
       HTMLImports.whenReady(function () {
 
           Polymer({
 
               is: "light9-collector-device",
 
               properties: {
 
                   graph: {type: Object, notify: true},
 
                   uri: {type: Object, notify: true},
 
                   attrs: {type: Array, notify: true},
 
               },
 
               observers: [
 
                   "initUpdates(updates)",
 
               ],
 
               initUpdates: function(updates) {
 
                   updates.addListener(function(msg) {
 
                       if (msg.outputAttrsSet && msg.outputAttrsSet.dev == this.uri.value) {
 
                           this.set('attrs', msg.outputAttrsSet.attrs);
 
                           this.attrs.forEach(function(row) {
 
                               row.valClass = row.val == 255 ? 'full' : (row.val ? 'nonzero' : '');
 
                           });
 
                       }
 
                   }.bind(this));
 
               },
 
           });
 
         Polymer({
 
           is: "light9-sequencer-ui",
 
           properties: {
 
             graph: {type: Object, notify: true},
 
             report: {type: Object, notify: true},
 
           },
 
           ready: function() {
 
             var source = new EventSource('updates');
 
             source.addEventListener('message', (e) => {
 
               const report = JSON.parse(e.data);
 
               report.roundT = Math.floor((report.t || 0) * 1000) / 1000;
 
               report.recentFps = Math.floor((report.recentFps || 0) * 10) / 10;
 
               report.recentDeltasStyle = (report.recentDeltas || []).map((dt) => {
 
                 const height = Math.min(40, dt / 0.025 * 20);
 
                 return `height: ${height}px;`
 
               });
 
               (report.songNotes || []).forEach((note) => {
 
                 note.rowClass = note.nonZero ? 'active' : 'inactive';
 
                 note.effectSettingsPairs = [];
 
                 const attrs = Object.keys(note.effectSettings);
 
                 attrs.sort();
 
                 attrs.forEach((attr) => {
 
                   note.effectSettingsPairs.push(
 
                     {effectAttr: attr, value: note.effectSettings[attr]});
 
                 });
 
               });
 
               this.report = report;
 
             });
 
           },
 
         });
 
       });
 
      </script>
 
    </dom-module>
 

	
 

	
 
    <dom-module id="light9-collector-ui">
 
      <template>
 
        <rdfdb-synced-graph graph="{{graph}}"></rdfdb-synced-graph>
 

	
 
        <h1>Collector <a href="stats">[stats]</a></h1>
 

	
 
        <h2>Devices</h2>
 
        <div style="column-width: 18em">
 
        <template is="dom-repeat" items="{{devices}}">
 
          <light9-collector-device
 
              graph="{{graph}}" updates="{{updates}}"
 
              uri="{{item}}"></light9-collector-device>
 
        </template>
 
        </div>
 
      </template>
 
      <script>
 
       class Updates {
 
           constructor() {
 
               this.listeners = [];
 
               
 
           }
 
           addListener(cb) {
 
               this.listeners.push(cb);
 
           }
 
           onMessage(msg) {
 
               this.listeners.forEach(function(lis) {
 
                   lis(msg);
 
               });
 
           }
 
       }
 
       HTMLImports.whenReady(function () {
 
           Polymer({
 
               is: "light9-collector-ui",
 
               properties: {
 
                   graph: {type: Object, notify: true},
 
                   updates: {type: Object, notify: true},
 
                   devices: {type: Array},
 
               },
 
               observers: [
 
                   'onGraph(graph)',
 
               ],
 
               ready: function() {
 
                   this.updates = new Updates();
 
                   var sock = new WebSocket(
 
                       window.location.href.replace(/^http/, 'ws') + 'updates');
 
                   sock.onmessage = function(ev) {
 
                       this.updates.onMessage(JSON.parse(ev.data));
 
                   }.bind(this);
 
               },
 
               onGraph: function(graph) {
 
                   this.graph.runHandler(this.findDevices.bind(this), 'findDevices');
 
               },
 
               findDevices: function() {
 
                   var U = function(x) {
 
                       return this.graph.Uri(x);
 
                   };
 
                   this.set('devices', []);
 

	
 
                   let classes = this.graph.subjects(U('rdf:type'), U(':DeviceClass'));
 
                   _.uniq(_.sortBy(classes, 'value'), true).forEach(function(dc) {
 
                       _.sortBy(this.graph.subjects(U('rdf:type'), dc), 'value').forEach(function(dev) {
 
                           this.push('devices', dev);
 
                       }.bind(this));
 
                   }.bind(this));
 
               }
 
           });
 
       });
 
      </script>
 
    </dom-module>
 
    
 
    list notes of song, time with note, eff attrs, function stuff
 
    update rates, stutters from reloads.
 
    <script>
 

	
 
     var source = new EventSource('updates');
 
     source.onmessage = function(e) {
 
       console.log(JSON.parse(e.data));
 
     };
 
    </script>
 
    <light9-sequencer-ui></light9-sequencer-ui>
 
        
 
  </body>
 
</html>
light9/effect/sequencer.py
Show inline comments
 
'''
 
copies from effectloop.py, which this should replace
 
'''
 

	
 
from __future__ import division
 
from rdflib import URIRef, Literal
 
from louie import dispatcher
 
from rdflib import URIRef
 
from twisted.internet import reactor, defer
 
from webcolors import rgb_to_hex
 
import json, logging, bisect
 
import treq
 
import math
 
import time
 
from twisted.internet.inotify import INotify
 
from twisted.python.filepath import FilePath
 
from louie import dispatcher
 
import cyclone.sse
 
import json, logging, bisect, time
 
import treq
 

	
 
from light9 import networking
 
from light9.namespaces import L9, RDF
 
from light9.vidref.musictime import MusicTime
 
from light9.effect import effecteval
 
from light9.effect.settings import DeviceSettings
 
from light9.effect.simple_outputs import SimpleOutputs
 

	
 
from greplin import scales
 
from txzmq import ZmqEndpoint, ZmqFactory, ZmqPushConnection
 

	
 
log = logging.getLogger('sequencer')
 
stats = scales.collection('/sequencer/',
 
                          scales.PmfStat('update'),
 
                          scales.PmfStat('compileGraph'),
 
                          scales.PmfStat('compileSong'),
 
                          scales.DoubleStat('recentFps'),
 
)
 

	
 
_zmqClient=None
 
class TwistedZmqClient(object):
 
    def __init__(self, service):
 
        zf = ZmqFactory()
 
        e = ZmqEndpoint('connect', 'tcp://%s:%s' % (service.host, service.port))
 
        self.conn = ZmqPushConnection(zf, e)
 
        
 
    def send(self, msg):
 
        self.conn.push(msg)
 

	
 

	
 
def toCollectorJson(client, session, settings):
 
    assert isinstance(settings, DeviceSettings)
 
    return json.dumps({'settings': settings.asList(),
 
                       'client': client,
 
                       'clientSession': session,
 
                       'sendTime': time.time(),
 
                  })
 
        
 
def sendToCollectorZmq(msg):
 
    global _zmqClient
 
    if _zmqClient is None:
 
        _zmqClient = TwistedZmqClient(networking.collectorZmq)
 
    _zmqClient.send(msg)
 
    return defer.succeed(0)
 
        
 
def sendToCollector(client, session, settings, useZmq=True):
 
    """deferred to the time in seconds it took to get a response from collector"""
 
    sendTime = time.time()
 
@@ -89,179 +87,184 @@ class Note(object):
 
        self.baseEffectSettings = {}  # {effectAttr: value}
 
        for s in g.objects(uri, L9['setting']):
 
            settingValues = dict(g.predicate_objects(s))
 
            ea = settingValues[L9['effectAttr']]
 
            self.baseEffectSettings[ea] = settingValues[L9['value']]
 
            
 
        floatVal = lambda s, p: float(g.value(s, p).toPython())
 
        originTime = floatVal(uri, L9['originTime'])
 
        self.points = []
 
        for curve in g.objects(uri, L9['curve']):
 
            self.points.extend(
 
                self.getCurvePoints(curve, L9['strength'], originTime))
 
        self.points.sort()
 

	
 
    def getCurvePoints(self, curve, attr, originTime):
 
        points = []
 
        po = list(self.graph.predicate_objects(curve))
 
        if dict(po).get(L9['attr'], None) != attr:
 
            return []
 
        for point in [row[1] for row in po if row[0] == L9['point']]:
 
            po2 = dict(self.graph.predicate_objects(point))
 
            points.append((
 
                originTime + float(po2[L9['time']]),
 
                float(po2[L9['value']])))
 
        return points
 
            
 
    def activeAt(self, t):
 
        return self.points[0][0] <= t <= self.points[-1][0]
 

	
 
    def evalCurve(self, t):
 
        i = bisect.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]
 
        if i >= len(self.points) - 1:
 
            return self.points[i][1]
 

	
 
        p1, p2 = self.points[i], self.points[i + 1]
 
        frac = (t - p1[0]) / (p2[0] - p1[0])
 
        y = p1[1] + (p2[1] - p1[1]) * frac
 
        return y
 
        
 
    def outputSettings(self, t):
 
        """
 
        list of (device, attr, value), and a report for web
 
        """
 
        report = {'note': str(self.uri)}
 
        report = {'note': str(self.uri),
 
                  'effectClass': self.effectEval.effect,
 
        }
 
        effectSettings = self.baseEffectSettings.copy()
 
        effectSettings[L9['strength']] = self.evalCurve(t)
 
        report['effectSettings'] = dict(
 
            (str(k), str(v))
 
            for k,v in sorted(effectSettings.items()))
 
        out = self.effectEval.outputFromEffect(
 
        report['nonZero'] = effectSettings[L9['strength']] > 0
 
        out, evalReport = self.effectEval.outputFromEffect(
 
            effectSettings.items(),
 
            songTime=t,
 
            # note: not using origin here since it's going away
 
            noteTime=t - self.points[0][0])
 
        print 'out', out.asList()
 
        report['devicesAffected'] = len(out.devices())
 
        return out, report
 

	
 

	
 
class CodeWatcher(object):
 
    def __init__(self, onChange):
 
        self.onChange = onChange
 

	
 
        self.notifier = INotify()
 
        self.notifier.startReading()
 
        self.notifier.watch(
 
            FilePath(effecteval.__file__.replace('.pyc', '.py')),
 
            callbacks=[self.codeChange])
 

	
 
    def codeChange(self, watch, path, mask):
 
        def go():
 
            log.info("reload effecteval")
 
            reload(effecteval)
 
            self.onChange()
 
        # in case we got an event at the start of the write
 
        reactor.callLater(.1, go) 
 
    
 
        
 

	
 
class Sequencer(object):
 
    def __init__(self, graph, sendToCollector, fps=40):
 
        self.graph = graph
 
        self.fps = fps
 
        self.sendToCollector = sendToCollector
 
        self.music = MusicTime(period=.2, pollCurvecalc=False)
 

	
 
        self.recentUpdateTimes = []
 
        self.lastStatLog = 0
 
        self._compileGraphCall = None
 
        self.notes = {} # song: [notes]
 
        self.simpleOutputs = SimpleOutputs(self.graph)
 
        self.graph.addHandler(self.compileGraph)
 
        self.update()
 

	
 
        self.codeWatcher = CodeWatcher(
 
            onChange=lambda: self.graph.addHandler(self.compileGraph))
 

	
 
    @stats.compileGraph.time()
 
    def compileGraph(self):
 
        """rebuild our data from the graph"""
 
        t1 = time.time()
 
        g = self.graph
 

	
 
        for song in g.subjects(RDF.type, L9['Song']):
 
            self.graph.addHandler(lambda song=song: self.compileSong(song))
 
        log.info('compileGraph took %.2f ms', 1000 * (time.time() - t1))
 
        
 
    @stats.compileSong.time()
 
    def compileSong(self, song):
 
        t1 = time.time()
 

	
 
        self.notes[song] = []
 
        for note in self.graph.objects(song, L9['note']):
 
            self.notes[song].append(Note(self.graph, note, effecteval,
 
                                         self.simpleOutputs))
 
        log.info('  compile %s took %.2f ms', song, 1000 * (time.time() - t1))
 

	
 
        
 
    @stats.update.time()
 
    def update(self):
 
        now = time.time()
 
        self.recentUpdateTimes = self.recentUpdateTimes[-20:] + [now]
 
        self.recentUpdateTimes = self.recentUpdateTimes[-40:] + [now]
 
        stats.recentFps = len(self.recentUpdateTimes) / (self.recentUpdateTimes[-1] - self.recentUpdateTimes[0] + .0001)
 
        if now > self.lastStatLog + 10:
 
            dispatcher.send('state', update={'recentFps': stats.recentFps})
 
        if 1 or now > self.lastStatLog + 1:
 
            dispatcher.send('state', update={
 
                'recentDeltas': sorted([round(t1 - t0, 4) for t0, t1 in
 
                                 zip(self.recentUpdateTimes[:-1],
 
                                     self.recentUpdateTimes[1:])]),
 
                'recentFps': stats.recentFps})
 
            self.lastStatLog = now
 
        
 
        reactor.callLater(1 / self.fps, self.update)
 

	
 
        musicState = self.music.getLatest()
 
        song = URIRef(musicState['song']) if musicState.get('song') else None
 
        dispatcher.send('state', update={'song': str(song)})
 
        if 't' not in musicState:
 
            return
 
        t = musicState['t']
 
        dispatcher.send('state', update={'song': str(song), 't': t})
 

	
 
        settings = []
 
        songNotes = sorted(self.notes.get(song, []))
 
        songNotes = sorted(self.notes.get(song, []), key=lambda n: n.uri)
 
        noteReports = []
 
        for note in songNotes:
 
            s, report = note.outputSettings(t)
 
            noteReports.append(report)
 
            settings.append(s)
 
        dispatcher.send('state', update={'songNotes': noteReports})
 
        self.sendToCollector(DeviceSettings.fromList(self.graph, settings))
 

	
 
import cyclone.sse
 
class Updates(cyclone.sse.SSEHandler):
 
    def __init__(self, application, request, **kwargs):
 
        cyclone.sse.SSEHandler.__init__(self, application, request,
 
                                        **kwargs)
 
        self.state = {}
 
        dispatcher.connect(self.updateState, 'state')
 
        self.numConnected = 0
 

	
 
    def updateState(self, update):
 
        self.state.update(update)
 
        
 
    def bind(self):
 
        print 'new client', self.settings.seq
 
        self.numConnected += 1
 
        
 
        if self.numConnected == 1:
 
            self.loop()
 

	
 
    def loop(self):
 
        if self.numConnected == 0:
 
            return
 
        self.sendEvent(self.state)
 
        reactor.callLater(2, self.loop)
 
        reactor.callLater(.1, self.loop)
 
        
 
    def unbind(self):
 
        self.numConnected -= 1
 
        print 'bye', self.numConnected
 

	
 
    
0 comments (0 inline, 0 general)