Changeset - b680d6f50a93
[Not reviewed]
default
0 2 1
drewp@bigasterisk.com - 7 years ago 2018-06-03 21:04:00
drewp@bigasterisk.com
start sequencer web report. WIP
Ignore-this: e97eadee6190d2c90cfb81f3542f4f2f
3 files changed with 220 insertions and 7 deletions:
0 comments (0 inline, 0 general)
bin/effectsequencer
Show inline comments
 
@@ -12,7 +12,7 @@ from greplin import scales
 
import optparse, sys, logging
 
import cyclone.web
 
from rdflib import URIRef
 
from light9.effect.sequencer import Sequencer, sendToCollector
 
from light9.effect.sequencer import Sequencer, sendToCollector, Updates
 
from light9 import clientsession
 

	
 
class App(object):
 
@@ -38,9 +38,13 @@ class App(object):
 
                                             settings))
 

	
 
        self.cycloneApp = cyclone.web.Application(handlers=[
 
            (r'/()', cyclone.web.StaticFileHandler,
 
             {"path" : "light9/effect/", "default_filename" : "sequencer.html"}),
 
            (r'/updates', Updates),
 
            (r'/stats', StatsForCyclone),
 
        ],
 
                                                  debug=True,
 
                                                  seq=self.seq,
 
                                                  graph=self.graph,
 
                                                  stats=self.stats)
 
        reactor.listenTCP(networking.effectSequencer.port, self.cycloneApp)
light9/effect/sequencer.html
Show inline comments
 
new file 100644
 
<!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">
 
      <template>
 
        <style>
 
         :host {
 
             display: block;
 
             break-inside: avoid-column;
 
         }
 
         h3 {
 
             margin-top: 12px;
 
             margin-bottom: 0;
 
         }
 
         td.nonzero {
 
             background: #310202;
 
             color: #e25757;
 
         }
 
         td.full {
 
             background: #2b0000;
 
             color: red;
 
             font-weight: bold;
 
         }
 
        </style>
 
        <h3><resource-display graph="{{graph}}" uri="{{uri}}"></resource-display></h3>
 
        <table class="borders">
 
          <tr>
 
            <th>output attr</th>
 
            <th>value</th>
 
            <th>output chan</th>
 
          </tr>
 
          <template is="dom-repeat" items="{{attrs}}">
 
            <tr>
 
              <td>{{item.attr}}</td>
 
              <td class$="{{item.valClass}}">{{item.val}} →</td>
 
              <td>{{item.chan}}</td>
 
            </tr>
 
          </template>
 

	
 
      </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));
 
               },
 
           });
 
       });
 
      </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>
 
  </body>
 
</html>
light9/effect/sequencer.py
Show inline comments
 
@@ -12,6 +12,7 @@ import math
 
import time
 
from twisted.internet.inotify import INotify
 
from twisted.python.filepath import FilePath
 
from louie import dispatcher
 

	
 
from light9 import networking
 
from light9.namespaces import L9, RDF
 
@@ -131,15 +132,21 @@ class Note(object):
 
        
 
    def outputSettings(self, t):
 
        """
 
        list of (device, attr, value)
 
        list of (device, attr, value), and a report for web
 
        """
 
        report = {'note': str(self.uri)}
 
        effectSettings = self.baseEffectSettings.copy()
 
        effectSettings[L9['strength']] = self.evalCurve(t)
 
        return self.effectEval.outputFromEffect(
 
        report['effectSettings'] = dict(
 
            (str(k), str(v))
 
            for k,v in sorted(effectSettings.items()))
 
        out = 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()
 
        return out, report
 

	
 

	
 
class CodeWatcher(object):
 
@@ -207,19 +214,54 @@ class Sequencer(object):
 
        self.recentUpdateTimes = self.recentUpdateTimes[-20:] + [now]
 
        stats.recentFps = len(self.recentUpdateTimes) / (self.recentUpdateTimes[-1] - self.recentUpdateTimes[0] + .0001)
 
        if now > self.lastStatLog + 10:
 
            log.info("%.2f fps", stats.recentFps)
 
            dispatcher.send('state', update={'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']
 

	
 
        settings = []
 
        songNotes = sorted(self.notes.get(song, []))
 
        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))
 
        
 
        for note in self.notes.get(song, []):
 
            settings.append(note.outputSettings(t))
 
        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)
 
        
 
    def unbind(self):
 
        self.numConnected -= 1
 
        print 'bye', self.numConnected
 
    
0 comments (0 inline, 0 general)