Changeset - c35ec37c3c6e
[Not reviewed]
default
0 7 1
Drew Perttula - 9 years ago 2016-06-10 06:56:34
drewp@bigasterisk.com
sequencer reloads effecteval on the fly. plus some /stats support.
Ignore-this: 964f4c9007de6532457e0a507d2106f1
8 files changed with 169 insertions and 68 deletions:
0 comments (0 inline, 0 general)
bin/effectsequencer
Show inline comments
 
@@ -32,7 +32,6 @@ class App(object):
 
                                       scales.IntStat('errors'),
 
                                       )
 
    def launch(self, *args):
 
        print 'launch'
 
        self.seq = Sequencer(
 
            self.graph,
 
            lambda settings: sendToCollector('effectSequencer', self.session,
light9/effect/effecteval.py
Show inline comments
 
new file 100644
 
from __future__ import division
 
from rdflib import URIRef, Literal
 
from light9.namespaces import L9, RDF
 
from webcolors import rgb_to_hex
 
import math
 

	
 

	
 
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):
 
        self.graph = graph
 
        self.effect = effect
 
        
 
        #for ds in g.objects(g.value(uri, L9['effectClass']), L9['deviceSetting']):
 
        #    self.setting = (g.value(ds, L9['device']), g.value(ds, L9['attr']))
 

	
 
    def outputFromEffect(self, effectSettings, songTime):
 
        """
 
        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.
 
        """
 
        attr, value = effectSettings[0]
 
        value = float(value)
 
        assert attr == L9['strength']
 
        c = int(255 * value)
 
        color = [0, 0, 0]
 
        if self.effect == L9['effect/RedStrip']: # throwaway
 

	
 
            mov = URIRef('http://light9.bigasterisk.com/device/moving1')
 
            col = [
 
                    (songTime + .0) % 1.0,
 
                    (songTime + .4) % 1.0,
 
                    (songTime + .8) % 1.0,
 
                ]
 
            return [
 
                # device, attr, lev
 
                
 
                (mov, L9['color'], Literal(rgb_to_hex([value*x*255 for x in col]))),
 
                (mov, L9['rx'], Literal(100 + 70 * math.sin(songTime*2))),
 
            ] * (value>0)
 

	
 
        elif self.effect == L9['effect/BlueStrip']:
 
            color[2] = c
 
        elif self.effect == L9['effect/WorkLight']:
 
            color[1] = c
 
        elif self.effect == L9['effect/Curtain']:
 
            color[0] = color[2] = 70/255 * c
 
        elif self.effect == L9['effect/Strobe']:
 
            attr, value = effectSettings[0]
 
            assert attr == L9['strength']
 
            strength = float(value)
 
            rate = 2
 
            duty = .3
 
            offset = 0
 
            f = (((songTime + offset) * rate) % 1.0)
 
            c = (f < duty) * strength
 
            col = rgb_to_hex([c * 255, c * 255, c * 255])
 
            return [
 
                (L9['device/colorStrip'], L9['color'], Literal(col)),
 
            ]
 
        else:
 
            color[0] = color[1] = color[2] = c
 

	
 
        return [
 
            # device, attr, lev
 
            (URIRef('http://light9.bigasterisk.com/device/moving1'),
 
             URIRef("http://light9.bigasterisk.com/color"),
 
             Literal(rgb_to_hex(color)))
 
            ]
 
        
 

	
 

	
 
    
light9/effect/sequencer.py
Show inline comments
 
@@ -9,13 +9,22 @@ 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 light9 import networking
 
from light9.namespaces import L9, RDF
 
from light9.vidref.musictime import MusicTime
 
from light9.effect import effecteval
 
from greplin import scales
 

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

	
 
                                       )
 
