changeset 1777:299d49de85a8

effectSequencer info display Ignore-this: 88d2b52511206f38b90b2658bc63ec9c
author Drew Perttula <drewp@bigasterisk.com>
date Mon, 04 Jun 2018 07:05:42 +0000
parents b680d6f50a93
children 178b11c9a429
files bin/keyboardcomposer light9/effect/effecteval.py light9/effect/sequencer.html light9/effect/sequencer.py
diffstat 4 files changed, 130 insertions(+), 154 deletions(-) [+]
line wrap: on
line diff
--- a/bin/keyboardcomposer	Sun Jun 03 21:04:00 2018 +0000
+++ b/bin/keyboardcomposer	Mon Jun 04 07:05:42 2018 +0000
@@ -460,12 +460,12 @@
             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)
 
--- a/light9/effect/effecteval.py	Sun Jun 03 21:04:00 2018 +0000
+++ b/light9/effect/effecteval.py	Mon Jun 04 07:05:42 2018 +0000
@@ -51,8 +51,9 @@
 
         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(
@@ -63,12 +64,12 @@
             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):
--- a/light9/effect/sequencer.html	Sun Jun 03 21:04:00 2018 +0000
+++ b/light9/effect/sequencer.html	Mon Jun 04 07:05:42 2018 +0000
@@ -13,155 +13,127 @@
     <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>
--- a/light9/effect/sequencer.py	Sun Jun 03 21:04:00 2018 +0000
+++ b/light9/effect/sequencer.py	Mon Jun 04 07:05:42 2018 +0000
@@ -3,16 +3,14 @@
 '''
 
 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
@@ -134,18 +132,21 @@
         """
         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
 
 
@@ -211,23 +212,27 @@
     @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)
@@ -236,7 +241,6 @@
         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,
@@ -249,7 +253,6 @@
         self.state.update(update)
         
     def bind(self):
-        print 'new client', self.settings.seq
         self.numConnected += 1
         
         if self.numConnected == 1:
@@ -259,9 +262,9 @@
         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
+