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
 
@@ -457,18 +457,18 @@ class KeyboardComposer(tk.Frame, SubClie
 
        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:
light9/effect/effecteval.py
Show inline comments
 
@@ -48,30 +48,31 @@ class EffectEval(object):
 
        """
 
        # 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)
light9/effect/sequencer.html
Show inline comments
 
@@ -10,158 +10,130 @@
 
    <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
 
@@ -131,24 +129,27 @@ class Note(object):
 
        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
 
@@ -208,60 +209,62 @@ class Sequencer(object):
 
        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)