def sendToCollector(client, session, settings):
 
    return treq.put(networking.collector.path('attrs'),
 
                    data=json.dumps({'settings': settings,
 
@@ -24,10 +33,11 @@ def sendToCollector(client, session, set
 

	
 

	
 
class Note(object):
 
    def __init__(self, graph, uri):
 
    def __init__(self, graph, uri, effectevalModule):
 
        g = self.graph = graph
 
        self.uri = uri
 
        self.effectEval = EffectEval(graph, g.value(uri, L9['effectClass']))
 
        self.effectEval = effectevalModule.EffectEval(
 
            graph, g.value(uri, L9['effectClass']))
 
        floatVal = lambda s, p: float(g.value(s, p).toPython())
 
        originTime = floatVal(uri, L9['originTime'])
 
        self.points = []
 
@@ -66,81 +76,56 @@ class Note(object):
 
        return self.effectEval.outputFromEffect(effectSettings, t)
 
                
 

	
 
class EffectEval(object):
 
    """
 
    runs one effect's code to turn effect attr settings into output
 
    device settings
 
    """
 
    def __init__(self, graph, effect):
 
        self.graph = graph
 
        self.effect = effect
 
        
 
        #for ds in g.objects(g.value(uri, L9['effectClass']), L9['deviceSetting']):
 
        #    self.setting = (g.value(ds, L9['device']), g.value(ds, L9['attr']))
 

	
 
    def outputFromEffect(self, effectSettings, songTime):
 
        """
 
        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.
 
        """
 
        attr, value = effectSettings[0]
 
        value = float(value)
 
        assert attr == L9['strength']
 
        c = int(255 * value)
 
        color = [0, 0, 0]
 
        if self.effect == L9['effect/RedStrip']: # throwaway
 

	
 
            mov = URIRef('http://light9.bigasterisk.com/device/moving1')
 
            col = [
 
                    (songTime + .1) % 1.0,
 
                    (songTime + .4) % 1.0,
 
                    (songTime + .8) % 1.0,
 
                ]
 
            print 'col', col
 
            return [
 
                # device, attr, lev
 
                
 
                (mov, L9['color'], Literal(rgb_to_hex([value*x*255 for x in col]))),
 
                (mov, L9['rx'], Literal(100 + 70 * math.sin(songTime*2))),
 
            ]
 

	
 
        elif self.effect == L9['effect/BlueStrip']:
 
            color[2] = c
 
        elif self.effect == L9['effect/WorkLight']:
 
            color[1] = c
 
        elif self.effect == L9['effect/Curtain']:
 
            color[0] = color[2] = 70/255 * c
 
        else:
 
            color[0] = color[1] = color[2] = c
 

	
 
        return [
 
            # device, attr, lev
 
            (URIRef('http://light9.bigasterisk.com/device/moving1'),
 
             URIRef("http://light9.bigasterisk.com/color"),
 
             Literal(rgb_to_hex(color)))
 
            ]
 
        
 
class Sequencer(object):
 
    def __init__(self, graph, sendToCollector):
 
        self.graph = graph
 
        self.sendToCollector = sendToCollector
 
        self.music = MusicTime(period=.2, pollCurvecalc=False)
 

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

	
 
        self.watchCode()
 

	
 
    def watchCode(self):
 
        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():
 
            reload(effecteval)
 
            self.graph.addHandler(self.compileGraph)
 
        # in case we got an event at the start of the write
 
        reactor.callLater(.1, go) 
 

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

	
 
        log.info("compileGraph")
 
        reload(effecteval)
 
        
 
        for song in g.subjects(RDF.type, L9['Song']):
 
            self.notes[song] = []
 
            for note in g.objects(song, L9['note']):
 
                self.notes[song].append(Note(g, note))
 
                self.notes[song].append(Note(g, note, effecteval))
 

	
 
    @stats.update.time()
 
    def update(self):
 
        now = time.time()
 
        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)
 
            self.lastStatLog = now
 
        
 
    def update(self):
 
        reactor.callLater(1/30, self.update)
 
        reactor.callLater(1/50, self.update)
 

	
 
        musicState = self.music.getLatest()
 
        song = URIRef(musicState['song']) if musicState.get('song') else None
 
@@ -155,6 +140,6 @@ class Sequencer(object):
 
            # away. might be better for collector not to merge our
 
            # past requests, and then we can omit zeroed notes?
 
            outs = note.outputSettings(t)
 
            print 'out', outs
 
            #print 'out', outs
 
            settings.extend(outs)
 
        self.sendToCollector(settings)
light9/subclient.py
Show inline comments
 
@@ -20,5 +20,4 @@ class SubClient:
 

	
 
    def _send_sub(self):
 
        outputSettings = self.get_output_settings()
 
        print outputSettings
 
        sendToCollector('subclient', self.session, outputSettings)
light9/web/index.html
Show inline comments
 
@@ -28,6 +28,7 @@
 
        <div>
 
          <span class="left"><a class="big" href="{{name}}/">{{name}}</a></span>
 
          <span><button on-click="click">window</button></span>
 
          <span><a href="{{name}}/stats">stats</a></span>
 
        </div>
 
      </template>
 
      <script>
 
@@ -49,16 +50,17 @@
 
    <h1>light9 home page</h1>
 

	
 
    <div style="float: left">
 
      <service-button-row name="."></service-button-row>
 
      <service-button-row name="rdfdb"></service-button-row>
 
      <service-button-row name="effectEval"></service-button-row>
 
      <service-button-row name="collector"></service-button-row>
 
      <service-button-row name="effectSequencer"></service-button-row>
 
      <service-button-row name="ascoltami"></service-button-row>
 
      <service-button-row name="subComposer"></service-button-row>
 
      <service-button-row name="subServer"></service-button-row>
 
      <service-button-row name="picamserve"></service-button-row>
 
      <service-button-row name="vidref"></service-button-row>
 
      <service-button-row name="."></service-button-row>
 
      <service-button-row name="live"></service-button-row>
 
      <service-button-row name="timeline"></service-button-row>
 
      <service-button-row name="subComposer"></service-button-row>
 
    </div>
 
    
 
  </body>
light9/web/timeline/timeline.coffee
Show inline comments
 
@@ -469,9 +469,22 @@ Polymer
 
    if elem
 
      elem.remove()
 
      delete @elemById[uri]
 

	
 
  anyPointsInView: (pts) ->
 
    for pt in pts
 
      if pt.e(1) > -100 && pt.e(1) < 2500
 
        return true
 
    return false
 
    
 
  setNote: (uri, curvePts, effectLabel) ->
 
    elem = @getOrCreateElem(uri, 'notes', 'path', {style:"fill:#53774b; stroke:#000000; stroke-width:1.5;"})
 
    areaId = uri + '/area'
 
    labelId = uri + '/label'
 
    if not @anyPointsInView(curvePts)
 
      @clearElem(areaId)
 
      @clearElem(labelId)
 
      return
 
    elem = @getOrCreateElem(areaId, 'notes', 'path',
 
      {style:"fill:#53774b; stroke:#000000; stroke-width:1.5;"})
 
    elem.setAttribute('d', svgPathFromPoints(curvePts))
 

	
 
    elem = @getOrCreateElem(uri+'/label', 'noteLabels', 'text', {style: "font-size:13px;line-height:125%;font-family:'Verana Sans';text-align:start;text-anchor:start;fill:#000000;"})
 
@@ -504,5 +517,9 @@ Polymer
 
    ])
 

	
 
  setAdjusterConnector: (uri, center, target) ->
 
    id = uri + '/adj'
 
    if not @anyPointsInView([center, target])
 
      @clearElem(uri)
 
      return
 
    elem = @getOrCreateElem(uri, 'connectors', 'path', {style: "fill:none;stroke:#d4d4d4;stroke-width:0.9282527;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:2.78475821, 2.78475821;stroke-dashoffset:0;"})
 
    elem.setAttribute('d', svgPathFromPoints([center, target]))
show/dance2016/effect.n3
Show inline comments
 
@@ -38,6 +38,10 @@ effect:usa a :Effect;
 
:fc1p3 :time 0.15; :value 0 .
 

	
 

	
 
effect:Strobe a :Effect;  rdfs:label "strobe";
 
  :publishAttr :strength, :rate, :offset, :duty .
 

	
 

	
 
effect:WorkLight a :Effect;
 
  rdfs:label "work light";
 
  :publishAttr :strength;
show/dance2016/song1.n3
Show inline comments
 
@@ -7,9 +7,18 @@
 
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
 

	
 
:a01 :effectAttr :chaseOffset; :value 0.12 .
 
<http://light9.bigasterisk.com/show/dance2016/song1> :note song:n1, song:n25, song:n26, song:n27, song:n28 .
 
<http://light9.bigasterisk.com/show/dance2016/song1> :note song:n0, song:n1, song:n2, song:n25, song:n26, song:n27, song:n28 .
 
:ao0 :effectAttr :chaseTempo; :value 100 .
 

	
 
song:n0 a :Note; :curve song:n0c0; :effectClass effect:WorkLight;
 
     :originTime 161.162 .
 

	
 
song:n0c0 a :Curve; :attr :strength; :point song:n0c0p0, song:n0c0p1, song:n0c0p2, song:n0c0p3 .
 
song:n0c0p0 :time 0.000; :value 0.000 .
 
song:n0c0p1 :time 1.000; :value 1.000 .
 
song:n0c0p2 :time 2.000; :value 1.000 .
 
song:n0c0p3 :time 3.000; :value 0.000 .
 

	
 
song:n1 a :Note; :attrOverride :ao0, :ao1; :curve song:n1c1;
 
     :effectClass effect:RedStrip; :originTime 41.011 .
 

	
 
@@ -19,6 +28,9 @@ song:n1c1p1 :time 1; :value 1 .
 
song:n1c1p2 :time 2; :value 1 .
 
song:n1c1p3 :time 3; :value 0 .
 

	
 
song:n2 a :Note; :curve song:n2c0; :effectClass effect:Strobe;
 
     :originTime 47.087 .
 

	
 
song:n25 a :Note; :curve song:n25c0; :effectClass effect:BlueStrip;
 
     :originTime 71.234 .
 

	
 
@@ -54,3 +66,9 @@ song:n28c0p0 :time 0.000; :value 0.000 .
 
song:n28c0p1 :time 1.000; :value 1.000 .
 
song:n28c0p2 :time 2.000; :value 1.000 .
 
song:n28c0p3 :time 3.000; :value 0.000 .
 

	
 
song:n2c0 a :Curve; :attr :strength; :point song:n2c0p0, song:n2c0p1, song:n2c0p2, song:n2c0p3 .
 
song:n2c0p0 :time 0.000; :value 0.000 .
 
song:n2c0p1 :time 1.000; :value 1.000 .
 
song:n2c0p2 :time 2.000; :value 1.000 .
 
song:n2c0p3 :time 3.000; :value 0.000 .
0 comments (0 inline, 0 general)