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
 
@@ -460,12 +460,12 @@ class KeyboardComposer(tk.Frame, SubClie
 
            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)
 

	
light9/effect/effecteval.py
Show inline comments
 
@@ -51,8 +51,9 @@ class EffectEval(object):
 

	
 
        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 @@ class EffectEval(object):
 
            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):
light9/effect/sequencer.html
Show inline comments
 
@@ -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>
light9/effect/sequencer.py
Show inline comments
 
@@ -3,16 +3,14 @@ copies from effectloop.py, which this sh
 
'''
 

	
 
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 @@ class Note(object):
 
        """
 
        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 @@ class Sequencer(object):
 
    @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 @@ class Sequencer(object):
 
        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 @@ class Updates(cyclone.sse.SSEHandler):
 
        self.state.update(update)
 
        
 
    def bind(self):
 
        print 'new client', self.settings.seq
 
        self.numConnected += 1
 
        
 
        if self.numConnected == 1:
 
@@ -259,9 +262,9 @@ class Updates(cyclone.sse.SSEHandler):
 
        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)