Changeset - 7772cc48e016
[Not reviewed]
default
0 93 0
drewp@bigasterisk.com - 6 years ago 2019-05-21 23:56:12
drewp@bigasterisk.com
reformat all python
Ignore-this: 1135b78893f8b3d31badddda7f45678f
93 files changed with 3498 insertions and 2407 deletions:
bin/tracker
181
142
bin/vidref
25
16
0 comments (0 inline, 0 general)
bin/ascoltami2
Show inline comments
 
@@ -4,7 +4,7 @@ from twisted.internet import reactor
 
import web, thread, sys, optparse, logging
 
from rdflib import URIRef
 
sys.path.append(".")
 
sys.path.append('/usr/lib/python2.7/dist-packages') # For gi
 
sys.path.append('/usr/lib/python2.7/dist-packages')  # For gi
 

	
 
import gi
 
gi.require_version('Gst', '1.0')
 
@@ -17,7 +17,9 @@ from light9 import networking, showconfi
 

	
 
from gi.repository import GObject, Gst, Gtk
 

	
 

	
 
class App(object):
 

	
 
    def __init__(self, graph, show):
 
        self.graph = graph
 
        self.player = Player(onEOS=self.onEOS)
 
@@ -32,21 +34,27 @@ class App(object):
 

	
 
        try:
 
            nextSong = self.playlist.nextSong(thisSongUri)
 
        except NoSuchSong: # we're at the end of the playlist
 
        except NoSuchSong:  # we're at the end of the playlist
 
            return
 

	
 
        self.player.setSong(songLocation(graph, nextSong), play=False)
 

	
 

	
 
if __name__ == "__main__":
 
    GObject.threads_init()
 
    Gst.init(None)
 

	
 
    parser = optparse.OptionParser()
 
    parser.add_option('--show',
 
        help='show URI, like http://light9.bigasterisk.com/show/dance2008', default=showconfig.showUri())
 
    parser.add_option("-v", "--verbose", action="store_true",
 
    parser.add_option(
 
        '--show',
 
        help='show URI, like http://light9.bigasterisk.com/show/dance2008',
 
        default=showconfig.showUri())
 
    parser.add_option("-v",
 
                      "--verbose",
 
                      action="store_true",
 
                      help="logging.DEBUG")
 
    parser.add_option("--twistedlog", action="store_true",
 
    parser.add_option("--twistedlog",
 
                      action="store_true",
 
                      help="twisted logging")
 
    (options, args) = parser.parse_args()
 

	
 
@@ -54,7 +62,7 @@ if __name__ == "__main__":
 

	
 
    if not options.show:
 
        raise ValueError("missing --show http://...")
 
            
 

	
 
    graph = showconfig.getGraph()
 
    app = App(graph, URIRef(options.show))
 
    if options.twistedlog:
bin/bcf_puppet_demo
Show inline comments
 
@@ -5,12 +5,13 @@ tiny bcf2000 controller demo
 
from bcf2000 import BCF2000
 
from twisted.internet import reactor
 

	
 

	
 
class PuppetSliders(BCF2000):
 

	
 
    def valueIn(self, name, value):
 
        if name == 'slider1':
 
            self.valueOut('slider5', value)
 

	
 

	
 

	
 
b = PuppetSliders()
 
reactor.run()
bin/bumppad
Show inline comments
 
#!bin/python
 
from __future__ import division,nested_scopes
 
from __future__ import division, nested_scopes
 
import sys, time, math
 
import Tkinter as tk
 

	
 
@@ -7,64 +7,82 @@ import run_local
 
import light9.dmxclient as dmxclient
 
from light9.TLUtility import make_attributes_from_args
 

	
 
from light9.Submaster import Submaster,sub_maxes
 
from light9.Submaster import Submaster, sub_maxes
 

	
 

	
 
class pad(tk.Frame):
 
    levs = None # Submaster : level
 
    def __init__(self,master,root,mag):
 
        make_attributes_from_args('master','mag')
 
        tk.Frame.__init__(self,master)
 
        self.levs={}
 
        for xy,key,subname in [
 
            ((1,1),'KP_Up','centered'),
 
            ((1,3),"KP_Down",'third-c'),
 
            ((0,2),'KP_Left','scoop-l'),
 
            ((2,2),'KP_Right','scoop-r'),
 
            ((1,0),'KP_Divide','cyc'),
 
            ((0,3),"KP_End",'hottest'),
 
            ((2,3),'KP_Next','deepblues'),
 
            ((0,4),'KP_Insert',"zip_red"),
 
            ((2,4),'KP_Delete',"zip_orange"),
 
            ((3,1),'KP_Add','strobedim'),
 
            ((3,3),'KP_Enter','zip_blue'),
 
            ((1,2),'KP_Begin','scoop-c'),                            
 
                        ]:
 
            
 
    levs = None  # Submaster : level
 

	
 
    def __init__(self, master, root, mag):
 
        make_attributes_from_args('master', 'mag')
 
        tk.Frame.__init__(self, master)
 
        self.levs = {}
 
        for xy, key, subname in [
 
            ((1, 1), 'KP_Up', 'centered'),
 
            ((1, 3), "KP_Down", 'third-c'),
 
            ((0, 2), 'KP_Left', 'scoop-l'),
 
            ((2, 2), 'KP_Right', 'scoop-r'),
 
            ((1, 0), 'KP_Divide', 'cyc'),
 
            ((0, 3), "KP_End", 'hottest'),
 
            ((2, 3), 'KP_Next', 'deepblues'),
 
            ((0, 4), 'KP_Insert', "zip_red"),
 
            ((2, 4), 'KP_Delete', "zip_orange"),
 
            ((3, 1), 'KP_Add', 'strobedim'),
 
            ((3, 3), 'KP_Enter', 'zip_blue'),
 
            ((1, 2), 'KP_Begin', 'scoop-c'),
 
        ]:
 

	
 
            sub = Submaster(subname)
 
            self.levs[sub]=0
 
            
 
            l = tk.Label(self,font="arial 12 bold",anchor='w',height=2,
 
                         relief='groove',bd=5,
 
                         text="%s\n%s" % (key.replace('KP_',''),sub.name))
 
            l.grid(column=xy[0],row=xy[1],sticky='news')
 
            
 
            root.bind("<KeyPress-%s>"%key,
 
                      lambda ev,sub=sub: self.bumpto(sub,1))
 
            root.bind("<KeyRelease-%s>"%key,
 
                      lambda ev,sub=sub: self.bumpto(sub,0))
 
    def bumpto(self,sub,lev):
 
        now=time.time()
 
        self.levs[sub]=lev*self.mag.get()
 
            self.levs[sub] = 0
 

	
 
            l = tk.Label(self,
 
                         font="arial 12 bold",
 
                         anchor='w',
 
                         height=2,
 
                         relief='groove',
 
                         bd=5,
 
                         text="%s\n%s" % (key.replace('KP_', ''), sub.name))
 
            l.grid(column=xy[0], row=xy[1], sticky='news')
 

	
 
            root.bind(
 
                "<KeyPress-%s>" % key, lambda ev, sub=sub: self.bumpto(sub, 1))
 
            root.bind("<KeyRelease-%s>" % key,
 
                      lambda ev, sub=sub: self.bumpto(sub, 0))
 

	
 
    def bumpto(self, sub, lev):
 
        now = time.time()
 
        self.levs[sub] = lev * self.mag.get()
 
        self.master.after_idle(self.output)
 

	
 
    def output(self):
 
        dmx = sub_maxes(*[s*l for s,l in self.levs.items()]).get_dmx_list()
 
        dmxclient.outputlevels(dmx,clientid="bumppad")
 
        
 
root=tk.Tk()
 
        dmx = sub_maxes(*[s * l for s, l in self.levs.items()]).get_dmx_list()
 
        dmxclient.outputlevels(dmx, clientid="bumppad")
 

	
 

	
 
root = tk.Tk()
 
root.tk_setPalette("maroon4")
 
root.wm_title("bumppad")
 
mag = tk.DoubleVar()
 

	
 
tk.Label(root,text="Keypad press/release activate sub; 1..5 set mag",
 
             font="Helvetica -12 italic",anchor='w').pack(side='bottom',fill='x')
 
                      
 
pad(root,root,mag).pack(side='left',fill='both',exp=1)
 
tk.Label(root,
 
         text="Keypad press/release activate sub; 1..5 set mag",
 
         font="Helvetica -12 italic",
 
         anchor='w').pack(side='bottom', fill='x')
 

	
 
pad(root, root, mag).pack(side='left', fill='both', exp=1)
 

	
 
magscl = tk.Scale(root,orient='vertical',from_=1,to=0,res=.01,
 
                   showval=1,variable=mag,label='mag',relief='raised',bd=1)
 
for i in range(1,6):
 
    root.bind("<Key-%s>"%i,lambda ev,i=i: mag.set(math.sqrt((i )/5)))
 
magscl.pack(side='left',fill='y')
 

	
 
magscl = tk.Scale(root,
 
                  orient='vertical',
 
                  from_=1,
 
                  to=0,
 
                  res=.01,
 
                  showval=1,
 
                  variable=mag,
 
                  label='mag',
 
                  relief='raised',
 
                  bd=1)
 
for i in range(1, 6):
 
    root.bind("<Key-%s>" % i, lambda ev, i=i: mag.set(math.sqrt((i) / 5)))
 
magscl.pack(side='left', fill='y')
 

	
 
root.mainloop()
bin/captureDevice
Show inline comments
 
@@ -26,15 +26,18 @@ from rdfdb.patch import Patch
 

	
 
stats = scales.collection('/webServer', scales.PmfStat('setAttr'))
 

	
 

	
 
class Camera(object):
 

	
 
    def __init__(self, imageUrl):
 
        self.imageUrl = imageUrl
 
    
 

	
 
    def takePic(self, uri, writePath):
 
        log.info('takePic %s', uri)
 
        return treq.get(self.imageUrl).addCallbacks(
 
            lambda r: self._done(writePath, r), log.error)
 
        
 
        return treq.get(
 
            self.imageUrl).addCallbacks(lambda r: self._done(writePath, r),
 
                                        log.error)
 

	
 
    @inlineCallbacks
 
    def _done(self, writePath, response):
 
        jpg = yield response.content()
 
@@ -46,18 +49,21 @@ class Camera(object):
 
            out.write(jpg)
 
        log.info('wrote %s', writePath)
 

	
 

	
 
def deferSleep(sec):
 
    d = Deferred()
 
    reactor.callLater(sec, d.callback, None)
 
    return d
 
    
 

	
 

	
 
class Capture(object):
 
    firstMoveTime = 3
 
    settleTime = .5
 

	
 
    def __init__(self, graph, dev):
 
        self.graph = graph
 
        self.dev = dev
 
        
 

	
 
        def steps(a, b, n):
 
            return [round(a + (b - a) * i / n, 5) for i in range(n)]
 

	
 
@@ -72,8 +78,8 @@ class Capture(object):
 
        # aura
 
        rxSteps = steps(0.15, .95, 10)
 
        rySteps = steps(0, .9, 5)
 
        zoomSteps = steps(.6, .9, 3)       
 
        
 
        zoomSteps = steps(.6, .9, 3)
 

	
 
        row = 0
 
        for ry in rySteps:
 
            xSteps = rxSteps[:]
 
@@ -82,31 +88,36 @@ class Capture(object):
 
            row += 1
 
            for rx in xSteps:
 
                for zoom in zoomSteps:
 
                    self.toGather.append(DeviceSettings(graph, [
 
                        (dev, L9['rx'], rx),
 
                        (dev, L9['ry'], ry),
 
                        (dev, L9['color'], '#ffffff'),
 
                        (dev, L9['zoom'], zoom),
 
                        #(dev, L9['focus'], 0.13),
 
                    ]))
 
                    self.toGather.append(
 
                        DeviceSettings(
 
                            graph,
 
                            [
 
                                (dev, L9['rx'], rx),
 
                                (dev, L9['ry'], ry),
 
                                (dev, L9['color'], '#ffffff'),
 
                                (dev, L9['zoom'], zoom),
 
                                #(dev, L9['focus'], 0.13),
 
                            ]))
 

	
 
        self.devTail = dev.rsplit('/')[-1]
 
        self.session = URIRef('/'.join([showconfig.showUri(),
 
                                   'capture', self.devTail, self.captureId]))
 
        self.session = URIRef('/'.join(
 
            [showconfig.showUri(), 'capture', self.devTail, self.captureId]))
 
        self.ctx = URIRef(self.session + '/index')
 
                
 
        self.graph.patch(Patch(addQuads=[
 
            (self.session, RDF.type, L9['CaptureSession'], self.ctx),
 
        ]))
 
                
 

	
 
        self.graph.patch(
 
            Patch(addQuads=[
 
                (self.session, RDF.type, L9['CaptureSession'], self.ctx),
 
            ]))
 

	
 
        self.numPics = 0
 
        self.settingsCache = set()
 
        self.step().addErrback(log.error)
 

	
 
    def off(self):
 
        return sendToCollector(client='captureDevice', session='main',
 
        return sendToCollector(client='captureDevice',
 
                               session='main',
 
                               settings=DeviceSettings(self.graph, []))
 
        
 

	
 
    @inlineCallbacks
 
    def step(self):
 
        if not self.toGather:
 
@@ -115,61 +126,73 @@ class Capture(object):
 
            reactor.stop()
 
            return
 
        settings = self.toGather.pop()
 
        
 

	
 
        log.info('[%s left] move to %r', len(self.toGather), settings)
 
        yield sendToCollector(client='captureDevice', session='main',
 
        yield sendToCollector(client='captureDevice',
 
                              session='main',
 
                              settings=settings)
 
        
 
        yield deferSleep(self.firstMoveTime if self.numPics == 0 else
 
                         self.settleTime)
 
        
 

	
 
        yield deferSleep(self.firstMoveTime if self.numPics ==
 
                         0 else self.settleTime)
 

	
 
        picId = 'pic%s' % self.numPics
 
        path = '/'.join([
 
            'capture', self.devTail, self.captureId, picId]) + '.jpg'
 
        path = '/'.join(['capture', self.devTail, self.captureId, picId
 
                        ]) + '.jpg'
 
        uri = URIRef(self.session + '/' + picId)
 
        
 

	
 
        yield camera.takePic(uri, os.path.join(showconfig.root(), path))
 
        self.numPics += 1
 

	
 
        writeCaptureDescription(self.graph, self.ctx, self.session, uri,
 
                                self.dev,
 
                                path, self.settingsCache, settings)
 
        
 
                                self.dev, path, self.settingsCache, settings)
 

	
 
        reactor.callLater(0, self.step)
 

	
 
        
 

	
 
camera = Camera('http://plus:8200/picamserve/pic?res=1080&resize=800&iso=800&redgain=1.6&bluegain=1.6&shutter=60000&x=0&w=1&y=0&h=.952')
 
camera = Camera(
 
    'http://plus:8200/picamserve/pic?res=1080&resize=800&iso=800&redgain=1.6&bluegain=1.6&shutter=60000&x=0&w=1&y=0&h=.952'
 
)
 

	
 

	
 
class Attrs(PrettyErrorHandler, cyclone.web.RequestHandler):
 

	
 
    def put(self):
 
        with stats.setAttr.time():
 
            client, clientSession, settings, sendTime = parseJsonMessage(self.request.body)
 
            client, clientSession, settings, sendTime = parseJsonMessage(
 
                self.request.body)
 
            self.set_status(202)
 

	
 

	
 
def launch(graph):
 

	
 
    cap = Capture(graph, dev=L9['device/aura5'])
 
    reactor.listenTCP(networking.captureDevice.port,
 
                      cyclone.web.Application(handlers=[
 
                          (r'/()', cyclone.web.StaticFileHandler,
 
                           {"path" : "light9/web", "default_filename" : "captureDevice.html"}),
 
                          (r'/()', cyclone.web.StaticFileHandler, {
 
                              "path": "light9/web",
 
                              "default_filename": "captureDevice.html"
 
                          }),
 
                          (r'/stats', StatsForCyclone),
 
                      ]),
 
                      interface='::')
 
    log.info('serving http on %s', networking.captureDevice.port)
 
    
 

	
 

	
 
def main():
 
    parser = optparse.OptionParser()
 
    parser.add_option("-v", "--verbose", action="store_true",
 
    parser.add_option("-v",
 
                      "--verbose",
 
                      action="store_true",
 
                      help="logging.DEBUG")
 
    (options, args) = parser.parse_args()
 
    log.setLevel(logging.DEBUG if options.verbose else logging.INFO)
 

	
 
    graph = SyncedGraph(networking.rdfdb.url, "captureDevice")
 

	
 
    graph.initiallySynced.addCallback(lambda _: launch(graph)).addErrback(log.error)
 
    graph.initiallySynced.addCallback(lambda _: launch(graph)).addErrback(
 
        log.error)
 
    reactor.run()
 

	
 

	
 
if __name__ == '__main__':
 
    main()
bin/clientdemo
Show inline comments
 
@@ -16,12 +16,13 @@ if __name__ == "__main__":
 
    g = SyncedGraph(networking.rdfdb.url, "clientdemo")
 

	
 
    from light9.Submaster import PersistentSubmaster
 
    sub = PersistentSubmaster(graph=g, uri=URIRef("http://light9.bigasterisk.com/sub/bcools"))
 
    sub = PersistentSubmaster(
 
        graph=g, uri=URIRef("http://light9.bigasterisk.com/sub/bcools"))
 

	
 
    #get sub to show its updating name, then push that all the way into KC gui so we can see just names refresh in there
 

	
 
    L9 = Namespace("http://light9.bigasterisk.com/")
 

	
 
    L9 = Namespace("http://light9.bigasterisk.com/")
 
    def updateDemoValue():
 
        v = list(g.objects(L9['demo'], L9['is']))
 
        print "demo value is %r" % v
 
@@ -29,8 +30,10 @@ if __name__ == "__main__":
 
    g.addHandler(updateDemoValue)
 

	
 
    def adj():
 
        g.patch(Patch(addQuads=[(L9['demo'], L9['is'], Literal(os.getpid()),
 
                                 L9['clientdemo'])],
 
                      delQuads=[]))
 
        g.patch(
 
            Patch(addQuads=[(L9['demo'], L9['is'], Literal(os.getpid()),
 
                             L9['clientdemo'])],
 
                  delQuads=[]))
 

	
 
    reactor.callLater(2, adj)
 
    reactor.run()
bin/collector
Show inline comments
 
@@ -29,6 +29,7 @@ from light9 import networking
 
from rdfdb.syncedgraph import SyncedGraph
 
from light9.greplin_cyclone import StatsForCyclone
 

	
 

	
 
def parseJsonMessage(msg):
 
    body = json.loads(msg)
 
    settings = []
 
@@ -40,14 +41,15 @@ def parseJsonMessage(msg):
 
        settings.append((URIRef(device), URIRef(attr), value))
 
    return body['client'], body['clientSession'], settings, body['sendTime']
 

	
 

	
 
def startZmq(port, collector):
 
    stats = scales.collection('/zmqServer',
 
                              scales.PmfStat('setAttr'))
 
    
 
    stats = scales.collection('/zmqServer', scales.PmfStat('setAttr'))
 

	
 
    zf = ZmqFactory()
 
    addr = 'tcp://*:%s' % port
 
    log.info('creating zmq endpoint at %r', addr)
 
    e = ZmqEndpoint('bind', addr)
 

	
 
    class Pull(ZmqPullConnection):
 
        #highWaterMark = 3
 
        def onPull(self, message):
 
@@ -55,26 +57,28 @@ def startZmq(port, collector):
 
                # todo: new compressed protocol where you send all URIs up
 
                # front and then use small ints to refer to devices and
 
                # attributes in subsequent requests.
 
                client, clientSession, settings, sendTime = parseJsonMessage(message[0])
 
                client, clientSession, settings, sendTime = parseJsonMessage(
 
                    message[0])
 
                collector.setAttrs(client, clientSession, settings, sendTime)
 
        
 

	
 
    s = Pull(zf, e)
 

	
 

	
 
class WebListeners(object):
 

	
 
    def __init__(self):
 
        self.clients = []
 
        self.pendingMessageForDev = {} # dev: (attrs, outputmap)
 
        self.pendingMessageForDev = {}  # dev: (attrs, outputmap)
 
        self.lastFlush = 0
 
        
 

	
 
    def addClient(self, client):
 
        self.clients.append([client, {}]) # seen = {dev: attrs}
 
        self.clients.append([client, {}])  # seen = {dev: attrs}
 
        log.info('added client %s %s', len(self.clients), client)
 

	
 
    def delClient(self, client):
 
        self.clients = [[c, t] for c, t in self.clients if c != client]
 
        log.info('delClient %s, %s left', client, len(self.clients))
 
        
 

	
 
    def outputAttrsSet(self, dev, attrs, outputMap):
 
        """called often- don't be slow"""
 

	
 
@@ -94,8 +98,8 @@ class WebListeners(object):
 
        while self.pendingMessageForDev:
 
            dev, (attrs, outputMap) = self.pendingMessageForDev.popitem()
 

	
 
            msg = None # lazy, since makeMsg is slow
 
            
 
            msg = None  # lazy, since makeMsg is slow
 

	
 
            # this omits repeats, but can still send many
 
            # messages/sec. Not sure if piling up messages for the browser
 
            # could lead to slowdowns in the real dmx output.
 
@@ -112,16 +116,23 @@ class WebListeners(object):
 
        attrRows = []
 
        for attr, val in attrs.items():
 
            output, index = outputMap[(dev, attr)]
 
            attrRows.append({'attr': attr.rsplit('/')[-1],
 
                             'val': val,
 
                             'chan': (output.shortId(), index + 1)})
 
            attrRows.append({
 
                'attr': attr.rsplit('/')[-1],
 
                'val': val,
 
                'chan': (output.shortId(), index + 1)
 
            })
 
        attrRows.sort(key=lambda r: r['chan'])
 
        for row in attrRows:
 
            row['chan'] = '%s %s' % (row['chan'][0], row['chan'][1])
 

	
 
        msg = json.dumps({'outputAttrsSet': {'dev': dev, 'attrs': attrRows}}, sort_keys=True)
 
        msg = json.dumps({'outputAttrsSet': {
 
            'dev': dev,
 
            'attrs': attrRows
 
        }},
 
                         sort_keys=True)
 
        return msg
 
    
 

	
 

	
 
class Updates(cyclone.websocket.WebSocketHandler):
 

	
 
    def connectionMade(self, *args, **kwargs):
 
@@ -134,23 +145,27 @@ class Updates(cyclone.websocket.WebSocke
 
    def messageReceived(self, message):
 
        json.loads(message)
 

	
 

	
 
stats = scales.collection('/webServer', scales.PmfStat('setAttr'))
 

	
 

	
 
class Attrs(PrettyErrorHandler, cyclone.web.RequestHandler):
 

	
 
    def put(self):
 
        with stats.setAttr.time():
 
            client, clientSession, settings, sendTime = parseJsonMessage(self.request.body)
 
            self.settings.collector.setAttrs(client, clientSession, settings, sendTime)
 
            client, clientSession, settings, sendTime = parseJsonMessage(
 
                self.request.body)
 
            self.settings.collector.setAttrs(client, clientSession, settings,
 
                                             sendTime)
 
            self.set_status(202)
 

	
 

	
 
            
 
def launch(graph, doLoadTest=False):
 
    try:
 
        # todo: drive outputs with config files
 
        outputs = [
 
            # EnttecDmx(L9['output/dmxA/'], '/dev/dmx3', 80),
 
             Udmx(L9['output/dmxA/'], bus=5, numChannels=80),
 
            Udmx(L9['output/dmxA/'], bus=5, numChannels=80),
 
            #DummyOutput(L9['output/dmxA/'], 80),
 
            Udmx(L9['output/dmxB/'], bus=7, numChannels=500),
 
        ]
 
@@ -162,15 +177,19 @@ def launch(graph, doLoadTest=False):
 
    c = Collector(graph, outputs, listeners)
 

	
 
    startZmq(networking.collectorZmq.port, c)
 
    
 

	
 
    reactor.listenTCP(networking.collector.port,
 
                      cyclone.web.Application(handlers=[
 
                          (r'/()', cyclone.web.StaticFileHandler,
 
                           {"path" : "light9/collector/web", "default_filename" : "index.html"}),
 
                          (r'/()', cyclone.web.StaticFileHandler, {
 
                              "path": "light9/collector/web",
 
                              "default_filename": "index.html"
 
                          }),
 
                          (r'/updates', Updates),
 
                          (r'/attrs', Attrs),
 
                          (r'/stats', StatsForCyclone),
 
                      ], collector=c, listeners=listeners),
 
                      ],
 
                                              collector=c,
 
                                              listeners=listeners),
 
                      interface='::')
 
    log.info('serving http on %s, zmq on %s', networking.collector.port,
 
             networking.collectorZmq.port)
 
@@ -180,28 +199,38 @@ def launch(graph, doLoadTest=False):
 
        # requests when there's free time
 
        def afterWarmup():
 
            log.info('running collector_loadtest')
 
            d = utils.getProcessValue('bin/python', ['bin/collector_loadtest.py'])
 
            d = utils.getProcessValue('bin/python',
 
                                      ['bin/collector_loadtest.py'])
 

	
 
            def done(*a):
 
                log.info('loadtest done')
 
                reactor.stop()
 

	
 
            d.addCallback(done)
 

	
 
        reactor.callLater(2, afterWarmup)
 
    
 

	
 

	
 
def main():
 
    parser = optparse.OptionParser()
 
    parser.add_option("-v", "--verbose", action="store_true",
 
    parser.add_option("-v",
 
                      "--verbose",
 
                      action="store_true",
 
                      help="logging.DEBUG")
 
    parser.add_option("--loadtest", action="store_true",
 
    parser.add_option("--loadtest",
 
                      action="store_true",
 
                      help="call myself with some synthetic load then exit")
 
    (options, args) = parser.parse_args()
 
    log.setLevel(logging.DEBUG if options.verbose else logging.INFO)
 

	
 
    logging.getLogger('colormath').setLevel(logging.INFO)
 
    
 

	
 
    graph = SyncedGraph(networking.rdfdb.url, "collector")
 

	
 
    graph.initiallySynced.addCallback(lambda _: launch(graph, options.loadtest)).addErrback(lambda e: reactor.crash())
 
    graph.initiallySynced.addCallback(lambda _: launch(graph, options.loadtest)
 
                                     ).addErrback(lambda e: reactor.crash())
 
    reactor.run()
 

	
 

	
 
if __name__ == '__main__':
 
    main()
bin/collector_loadtest.py
Show inline comments
 
@@ -7,6 +7,8 @@ from twisted.internet import reactor
 
import time
 
import logging
 
log.setLevel(logging.DEBUG)
 

	
 

	
 
def loadTest():
 
    print "scheduling loadtest"
 
    n = 2500
 
@@ -14,23 +16,27 @@ def loadTest():
 
    session = "loadtest%s" % time.time()
 
    offset = 0
 
    for i in range(n):
 

	
 
        def send(i):
 
            if i % 100 == 0:
 
                log.info('sendToCollector %s', i)
 
            d = sendToCollector("http://localhost:999999/", session,
 
                    [[DEV["backlight1"], L9["color"], "#ffffff"], 
 
                     [DEV["backlight2"], L9["color"], "#ffffff"], 
 
                     [DEV["backlight3"], L9["color"], "#ffffff"], 
 
                     [DEV["backlight4"], L9["color"], "#ffffff"], 
 
                     [DEV["backlight5"], L9["color"], "#ffffff"], 
 
                     [DEV["down2"], L9["color"], "#ffffff"], 
 
                     [DEV["down3"], L9["color"], "#ffffff"], 
 
                     [DEV["down4"], L9["color"], "#ffffff"], 
 
                     [DEV["houseSide"], L9["level"], .8], 
 
                     [DEV["backlight5"], L9["uv"], 0.011]])
 
                                [[DEV["backlight1"], L9["color"], "#ffffff"],
 
                                 [DEV["backlight2"], L9["color"], "#ffffff"],
 
                                 [DEV["backlight3"], L9["color"], "#ffffff"],
 
                                 [DEV["backlight4"], L9["color"], "#ffffff"],
 
                                 [DEV["backlight5"], L9["color"], "#ffffff"],
 
                                 [DEV["down2"], L9["color"], "#ffffff"],
 
                                 [DEV["down3"], L9["color"], "#ffffff"],
 
                                 [DEV["down4"], L9["color"], "#ffffff"],
 
                                 [DEV["houseSide"], L9["level"], .8],
 
                                 [DEV["backlight5"], L9["uv"], 0.011]])
 

	
 
            def ontime(dt, i=i):
 
                times[i] = dt
 

	
 
            d.addCallback(ontime)
 

	
 
        reactor.callLater(offset, send, i)
 
        offset += .002
 

	
 
@@ -39,8 +45,10 @@ def loadTest():
 
        with open('/tmp/times', 'w') as f:
 
            f.write(''.join('%s\n' % t for t in times))
 
        reactor.stop()
 
    reactor.callLater(offset+.5, done)
 

	
 
    reactor.callLater(offset + .5, done)
 
    reactor.run()
 

	
 

	
 
if __name__ == '__main__':
 
    loadTest()
bin/curvecalc
Show inline comments
 
#!bin/python
 

	
 
"""
 
now launches like this:
 
% bin/curvecalc http://light9.bigasterisk.com/show/dance2007/song1
 
@@ -12,7 +11,7 @@ todo: curveview should preserve more obj
 
from __future__ import division
 

	
 
import sys
 
sys.path.append('/usr/lib/python2.7/dist-packages') # For gtk
 
sys.path.append('/usr/lib/python2.7/dist-packages')  # For gtk
 
from twisted.internet import gtk3reactor
 
gtk3reactor.install()
 
from twisted.internet import reactor
 
@@ -24,13 +23,13 @@ from gi.repository import GObject
 
from gi.repository import Gdk
 

	
 
from urlparse import parse_qsl
 
import louie as dispatcher 
 
import louie as dispatcher
 
from rdflib import URIRef, Literal, RDF, RDFS
 
import logging
 

	
 
from run_local import log
 
from light9 import showconfig, networking
 
from light9.curvecalc import curveview 
 
from light9.curvecalc import curveview
 
from light9.curvecalc.curve import Curveset
 
from light9.curvecalc.curveedit import serveCurveEdit
 
from light9.curvecalc.musicaccess import Music
 
@@ -46,34 +45,40 @@ from rdfdb.patch import Patch
 
from rdfdb.syncedgraph import SyncedGraph
 
from light9.wavelength import wavelength
 

	
 

	
 
class SubtermExists(ValueError):
 
    pass
 

	
 

	
 
class Main(object):
 

	
 
    def __init__(self, graph, opts, session, curveset, music):
 
        self.graph, self.opts, self.session = graph, opts, session
 
        self.curveset, self.music = curveset, music
 
        self.lastSeenInputTime = 0
 
        self.currentSubterms = [] # Subterm objects that are synced to the graph
 
        self.currentSubterms = [
 
        ]  # Subterm objects that are synced to the graph
 

	
 
        self.setTheme()
 
        wtree = self.wtree = Gtk.Builder()
 
        wtree.add_from_file("light9/curvecalc/curvecalc.glade")
 
        mainwin = wtree.get_object("MainWindow")
 
        
 

	
 
        mainwin.connect("destroy", self.onQuit)
 
        wtree.connect_signals(self)
 

	
 
        mainwin.show_all()
 

	
 
        mainwin.connect("delete-event", lambda *args: reactor.crash())
 

	
 
        def updateTitle():
 
            mainwin.set_title("curvecalc - %s" %
 
                              graph.label(
 
                                  graph.value(session, L9['currentSong'])))
 
            mainwin.set_title(
 
                "curvecalc - %s" %
 
                graph.label(graph.value(session, L9['currentSong'])))
 

	
 
        graph.addHandler(updateTitle)
 

	
 
        songChoice = Observable(None) # to be connected with the session song
 
        songChoice = Observable(None)  # to be connected with the session song
 

	
 
        self.registerGraphToSongChoice(wtree, session, graph, songChoice)
 
        self.registerSongChoiceToGraph(session, graph, songChoice)
 
@@ -82,28 +87,31 @@ class Main(object):
 
        ec = EditChoice(graph, songChoice, label="Editing song:")
 
        wtree.get_object("currentSongEditChoice").add(ec)
 
        ec.show()
 
        
 

	
 
        wtree.get_object("subterms").connect("add", self.onSubtermChildAdded)
 
        
 
        self.refreshCurveView()       
 
        
 

	
 
        self.refreshCurveView()
 

	
 
        self.makeStatusLines(wtree.get_object("status"))
 
        self.setupNewSubZone()
 
        self.acceptDragsOnCurveViews()
 
                
 

	
 
        # may not work
 
        wtree.get_object("paned1").set_position(600)
 

	
 
    def registerGraphToSongChoice(self, wtree, session, graph, songChoice):
 

	
 
        def setSong():
 
            current = graph.value(session, L9['currentSong'])
 
            if not wtree.get_object("followPlayerSongChoice").get_active():
 
                songChoice(current)
 
                dispatcher.send("song_has_changed")
 

	
 
        graph.addHandler(setSong)
 

	
 
    def registerSongChoiceToGraph(self, session, graph, songChoice):
 
        self.muteSongChoiceUntil = 0
 

	
 
        def songChoiceToGraph(newSong):
 
            if newSong is Local:
 
                raise NotImplementedError('what do i patch')
 
@@ -116,12 +124,12 @@ class Main(object):
 
                log.debug('muted')
 
                return
 
            self.muteSongChoiceUntil = now + 1
 
            
 
            graph.patchObject(context=session, subject=session,
 
                              predicate=L9['currentSong'], newObject=newSong)
 
            
 
            
 
            
 

	
 
            graph.patchObject(context=session,
 
                              subject=session,
 
                              predicate=L9['currentSong'],
 
                              newObject=newSong)
 

	
 
        songChoice.subscribe(songChoiceToGraph)
 

	
 
    def registerCurrentPlayerSongToUi(self, wtree, graph, songChoice):
 
@@ -129,47 +137,51 @@ class Main(object):
 
        and
 
        current_player_song 'song' param -> songChoice, if you're in autofollow
 
        """
 

	
 
        def current_player_song(song):
 
            # (this is run on every frame)
 
            ps = wtree.get_object("playerSong")
 
            if URIRef(ps.get_uri()) != song:
 
                log.debug("update playerSong to %s", ps.get_uri())
 

	
 
                def setLabel():
 
                    ps.set_label(graph.label(song))
 

	
 
                graph.addHandler(setLabel)
 
                ps.set_uri(song)
 
            if song != songChoice():
 
                if wtree.get_object("followPlayerSongChoice").get_active():
 
                    log.debug('followPlayerSongChoice is on')
 
                    songChoice(song)
 
                
 

	
 
        dispatcher.connect(current_player_song, "current_player_song")
 
        self.current_player_song = current_player_song
 
        
 

	
 
    def setupNewSubZone(self):
 
        self.wtree.get_object("newSubZone").drag_dest_set(
 
            flags=Gtk.DestDefaults.ALL,
 
            targets=[Gtk.TargetEntry('text/uri-list', 0, 0)],
 
            actions=Gdk.DragAction.COPY)
 
        
 

	
 
    def acceptDragsOnCurveViews(self):
 
        w = self.wtree.get_object("curves")
 
        w.drag_dest_set(flags=Gtk.DestDefaults.ALL,
 
                        targets=[Gtk.TargetEntry('text/uri-list', 0, 0)],
 
                        actions=Gdk.DragAction.COPY)
 
        def recv(widget, context, x, y, selection,
 
                       targetType, time):
 

	
 
        def recv(widget, context, x, y, selection, targetType, time):
 
            subUri = URIRef(selection.data.strip())
 
            print "into curves", subUri
 
            with self.graph.currentState(
 
                    tripleFilter=(subUri, RDFS.label, None)) as current:
 
            with self.graph.currentState(tripleFilter=(subUri, RDFS.label,
 
                                                       None)) as current:
 
                subName = current.label(subUri)
 

	
 
            if '?' in subUri:
 
                subName = self.handleSubtermDrop(subUri)
 
            else:
 
                try:
 
                    self.makeSubterm(subName, withCurve=True,
 
                    self.makeSubterm(subName,
 
                                     withCurve=True,
 
                                     sub=subUri,
 
                                     expr="%s(t)" % subName)
 
                except SubtermExists:
 
@@ -177,23 +189,26 @@ class Main(object):
 
                    # correct-- user mihgt need to fix things
 
                    pass
 
            curveView = self.curvesetView.row(subName).curveView
 
            t = self.lastSeenInputTime # curveView.current_time() # new curve hasn't heard the time yet. this has gotten too messy- everyone just needs to be able to reach the time source
 
            t = self.lastSeenInputTime  # curveView.current_time() # new curve hasn't heard the time yet. this has gotten too messy- everyone just needs to be able to reach the time source
 
            print "time", t
 
            curveView.add_points([(t - .5, 0),
 
                                  (t, 1)])
 
            curveView.add_points([(t - .5, 0), (t, 1)])
 

	
 
        w.connect("drag-data-received", recv)
 
        
 

	
 
    def onDragDataInNewSubZone(self, widget, context, x, y, selection,
 
                       targetType, time):
 
                               targetType, time):
 
        data = URIRef(selection.data.strip())
 
        if '?' in data:
 
            self.handleSubtermDrop(data)
 
            return
 
        with self.graph.currentState(tripleFilter=(data, None, None)) as current:
 
        with self.graph.currentState(tripleFilter=(data, None,
 
                                                   None)) as current:
 
            subName = current.label(data)
 
        self.makeSubterm(newname=subName, withCurve=True, sub=data,
 
        self.makeSubterm(newname=subName,
 
                         withCurve=True,
 
                         sub=data,
 
                         expr="%s(t)" % subName)
 
        
 

	
 
    def handleSubtermDrop(self, data):
 
        params = parse_qsl(data.split('?')[1])
 
        flattened = dict(params)
 
@@ -216,21 +231,21 @@ class Main(object):
 

	
 
    def onRedrawCurves(self, *args):
 
        dispatcher.send("all curves rebuild")
 
        
 

	
 
    def onSubtermsMap(self, *args):
 
        # if this was called too soon, like in __init__, the gtktable
 
        # would get its children but it wouldn't lay anything out that
 
        # I can see, and I'm not sure why. Waiting for map event is
 
        # just a wild guess.
 
        self.graph.addHandler(self.set_subterms_from_graph)
 
        
 

	
 
    def onNewSubterm(self, *args):
 
        self.makeSubterm(Literal(""), withCurve=False)
 
        return
 

	
 
        # pretty sure i don't want this back, but not completely sure
 
        # what the UX should be to get the new curve.
 
        
 

	
 
        dialog = self.wtree.get_object("newSubterm")
 
        # the plan is to autocomplete this on existing subterm names
 
        # (but let you make one up, too)
 
@@ -245,9 +260,9 @@ class Main(object):
 

	
 
    def currentSong(self):
 

	
 
        with self.graph.currentState(
 
                tripleFilter=(self.session, L9['currentSong'], None)
 
        ) as current:
 
        with self.graph.currentState(tripleFilter=(self.session,
 
                                                   L9['currentSong'],
 
                                                   None)) as current:
 
            return current.value(self.session, L9['currentSong'])
 

	
 
    def songSubtermsContext(self):
 
@@ -277,13 +292,13 @@ class Main(object):
 
            (uri, RDF.type, L9.Subterm, ctx),
 
            (uri, RDFS.label, Literal(newname), ctx),
 
            (self.currentSong(), L9['subterm'], uri, ctx),
 
            ]
 
        ]
 
        if sub is not None:
 
            quads.append((uri, L9['sub'], sub, ctx))
 
        if expr is not None:
 
            quads.append((uri, L9['expression'], Literal(expr), ctx))
 
        self.graph.patch(Patch(addQuads=quads))
 
            
 

	
 
        return uri
 

	
 
    def all_subterm_labels(self):
 
@@ -301,7 +316,7 @@ class Main(object):
 
                if sub is not None:
 
                    labels.append(current.label(sub))
 
        return labels
 
        
 

	
 
    def set_subterms_from_graph(self):
 
        """rebuild all the gtktable 'subterms' widgets and the
 
        self.currentSubterms list"""
 
@@ -311,7 +326,7 @@ class Main(object):
 
        for st in set(self.graph.objects(song, L9['subterm'])):
 
            log.debug("song %s has subterm %s", song, st)
 
            term = Subterm(self.graph, st, self.songSubtermsContext(),
 
                               self.curveset)
 
                           self.curveset)
 
            newList.append(term)
 
        self.currentSubterms[:] = newList
 

	
 
@@ -335,13 +350,12 @@ class Main(object):
 
          * { font-size: 92%; }
 
          .button:link { font-size: 7px }
 
        ''')
 
        
 

	
 
        screen = Gdk.Display.get_default_screen(Gdk.Display.get_default())
 
        for p in providers:
 
            Gtk.StyleContext.add_provider_for_screen(
 
                screen, p,
 
                Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
 
        
 
                screen, p, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
 

	
 
    def onSubtermChildAdded(self, subtermsTable, *args):
 
        # this would probably work, but isn't getting called
 
        log.info("onSubtermChildAdded")
 
@@ -369,7 +383,7 @@ class Main(object):
 
        ns.update(globals())
 
        ns.update(self.__dict__)
 
        togglePyConsole(self, item, ns)
 
        
 

	
 
    def onSeeCurrentTime(self, item):
 
        dispatcher.send("see time")
 

	
 
@@ -395,15 +409,14 @@ class Main(object):
 
    def makeStatusLines(self, master):
 
        """various labels that listen for dispatcher signals"""
 
        for row, (signame, textfilter) in enumerate([
 
            ('input time', lambda t: "%.2fs"%t),
 
            ('output levels',
 
             lambda levels: textwrap.fill("; ".join(["%s:%.2f"%(n,v)
 
                                                     for n,v in
 
                                                     levels.items()[:2]
 
                                                     if v>0]),70)),
 
            ('update period', lambda t: "%.1fms"%(t*1000)),
 
            ('input time', lambda t: "%.2fs" % t),
 
            ('output levels', lambda levels: textwrap.fill(
 
                "; ".join([
 
                    "%s:%.2f" % (n, v) for n, v in levels.items()[:2] if v > 0
 
                ]), 70)),
 
            ('update period', lambda t: "%.1fms" % (t * 1000)),
 
            ('update status', lambda x: str(x)),
 
            ]):
 
        ]):
 
            key = Gtk.Label("%s:" % signame)
 
            value = Gtk.Label("")
 
            master.resize(row + 1, 2)
 
@@ -412,28 +425,31 @@ class Main(object):
 
            key.set_alignment(1, 0)
 
            value.set_alignment(0, 0)
 

	
 
            dispatcher.connect(lambda val, value=value, tf=textfilter:
 
                               value.set_text(tf(val)),
 
                               signame, weak=False)
 
            dispatcher.connect(lambda val, value=value, tf=textfilter: value.
 
                               set_text(tf(val)),
 
                               signame,
 
                               weak=False)
 
        dispatcher.connect(lambda val: setattr(self, 'lastSeenInputTime', val),
 
                           'input time', weak=False)
 
                           'input time',
 
                           weak=False)
 
        master.show_all()
 

	
 
    def refreshCurveView(self):
 
        wtree = self.wtree
 
        mtimes = [os.path.getmtime(f) for f in [
 
            'light9/curvecalc/curveview.py',
 
            'light9/curvecalc/zoomcontrol.py',
 
            ]]
 
        mtimes = [
 
            os.path.getmtime(f) for f in [
 
                'light9/curvecalc/curveview.py',
 
                'light9/curvecalc/zoomcontrol.py',
 
            ]
 
        ]
 

	
 
        if (not hasattr(self, 'curvesetView') or
 
            self.curvesetView._mtimes != mtimes):
 
                self.curvesetView._mtimes != mtimes):
 
            print "reload curveview.py"
 
            curvesVBox = wtree.get_object("curves")
 
            zoomControlBox = wtree.get_object("zoomControlBox")
 
            [curvesVBox.remove(c) for c in curvesVBox.get_children()]
 
            [zoomControlBox.remove(c) for c in
 
             zoomControlBox.get_children()]
 
            [zoomControlBox.remove(c) for c in zoomControlBox.get_children()]
 
            try:
 
                linecache.clearcache()
 
                reload(curveview)
 
@@ -443,8 +459,8 @@ class Main(object):
 
                    self.curvesetView.live = False
 

	
 
                # mem problem somewhere; need to hold a ref to this
 
                self.curvesetView = curveview.Curvesetview(self.graph, 
 
                    curvesVBox, zoomControlBox, self.curveset)
 
                self.curvesetView = curveview.Curvesetview(
 
                    self.graph, curvesVBox, zoomControlBox, self.curveset)
 
                self.curvesetView._mtimes = mtimes
 

	
 
                # this is scheduled after some tk shuffling, to
 
@@ -463,6 +479,7 @@ class MaxTime(object):
 
    """
 
    looks up the time in seconds for the session's current song
 
    """
 

	
 
    def __init__(self, graph, session):
 
        self.graph, self.session = graph, session
 
        graph.addHandler(self.update)
 
@@ -480,6 +497,7 @@ class MaxTime(object):
 
    def get(self):
 
        return self.maxtime
 

	
 

	
 
def launch(args, graph, session, opts, startTime, music):
 

	
 
    try:
 
@@ -492,7 +510,7 @@ def launch(args, graph, session, opts, s
 
        pass
 

	
 
    curveset = Curveset(graph=graph, session=session)
 
        
 

	
 
    log.debug("startup: output %s", time.time() - startTime)
 

	
 
    mt = MaxTime(graph, session)
 
@@ -501,9 +519,8 @@ def launch(args, graph, session, opts, s
 
    start = Main(graph, opts, session, curveset, music)
 
    out = Output(graph, session, music, curveset, start.currentSubterms)
 

	
 
    dispatcher.send("show all")
 

	
 
    dispatcher.send("show all")
 
        
 
    if opts.startup_only:
 
        log.debug("quitting now because of --startup-only")
 
        return
 
@@ -515,22 +532,24 @@ def launch(args, graph, session, opts, s
 
            requestHandler.set_status(404)
 
            requestHandler.write("not hovering over any time")
 
            return
 
        with graph.currentState(
 
                tripleFilter=(session, L9['currentSong'], None)) as g:
 
        with graph.currentState(tripleFilter=(session, L9['currentSong'],
 
                                              None)) as g:
 
            song = g.value(session, L9['currentSong'])
 
            json.dump({"song": song, "hoverTime" : times[0]}, requestHandler)
 
        
 
            json.dump({"song": song, "hoverTime": times[0]}, requestHandler)
 

	
 
    serveCurveEdit(networking.curveCalc.port, hoverTimeResponse, start.curveset)
 

	
 

	
 
def main():
 
    startTime = time.time()
 
    parser = optparse.OptionParser()
 
    parser.set_usage("%prog [opts] [songURI]")
 
    parser.add_option("--debug", action="store_true",
 
                      help="log at DEBUG")
 
    parser.add_option("--reload", action="store_true",
 
    parser.add_option("--debug", action="store_true", help="log at DEBUG")
 
    parser.add_option("--reload",
 
                      action="store_true",
 
                      help="live reload of themes and code")
 
    parser.add_option("--startup-only", action='store_true',
 
    parser.add_option("--startup-only",
 
                      action='store_true',
 
                      help="quit after loading everything (for timing tests)")
 
    parser.add_option("--profile", help='"hotshot" or "stat"')
 
    clientsession.add_option(parser)
 
@@ -540,16 +559,15 @@ def main():
 

	
 
    log.debug("startup: music %s", time.time() - startTime)
 

	
 

	
 
    session = clientsession.getUri('curvecalc', opts)
 

	
 
    music = Music()
 
    graph = SyncedGraph(networking.rdfdb.url, "curvecalc")
 

	
 
    graph.initiallySynced.addCallback(
 
        lambda _: launch(args, graph, session, opts, startTime, music))
 
    graph.initiallySynced.addCallback(lambda _: launch(args, graph, session,
 
                                                       opts, startTime, music))
 
    from light9 import prof
 
    prof.run(reactor.run, profile=opts.profile)
 

	
 

	
 
main()
 

	
bin/dmx_color_test.py
Show inline comments
 
@@ -7,6 +7,7 @@ from twisted.internet import reactor, ta
 
log.setLevel(logging.INFO)
 
firstDmxChannel = 10
 

	
 

	
 
def step():
 
    hue = (time.time() * .2) % 1.0
 
    r, g, b = colorsys.hsv_to_rgb(hue, 1, 1)
bin/dmxserver
Show inline comments
 
@@ -27,10 +27,10 @@ todo:
 
from __future__ import division
 
from twisted.internet import reactor
 
from twisted.web import xmlrpc, server
 
import sys,time,os
 
import sys, time, os
 
from optparse import OptionParser
 
import run_local
 
import txosc.dispatch, txosc.async
 
import txosc.dispatch, txosc. async
 
from light9.io import ParportDMX, UsbDMX
 

	
 
from light9.updatefreq import Updatefreq
 
@@ -39,21 +39,26 @@ from light9 import networking
 
from txzmq import ZmqEndpoint, ZmqFactory, ZmqPullConnection, ZmqRequestTimeoutError
 
import json
 

	
 

	
 
def startZmq(port, outputlevels):
 
    zf = ZmqFactory()
 
    e = ZmqEndpoint('bind', 'tcp://*:%s' % port)
 
    s = ZmqPullConnection(zf, e)
 

	
 
    def onPull(message):
 
        msg = json.loads(message[0])
 
        outputlevels(msg['clientid'], msg['levellist'])
 

	
 
    s.onPull = onPull
 

	
 

	
 
class ReceiverApplication(object):
 
    """
 
    receive UDP OSC messages. address is /dmx/1 for dmx channel 1,
 
    arguments are 0-1 floats for that channel and any number of
 
    following channels.
 
    """
 

	
 
    def __init__(self, port, lightServer):
 
        self.port = port
 
        self.lightServer = lightServer
 
@@ -61,7 +66,7 @@ class ReceiverApplication(object):
 
        self.receiver.addCallback("/dmx/*", self.pixel_handler)
 
        self._server_port = reactor.listenUDP(
 
            self.port,
 
            txosc.async.DatagramServerProtocol(self.receiver),
 
            txosc. async .DatagramServerProtocol(self.receiver),
 
            interface='0.0.0.0')
 
        print "Listening OSC on udp port %s" % (self.port)
 

	
 
@@ -70,127 +75,127 @@ class ReceiverApplication(object):
 
        startChannel = int(message.address.split('/')[2])
 
        levels = [a.value for a in message.arguments]
 
        allLevels = [0] * (startChannel - 1) + levels
 
        self.lightServer.xmlrpc_outputlevels("osc@%s" % startChannel,
 
                                             allLevels)
 
        self.lightServer.xmlrpc_outputlevels("osc@%s" % startChannel, allLevels)
 

	
 

	
 
class XMLRPCServe(xmlrpc.XMLRPC):
 
    def __init__(self,options):
 

	
 
    def __init__(self, options):
 

	
 
        xmlrpc.XMLRPC.__init__(self)
 
        
 
        self.clientlevels={} # clientID : list of levels
 
        self.lastseen={} # clientID : time last seen
 
        self.clientfreq={} # clientID : updatefreq
 
        
 
        self.combinedlevels=[] # list of levels, after max'ing the clients
 
        self.clientschanged=1 # have clients sent anything since the last send?
 
        self.options=options
 
        self.lastupdate=0 # time of last dmx send
 
        self.laststatsprint=0  # time
 

	
 
        self.clientlevels = {}  # clientID : list of levels
 
        self.lastseen = {}  # clientID : time last seen
 
        self.clientfreq = {}  # clientID : updatefreq
 

	
 
        self.combinedlevels = []  # list of levels, after max'ing the clients
 
        self.clientschanged = 1  # have clients sent anything since the last send?
 
        self.options = options
 
        self.lastupdate = 0  # time of last dmx send
 
        self.laststatsprint = 0  # time
 

	
 
        # desired seconds between sendlevels() calls
 
        self.calldelay=1/options.updates_per_sec 
 
        self.calldelay = 1 / options.updates_per_sec
 

	
 
        print "starting parport connection"
 
        self.parportdmx = UsbDMX(dimmers=90, port=options.dmx_device)
 
        if os.environ.get('DMXDUMMY',0):
 
        if os.environ.get('DMXDUMMY', 0):
 
            self.parportdmx.godummy()
 
        else:
 
            self.parportdmx.golive()
 
            
 

	
 
        self.updatefreq=Updatefreq() # freq of actual dmx sends
 
        self.num_unshown_updates=None
 
        self.lastshownlevels=None
 
        self.updatefreq = Updatefreq()  # freq of actual dmx sends
 
        self.num_unshown_updates = None
 
        self.lastshownlevels = None
 
        # start the loop
 
        self.sendlevels()
 

	
 
        # the other loop
 
        self.purgeclients()
 
        
 

	
 
    def purgeclients(self):
 
        
 
        """forget about any clients who haven't sent levels in a while.
 
        this runs in a loop"""
 

	
 
        purge_age=10 # seconds
 
        
 
        reactor.callLater(1,self.purgeclients)
 
        purge_age = 10  # seconds
 

	
 
        now=time.time()
 
        reactor.callLater(1, self.purgeclients)
 

	
 
        now = time.time()
 
        cids = self.lastseen.keys()
 
        for cid in cids:
 
            lastseen=self.lastseen[cid]
 
            lastseen = self.lastseen[cid]
 
            if lastseen < now - purge_age:
 
                print ("forgetting client %s (no activity for %s sec)" %
 
                       (cid,purge_age))
 
                print("forgetting client %s (no activity for %s sec)" %
 
                      (cid, purge_age))
 
                try:
 
                    del self.clientlevels[cid]
 
                except KeyError:
 
                    pass
 
                del self.clientfreq[cid]
 
                del self.lastseen[cid]
 
        
 

	
 
    def sendlevels(self):
 
        
 
        """sends to dmx if levels have changed, or if we havent sent
 
        in a while"""
 

	
 
        reactor.callLater(self.calldelay,self.sendlevels)
 
        reactor.callLater(self.calldelay, self.sendlevels)
 

	
 
        if self.clientschanged:
 
            # recalc levels
 

	
 
            self.calclevels()
 
         
 
            if (self.num_unshown_updates is None or # first time
 
                self.options.fast_updates or # show always
 
                (self.combinedlevels!=self.lastshownlevels and # changed
 
                 self.num_unshown_updates>5)): # not too frequent
 
                self.num_unshown_updates=0
 

	
 
            if (self.num_unshown_updates is None or  # first time
 
                    self.options.fast_updates or  # show always
 
                (
 
                    self.combinedlevels != self.lastshownlevels and  # changed
 
                    self.num_unshown_updates > 5)):  # not too frequent
 
                self.num_unshown_updates = 0
 
                self.printlevels()
 
                self.lastshownlevels=self.combinedlevels[:]
 
                self.lastshownlevels = self.combinedlevels[:]
 
            else:
 
                self.num_unshown_updates+=1
 
                self.num_unshown_updates += 1
 

	
 
        if time.time()>self.laststatsprint+2:
 
            self.laststatsprint=time.time()
 
        if time.time() > self.laststatsprint + 2:
 
            self.laststatsprint = time.time()
 
            self.printstats()
 

	
 
        # used to be a fixed 1 in here, for the max delay between
 
        # calls, instead of calldelay
 
        if self.clientschanged or time.time() > self.lastupdate + self.calldelay:
 
            self.lastupdate=time.time()
 
        if self.clientschanged or time.time(
 
        ) > self.lastupdate + self.calldelay:
 
            self.lastupdate = time.time()
 
            self.sendlevels_dmx()
 

	
 
        self.clientschanged=0 # clear the flag
 
        
 
        self.clientschanged = 0  # clear the flag
 

	
 
    def calclevels(self):
 
        """combine all the known client levels into self.combinedlevels"""
 
        self.combinedlevels=[]
 
        for chan in range(0,self.parportdmx.dimmers):
 
            x=0
 
        self.combinedlevels = []
 
        for chan in range(0, self.parportdmx.dimmers):
 
            x = 0
 
            for clientlist in self.clientlevels.values():
 
                if len(clientlist)>chan:
 
                if len(clientlist) > chan:
 
                    # clamp client levels to 0..1
 
                    cl=max(0,min(1,clientlist[chan]))
 
                    x=max(x,cl)
 
                    cl = max(0, min(1, clientlist[chan]))
 
                    x = max(x, cl)
 
            self.combinedlevels.append(x)
 

	
 
    def printlevels(self):
 
        """write all the levels to stdout"""
 
        print "Levels:","".join(["% 2d "%(x*100) for
 
                                 x in self.combinedlevels])
 
    
 
        print "Levels:", "".join(
 
            ["% 2d " % (x * 100) for x in self.combinedlevels])
 

	
 
    def printstats(self):
 
        """print the clock, freq, etc, with a \r at the end"""
 

	
 
        sys.stdout.write("dmxserver up at %s, [polls %s] "%
 
                         (time.strftime("%H:%M:%S"),
 
                          str(self.updatefreq),
 
                          ))
 
        for cid,freq in self.clientfreq.items():
 
            sys.stdout.write("[%s %s] " % (cid,str(freq)))
 
        sys.stdout.write("dmxserver up at %s, [polls %s] " % (
 
            time.strftime("%H:%M:%S"),
 
            str(self.updatefreq),
 
        ))
 
        for cid, freq in self.clientfreq.items():
 
            sys.stdout.write("[%s %s] " % (cid, str(freq)))
 
        sys.stdout.write("\r")
 
        sys.stdout.flush()
 

	
 
@@ -198,18 +203,18 @@ class XMLRPCServe(xmlrpc.XMLRPC):
 
        """output self.combinedlevels to dmx, and keep the updates/sec stats"""
 
        # they'll get divided by 100
 
        if self.parportdmx:
 
            self.parportdmx.sendlevels([l*100 for l in self.combinedlevels])
 
            self.parportdmx.sendlevels([l * 100 for l in self.combinedlevels])
 
        self.updatefreq.update()
 
    
 
    def xmlrpc_echo(self,x):
 

	
 
    def xmlrpc_echo(self, x):
 
        return x
 
    
 
    def xmlrpc_outputlevels(self,cid,levellist):
 

	
 
    def xmlrpc_outputlevels(self, cid, levellist):
 
        """send a unique id for your client (name+pid maybe), then
 
        the variable-length dmx levellist (scaled 0..1)"""
 
        if levellist!=self.clientlevels.get(cid,None):
 
            self.clientlevels[cid]=levellist
 
            self.clientschanged=1
 
        if levellist != self.clientlevels.get(cid, None):
 
            self.clientlevels[cid] = levellist
 
            self.clientschanged = 1
 
        self.trackClientFreq(cid)
 
        return "ok"
 

	
 
@@ -227,43 +232,50 @@ class XMLRPCServe(xmlrpc.XMLRPC):
 
            i -= 1
 
        if i < 0:
 
            return []
 
        trunc = trunc[:i+1]
 
        trunc = trunc[:i + 1]
 
        return trunc
 
    
 

	
 
    def trackClientFreq(self, cid):
 
        if cid not in self.lastseen:
 
            print "hello new client %s" % cid
 
            self.clientfreq[cid]=Updatefreq()
 
        self.lastseen[cid]=time.time()
 
            self.clientfreq[cid] = Updatefreq()
 
        self.lastseen[cid] = time.time()
 
        self.clientfreq[cid].update()
 
        
 

	
 

	
 
parser=OptionParser()
 
parser.add_option("-f","--fast-updates",action='store_true',
 
parser = OptionParser()
 
parser.add_option("-f",
 
                  "--fast-updates",
 
                  action='store_true',
 
                  help=('display all dmx output to stdout instead '
 
                        'of the usual reduced output'))
 
parser.add_option("-r","--updates-per-sec",type='float',default=20,
 
parser.add_option("-r",
 
                  "--updates-per-sec",
 
                  type='float',
 
                  default=20,
 
                  help=('dmx output frequency'))
 
parser.add_option("-d","--dmx-device", default='/dev/dmx0',
 
parser.add_option("-d",
 
                  "--dmx-device",
 
                  default='/dev/dmx0',
 
                  help='dmx device name')
 
parser.add_option("-n", "--dummy", action="store_true",
 
parser.add_option("-n",
 
                  "--dummy",
 
                  action="store_true",
 
                  help="dummy mode, same as DMXDUMMY=1 env variable")
 
(options,songfiles)=parser.parse_args()
 
(options, songfiles) = parser.parse_args()
 

	
 
print options
 

	
 
if options.dummy:
 
    os.environ['DMXDUMMY'] = "1"
 

	
 

	
 
port = networking.dmxServer.port
 
print "starting xmlrpc server on port %s" % port
 
xmlrpcServe = XMLRPCServe(options)
 
reactor.listenTCP(port,server.Site(xmlrpcServe))
 
reactor.listenTCP(port, server.Site(xmlrpcServe))
 

	
 
startZmq(networking.dmxServerZmq.port, xmlrpcServe.xmlrpc_outputlevels)
 
                  
 

	
 
oscApp = ReceiverApplication(9051, xmlrpcServe)
 

	
 
reactor.run()
 

	
bin/effecteval
Show inline comments
 
@@ -8,8 +8,8 @@ import cyclone.web, cyclone.websocket, c
 
import sys, optparse, logging, subprocess, json, itertools
 
from rdflib import URIRef, Literal
 

	
 
sys.path.append('/usr/lib/pymodules/python2.7/') # for numpy, on rpi
 
sys.path.append('/usr/lib/python2.7/dist-packages') # For numpy
 
sys.path.append('/usr/lib/pymodules/python2.7/')  # for numpy, on rpi
 
sys.path.append('/usr/lib/python2.7/dist-packages')  # For numpy
 
from light9 import networking, showconfig
 
from light9.effecteval.effect import EffectNode
 
from light9.effect.edit import getMusicStatus, songNotePatch
 
@@ -22,19 +22,24 @@ from greplin import scales
 

	
 
from lib.cycloneerr import PrettyErrorHandler
 

	
 

	
 
class EffectEdit(PrettyErrorHandler, cyclone.web.RequestHandler):
 

	
 
    def get(self):
 
        self.set_header('Content-Type', 'text/html')        
 
        self.set_header('Content-Type', 'text/html')
 
        self.write(open("light9/effecteval/effect.html").read())
 

	
 
    def delete(self):
 
        graph = self.settings.graph
 
        uri = URIRef(self.get_argument('uri'))
 
        with graph.currentState(tripleFilter=(None, L9['effect'], uri)) as g:
 
            song = ctx = list(g.subjects(L9['effect'], uri))[0]
 
        self.settings.graph.patch(Patch(delQuads=[
 
            (song, L9['effect'], uri, ctx),
 
        self.settings.graph.patch(
 
            Patch(delQuads=[
 
                (song, L9['effect'], uri, ctx),
 
            ]))
 
        
 

	
 

	
 
@inlineCallbacks
 
def currentSong():
 
    s = (yield getMusicStatus())['song']
 
@@ -42,13 +47,17 @@ def currentSong():
 
        raise ValueError("no current song")
 
    returnValue(URIRef(s))
 

	
 

	
 
class SongEffects(PrettyErrorHandler, cyclone.web.RequestHandler):
 

	
 
    def wideOpenCors(self):
 
        self.set_header('Access-Control-Allow-Origin', '*')
 
        self.set_header('Access-Control-Allow-Methods', 'GET, PUT, POST, DELETE, OPTIONS')
 
        self.set_header('Access-Control-Allow-Methods',
 
                        'GET, PUT, POST, DELETE, OPTIONS')
 
        self.set_header('Access-Control-Max-Age', '1000')
 
        self.set_header('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-Requested-With')
 
    
 
        self.set_header('Access-Control-Allow-Headers',
 
                        'Content-Type, Authorization, X-Requested-With')
 

	
 
    def options(self):
 
        self.wideOpenCors()
 
        self.write('')
 
@@ -60,27 +69,34 @@ class SongEffects(PrettyErrorHandler, cy
 

	
 
        try:
 
            song = URIRef(self.get_argument('uri'))
 
        except Exception: # which?
 
        except Exception:  # which?
 
            song = yield currentSong()
 

	
 
        event = self.get_argument('event', default='default')
 
        
 

	
 
        note = self.get_argument('note', default=None)
 
        if note is not None:
 
            note = URIRef(note)
 

	
 
        log.info("adding to %s", song)
 
        note, p = yield songNotePatch(self.settings.graph, dropped, song, event, ctx=song, note=note)
 
        
 
        note, p = yield songNotePatch(self.settings.graph,
 
                                      dropped,
 
                                      song,
 
                                      event,
 
                                      ctx=song,
 
                                      note=note)
 

	
 
        self.settings.graph.patch(p)
 
        self.settings.graph.suggestPrefixes(song, {'song': URIRef(song + '/')})
 
        self.write(json.dumps({'note': note}))
 
        
 

	
 

	
 
class SongEffectsUpdates(cyclone.websocket.WebSocketHandler):
 

	
 
    def connectionMade(self, *args, **kwargs):
 
        self.graph = self.settings.graph
 
        self.graph.addHandler(self.updateClient)
 
        
 

	
 
    def updateClient(self):
 
        # todo: abort if client is gone
 
        playlist = self.graph.value(showconfig.showUri(), L9['playList'])
 
@@ -88,14 +104,18 @@ class SongEffectsUpdates(cyclone.websock
 
        out = []
 
        for s in songs:
 
            out.append({'uri': s, 'label': self.graph.label(s)})
 
            out[-1]['effects'] = [{'uri': uri, 'label': self.graph.label(uri)} for uri in sorted(self.graph.objects(s, L9['effect']))]
 
            out[-1]['effects'] = [{
 
                'uri': uri,
 
                'label': self.graph.label(uri)
 
            } for uri in sorted(self.graph.objects(s, L9['effect']))]
 
        self.sendMessage({'songs': out})
 
        
 
        
 

	
 

	
 
class EffectUpdates(cyclone.websocket.WebSocketHandler):
 
    """
 
    stays alive for the life of the effect page
 
    """
 

	
 
    def connectionMade(self, *args, **kwargs):
 
        log.info("websocket opened")
 
        self.uri = URIRef(self.get_argument('uri'))
 
@@ -113,7 +133,7 @@ class EffectUpdates(cyclone.websocket.We
 
        en = EffectNode(self.graph, self.uri)
 
        codeLines = [c.code for c in en.codes]
 
        self.sendMessage({'codeLines': codeLines})
 
        
 

	
 
    def connectionLost(self, reason):
 
        log.info("websocket closed")
 

	
 
@@ -121,12 +141,12 @@ class EffectUpdates(cyclone.websocket.We
 
        log.info("got message %s" % message)
 
        # write a patch back to the graph
 

	
 

	
 
def replaceObjects(graph, c, s, p, newObjs):
 
    patch = graph.getObjectPatch(
 
        context=c,
 
        subject=s,
 
        predicate=p,
 
        newObject=newObjs[0])
 
    patch = graph.getObjectPatch(context=c,
 
                                 subject=s,
 
                                 predicate=p,
 
                                 newObject=newObjs[0])
 

	
 
    moreAdds = []
 
    for line in newObjs[1:]:
 
@@ -135,8 +155,9 @@ def replaceObjects(graph, c, s, p, newOb
 
                      addQuads=patch.addQuads + moreAdds)
 
    graph.patch(fullPatch)
 

	
 
        
 

	
 
class Code(PrettyErrorHandler, cyclone.web.RequestHandler):
 

	
 
    def put(self):
 
        effect = URIRef(self.get_argument('uri'))
 
        codeLines = []
 
@@ -150,16 +171,18 @@ class Code(PrettyErrorHandler, cyclone.w
 
        if not codeLines:
 
            log.info("no codelines received on PUT /code")
 
            return
 
        with self.settings.graph.currentState(
 
                tripleFilter=(None, L9['effect'], effect)) as g:
 
        with self.settings.graph.currentState(tripleFilter=(None, L9['effect'],
 
                                                            effect)) as g:
 
            song = g.subjects(L9['effect'], effect).next()
 
            
 

	
 
        replaceObjects(self.settings.graph, song, effect, L9['code'], codeLines)
 
            
 

	
 
        # right here we could tell if the code has a python error and return it
 
        self.send_error(202)
 
        
 

	
 

	
 
class EffectEval(PrettyErrorHandler, cyclone.web.RequestHandler):
 

	
 
    @inlineCallbacks
 
    def get(self):
 
        # return dmx list for that effect
 
@@ -176,6 +199,7 @@ class EffectEval(PrettyErrorHandler, cyc
 
# Completely not sure where the effect background loop should
 
# go. Another process could own it, and get this request repeatedly:
 
class SongEffectsEval(PrettyErrorHandler, cyclone.web.RequestHandler):
 

	
 
    def get(self):
 
        song = URIRef(self.get_argument('song'))
 
        effects = effectsForSong(self.settings.graph, song)
 
@@ -183,34 +207,44 @@ class SongEffectsEval(PrettyErrorHandler
 
        self.write(maxDict(effectDmxDict(e) for e in effects))
 
        # return dmx dict for all effects in the song, already combined
 

	
 

	
 
class App(object):
 

	
 
    def __init__(self, show, outputWhere):
 
        self.show = show
 
        self.outputWhere = outputWhere
 
        self.graph = SyncedGraph(networking.rdfdb.url, "effectEval")
 
        self.graph.initiallySynced.addCallback(self.launch).addErrback(log.error)
 
        self.graph.initiallySynced.addCallback(self.launch).addErrback(
 
            log.error)
 

	
 
        self.stats = scales.collection('/',
 
                                       scales.PmfStat('sendLevels'),
 
                                       scales.PmfStat('getMusic'),
 
                                       scales.PmfStat('evals'),
 
                                       scales.PmfStat('sendOutput'),
 
                                       scales.IntStat('errors'),
 
                                       )
 
        self.stats = scales.collection(
 
            '/',
 
            scales.PmfStat('sendLevels'),
 
            scales.PmfStat('getMusic'),
 
            scales.PmfStat('evals'),
 
            scales.PmfStat('sendOutput'),
 
            scales.IntStat('errors'),
 
        )
 

	
 
    def launch(self, *args):
 
        log.info('launch')
 
        if self.outputWhere:
 
            self.loop = makeEffectLoop(self.graph, self.stats, self.outputWhere)
 
            self.loop.startLoop()
 
        
 

	
 
        SFH = cyclone.web.StaticFileHandler
 
        self.cycloneApp = cyclone.web.Application(handlers=[
 
            (r'/()', SFH,
 
             {'path': 'light9/effecteval', 'default_filename': 'index.html'}),
 
            (r'/()', SFH, {
 
                'path': 'light9/effecteval',
 
                'default_filename': 'index.html'
 
            }),
 
            (r'/effect', EffectEdit),
 
            (r'/effect\.js', StaticCoffee, {'src': 'light9/effecteval/effect.coffee'}),
 
            (r'/(effect-components\.html)', SFH, {'path': 'light9/effecteval'}),
 
            (r'/effect\.js', StaticCoffee, {
 
                'src': 'light9/effecteval/effect.coffee'
 
            }),
 
            (r'/(effect-components\.html)', SFH, {
 
                'path': 'light9/effecteval'
 
            }),
 
            (r'/effectUpdates', EffectUpdates),
 
            (r'/code', Code),
 
            (r'/songEffectsUpdates', SongEffectsUpdates),
 
@@ -224,25 +258,33 @@ class App(object):
 
                                                  stats=self.stats)
 
        reactor.listenTCP(networking.effectEval.port, self.cycloneApp)
 
        log.info("listening on %s" % networking.effectEval.port)
 
        
 

	
 

	
 
class StaticCoffee(PrettyErrorHandler, cyclone.web.RequestHandler):
 

	
 
    def initialize(self, src):
 
        super(StaticCoffee, self).initialize()
 
        self.src = src
 

	
 
    def get(self):
 
        self.set_header('Content-Type', 'application/javascript')
 
        self.write(subprocess.check_output([
 
            '/usr/bin/coffee', '--compile', '--print', self.src]))
 
        self.write(
 
            subprocess.check_output(
 
                ['/usr/bin/coffee', '--compile', '--print', self.src]))
 

	
 
        
 

	
 
if __name__ == "__main__":
 
    parser = optparse.OptionParser()
 
    parser.add_option('--show',
 
    parser.add_option(
 
        '--show',
 
        help='show URI, like http://light9.bigasterisk.com/show/dance2008',
 
                      default=showconfig.showUri())
 
    parser.add_option("-v", "--verbose", action="store_true",
 
        default=showconfig.showUri())
 
    parser.add_option("-v",
 
                      "--verbose",
 
                      action="store_true",
 
                      help="logging.DEBUG")
 
    parser.add_option("--twistedlog", action="store_true",
 
    parser.add_option("--twistedlog",
 
                      action="store_true",
 
                      help="twisted logging")
 
    parser.add_option("--output", metavar="WHERE", help="dmx or leds")
 
    (options, args) = parser.parse_args()
 
@@ -250,7 +292,7 @@ if __name__ == "__main__":
 

	
 
    if not options.show:
 
        raise ValueError("missing --show http://...")
 
            
 

	
 
    app = App(URIRef(options.show), options.output)
 
    if options.twistedlog:
 
        from twisted.python import log as twlog
bin/effectsequencer
Show inline comments
 
@@ -17,7 +17,9 @@ from light9.collector.collector_client i
 

	
 
from light9 import clientsession
 

	
 

	
 
class App(object):
 

	
 
    def __init__(self, show, session):
 
        self.show = show
 
        self.session = session
 
@@ -25,23 +27,25 @@ class App(object):
 
        self.graph = SyncedGraph(networking.rdfdb.url, "effectSequencer")
 
        self.graph.initiallySynced.addCallback(self.launch)
 

	
 
        self.stats = scales.collection(
 
            '/',
 
            scales.PmfStat('sendLevels'),
 
            scales.PmfStat('getMusic'),
 
            scales.PmfStat('evals'),
 
            scales.PmfStat('sendOutput'),
 
            scales.IntStat('errors'),
 
        )
 

	
 
        self.stats = scales.collection('/',
 
                                       scales.PmfStat('sendLevels'),
 
                                       scales.PmfStat('getMusic'),
 
                                       scales.PmfStat('evals'),
 
                                       scales.PmfStat('sendOutput'),
 
                                       scales.IntStat('errors'),
 
                                       )
 
    def launch(self, *args):
 
        self.seq = Sequencer(
 
            self.graph,
 
            lambda settings: sendToCollector('effectSequencer', self.session,
 
                                             settings))
 
            self.graph, lambda settings: sendToCollector(
 
                'effectSequencer', self.session, settings))
 

	
 
        self.cycloneApp = cyclone.web.Application(handlers=[
 
            (r'/()', cyclone.web.StaticFileHandler,
 
             {"path" : "light9/effect/", "default_filename" : "sequencer.html"}),
 
            (r'/()', cyclone.web.StaticFileHandler, {
 
                "path": "light9/effect/",
 
                "default_filename": "sequencer.html"
 
            }),
 
            (r'/updates', Updates),
 
            (r'/stats', StatsForCyclone),
 
        ],
 
@@ -55,12 +59,16 @@ class App(object):
 

	
 
if __name__ == "__main__":
 
    parser = optparse.OptionParser()
 
    parser.add_option('--show',
 
    parser.add_option(
 
        '--show',
 
        help='show URI, like http://light9.bigasterisk.com/show/dance2008',
 
                      default=showconfig.showUri())
 
    parser.add_option("-v", "--verbose", action="store_true",
 
        default=showconfig.showUri())
 
    parser.add_option("-v",
 
                      "--verbose",
 
                      action="store_true",
 
                      help="logging.DEBUG")
 
    parser.add_option("--twistedlog", action="store_true",
 
    parser.add_option("--twistedlog",
 
                      action="store_true",
 
                      help="twisted logging")
 
    clientsession.add_option(parser)
 
    (options, args) = parser.parse_args()
 
@@ -68,7 +76,7 @@ if __name__ == "__main__":
 

	
 
    if not options.show:
 
        raise ValueError("missing --show http://...")
 
        
 

	
 
    session = clientsession.getUri('effectSequencer', options)
 

	
 
    app = App(URIRef(options.show), session)
bin/homepageConfig
Show inline comments
 
@@ -17,6 +17,7 @@ if not webServer:
 
    raise ValueError('no %r :webServer' % netHome)
 
print "listen %s;" % splitport(urlparse(webServer).netloc)[1]
 

	
 

	
 
def location(path, server):
 
    print """
 
    location /%(path)s/ {
 
@@ -32,6 +33,7 @@ def location(path, server):
 
      rewrite /[^/]+/(.*) /$1 break;
 
    }""" % vars()
 

	
 

	
 
for role, server in sorted(graph.predicate_objects(netHome)):
 
    if not server.startswith('http') or role == L9['webServer']:
 
        continue
 
@@ -41,11 +43,11 @@ for role, server in sorted(graph.predica
 
    server = server.rstrip('/')
 
    location(path, server)
 

	
 

	
 

	
 
showPath = showconfig.showUri().split('/', 3)[-1]
 
print """
 
    location /%(path)s {
 
      root %(root)s;
 
    }""" % {'path': showPath,
 
            'root': showconfig.root()[:-len(showPath)]}
 
    }""" % {
 
    'path': showPath,
 
    'root': showconfig.root()[:-len(showPath)]
 
}
bin/inputdemo
Show inline comments
 
#!bin/python
 
import sys
 
sys.path.append('/usr/lib/python2.7/dist-packages') # For gtk
 
sys.path.append('/usr/lib/python2.7/dist-packages')  # For gtk
 
from twisted.internet import gtk3reactor
 
gtk3reactor.install()
 
from twisted.internet import reactor
 
@@ -14,12 +14,13 @@ from rdfdb.syncedgraph import SyncedGrap
 
import cyclone.httpclient
 
from light9.curvecalc.client import sendLiveInputPoint
 

	
 

	
 
class App(object):
 

	
 
    def __init__(self):
 
        parser = optparse.OptionParser()
 
        parser.set_usage("%prog [opts] [curve uri]")
 
        parser.add_option("--debug", action="store_true",
 
                          help="log at DEBUG")
 
        parser.add_option("--debug", action="store_true", help="log at DEBUG")
 
        clientsession.add_option(parser)
 
        opts, args = parser.parse_args()
 

	
 
@@ -30,16 +31,20 @@ class App(object):
 

	
 
        self.graph.initiallySynced.addCallback(lambda _: self.launch())
 

	
 
        self.curve = args[0] if args else URIRef('http://light9.bigasterisk.com/show/dance2014/song1/curve/c-1401259747.675542')
 
        self.curve = args[0] if args else URIRef(
 
            'http://light9.bigasterisk.com/show/dance2014/song1/curve/c-1401259747.675542'
 
        )
 
        print "sending points on curve %s" % self.curve
 
        
 

	
 
        reactor.run()
 

	
 
    def launch(self):
 
        win = Gtk.Window()
 

	
 
        slider = Gtk.Scale.new_with_range(orientation=Gtk.Orientation.VERTICAL,
 
                                          min=0, max=1, step=.001)
 
                                          min=0,
 
                                          max=1,
 
                                          step=.001)
 
        slider.props.inverted = True
 
        slider.connect('value-changed', self.onChanged)
 

	
 
@@ -52,8 +57,10 @@ class App(object):
 
    def onChanged(self, scale):
 
        t1 = time.time()
 
        d = sendLiveInputPoint(self.curve, scale.get_value())
 

	
 
        @d.addCallback
 
        def done(result):
 
            print "posted in %.1f ms" % (1000 * (time.time() - t1))
 

	
 

	
 
App()
bin/inputquneo
Show inline comments
 
@@ -14,18 +14,20 @@ from rdfdb.syncedgraph import SyncedGrap
 
from light9 import networking
 

	
 
import sys
 
sys.path.append('/usr/lib/python2.7/dist-packages') # For pygame
 
sys.path.append('/usr/lib/python2.7/dist-packages')  # For pygame
 
import pygame.midi
 

	
 
curves = {
 
    23: URIRef('http://light9.bigasterisk.com/show/dance2014/song1/curve/c-2'),
 
    24: URIRef('http://light9.bigasterisk.com/show/dance2014/song1/curve/c-3'),
 
    25: URIRef('http://light9.bigasterisk.com/show/dance2014/song1/curve/c-4'),
 
    6:URIRef('http://light9.bigasterisk.com/show/dance2014/song1/curve/c-5'),
 
    6: URIRef('http://light9.bigasterisk.com/show/dance2014/song1/curve/c-5'),
 
    18: URIRef('http://light9.bigasterisk.com/show/dance2014/song1/curve/c-6'),
 
}
 

	
 

	
 
class WatchMidi(object):
 

	
 
    def __init__(self, graph):
 
        self.graph = graph
 
        pygame.midi.init()
 
@@ -36,7 +38,7 @@ class WatchMidi(object):
 

	
 
        self.noteIsOn = {}
 

	
 
        self.effectMap = {} # note: effect class uri
 
        self.effectMap = {}  # note: effect class uri
 
        self.graph.addHandler(self.setupNotes)
 

	
 
    def setupNotes(self):
 
@@ -45,14 +47,15 @@ class WatchMidi(object):
 
            if qn:
 
                self.effectMap[int(qn)] = e
 
        log.info("setup with %s effects", len(self.effectMap))
 
        
 

	
 
    def findQuneo(self):
 
        for dev in range(pygame.midi.get_count()):
 
            interf, name, isInput, isOutput, opened = pygame.midi.get_device_info(dev)
 
            interf, name, isInput, isOutput, opened = pygame.midi.get_device_info(
 
                dev)
 
            if 'QUNEO' in name and isInput:
 
                return dev
 
        raise ValueError("didn't find quneo input device")
 
        
 

	
 
    def step(self):
 
        if not self.inp.poll():
 
            return
 
@@ -62,7 +65,6 @@ class WatchMidi(object):
 
            if status in [NOTEON, NOTEOFF]:
 
                print status, d1, d2
 

	
 

	
 
            if status == NOTEON:
 
                if not self.noteIsOn.get(d1):
 
                    self.noteIsOn[d1] = True
 
@@ -71,7 +73,10 @@ class WatchMidi(object):
 
                        cyclone.httpclient.fetch(
 
                            url=networking.effectEval.path('songEffects'),
 
                            method='POST',
 
                            headers={'Content-Type': ['application/x-www-form-urlencoded']},
 
                            headers={
 
                                'Content-Type':
 
                                ['application/x-www-form-urlencoded']
 
                            },
 
                            postdata=urllib.urlencode([('drop', e)]),
 
                        )
 
                    except KeyError:
 
@@ -80,10 +85,9 @@ class WatchMidi(object):
 
            if status == NOTEOFF:
 
                self.noteIsOn[d1] = False
 

	
 

	
 
            if 0:
 
                # curve editing mode, not done yet
 
                for group in [(23,24,25), (6, 18)]:
 
                for group in [(23, 24, 25), (6, 18)]:
 
                    if d1 in group:
 
                        if not self.noteIsOn.get(group):
 
                            print "start zero"
 
@@ -91,18 +95,20 @@ class WatchMidi(object):
 
                            for d in group:
 
                                sendLiveInputPoint(curves[d], 0)
 
                            self.noteIsOn[group] = True
 
                        else: # miss first update
 
                        else:  # miss first update
 
                            sendLiveInputPoint(curves[d1], d2 / 127)
 

	
 
                    if status == 128: #noteoff
 
                    if status == 128:  #noteoff
 
                        for d in group:
 
                            sendLiveInputPoint(curves[d], 0)
 
                        self.noteIsOn[group] = False
 

	
 

	
 
def main():
 
    log.setLevel(logging.DEBUG)
 
    graph = SyncedGraph(networking.rdfdb.url, "inputQuneo")
 
    wm = WatchMidi(graph)
 
    reactor.run()
 

	
 

	
 
main()
bin/kcclient
Show inline comments
 
#!/usr/bin/env python
 

	
 
"""send KeyboardComposer a fade request, for use from the shell"""
 

	
 
import sys
 
@@ -10,10 +9,8 @@ from light9 import networking
 
subname = sys.argv[1]
 
level = sys.argv[2]
 
fadesecs = '0'
 
if len(sys.argv)>3:
 
if len(sys.argv) > 3:
 
    fadesecs = sys.argv[3]
 

	
 
levelServer = Resource(networking.keyboardComposer.url)
 
levelServer.post('fadesub', subname=subname, level=level, secs=fadesecs)
 

	
 

	
bin/keyboardcomposer
Show inline comments
 
@@ -27,43 +27,58 @@ from light9.effect.simple_outputs import
 

	
 
from bcf2000 import BCF2000
 

	
 
nudge_keys = {
 
    'up'   : list('qwertyui'),
 
    'down' : list('asdfghjk')
 
}
 
nudge_keys = {'up': list('qwertyui'), 'down': list('asdfghjk')}
 

	
 

	
 
class DummySliders:
 

	
 
    def valueOut(self, name, value):
 
        pass
 

	
 
    def close(self):
 
        pass
 

	
 
    def reopen(self):
 
        pass
 

	
 

	
 
class SubScale(tk.Scale, Fadable):
 

	
 
    def __init__(self, master, *args, **kw):
 
        self.scale_var = kw.get('variable') or tk.DoubleVar()
 
        kw.update({'variable' : self.scale_var,
 
                   'from' : 1, 'to' : 0, 'showvalue' : 0,
 
                   'sliderlength' : 15, 'res' : 0.01,
 
                   'width' : 40, 'troughcolor' : 'black', 'bg' : 'grey40',
 
                   'highlightthickness' : 1, 'bd' : 1,
 
                   'highlightcolor' : 'red', 'highlightbackground' : 'black',
 
                   'activebackground' : 'red'})
 
        kw.update({
 
            'variable': self.scale_var,
 
            'from': 1,
 
            'to': 0,
 
            'showvalue': 0,
 
            'sliderlength': 15,
 
            'res': 0.01,
 
            'width': 40,
 
            'troughcolor': 'black',
 
            'bg': 'grey40',
 
            'highlightthickness': 1,
 
            'bd': 1,
 
            'highlightcolor': 'red',
 
            'highlightbackground': 'black',
 
            'activebackground': 'red'
 
        })
 
        tk.Scale.__init__(self, master, *args, **kw)
 
        Fadable.__init__(self, var=self.scale_var, wheel_step=0.05)
 
        self.draw_indicator_colors()
 

	
 
    def draw_indicator_colors(self):
 
        if self.scale_var.get() == 0:
 
            self['troughcolor'] = 'black'
 
        else:
 
            self['troughcolor'] = 'blue'
 

	
 

	
 
class SubmasterBox(tk.Frame):
 
    """
 
    this object owns the level of the submaster (the rdf graph is the
 
    real authority)
 
    """
 

	
 
    def __init__(self, master, graph, sub, session, col, row):
 
        self.graph = graph
 
        self.sub = sub
 
@@ -71,22 +86,31 @@ class SubmasterBox(tk.Frame):
 
        self.col, self.row = col, row
 
        bg = self.graph.value(sub, L9.color, default='#000000')
 
        rgb = webcolors.hex_to_rgb(bg)
 
        hsv = colorsys.rgb_to_hsv(*[x/255 for x in rgb])
 
        darkBg = webcolors.rgb_to_hex(tuple([int(x * 255) for x in colorsys.hsv_to_rgb(
 
            hsv[0], hsv[1], .2)]))
 
        hsv = colorsys.rgb_to_hsv(*[x / 255 for x in rgb])
 
        darkBg = webcolors.rgb_to_hex(
 
            tuple([
 
                int(x * 255) for x in colorsys.hsv_to_rgb(hsv[0], hsv[1], .2)
 
            ]))
 
        tk.Frame.__init__(self, master, bd=1, relief='raised', bg=bg)
 
        self.name = self.graph.label(sub)
 
        self.slider_var = tk.DoubleVar()
 
        self.pauseTrace = False
 
        self.scale = SubScale(self, variable=self.slider_var, width=20)
 

	
 
        self.namelabel = tk.Label(self, font="Arial 9", bg=darkBg,
 
            fg='white', pady=0)
 
        self.namelabel = tk.Label(self,
 
                                  font="Arial 9",
 
                                  bg=darkBg,
 
                                  fg='white',
 
                                  pady=0)
 
        self.graph.addHandler(self.updateName)
 

	
 
        self.namelabel.pack(side=tk.TOP)
 
        levellabel = tk.Label(self, textvariable=self.slider_var, font="Arial 6",
 
            bg='black', fg='white', pady=0)
 
        levellabel = tk.Label(self,
 
                              textvariable=self.slider_var,
 
                              font="Arial 6",
 
                              bg='black',
 
                              fg='white',
 
                              pady=0)
 
        levellabel.pack(side=tk.TOP)
 
        self.scale.pack(side=tk.BOTTOM, expand=1, fill=tk.BOTH)
 

	
 
@@ -111,7 +135,9 @@ class SubmasterBox(tk.Frame):
 
        self.updateGraphWithLevel(self.sub, self.slider_var.get())
 

	
 
        # needs fixing: plan is to use dispatcher or a method call to tell a hardware-mapping object who changed, and then it can make io if that's a current hw slider
 
        dispatcher.send("send_to_hw", sub=self.sub, hwCol=self.col + 1,
 
        dispatcher.send("send_to_hw",
 
                        sub=self.sub,
 
                        hwCol=self.col + 1,
 
                        boxRow=self.row)
 

	
 
    def updateGraphWithLevel(self, uri, level):
 
@@ -125,8 +151,10 @@ class SubmasterBox(tk.Frame):
 
                                subject=self.session,
 
                                predicate=L9['subSetting'],
 
                                nodeClass=L9['SubSetting'],
 
                                keyPred=L9['sub'], newKey=uri,
 
                                valuePred=L9['level'], newValue=Literal(level))
 
                                keyPred=L9['sub'],
 
                                newKey=uri,
 
                                valuePred=L9['level'],
 
                                newValue=Literal(level))
 

	
 
    def updateLevelFromGraph(self):
 
        """read rdf level, write it to subbox.slider_var"""
 
@@ -135,30 +163,34 @@ class SubmasterBox(tk.Frame):
 

	
 
        for setting in graph.objects(self.session, L9['subSetting']):
 
            if graph.value(setting, L9['sub']) == self.sub:
 
                self.pauseTrace = True # don't bounce this update back to server
 
                self.pauseTrace = True  # don't bounce this update back to server
 
                try:
 
                    self.slider_var.set(graph.value(setting, L9['level']))
 
                finally:
 
                    self.pauseTrace = False
 

	
 
    def updateName(self):
 

	
 
        def shortUri(u):
 
            return '.../' + u.split('/')[-1]
 
        self.namelabel.config(text=self.graph.label(self.sub) or shortUri(self.sub))
 

	
 
        self.namelabel.config(
 
            text=self.graph.label(self.sub) or shortUri(self.sub))
 

	
 

	
 
class KeyboardComposer(tk.Frame, SubClient):
 
    def __init__(self, root, graph, session,
 
                 hw_sliders=True):
 

	
 
    def __init__(self, root, graph, session, hw_sliders=True):
 
        tk.Frame.__init__(self, root, bg='black')
 
        SubClient.__init__(self)
 
        self.graph = graph
 
        self.session = session
 

	
 
        self.subbox = {} # sub uri : SubmasterBox
 
        self.slider_table = {} # coords : SubmasterBox
 
        self.rows = [] # this holds Tk Frames for each row
 
        self.subbox = {}  # sub uri : SubmasterBox
 
        self.slider_table = {}  # coords : SubmasterBox
 
        self.rows = []  # this holds Tk Frames for each row
 

	
 
        self.current_row = 0 # should come from session graph
 
        self.current_row = 0  # should come from session graph
 

	
 
        self.use_hw_sliders = hw_sliders
 
        self.connect_to_hw(hw_sliders)
 
@@ -170,7 +202,7 @@ class KeyboardComposer(tk.Frame, SubClie
 

	
 
        self.codeWatcher = CodeWatcher(
 
            onChange=lambda: self.graph.addHandler(self.redraw_sliders))
 
        
 

	
 
        self.send_levels_loop(delay=.05)
 
        self.graph.addHandler(self.rowFromGraph)
 

	
 
@@ -180,19 +212,28 @@ class KeyboardComposer(tk.Frame, SubClie
 

	
 
        self.sliders_status_var = tk.IntVar()
 
        self.sliders_status_var.set(self.use_hw_sliders)
 
        self.sliders_checkbutton = tk.Checkbutton(self.buttonframe,
 
            text="Sliders", variable=self.sliders_status_var,
 
        self.sliders_checkbutton = tk.Checkbutton(
 
            self.buttonframe,
 
            text="Sliders",
 
            variable=self.sliders_status_var,
 
            command=lambda: self.toggle_slider_connectedness(),
 
            bg='black', fg='white')
 
            bg='black',
 
            fg='white')
 
        self.sliders_checkbutton.pack(side=tk.LEFT)
 

	
 
        self.alltozerobutton = tk.Button(self.buttonframe, text="All to Zero",
 
            command=self.alltozero, bg='black', fg='white')
 
        self.alltozerobutton = tk.Button(self.buttonframe,
 
                                         text="All to Zero",
 
                                         command=self.alltozero,
 
                                         bg='black',
 
                                         fg='white')
 
        self.alltozerobutton.pack(side='left')
 

	
 
        self.save_stage_button = tk.Button(self.buttonframe, text="Save",
 
        self.save_stage_button = tk.Button(
 
            self.buttonframe,
 
            text="Save",
 
            command=lambda: self.save_current_stage(self.sub_name.get()),
 
            bg='black', fg='white')
 
            bg='black',
 
            fg='white')
 
        self.save_stage_button.pack(side=tk.LEFT)
 
        self.sub_name = tk.Entry(self.buttonframe, bg='black', fg='white')
 
        self.sub_name.pack(side=tk.LEFT)
 
@@ -222,11 +263,9 @@ class KeyboardComposer(tk.Frame, SubClie
 

	
 
        withgroups = []
 
        for effect in self.graph.subjects(RDF.type, L9['Effect']):
 
            withgroups.append((
 
                self.graph.value(effect, L9['group']),
 
                self.graph.value(effect, L9['order']),
 
                self.graph.label(effect),
 
                effect))
 
            withgroups.append((self.graph.value(effect, L9['group']),
 
                               self.graph.value(effect, L9['order']),
 
                               self.graph.label(effect), effect))
 
        withgroups.sort()
 

	
 
        log.info("withgroups %s", withgroups)
 
@@ -240,13 +279,15 @@ class KeyboardComposer(tk.Frame, SubClie
 
                rowcount += 1
 
                col = 0
 

	
 
            subbox = SubmasterBox(row, self.graph, effect, self.session, col, rowcount)
 
            subbox = SubmasterBox(row, self.graph, effect, self.session, col,
 
                                  rowcount)
 
            subbox.place(relx=col / 8, rely=0, relwidth=1 / 8, relheight=1)
 
            self.subbox[effect] = self.slider_table[(rowcount, col)] = subbox
 

	
 
            self.setup_key_nudgers(subbox.scale)
 

	
 
            self.effectEval[effect] = light9.effect.effecteval.EffectEval(self.graph, effect, simpleOutputs)
 
            self.effectEval[effect] = light9.effect.effecteval.EffectEval(
 
                self.graph, effect, simpleOutputs)
 

	
 
            col = (col + 1) % 8
 
            last_group = group
 
@@ -277,15 +318,19 @@ class KeyboardComposer(tk.Frame, SubClie
 
        keyhintrow = tk.Frame(self)
 

	
 
        col = 0
 
        for upkey, downkey in zip(nudge_keys['up'],
 
                                  nudge_keys['down']):
 
        for upkey, downkey in zip(nudge_keys['up'], nudge_keys['down']):
 
            # what a hack!
 
            downkey = downkey.replace('semicolon', ';')
 
            upkey, downkey = (upkey.upper(), downkey.upper())
 

	
 
            # another what a hack!
 
            keylabel = tk.Label(keyhintrow, text='%s\n%s' % (upkey, downkey),
 
                width=1, font=('Arial', 10), bg='red', fg='white', anchor='c')
 
            keylabel = tk.Label(keyhintrow,
 
                                text='%s\n%s' % (upkey, downkey),
 
                                width=1,
 
                                font=('Arial', 10),
 
                                bg='red',
 
                                fg='white',
 
                                anchor='c')
 
            keylabel.pack(side=tk.LEFT, expand=1, fill=tk.X)
 
            col += 1
 

	
 
@@ -322,7 +367,9 @@ class KeyboardComposer(tk.Frame, SubClie
 
        self.change_row(self.current_row + diff)
 

	
 
    def rowFromGraph(self):
 
        self.change_row(int(self.graph.value(self.session, L9['currentRow'], default=0)), fromGraph=True)
 
        self.change_row(int(
 
            self.graph.value(self.session, L9['currentRow'], default=0)),
 
                        fromGraph=True)
 

	
 
    def change_row(self, row, fromGraph=False):
 
        old_row = self.current_row
 
@@ -357,8 +404,7 @@ class KeyboardComposer(tk.Frame, SubClie
 
                self.sliders.valueOut("button-upper%d" % col, False)
 
                self.sliders.valueOut("slider%d" % col, 0)
 
                continue
 
            self.send_to_hw(sub=subbox.sub, hwCol=col,
 
                            boxRow=self.current_row)
 
            self.send_to_hw(sub=subbox.sub, hwCol=col, boxRow=self.current_row)
 

	
 
    def got_nudger(self, number, direction, full=0):
 
        try:
 
@@ -382,7 +428,7 @@ class KeyboardComposer(tk.Frame, SubClie
 
        try:
 
            subbox = self.slider_table[(self.current_row, col)]
 
        except KeyError:
 
            return # no slider assigned at that column
 
            return  # no slider assigned at that column
 

	
 
        if hasattr(self, 'pendingHwSet'):
 
            import twisted.internet.error
 
@@ -400,7 +446,7 @@ class KeyboardComposer(tk.Frame, SubClie
 

	
 
        if boxRow != self.current_row:
 
            return
 
        
 

	
 
        try:
 
            level = self.get_levels()[sub]
 
        except KeyError:
 
@@ -421,13 +467,16 @@ class KeyboardComposer(tk.Frame, SubClie
 
        """group is a URI or None"""
 
        row = tk.Frame(self, bd=2, bg='black')
 
        row.subGroup = group
 

	
 
        def onDrop(ev):
 
            self.change_group(sub=URIRef(ev.data), row=row)
 
            return "link"
 
        
 
        dropTargetRegister(row, onDrop=onDrop, typeList=['*'],
 

	
 
        dropTargetRegister(row,
 
                           onDrop=onDrop,
 
                           typeList=['*'],
 
                           hoverStyle=dict(background="#555500"))
 
        
 

	
 
        row.pack(expand=1, fill=tk.BOTH)
 
        self.setup_key_nudgers(row)
 
        self.rows.append(row)
 
@@ -437,9 +486,10 @@ class KeyboardComposer(tk.Frame, SubClie
 
        """update this sub's group, and maybe other sub groups as needed, so
 
        this sub displays in this row"""
 
        group = row.subGroup
 
        self.graph.patchObject(
 
            context=self.session,
 
            subject=sub, predicate=L9['group'], newObject=group)
 
        self.graph.patchObject(context=self.session,
 
                               subject=sub,
 
                               predicate=L9['group'],
 
                               newObject=group)
 

	
 
    def highlight_row(self, row):
 
        row = self.rows[row]
 
@@ -450,8 +500,9 @@ class KeyboardComposer(tk.Frame, SubClie
 
        row['bg'] = 'black'
 

	
 
    def get_levels(self):
 
        return dict([(uri, box.slider_var.get())
 
            for uri, box in self.subbox.items()])
 
        return dict([
 
            (uri, box.slider_var.get()) for uri, box in self.subbox.items()
 
        ])
 

	
 
    def get_output_settings(self, _graph=None):
 
        _graph = _graph or self.graph
 
@@ -481,11 +532,11 @@ class KeyboardComposer(tk.Frame, SubClie
 
            (effect, RDF.type, L9['Effect'], ctx),
 
            (effect, RDFS.label, Literal(subname), ctx),
 
            (effect, L9['publishAttr'], L9['strength'], ctx),
 
            ])
 
        ])
 

	
 
        self.graph.suggestPrefixes(ctx, {'eff': effect + '/'})
 
        self.graph.patch(Patch(addQuads=stmts, delQuads=[]))
 
              
 

	
 
        self.sub_name.delete(0, tk.END)
 

	
 
    def alltozero(self):
 
@@ -493,6 +544,7 @@ class KeyboardComposer(tk.Frame, SubClie
 
            if subbox.scale.scale_var.get() != 0:
 
                subbox.scale.fade(value=0.0, length=0)
 

	
 

	
 
# move to web lib
 
def postArgGetter(request):
 
    """return a function that takes arg names and returns string
 
@@ -500,19 +552,23 @@ def postArgGetter(request):
 
    support for repeated args."""
 
    # this is something nevow normally does for me
 
    request.content.seek(0)
 
    fields = cgi.FieldStorage(request.content, request.received_headers,
 
    fields = cgi.FieldStorage(request.content,
 
                              request.received_headers,
 
                              environ={'REQUEST_METHOD': 'POST'})
 

	
 
    def getArg(n):
 
        try:
 
            return request.args[n][0]
 
        except KeyError:
 
            return fields[n].value
 

	
 
    return getArg
 

	
 

	
 
class LevelServerHttp(resource.Resource):
 
    isLeaf = True
 
    def __init__(self,name_to_subbox):
 

	
 
    def __init__(self, name_to_subbox):
 
        self.name_to_subbox = name_to_subbox
 

	
 
    def render_POST(self, request):
 
@@ -521,15 +577,18 @@ class LevelServerHttp(resource.Resource)
 
        if request.path == '/fadesub':
 
            # fadesub?subname=scoop&level=0&secs=.2
 
            self.name_to_subbox[arg('subname')].scale.fade(
 
                float(arg('level')),
 
                float(arg('secs')))
 
                float(arg('level')), float(arg('secs')))
 
            return "set %s to %s" % (arg('subname'), arg('level'))
 
        else:
 
            raise NotImplementedError(repr(request))
 

	
 

	
 
class Sliders(BCF2000):
 

	
 
    def __init__(self, kc):
 
        devices = ['/dev/snd/midiC3D0', '/dev/snd/midiC2D0', '/dev/snd/midiC1D0']
 
        devices = [
 
            '/dev/snd/midiC3D0', '/dev/snd/midiC2D0', '/dev/snd/midiC1D0'
 
        ]
 
        for dev in devices:
 
            try:
 
                BCF2000.__init__(self, dev=dev)
 
@@ -541,6 +600,7 @@ class Sliders(BCF2000):
 

	
 
        self.kc = kc
 
        log.info('found sliders on %s', dev)
 

	
 
    def valueIn(self, name, value):
 
        kc = self.kc
 
        if name.startswith("slider"):
 
@@ -572,21 +632,25 @@ class Sliders(BCF2000):
 
            kc.change_row(kc.current_row + diff)
 
            self.valueOut(name, 0)
 

	
 

	
 
def launch(opts, root, graph, session):
 
    tl = toplevelat("Keyboard Composer - %s" % opts.session,
 
                    existingtoplevel=root, graph=graph, session=session)
 
                    existingtoplevel=root,
 
                    graph=graph,
 
                    session=session)
 

	
 
    kc = KeyboardComposer(tl, graph, session,
 
                          hw_sliders=not opts.no_sliders)
 
    kc = KeyboardComposer(tl, graph, session, hw_sliders=not opts.no_sliders)
 
    kc.pack(fill=tk.BOTH, expand=1)
 

	
 
    for helpline in ["Bindings: B3 mute"]:
 
        tk.Label(root,text=helpline, font="Helvetica -12 italic",
 
                 anchor='w').pack(side='top',fill='x')
 
            
 
        tk.Label(root, text=helpline, font="Helvetica -12 italic",
 
                 anchor='w').pack(side='top', fill='x')
 

	
 

	
 
if __name__ == "__main__":
 
    parser = OptionParser()
 
    parser.add_option('--no-sliders', action='store_true',
 
    parser.add_option('--no-sliders',
 
                      action='store_true',
 
                      help="don't attach to hardware sliders")
 
    clientsession.add_option(parser)
 
    parser.add_option('-v', action='store_true', help="log info level")
 
@@ -598,16 +662,16 @@ if __name__ == "__main__":
 

	
 
    # i think this also needs delayed start (like subcomposer has), to have a valid graph
 
    # before setting any stuff from the ui
 
    
 

	
 
    root = tk.Tk()
 
    initTkdnd(root.tk, 'tkdnd/trunk/')
 

	
 
    session = clientsession.getUri('keyboardcomposer', opts)
 

	
 
    graph.initiallySynced.addCallback(
 
        lambda _: launch(opts, root, graph, session))
 
    graph.initiallySynced.addCallback(lambda _: launch(opts, root, graph,
 
                                                       session))
 

	
 
    root.protocol('WM_DELETE_WINDOW', reactor.stop)
 

	
 
    tksupport.install(root,ms=20)
 
    tksupport.install(root, ms=20)
 
    prof.run(reactor.run, profile=None)
bin/lightsim
Show inline comments
 
@@ -21,26 +21,33 @@ from light9.namespaces import L9
 
from lightsim.openglsim import Surface
 

	
 
log = logging.getLogger()
 
logging.basicConfig(format="%(asctime)s %(levelname)-5s %(name)s %(filename)s:%(lineno)d: %(message)s")
 
logging.basicConfig(
 
    format=
 
    "%(asctime)s %(levelname)-5s %(name)s %(filename)s:%(lineno)d: %(message)s")
 
log.setLevel(logging.DEBUG)
 

	
 

	
 
def filenamesForChan(graph, chan):
 
    for lyr in graph.objects(chan, L9['previewLayer']):
 
        for imgPath in graph.objects(lyr, L9['path']):
 
            yield imgPath
 

	
 

	
 
_lastLevels = None
 

	
 

	
 
def poll(graph, serv, pollFreq, oglSurface):
 
    pollFreq.update()
 
    dispatcher.send("status", key="pollFreq", value=str(pollFreq))
 
    d = serv.callRemote("currentlevels", dmxclient._id)
 

	
 
    def received(dmxLevels):
 
        global _lastLevels
 
        if dmxLevels == _lastLevels:
 
            return
 
        _lastLevels = dmxLevels
 

	
 
        level = {} # filename : level
 
        level = {}  # filename : level
 
        for i, lev in enumerate(dmxLevels):
 
            if lev == 0:
 
                continue
 
@@ -54,18 +61,21 @@ def poll(graph, serv, pollFreq, oglSurfa
 
                level[str(imgPath)] = lev
 

	
 
        oglSurface.newLevels(levels=level)
 

	
 
    d.addCallback(received)
 
    return d
 

	
 

	
 
class StatusKeys(QWidget):
 
    """listens for dispatcher signal 'status' and displays the key/value args"""
 

	
 
    def __init__(self, parent):
 
        QWidget.__init__(self)
 
        self.layout = QVBoxLayout()
 
        self.setLayout(self.layout)
 
        self.row = {} # key name : (Frame, value Label)
 
        self.row = {}  # key name : (Frame, value Label)
 
        dispatcher.connect(self.status, "status")
 
        
 

	
 
    def status(self, key, value):
 
        if key not in self.row:
 
            row = QWidget()
 
@@ -81,7 +91,9 @@ class StatusKeys(QWidget):
 
            lab = self.row[key]
 
            lab.setText(value)
 

	
 

	
 
class Window(QMainWindow):
 

	
 
    def __init__(self, filenames):
 
        QMainWindow.__init__(self, None)
 
        self.setWindowTitle(dmxclient._id)
 
@@ -90,13 +102,14 @@ class Window(QMainWindow):
 
        self.setCentralWidget(w)
 
        mainLayout = QVBoxLayout()
 
        w.setLayout(mainLayout)
 
        
 
        self.glWidget = Surface(self, filenames, imgRescaleTo=128*2)
 

	
 
        self.glWidget = Surface(self, filenames, imgRescaleTo=128 * 2)
 

	
 
        mainLayout.addWidget(self.glWidget)
 

	
 
        status = StatusKeys(mainLayout)
 
        mainLayout.addWidget(status)      
 
        mainLayout.addWidget(status)
 

	
 

	
 
def requiredImages(graph):
 
    """filenames that we'll need to show, based on a config structure
 
@@ -110,6 +123,7 @@ def requiredImages(graph):
 
            filenames.append(str(p))
 
    return filenames
 

	
 

	
 
if __name__ == '__main__':
 
    app = reactor.qApp
 

	
 
@@ -123,4 +137,3 @@ if __name__ == '__main__':
 
    LoopingCall(poll, graph, serv, pollFreq, window.glWidget).start(.05)
 

	
 
    reactor.run()
 

	
bin/listsongs
Show inline comments
 
#!bin/python
 

	
 
"""for completion, print the available song uris on stdout
 

	
 
in .zshrc:
 
@@ -17,6 +16,7 @@ from rdfdb.syncedgraph import SyncedGrap
 

	
 
graph = SyncedGraph(networking.rdfdb.url, "listsongs")
 

	
 

	
 
@graph.initiallySynced.addCallback
 
def printSongs(result):
 
    with graph.currentState() as current:
 
@@ -24,4 +24,5 @@ def printSongs(result):
 
            print song
 
    reactor.stop()
 

	
 

	
 
reactor.run()
bin/mpd_timing_test
Show inline comments
 
#!/usr/bin/python
 

	
 
"""
 
records times coming out of ascoltami
 

	
 
@@ -17,5 +16,5 @@ import xmlrpclib, time
 
s = xmlrpclib.ServerProxy("http://localhost:8040")
 
start = time.time()
 
while 1:
 
    print time.time()-start,s.gettime()
 
    print time.time() - start, s.gettime()
 
    time.sleep(.01)
bin/musicPad
Show inline comments
 
@@ -11,7 +11,7 @@ logging.basicConfig(level=logging.INFO)
 
log = logging.getLogger()
 

	
 
introPad = 4
 
postPad = 9 # 5 + autostop + 4
 
postPad = 9  # 5 + autostop + 4
 

	
 
playlist = Playlist.fromShow(showconfig.getGraph(), showconfig.showUri())
 
for p in playlist.allSongPaths():
 
@@ -22,18 +22,16 @@ for p in playlist.allSongPaths():
 
    try:
 
        os.makedirs(outputDir)
 
    except OSError:
 
        pass # exists
 
        pass  # exists
 
    outputPath = os.path.join(outputDir, os.path.basename(p))
 
    outputWave = wave.open(outputPath, 'w')
 
    outputWave.setparams(inputWave.getparams())
 

	
 
    bytesPerSecond = (inputWave.getnchannels() * inputWave.getsampwidth() *
 
                      inputWave.getframerate())
 
    
 

	
 
    outputWave.writeframesraw("\x00" * (bytesPerSecond * introPad))
 
    outputWave.writeframesraw(inputWave.readframes(inputWave.getnframes()))
 
    outputWave.writeframesraw("\x00" * (bytesPerSecond * postPad))
 
    outputWave.close()
 
    log.info("wrote %s", outputPath)
 

	
 
    
bin/musictime
Show inline comments
 
@@ -6,9 +6,12 @@ import Tkinter as tk
 
import time
 
import restkit, jsonlib
 

	
 

	
 
class MusicTime:
 

	
 
    def __init__(self, url):
 
        self.player = restkit.Resource(url)
 

	
 
    def get_music_time(self):
 
        playtime = None
 
        while not playtime:
 
@@ -20,31 +23,43 @@ class MusicTime:
 
                time.sleep(2)
 
        return playtime
 

	
 

	
 
class MusicTimeTk(tk.Frame, MusicTime):
 

	
 
    def __init__(self, master, url):
 
        tk.Frame.__init__(self)
 
        MusicTime.__init__(self, url)
 
        self.timevar = tk.DoubleVar()
 
        self.timelabel = tk.Label(self, textvariable=self.timevar, bd=2,
 
            relief='raised', width=10, padx=2, pady=2, anchor='w')
 
        self.timelabel = tk.Label(self,
 
                                  textvariable=self.timevar,
 
                                  bd=2,
 
                                  relief='raised',
 
                                  width=10,
 
                                  padx=2,
 
                                  pady=2,
 
                                  anchor='w')
 
        self.timelabel.pack(expand=1, fill='both')
 

	
 
        def print_time(evt, *args):
 
            self.timevar.set(self.get_music_time())
 
            print self.timevar.get(), evt.keysym
 

	
 
        self.timelabel.bind('<KeyPress>', print_time)
 
        self.timelabel.bind('<1>', print_time)
 
        self.timelabel.focus()
 
        self.update_time()
 

	
 
    def update_time(self):
 
        self.timevar.set(self.get_music_time())
 
        self.after(100, self.update_time)
 

	
 

	
 
if __name__ == "__main__":
 
    from optparse import OptionParser
 
    parser = OptionParser()
 
    parser.add_option("-u", "--url", default=light9.networking.musicPlayer.url)
 
    options, args = parser.parse_args()
 
    
 

	
 
    root = tk.Tk()
 
    root.title("Time")
 
    MusicTimeTk(root, options.url).pack(expand=1, fill='both')
bin/paintserver
Show inline comments
 
@@ -17,31 +17,38 @@ from lib.cycloneerr import PrettyErrorHa
 
from light9.namespaces import RDF, L9, DEV
 

	
 

	
 

	
 
class Solve(PrettyErrorHandler, cyclone.web.RequestHandler):
 

	
 
class Solve(PrettyErrorHandler, cyclone.web.RequestHandler):
 
    def post(self):
 
        painting = json.loads(self.request.body)
 
        with self.settings.stats.solve.time():
 
            img = self.settings.solver.draw(painting)
 
            sample, sampleDist = self.settings.solver.bestMatch(img, device=DEV['aura2'])
 
            sample, sampleDist = self.settings.solver.bestMatch(
 
                img, device=DEV['aura2'])
 
            with self.settings.graph.currentState() as g:
 
                bestPath = g.value(sample, L9['imagePath']).replace(L9[''], '')
 
            #out = solver.solve(painting)
 
            #layers = solver.simulationLayers(out)
 
            
 
        self.write(json.dumps({
 
            'bestMatch': {'uri': sample, 'path': bestPath, 'dist': sampleDist},
 
        #    'layers': layers,
 
        #    'out': out,
 
        }))
 

	
 
        self.write(
 
            json.dumps({
 
                'bestMatch': {
 
                    'uri': sample,
 
                    'path': bestPath,
 
                    'dist': sampleDist
 
                },
 
                #    'layers': layers,
 
                #    'out': out,
 
            }))
 

	
 
    def reloadSolver(self):
 
        reload(light9.paint.solve)
 
        self.settings.solver = light9.paint.solve.Solver(self.settings.graph)
 
        self.settings.solver.loadSamples()
 

	
 

	
 
class BestMatches(PrettyErrorHandler, cyclone.web.RequestHandler):
 

	
 
    def post(self):
 
        body = json.loads(self.request.body)
 
        painting = body['painting']
 
@@ -49,35 +56,40 @@ class BestMatches(PrettyErrorHandler, cy
 
        with self.settings.stats.solve.time():
 
            img = self.settings.solver.draw(painting)
 
            outSettings = self.settings.solver.bestMatches(img, devs)
 
            self.write(json.dumps({
 
                'settings': outSettings.asList()
 
                }))
 
        
 
            self.write(json.dumps({'settings': outSettings.asList()}))
 

	
 

	
 
class App(object):
 

	
 
    def __init__(self, show, session):
 
        self.show = show
 
        self.session = session
 

	
 
        self.graph = SyncedGraph(networking.rdfdb.url, "paintServer")
 
        self.graph.initiallySynced.addCallback(self.launch).addErrback(log.error)
 
        
 
        self.stats = scales.collection('/', scales.PmfStat('solve'),
 
                                       )
 
       
 
        self.graph.initiallySynced.addCallback(self.launch).addErrback(
 
            log.error)
 

	
 
        self.stats = scales.collection(
 
            '/',
 
            scales.PmfStat('solve'),
 
        )
 

	
 
    def launch(self, *args):
 

	
 
        self.solver = light9.paint.solve.Solver(self.graph, sessions=[
 
            L9['show/dance2017/capture/aura1/cap1876596'],
 
            L9['show/dance2017/capture/aura2/cap1876792'],
 
            L9['show/dance2017/capture/aura3/cap1877057'],
 
            L9['show/dance2017/capture/aura4/cap1877241'],
 
            L9['show/dance2017/capture/aura5/cap1877406'],
 
            L9['show/dance2017/capture/q1/cap1874255'],
 
            L9['show/dance2017/capture/q2/cap1873665'],
 
            L9['show/dance2017/capture/q3/cap1876223'],
 
        ])
 
        self.solver = light9.paint.solve.Solver(
 
            self.graph,
 
            sessions=[
 
                L9['show/dance2017/capture/aura1/cap1876596'],
 
                L9['show/dance2017/capture/aura2/cap1876792'],
 
                L9['show/dance2017/capture/aura3/cap1877057'],
 
                L9['show/dance2017/capture/aura4/cap1877241'],
 
                L9['show/dance2017/capture/aura5/cap1877406'],
 
                L9['show/dance2017/capture/q1/cap1874255'],
 
                L9['show/dance2017/capture/q2/cap1873665'],
 
                L9['show/dance2017/capture/q3/cap1876223'],
 
            ])
 
        self.solver.loadSamples()
 
        
 

	
 
        self.cycloneApp = cyclone.web.Application(handlers=[
 
            (r'/stats', StatsForCyclone),
 
            (r'/solve', Solve),
 
@@ -93,12 +105,16 @@ class App(object):
 

	
 
if __name__ == "__main__":
 
    parser = optparse.OptionParser()
 
    parser.add_option('--show',
 
    parser.add_option(
 
        '--show',
 
        help='show URI, like http://light9.bigasterisk.com/show/dance2008',
 
                      default=showconfig.showUri())
 
    parser.add_option("-v", "--verbose", action="store_true",
 
        default=showconfig.showUri())
 
    parser.add_option("-v",
 
                      "--verbose",
 
                      action="store_true",
 
                      help="logging.DEBUG")
 
    parser.add_option("--twistedlog", action="store_true",
 
    parser.add_option("--twistedlog",
 
                      action="store_true",
 
                      help="twisted logging")
 
    clientsession.add_option(parser)
 
    (options, args) = parser.parse_args()
 
@@ -106,7 +122,7 @@ if __name__ == "__main__":
 

	
 
    if not options.show:
 
        raise ValueError("missing --show http://...")
 
        
 

	
 
    session = clientsession.getUri('paint', options)
 

	
 
    app = App(URIRef(options.show), session)
bin/picamserve
Show inline comments
 
#!env_pi/bin/python
 
from __future__ import division
 
from run_local import log
 
import sys;sys.path.append('/usr/lib/python2.7/dist-packages/')
 
import sys
 
sys.path.append('/usr/lib/python2.7/dist-packages/')
 
import io, logging, traceback, time
 
import cyclone.web
 
from twisted.internet import reactor, threads
 
@@ -12,16 +13,24 @@ try:
 
    import picamera
 
    cameraCls = picamera.PiCamera
 
except ImportError:
 

	
 
    class cameraCls(object):
 
        def __enter__(self): return self
 
        def __exit__(self, *a): pass
 

	
 
        def __enter__(self):
 
            return self
 

	
 
        def __exit__(self, *a):
 
            pass
 

	
 
        def capture(self, out, *a, **kw):
 
            out.write(open('yuv.demo').read())
 

	
 
        def capture_continuous(self, *a, **kw):
 
            for i in range(1000):
 
                time.sleep(1)
 
                yield str(i)
 

	
 

	
 
def setCameraParams(c, arg):
 
    res = int(arg('res', 480))
 
    c.resolution = {
 
@@ -33,14 +42,15 @@ def setCameraParams(c, arg):
 
    c.exposure_mode = arg('exposure_mode', 'fixedfps')
 
    c.awb_mode = arg('awb_mode', 'off')
 
    c.brightness = int(arg('brightness', 50))
 
    c.exposure_compensation= int(arg('exposure_compensation', 0))
 
    c.exposure_compensation = int(arg('exposure_compensation', 0))
 
    c.awb_gains = (float(arg('redgain', 1)), float(arg('bluegain', 1)))
 
    c.ISO = int(arg('iso', 250))
 
    c.rotation = int(arg('rotation', '0'))
 

	
 

	
 
def setupCrop(c, arg):
 
    c.crop = (float(arg('x', 0)), float(arg('y', 0)),
 
              float(arg('w', 1)), float(arg('h', 1)))
 
    c.crop = (float(arg('x', 0)), float(arg('y', 0)), float(arg('w', 1)),
 
              float(arg('h', 1)))
 
    rw = rh = int(arg('resize', 100))
 
    # width 1920, showing w=.3 of image, resize=100 -> scale is 100/.3*1920
 
    # scl is [ output px / camera px ]
 
@@ -53,7 +63,8 @@ def setupCrop(c, arg):
 
        # height is the constraint
 
        rw = int(scl2 * c.crop[2] * c.resolution[0])
 
    return rw, rh
 
    
 

	
 

	
 
@prof.logTime
 
def getFrame(c, arg):
 
    setCameraParams(c, arg)
 
@@ -61,8 +72,10 @@ def getFrame(c, arg):
 
    out = io.BytesIO('w')
 
    prof.logTime(c.capture)(out, 'jpeg', use_video_port=True, resize=resize)
 
    return out.getvalue()
 
    
 

	
 

	
 
class Pic(cyclone.web.RequestHandler):
 

	
 
    def get(self):
 
        try:
 
            self.set_header('Content-Type', 'image/jpeg')
 
@@ -70,20 +83,24 @@ class Pic(cyclone.web.RequestHandler):
 
        except Exception:
 
            traceback.print_exc()
 

	
 

	
 
def captureContinuousAsync(c, resize, onFrame):
 
    """
 
    Calls c.capture_continuous is called in another thread. onFrame is
 
    called in this reactor thread with each (frameTime, frame)
 
    result. Runs until onFrame raises StopIteration.
 
    """
 

	
 
    def runner(c, resize):
 
        stream = io.BytesIO()
 
        t = time.time()
 
        for nextFrame in c.capture_continuous(stream, 'jpeg', use_video_port=True,
 
        for nextFrame in c.capture_continuous(stream,
 
                                              'jpeg',
 
                                              use_video_port=True,
 
                                              resize=resize):
 
            t2 = time.time()
 
            log.debug(" - framecap got %s bytes in %.1f ms",
 
                     len(stream.getvalue()), 1000 * (t2 - t))
 
                      len(stream.getvalue()), 1000 * (t2 - t))
 
            try:
 
                # This is slow, like 13ms. Hopefully
 
                # capture_continuous is working on gathering the next
 
@@ -91,8 +108,8 @@ def captureContinuousAsync(c, resize, on
 
                # Instead, we could be stashing frames onto a queue or
 
                # something that the main thread can pull when
 
                # possible (and toss if it gets behind).
 
                threads.blockingCallFromThread(reactor,
 
                                               onFrame, t, stream.getvalue())
 
                threads.blockingCallFromThread(reactor, onFrame, t,
 
                                               stream.getvalue())
 
            except StopIteration:
 
                break
 
            t3 = time.time()
 
@@ -100,30 +117,35 @@ def captureContinuousAsync(c, resize, on
 
            stream.truncate()
 
            stream.seek(0)
 
            t = time.time()
 

	
 
    return threads.deferToThread(runner, c, resize)
 

	
 

	
 
class FpsReport(object):
 

	
 
    def __init__(self):
 
        self.frameTimes = []
 
        self.lastFpsLog = 0
 
        
 

	
 
    def frame(self):
 
        now = time.time()
 
        
 

	
 
        self.frameTimes.append(now)
 
        
 

	
 
        if len(self.frameTimes) > 15:
 
            del self.frameTimes[:5]
 
            
 

	
 
        if now > self.lastFpsLog + 2 and len(self.frameTimes) > 5:
 
            deltas = [(b - a) for a, b in zip(self.frameTimes[:-1],
 
                                              self.frameTimes[1:])]
 
            deltas = [(b - a)
 
                      for a, b in zip(self.frameTimes[:-1], self.frameTimes[1:])
 
                     ]
 
            avg = sum(deltas) / len(deltas)
 
            log.info("fps: %.1f", 1 / avg)
 
            self.lastFpsLog = now
 
        
 
    
 

	
 

	
 
class Pics(cyclone.web.RequestHandler):
 

	
 
    @inlineCallbacks
 
    def get(self):
 
        try:
 
@@ -131,21 +153,22 @@ class Pics(cyclone.web.RequestHandler):
 
            c = self.settings.camera
 
            setCameraParams(c, self.get_argument)
 
            resize = setupCrop(c, self.get_argument)
 
                          
 

	
 
            self.running = True
 
            log.info("connection open from %s", self.request.remote_ip)
 
            fpsReport = FpsReport()
 
            
 

	
 
            def onFrame(frameTime, frame):
 
                if not self.running:
 
                    raise StopIteration
 
                    
 

	
 
                now = time.time()
 
                self.write("%s %s\n" % (len(frame), frameTime))
 
                self.write(frame)
 
                self.flush()
 

	
 
                fpsReport.frame()
 

	
 
            # another camera request coming in at the same time breaks
 
            # the server. it would be nice if this request could
 
            # let-go-and-reopen when it knows about another request
 
@@ -153,21 +176,30 @@ class Pics(cyclone.web.RequestHandler):
 
            yield captureContinuousAsync(c, resize, onFrame)
 
        except Exception:
 
            traceback.print_exc()
 
            
 

	
 
    def on_connection_close(self, *a, **kw):
 
        log.info("connection closed")
 
        self.running = False
 
            
 

	
 

	
 
log.setLevel(logging.INFO)
 

	
 
with cameraCls() as camera:
 
    port = 8208
 
    reactor.listenTCP(port, cyclone.web.Application(handlers=[
 
        (r'/pic', Pic),
 
        (r'/pics', Pics),
 
        (r'/static/(.*)', cyclone.web.StaticFileHandler, {'path': 'light9/web/'}),
 
        (r'/(|gui.js)', cyclone.web.StaticFileHandler, {'path': 'light9/vidref/',
 
                                                 'default_filename': 'index.html'}),
 
        ], debug=True, camera=camera))
 
    reactor.listenTCP(
 
        port,
 
        cyclone.web.Application(handlers=[
 
            (r'/pic', Pic),
 
            (r'/pics', Pics),
 
            (r'/static/(.*)', cyclone.web.StaticFileHandler, {
 
                'path': 'light9/web/'
 
            }),
 
            (r'/(|gui.js)', cyclone.web.StaticFileHandler, {
 
                'path': 'light9/vidref/',
 
                'default_filename': 'index.html'
 
            }),
 
        ],
 
                                debug=True,
 
                                camera=camera))
 
    log.info("serving on %s" % port)
 
    reactor.run()
bin/rdfdb
Show inline comments
 
@@ -5,8 +5,9 @@ from light9 import networking, showconfi
 
import rdfdb.service
 

	
 
rdfdb.service.main(
 
    dirUriMap={os.environ['LIGHT9_SHOW'].rstrip('/') + '/':
 
               showconfig.showUri() + '/'},
 
    dirUriMap={
 
        os.environ['LIGHT9_SHOW'].rstrip('/') + '/': showconfig.showUri() + '/'
 
    },
 
    prefixes={
 
        'show': showconfig.showUri() + '/',
 
        '': 'http://light9.bigasterisk.com/',
 
@@ -17,5 +18,4 @@ rdfdb.service.main(
 
        'dev': 'http://light9.bigasterisk.com/device/',
 
    },
 
    port=networking.rdfdb.port,
 
    )
 

	
 
)
bin/run_local.py
Show inline comments
 
@@ -4,30 +4,37 @@
 

	
 
import sys, os, socket
 

	
 

	
 
def fixSysPath():
 
    root = os.path.abspath(os.path.join(os.path.dirname(sys.argv[0]), '..')) + '/'
 
    root = os.path.abspath(os.path.join(os.path.dirname(sys.argv[0]),
 
                                        '..')) + '/'
 

	
 
    # this is site-packages/zope.interface-4.5.0-py2.7-nspkg.pth,
 
    # slightly edited.
 
    import types
 
    has_mfs = sys.version_info > (3, 5);
 
    has_mfs = sys.version_info > (3, 5)
 
    p = root + 'env/local/lib/python2.7/site-packages/zope'
 
    importlib = has_mfs and __import__('importlib.util');
 
    has_mfs and __import__('importlib.machinery');
 
    importlib = has_mfs and __import__('importlib.util')
 
    has_mfs and __import__('importlib.machinery')
 
    m = has_mfs and sys.modules.setdefault(
 
        'zope', importlib.util.module_from_spec(
 
            importlib.machinery.PathFinder.find_spec(
 
                'zope', [os.path.dirname(p)])));
 
    m = m or sys.modules.setdefault('zope', types.ModuleType('zope'));
 
    mp = (m or []) and m.__dict__.setdefault('__path__',[]);
 
        'zope',
 
        importlib.util.module_from_spec(
 
            importlib.machinery.PathFinder.find_spec('zope',
 
                                                     [os.path.dirname(p)])))
 
    m = m or sys.modules.setdefault('zope', types.ModuleType('zope'))
 
    mp = (m or []) and m.__dict__.setdefault('__path__', [])
 
    (p not in mp) and mp.append(p)
 
    
 

	
 
    p = root + 'env/local/lib/python2.7/site-packages/greplin'
 
    importlib = has_mfs and __import__('importlib.util');
 
    has_mfs and __import__('importlib.machinery');
 
    m = has_mfs and sys.modules.setdefault('greplin', importlib.util.module_from_spec(importlib.machinery.PathFinder.find_spec('greplin', [os.path.dirname(p)])));
 
    m = m or sys.modules.setdefault('greplin', types.ModuleType('greplin'));
 
    mp = (m or []) and m.__dict__.setdefault('__path__',[]);
 
    importlib = has_mfs and __import__('importlib.util')
 
    has_mfs and __import__('importlib.machinery')
 
    m = has_mfs and sys.modules.setdefault(
 
        'greplin',
 
        importlib.util.module_from_spec(
 
            importlib.machinery.PathFinder.find_spec('greplin',
 
                                                     [os.path.dirname(p)])))
 
    m = m or sys.modules.setdefault('greplin', types.ModuleType('greplin'))
 
    mp = (m or []) and m.__dict__.setdefault('__path__', [])
 
    (p not in mp) and mp.append(p)
 

	
 
    sys.path = [
 
@@ -46,6 +53,7 @@ def fixSysPath():
 
        root + 'env/lib/python2.7/site-packages/gtk-2.0',
 
    ]
 

	
 

	
 
fixSysPath()
 

	
 
from twisted.python.failure import Failure
 
@@ -55,12 +63,14 @@ try:
 
except ImportError:
 
    pass
 
else:
 

	
 
    def rce(self, exc, val, tb):
 
        sys.stderr.write("Exception in Tkinter callback\n")
 
        if True:
 
            sys.excepthook(exc, val, tb)
 
        else:
 
            Failure(val, exc, tb).printDetailedTraceback()
 

	
 
    Tkinter.Tk.report_callback_exception = rce
 

	
 
import coloredlogs, logging, time
 
@@ -71,10 +81,12 @@ except ImportError:
 
    pass
 

	
 
progName = sys.argv[0].split('/')[-1]
 
log = logging.getLogger() # this has to get the root logger
 
log.name = progName # but we can rename it for clarity
 
log = logging.getLogger()  # this has to get the root logger
 
log.name = progName  # but we can rename it for clarity
 

	
 

	
 
class FractionTimeFilter(logging.Filter):
 

	
 
    def filter(self, record):
 
        record.fractionTime = (
 
            time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(record.created)) +
 
@@ -82,6 +94,7 @@ class FractionTimeFilter(logging.Filter)
 
        # Don't filter the record.
 
        return 1
 

	
 

	
 
coloredlogs.install(
 
    level='DEBUG',
 
    fmt='%(fractionTime)s %(name)s[%(process)d] %(levelname)s %(message)s')
 
@@ -90,10 +103,13 @@ logging.getLogger().handlers[0].addFilte
 

	
 
def setTerminalTitle(s):
 
    if os.environ.get('TERM', '') in ['xterm', 'rxvt', 'rxvt-unicode-256color']:
 
        print "\033]0;%s\007" % s # not escaped/protected correctly
 
        print "\033]0;%s\007" % s  # not escaped/protected correctly
 

	
 

	
 
if 'listsongs' not in sys.argv[0] and 'homepageConfig' not in sys.argv[0]:
 
    setTerminalTitle('[%s] %s' % (socket.gethostname(), ' '.join(sys.argv).replace('bin/', '')))
 
    setTerminalTitle(
 
        '[%s] %s' %
 
        (socket.gethostname(), ' '.join(sys.argv).replace('bin/', '')))
 

	
 
# see http://www.youtube.com/watch?v=3cIOT9kM--g for commands that make
 
# profiles and set background images
bin/staticclient
Show inline comments
 
@@ -16,7 +16,8 @@ from light9 import dmxclient, showconfig
 

	
 
if __name__ == "__main__":
 
    parser = OptionParser(usage="%prog")
 
    parser.add_option('--chan', help='channel number, starts at 1', type=int) #todo: or name or uri
 
    parser.add_option('--chan', help='channel number, starts at 1',
 
                      type=int)  #todo: or name or uri
 
    parser.add_option('--level', help='0..1', type=float)
 
    parser.add_option('-v', action='store_true', help="log debug level")
 

	
 
@@ -26,9 +27,11 @@ if __name__ == "__main__":
 

	
 
    levels = [0] * (opts.chan - 1) + [opts.level]
 
    log.info('staticclient will write this forever: %r', levels)
 

	
 
    def write():
 
        log.debug('writing %r', levels)
 
        dmxclient.outputlevels(levels, twisted=1)
 

	
 
    log.info('looping...')
 
    task.LoopingCall(write).start(1)
 
    reactor.run()
bin/subcomposer
Show inline comments
 
@@ -69,6 +69,7 @@ class Subcomposer(tk.Frame):
 
      Submaster.editLevel -> graph (handled in Submaster)
 

	
 
    """
 

	
 
    def __init__(self, master, graph, session):
 
        tk.Frame.__init__(self, master, bg='black')
 
        self.graph = graph
 
@@ -83,11 +84,12 @@ class Subcomposer(tk.Frame):
 
        self._currentChoice = Observable(Local)
 

	
 
        # this is a PersistentSubmaster (even for local)
 
        self.currentSub = Observable(Submaster.PersistentSubmaster(
 
                    graph, self.switchToLocal()))
 
        self.currentSub = Observable(
 
            Submaster.PersistentSubmaster(graph, self.switchToLocal()))
 

	
 
        def pc(val):
 
            log.info("change viewed sub to %s", val)
 

	
 
        self._currentChoice.subscribe(pc)
 

	
 
        ec = self.editChoice = EditChoice(self, self.graph, self._currentChoice)
 
@@ -105,39 +107,41 @@ class Subcomposer(tk.Frame):
 
        e.pack()
 
        b = tk.Button(box, text="Make global")
 
        b.pack()
 

	
 
        def clicked(*args):
 
            self.makeGlobal(newName=e.get())
 
            box.destroy()
 
            
 

	
 
        b.bind("<Button-1>", clicked)
 
        e.focus()
 
        
 

	
 
    def makeGlobal(self, newName):
 
        """promote our local submaster into a non-local, named one"""
 
        uri = self.currentSub().uri
 
        newUri = showconfig.showUri() + ("/sub/%s" %
 
                                         urllib.quote(newName, safe=''))
 
        with self.graph.currentState(
 
                tripleFilter=(uri, None, None)) as current:
 
        with self.graph.currentState(tripleFilter=(uri, None, None)) as current:
 
            if (uri, RDF.type, L9['LocalSubmaster']) not in current:
 
                raise ValueError("%s is not a local submaster" % uri)
 
            if (newUri, None, None) in current:
 
                raise ValueError("new uri %s is in use" % newUri)
 
                
 

	
 
        # the local submaster was storing in ctx=self.session, but now
 
        # we want it to be in ctx=uri
 

	
 
        self.relocateSub(newUri, newName)
 

	
 
        # these are in separate patches for clarity as i'm debugging this
 
        self.graph.patch(Patch(addQuads=[
 
            (newUri, RDFS.label, Literal(newName), newUri),
 
        ], delQuads=[
 
            (newUri, RDF.type, L9['LocalSubmaster'], newUri),
 
                           ]))
 
        self.graph.patchObject(self.session,
 
                               self.session, L9['currentSub'], newUri)
 
        
 
        self.graph.patch(
 
            Patch(addQuads=[
 
                (newUri, RDFS.label, Literal(newName), newUri),
 
            ],
 
                  delQuads=[
 
                      (newUri, RDF.type, L9['LocalSubmaster'], newUri),
 
                  ]))
 
        self.graph.patchObject(self.session, self.session, L9['currentSub'],
 
                               newUri)
 

	
 
    def relocateSub(self, newUri, newName):
 
        # maybe this goes in Submaster
 
        uri = self.currentSub().uri
 
@@ -146,15 +150,16 @@ class Subcomposer(tk.Frame):
 
            if u == uri:
 
                return newUri
 
            return u
 

	
 
        delQuads = self.currentSub().allQuads()
 
        addQuads = [(repl(s), p, repl(o), newUri) for s,p,o,c in delQuads]
 
        addQuads = [(repl(s), p, repl(o), newUri) for s, p, o, c in delQuads]
 
        # patch can't span contexts yet
 
        self.graph.patch(Patch(addQuads=addQuads, delQuads=[]))
 
        self.graph.patch(Patch(addQuads=[], delQuads=delQuads))
 
        
 
        
 

	
 
    def setupSubChoiceLinks(self):
 
        graph = self.graph
 

	
 
        def ann():
 
            print "currently: session=%s currentSub=%r _currentChoice=%r" % (
 
                self.session, self.currentSub(), self._currentChoice())
 
@@ -165,7 +170,7 @@ class Subcomposer(tk.Frame):
 
            # are failing. this calms things down.
 
            log.warn('skip graphChanged')
 
            return
 
        
 

	
 
            s = graph.value(self.session, L9['currentSub'])
 
            log.debug('HANDLER getting session currentSub from graph: %s', s)
 
            if s is None:
 
@@ -176,12 +181,12 @@ class Subcomposer(tk.Frame):
 
        def subChanged(newSub):
 
            log.debug('HANDLER currentSub changed to %s', newSub)
 
            if newSub is None:
 
                graph.patchObject(self.session,
 
                                  self.session, L9['currentSub'], None)
 
                graph.patchObject(self.session, self.session, L9['currentSub'],
 
                                  None)
 
                return
 
            self.sendupdate()
 
            graph.patchObject(self.session,
 
                              self.session, L9['currentSub'], newSub.uri)
 
            graph.patchObject(self.session, self.session, L9['currentSub'],
 
                              newSub.uri)
 

	
 
            localStmt = (newSub.uri, RDF.type, L9['LocalSubmaster'])
 
            with graph.currentState(tripleFilter=localStmt) as current:
 
@@ -195,7 +200,7 @@ class Subcomposer(tk.Frame):
 
                    self._currentChoice(newSub.uri)
 

	
 
        dispatcher.connect(self.levelsChanged, "sub levels changed")
 
            
 

	
 
        @self._currentChoice.subscribe
 
        def choiceChanged(newChoice):
 
            log.debug('HANDLER choiceChanged to %s', newChoice)
 
@@ -203,13 +208,14 @@ class Subcomposer(tk.Frame):
 
                newChoice = self.switchToLocal()
 
            if newChoice is not None:
 
                newSub = Submaster.PersistentSubmaster(graph, newChoice)
 
                log.debug('write new choice to currentSub, from %r to %r', self.currentSub(), newSub)
 
                log.debug('write new choice to currentSub, from %r to %r',
 
                          self.currentSub(), newSub)
 
                self.currentSub(newSub)
 

	
 
    def levelsChanged(self, sub):
 
        if sub == self.currentSub():
 
            self.sendupdate()
 
        
 

	
 
    def switchToLocal(self):
 
        """
 
        change our display to a local submaster
 
@@ -220,27 +226,30 @@ class Subcomposer(tk.Frame):
 
        self.localSerial += 1
 
        new = URIRef("http://light9.bigasterisk.com/sub/local/%s" % localId)
 
        log.debug('making up a local sub %s', new)
 
        self.graph.patch(Patch(addQuads=[
 
            (new, RDF.type, L9['Submaster'], self.session),
 
            (new, RDF.type, L9['LocalSubmaster'], self.session),
 
        ]))
 
        
 
        self.graph.patch(
 
            Patch(addQuads=[
 
                (new, RDF.type, L9['Submaster'], self.session),
 
                (new, RDF.type, L9['LocalSubmaster'], self.session),
 
            ]))
 

	
 
        return new
 
        
 

	
 
    def setupLevelboxUi(self):
 
        self.levelbox = Levelbox(self, self.graph, self.currentSub)
 
        self.levelbox.pack(side='top')
 

	
 
        tk.Button(self, text="All to zero",
 
             command=lambda *args: self.currentSub().clear()).pack(side='top')
 
        tk.Button(
 
            self,
 
            text="All to zero",
 
            command=lambda *args: self.currentSub().clear()).pack(side='top')
 

	
 
    def savenewsub(self, subname):
 
        leveldict={}
 
        for i,lev in zip(range(len(self.levels)),self.levels):
 
            if lev!=0:
 
                leveldict[get_channel_name(i+1)]=lev
 
        leveldict = {}
 
        for i, lev in zip(range(len(self.levels)), self.levels):
 
            if lev != 0:
 
                leveldict[get_channel_name(i + 1)] = lev
 

	
 
        s=Submaster.Submaster(subname, levels=leveldict)
 
        s = Submaster.Submaster(subname, levels=leveldict)
 
        s.save()
 

	
 
    def sendupdate(self):
 
@@ -252,14 +261,15 @@ def launch(opts, args, root, graph, sess
 
    if not opts.no_geometry:
 
        toplevelat("subcomposer - %s" % opts.session, root, graph, session)
 

	
 
        
 
    sc = Subcomposer(root, graph, session)
 
    sc.pack()
 
    
 

	
 
    subcomposerweb.init(graph, session, sc.currentSub)
 

	
 
    tk.Label(root,text="Bindings: B1 adjust level; B2 set full; B3 instant bump",
 
             font="Helvetica -12 italic",anchor='w').pack(side='top',fill='x')
 
    tk.Label(root,
 
             text="Bindings: B1 adjust level; B2 set full; B3 instant bump",
 
             font="Helvetica -12 italic",
 
             anchor='w').pack(side='top', fill='x')
 

	
 
    if len(args) == 1:
 
        # it might be a little too weird that cmdline arg to this
 
@@ -269,8 +279,7 @@ def launch(opts, args, root, graph, sess
 
        # same window pos), so maybe it doesn't matter. But still,
 
        # this tool should probably default to making new sessions
 
        # usually instead of loading the same one
 
        graph.patchObject(session,
 
                          session, L9['currentSub'], URIRef(args[0]))
 
        graph.patchObject(session, session, L9['currentSub'], URIRef(args[0]))
 

	
 
    task.LoopingCall(sc.sendupdate).start(10)
 

	
 
@@ -279,7 +288,8 @@ def launch(opts, args, root, graph, sess
 

	
 
if __name__ == "__main__":
 
    parser = OptionParser(usage="%prog [suburi]")
 
    parser.add_option('--no-geometry', action='store_true',
 
    parser.add_option('--no-geometry',
 
                      action='store_true',
 
                      help="don't save/restore window geometry")
 
    parser.add_option('-v', action='store_true', help="log debug level")
 

	
 
@@ -287,8 +297,8 @@ if __name__ == "__main__":
 
    opts, args = parser.parse_args()
 

	
 
    log.setLevel(logging.DEBUG if opts.v else logging.INFO)
 
    
 
    root=tk.Tk()
 

	
 
    root = tk.Tk()
 
    root.config(bg='black')
 
    root.tk_setPalette("#004633")
 

	
 
@@ -297,8 +307,9 @@ if __name__ == "__main__":
 
    graph = SyncedGraph(networking.rdfdb.url, "subcomposer")
 
    session = clientsession.getUri('subcomposer', opts)
 

	
 
    graph.initiallySynced.addCallback(lambda _: launch(opts, args, root, graph, session))
 
    graph.initiallySynced.addCallback(lambda _: launch(opts, args, root, graph,
 
                                                       session))
 

	
 
    root.protocol('WM_DELETE_WINDOW', reactor.stop)
 
    tksupport.install(root,ms=10)
 
    tksupport.install(root, ms=10)
 
    prof.run(reactor.run, profile=False)
bin/subserver
Show inline comments
 
@@ -16,17 +16,19 @@ from light9.namespaces import L9, DCTERM
 
from light9 import networking, showconfig
 

	
 
from lib.cycloneerr import PrettyErrorHandler
 
        
 

	
 

	
 
class Static(PrettyErrorHandler, cyclone.web.StaticFileHandler):
 

	
 
    def get(self, path, *args, **kw):
 
        if path in ['', 'effects']:
 
            return self.respondStaticJade("light9/subserver/%s.jade" %
 
                                          (path or 'index'))
 
            
 

	
 
        if path.endswith(".js"):
 
            return self.responseStaticCoffee(
 
                'light9/subserver/%s' %
 
                path.replace(".js", ".coffee")) # potential security hole
 
                path.replace(".js", ".coffee"))  # potential security hole
 

	
 
        cyclone.web.StaticFileHandler.get(self, path, *args, **kw)
 

	
 
@@ -35,14 +37,18 @@ class Static(PrettyErrorHandler, cyclone
 
        self.write(html)
 

	
 
    def responseStaticCoffee(self, src):
 
        self.write(subprocess.check_output([
 
            '/usr/bin/coffee', '--compile', '--print', src]))
 
        self.write(
 
            subprocess.check_output(
 
                ['/usr/bin/coffee', '--compile', '--print', src]))
 

	
 

	
 
class Snapshot(PrettyErrorHandler, cyclone.web.RequestHandler):
 

	
 
    @defer.inlineCallbacks
 
    def post(self):
 
        about = URIRef(self.get_argument("about"))
 
        response = yield cyclone.httpclient.fetch(networking.vidref.path("snapshot"), method="POST", timeout=1)
 
        response = yield cyclone.httpclient.fetch(
 
            networking.vidref.path("snapshot"), method="POST", timeout=1)
 

	
 
        snapUri = URIRef(json.loads(response.body)['snapshot'])
 
        # vidref could write about when it was taken, etc. would it be
 
@@ -50,15 +56,17 @@ class Snapshot(PrettyErrorHandler, cyclo
 
        # the graph, and then it doesn't even have to return anything?
 

	
 
        ctx = showconfig.showUri() + "/snapshots"
 
        
 
        self.settings.graph.patch(Patch(addQuads=[
 
            (about, L9['image'], snapUri, ctx),
 
            (snapUri, DCTERMS['created'],
 
             Literal(datetime.datetime.now(tzlocal())), ctx),
 

	
 
        self.settings.graph.patch(
 
            Patch(addQuads=[
 
                (about, L9['image'], snapUri, ctx),
 
                (snapUri, DCTERMS['created'],
 
                 Literal(datetime.datetime.now(tzlocal())), ctx),
 
            ]))
 
        
 

	
 
        self.write(json.dumps({'snapshot': snapUri}))
 

	
 

	
 
def newestImage(subject):
 
    newest = (None, None)
 
    for img in graph.objects(subject, L9['image']):
 
@@ -66,10 +74,13 @@ def newestImage(subject):
 
        if created > newest[0]:
 
            newest = (created, img)
 
    return newest[1]
 
        
 

	
 

	
 
if __name__ == "__main__":
 
    parser = optparse.OptionParser()
 
    parser.add_option("-v", "--verbose", action="store_true",
 
    parser.add_option("-v",
 
                      "--verbose",
 
                      action="store_true",
 
                      help="logging.DEBUG")
 
    (options, args) = parser.parse_args()
 

	
 
@@ -77,13 +88,17 @@ if __name__ == "__main__":
 

	
 
    graph = SyncedGraph(networking.rdfdb.url, "subServer")
 

	
 
    
 
    port = networking.subServer.port
 
    reactor.listenTCP(port, cyclone.web.Application(handlers=[
 
        (r'/snapshot', Snapshot),
 
        (r'/(.*)', Static,
 
         {"path" : "light9/subserver",
 
          "default_filename" : "index.jade"}),
 
        ], debug=True, graph=graph))
 
    reactor.listenTCP(
 
        port,
 
        cyclone.web.Application(handlers=[
 
            (r'/snapshot', Snapshot),
 
            (r'/(.*)', Static, {
 
                "path": "light9/subserver",
 
                "default_filename": "index.jade"
 
            }),
 
        ],
 
                                debug=True,
 
                                graph=graph))
 
    log.info("serving on %s" % port)
 
    reactor.run()
bin/tkdnd_minimal_drop.py
Show inline comments
 
@@ -13,8 +13,7 @@ label.config(text="drop target %s" % lab
 
frame1 = tk.Frame()
 
frame1.pack()
 

	
 
labelInner = tk.Label(frame1, borderwidth=2,
 
                      relief='groove', padx=10, pady=10)
 
labelInner = tk.Label(frame1, borderwidth=2, relief='groove', padx=10, pady=10)
 
labelInner.pack(side='left')
 
labelInner.config(text="drop target inner %s" % labelInner._w)
 
tk.Label(frame1, text="not a target").pack(side='left')
 
@@ -22,21 +21,36 @@ tk.Label(frame1, text="not a target").pa
 

	
 
def onDrop(ev):
 
    print "onDrop", ev
 

	
 

	
 
def enter(ev):
 
    print 'enter', ev
 

	
 

	
 
def leave(ev):
 
    print 'leave', ev
 
dropTargetRegister(label, onDrop=onDrop, onDropEnter=enter, onDropLeave=leave,
 

	
 

	
 
dropTargetRegister(label,
 
                   onDrop=onDrop,
 
                   onDropEnter=enter,
 
                   onDropLeave=leave,
 
                   hoverStyle=dict(background="yellow", relief='groove'))
 

	
 
dropTargetRegister(labelInner, onDrop=onDrop, onDropEnter=enter, onDropLeave=leave,
 
dropTargetRegister(labelInner,
 
                   onDrop=onDrop,
 
                   onDropEnter=enter,
 
                   onDropLeave=leave,
 
                   hoverStyle=dict(background="yellow", relief='groove'))
 

	
 

	
 
def prn():
 
    print "cont", root.winfo_containing(201,151)
 
    print "cont", root.winfo_containing(201, 151)
 

	
 

	
 
b = tk.Button(root, text="coord", command=prn)
 
b.pack()
 

	
 
#tk.mainloop()
 
tksupport.install(root,ms=10)
 
tksupport.install(root, ms=10)
 
reactor.run()
bin/tracker
Show inline comments
 
#!/usr/bin/python
 
from __future__ import division,nested_scopes
 
from __future__ import division, nested_scopes
 

	
 
import sys
 
sys.path.append("../../editor/pour")
 
sys.path.append("../light8")
 

	
 
from Submaster import Submaster
 
from skim.zooming import Zooming,Pair
 
from math import sqrt,sin,cos
 
from skim.zooming import Zooming, Pair
 
from math import sqrt, sin, cos
 
from pygame.rect import Rect
 
from xmlnodebase import xmlnodeclass,collectiveelement,xmldocfile
 
from xmlnodebase import xmlnodeclass, collectiveelement, xmldocfile
 
from dispatch import dispatcher
 

	
 
import dmxclient
 

	
 
import Tkinter as tk
 

	
 
defaultfont="arial 8"
 
defaultfont = "arial 8"
 

	
 
def pairdist(pair1,pair2):
 

	
 
def pairdist(pair1, pair2):
 
    return pair1.dist(pair2)
 

	
 
def canvashighlighter(canvas,obj,attribute,normalval,highlightval):
 

	
 
def canvashighlighter(canvas, obj, attribute, normalval, highlightval):
 
    """creates bindings on a canvas obj that make attribute go
 
    from normal to highlight when the mouse is over the obj"""
 
    canvas.tag_bind(obj,"<Enter>",
 
                    lambda ev: canvas.itemconfig(obj,**{attribute:highlightval}))
 
    canvas.tag_bind(obj,"<Leave>",
 
                    lambda ev: canvas.itemconfig(obj,**{attribute:normalval}))
 
    canvas.tag_bind(
 
        obj, "<Enter>", lambda ev: canvas.itemconfig(
 
            obj, **{attribute: highlightval}))
 
    canvas.tag_bind(
 
        obj,
 
        "<Leave>", lambda ev: canvas.itemconfig(obj, **{attribute: normalval}))
 

	
 

	
 
class Field(xmlnodeclass):
 
    
 
    """one light has a field of influence. for any point on the
 
    canvas, you can ask this field how strong it is. """
 

	
 
    def name(self,newval=None):
 
    def name(self, newval=None):
 
        """light/sub name"""
 
        return self._getorsetattr("name",newval)
 
    def center(self,x=None,y=None):
 
        return self._getorsetattr("name", newval)
 

	
 
    def center(self, x=None, y=None):
 
        """x,y float coords for the center of this light in the field. returns
 
        a Pair, although it accepts x,y"""
 
        return Pair(self._getorsettypedattr("x",float,x),
 
                    self._getorsettypedattr("y",float,y))
 
        return Pair(self._getorsettypedattr("x", float, x),
 
                    self._getorsettypedattr("y", float, y))
 

	
 
    def falloff(self,dist=None):
 
        
 
    def falloff(self, dist=None):
 
        """linear falloff from 1 at center, to 0 at dist pixels away
 
        from center"""
 
        return self._getorsettypedattr("falloff",float,dist)
 
        return self._getorsettypedattr("falloff", float, dist)
 

	
 
    def getdistforintensity(self,intens):
 
    def getdistforintensity(self, intens):
 
        """returns the distance you'd have to be for the given intensity (0..1)"""
 
        return (1-intens)*self.falloff()
 
        return (1 - intens) * self.falloff()
 

	
 
    def calc(self,x,y):
 
    def calc(self, x, y):
 
        """returns field strength at point x,y"""
 
        dist=pairdist(Pair(x,y),self.center())
 
        return max(0,(self.falloff()-dist)/self.falloff())
 
        dist = pairdist(Pair(x, y), self.center())
 
        return max(0, (self.falloff() - dist) / self.falloff())
 

	
 

	
 
class Fieldset(collectiveelement):
 
    """group of fields. persistent."""
 
    def childtype(self): return Field
 

	
 
    def childtype(self):
 
        return Field
 

	
 
    def version(self):
 
        """read-only version attribute on fieldset tag"""
 
        return self._getorsetattr("version",None)
 
        return self._getorsetattr("version", None)
 

	
 
    def report(self,x,y):
 
    def report(self, x, y):
 
        """reports active fields and their intensities"""
 
        active=0
 
        active = 0
 
        for f in self.getall():
 
            name=f.name()
 
            intens=f.calc(x,y)
 
            if intens>0:
 
                print name,intens,
 
                active+=1
 
        if active>0:
 
            name = f.name()
 
            intens = f.calc(x, y)
 
            if intens > 0:
 
                print name, intens,
 
                active += 1
 
        if active > 0:
 
            print
 
        self.dmxsend(x,y)
 
    def dmxsend(self,x,y):
 
        self.dmxsend(x, y)
 

	
 
    def dmxsend(self, x, y):
 
        """output lights to dmx"""
 
        levels=dict([(f.name(),f.calc(x,y)) for f in self.getall()])
 
        dmxlist=Submaster(None,levels).get_dmx_list()
 
        levels = dict([(f.name(), f.calc(x, y)) for f in self.getall()])
 
        dmxlist = Submaster(None, levels).get_dmx_list()
 
        dmxclient.outputlevels(dmxlist)
 
        
 
        
 

	
 
    def getbounds(self):
 
        """returns xmin,xmax,ymin,ymax for the non-zero areas of this field"""
 
        r=None
 
        r = None
 
        for f in self.getall():
 
            rad=f.getdistforintensity(0)
 
            fx,fy=f.center()
 
            fieldrect=Rect(fx-rad,fy-rad,rad*2,rad*2)
 
            rad = f.getdistforintensity(0)
 
            fx, fy = f.center()
 
            fieldrect = Rect(fx - rad, fy - rad, rad * 2, rad * 2)
 
            if r is None:
 
                r=fieldrect
 
                r = fieldrect
 
            else:
 
                r=r.union(fieldrect)
 
        return r.left,r.right,r.top,r.bottom
 
                r = r.union(fieldrect)
 
        return r.left, r.right, r.top, r.bottom
 

	
 

	
 
class Fieldsetfile(xmldocfile):
 
    def __init__(self,filename):
 
        self._openornew(filename,topleveltype=Fieldset)
 

	
 
    def __init__(self, filename):
 
        self._openornew(filename, topleveltype=Fieldset)
 

	
 
    def fieldset(self):
 
        return self._gettoplevel()
 

	
 

	
 
########################################################################
 
########################################################################
 

	
 

	
 
class FieldDisplay:
 
    """the view for a Field."""
 
    def __init__(self,canvas,field):
 
        self.canvas=canvas
 
        self.field=field
 
        self.tags=[str(id(self))] # canvas tag to id our objects
 
        
 

	
 
    def __init__(self, canvas, field):
 
        self.canvas = canvas
 
        self.field = field
 
        self.tags = [str(id(self))]  # canvas tag to id our objects
 

	
 
    def setcoords(self):
 
        """adjust canvas obj coords to match the field"""
 
        # this uses the canvas object ids saved by makeobjs
 
        f=self.field
 
        c=self.canvas
 
        w2c=self.canvas.world2canvas
 
        f = self.field
 
        c = self.canvas
 
        w2c = self.canvas.world2canvas
 

	
 
        # rings
 
        for intens,ring in self.rings.items():
 
            rad=f.getdistforintensity(intens)
 
            p1=w2c(*(f.center()-Pair(rad,rad)))
 
            p2=w2c(*(f.center()+Pair(rad,rad)))
 
            c.coords(ring,p1[0],p1[1],p2[0],p2[1])
 
        for intens, ring in self.rings.items():
 
            rad = f.getdistforintensity(intens)
 
            p1 = w2c(*(f.center() - Pair(rad, rad)))
 
            p2 = w2c(*(f.center() + Pair(rad, rad)))
 
            c.coords(ring, p1[0], p1[1], p2[0], p2[1])
 

	
 
        # text
 
        p1=w2c(*f.center())
 
        c.coords(self.txt,*p1)
 
        p1 = w2c(*f.center())
 
        c.coords(self.txt, *p1)
 

	
 
    def makeobjs(self):
 
        """(re)create the canvas objs (null coords) and make their bindings"""
 
        c=self.canvas
 
        f=self.field
 
        c = self.canvas
 
        f = self.field
 
        c.delete(self.tags)
 

	
 
        w2c=self.canvas.world2canvas
 
        w2c = self.canvas.world2canvas
 

	
 
        # make rings
 
        self.rings={} # rad,canvasobj
 
        for intens,color in (#(1,'white'),
 
                             (.8,'gray90'),(.6,'gray80'),(.4,'gray60'),(.2,'gray50'),
 
                             (0,'#000080')):
 
            self.rings[intens]=c.create_oval(0,0,0,0,
 
                                          outline=color,width=2,tags=self.tags,
 
                                          outlinestipple='gray50')
 
        self.rings = {}  # rad,canvasobj
 
        for intens, color in (  #(1,'white'),
 
            (.8, 'gray90'), (.6, 'gray80'), (.4, 'gray60'), (.2, 'gray50'),
 
            (0, '#000080')):
 
            self.rings[intens] = c.create_oval(0,
 
                                               0,
 
                                               0,
 
                                               0,
 
                                               outline=color,
 
                                               width=2,
 
                                               tags=self.tags,
 
                                               outlinestipple='gray50')
 

	
 
        # make text
 
        self.txt=c.create_text(0,0,text=f.name(),font=defaultfont+" bold",
 
                               fill='white',anchor='c',
 
                               tags=self.tags)
 
        self.txt = c.create_text(0,
 
                                 0,
 
                                 text=f.name(),
 
                                 font=defaultfont + " bold",
 
                                 fill='white',
 
                                 anchor='c',
 
                                 tags=self.tags)
 

	
 
        # highlight text bindings
 
        canvashighlighter(c,self.txt,'fill',normalval='white',highlightval='red')
 
        canvashighlighter(c,
 
                          self.txt,
 
                          'fill',
 
                          normalval='white',
 
                          highlightval='red')
 

	
 
        # position drag bindings
 
        def press(ev):
 
            self._lastmouse=ev.x,ev.y
 
            self._lastmouse = ev.x, ev.y
 

	
 
        def motion(ev):
 
            dcan=Pair(*[a-b for a,b in zip((ev.x,ev.y),self._lastmouse)])
 
            dworld=c.canvas2world_vector(*dcan)
 
            self.field.center(*(self.field.center()+dworld))
 
            self._lastmouse=ev.x,ev.y
 
            self.setcoords() # redraw
 
            dcan = Pair(*[a - b for a, b in zip((ev.x, ev.y), self._lastmouse)])
 
            dworld = c.canvas2world_vector(*dcan)
 
            self.field.center(*(self.field.center() + dworld))
 
            self._lastmouse = ev.x, ev.y
 
            self.setcoords()  # redraw
 

	
 
        def release(ev):
 
            if hasattr(self,'_lastmouse'):
 
            if hasattr(self, '_lastmouse'):
 
                del self._lastmouse
 
            dispatcher.send("field coord changed") # updates bounds
 
            
 
        c.tag_bind(self.txt,"<ButtonPress-1>",press)
 
        c.tag_bind(self.txt,"<B1-Motion>",motion)
 
        c.tag_bind(self.txt,"<B1-ButtonRelease>",release)
 
            dispatcher.send("field coord changed")  # updates bounds
 

	
 
        c.tag_bind(self.txt, "<ButtonPress-1>", press)
 
        c.tag_bind(self.txt, "<B1-Motion>", motion)
 
        c.tag_bind(self.txt, "<B1-ButtonRelease>", release)
 

	
 
        # radius drag bindings
 
        outerring=self.rings[0]
 
        canvashighlighter(c,outerring,
 
                          'outline',normalval='#000080',highlightval='#4040ff')
 
        outerring = self.rings[0]
 
        canvashighlighter(c,
 
                          outerring,
 
                          'outline',
 
                          normalval='#000080',
 
                          highlightval='#4040ff')
 

	
 
        def motion(ev):
 
            worldmouse=self.canvas.canvas2world(ev.x,ev.y)
 
            currentdist=pairdist(worldmouse,self.field.center())
 
            worldmouse = self.canvas.canvas2world(ev.x, ev.y)
 
            currentdist = pairdist(worldmouse, self.field.center())
 
            self.field.falloff(currentdist)
 
            self.setcoords()
 
        c.tag_bind(outerring,"<B1-Motion>",motion)
 
        c.tag_bind(outerring,"<B1-ButtonRelease>",release) # from above
 

	
 
        c.tag_bind(outerring, "<B1-Motion>", motion)
 
        c.tag_bind(outerring, "<B1-ButtonRelease>", release)  # from above
 

	
 
        self.setcoords()
 
    
 

	
 

	
 
class Tracker(tk.Frame):
 

	
 
    """whole tracker widget, which is mostly a view for a
 
    Fieldset. tracker makes its own fieldset"""
 

	
 
    # world coords of the visible canvas (preserved even in window resizes)
 
    xmin=0
 
    xmax=100
 
    ymin=0
 
    ymax=100
 
    xmin = 0
 
    xmax = 100
 
    ymin = 0
 
    ymax = 100
 

	
 
    fieldsetfile = None
 
    displays = None  # Field : FieldDisplay. we keep these in sync with the fieldset
 

	
 
    fieldsetfile=None
 
    displays=None # Field : FieldDisplay. we keep these in sync with the fieldset
 
    
 
    def __init__(self,master):
 
        tk.Frame.__init__(self,master)
 
    def __init__(self, master):
 
        tk.Frame.__init__(self, master)
 

	
 
        self.displays={}
 
        
 
        c=self.canvas=Zooming(self,bg='black',closeenough=5)
 
        c.pack(fill='both',exp=1)
 
        self.displays = {}
 

	
 
        c = self.canvas = Zooming(self, bg='black', closeenough=5)
 
        c.pack(fill='both', exp=1)
 

	
 
        # preserve edge coords over window resize
 
        c.bind("<Configure>",self.configcoords)
 
        
 
        c.bind("<Motion>",
 
               lambda ev: self._fieldset().report(*c.canvas2world(ev.x,ev.y)))
 
        c.bind("<Configure>", self.configcoords)
 

	
 
        c.bind("<Motion>", lambda ev: self._fieldset().report(*c.canvas2world(
 
            ev.x, ev.y)))
 

	
 
        def save(ev):
 
            print "saving"
 
            self.fieldsetfile.save()
 
        master.bind("<Key-s>",save)
 
        dispatcher.connect(self.autobounds,"field coord changed")
 

	
 
        master.bind("<Key-s>", save)
 
        dispatcher.connect(self.autobounds, "field coord changed")
 

	
 
    def _fieldset(self):
 
        return self.fieldsetfile.fieldset()
 

	
 
    def load(self,filename):
 
        self.fieldsetfile=Fieldsetfile(filename)
 
    def load(self, filename):
 
        self.fieldsetfile = Fieldsetfile(filename)
 
        self.displays.clear()
 
        for f in self.fieldsetfile.fieldset().getall():
 
            self.displays[f]=FieldDisplay(self.canvas,f)
 
            self.displays[f] = FieldDisplay(self.canvas, f)
 
            self.displays[f].makeobjs()
 
        self.autobounds()
 

	
 
    def configcoords(self,*args):
 
    def configcoords(self, *args):
 
        # force our canvas coords to stay at the edges of the window
 
        c=self.canvas
 
        cornerx,cornery=c.canvas2world(0,0)
 
        c.move(cornerx-self.xmin,
 
               cornery-self.ymin)
 
        c.setscale(0,0,
 
                   c.winfo_width()/(self.xmax-self.xmin),
 
                   c.winfo_height()/(self.ymax-self.ymin))
 
        c = self.canvas
 
        cornerx, cornery = c.canvas2world(0, 0)
 
        c.move(cornerx - self.xmin, cornery - self.ymin)
 
        c.setscale(0, 0,
 
                   c.winfo_width() / (self.xmax - self.xmin),
 
                   c.winfo_height() / (self.ymax - self.ymin))
 

	
 
    def autobounds(self):
 
        """figure out our bounds from the fieldset, and adjust the display zooms.
 
        writes the corner coords onto the canvas."""
 
        self.xmin,self.xmax,self.ymin,self.ymax=self._fieldset().getbounds()
 
        self.xmin, self.xmax, self.ymin, self.ymax = self._fieldset().getbounds(
 
        )
 

	
 
        self.configcoords()
 
        
 
        c=self.canvas
 

	
 
        c = self.canvas
 
        c.delete('cornercoords')
 
        for x,anc2 in ((self.xmin,'w'),(self.xmax,'e')):
 
            for y,anc1 in ((self.ymin,'n'),(self.ymax,'s')):
 
                pos=c.world2canvas(x,y)
 
                c.create_text(pos[0],pos[1],text="%s,%s"%(x,y),
 
                              fill='white',anchor=anc1+anc2,
 
        for x, anc2 in ((self.xmin, 'w'), (self.xmax, 'e')):
 
            for y, anc1 in ((self.ymin, 'n'), (self.ymax, 's')):
 
                pos = c.world2canvas(x, y)
 
                c.create_text(pos[0],
 
                              pos[1],
 
                              text="%s,%s" % (x, y),
 
                              fill='white',
 
                              anchor=anc1 + anc2,
 
                              tags='cornercoords')
 
        [d.setcoords() for d in self.displays.values()]
 

	
 

	
 
########################################################################
 
########################################################################
 
                
 
root=tk.Tk()
 

	
 
root = tk.Tk()
 
root.wm_geometry('700x350')
 
tra=Tracker(root)
 
tra.pack(fill='both',exp=1)
 
tra = Tracker(root)
 
tra.pack(fill='both', exp=1)
 

	
 
tra.load("fieldsets/demo")
 

	
bin/vidref
Show inline comments
 
#!bin/python
 
from run_local import log
 
import sys
 
sys.path.append('/usr/lib/python2.7/dist-packages') # For gtk
 
sys.path.append('/usr/lib/python2.7/dist-packages')  # For gtk
 
from twisted.internet import gtk2reactor
 
gtk2reactor.install()
 
from twisted.internet import reactor, defer
 
@@ -15,19 +15,19 @@ from light9.vidref.main import Gui
 
from light9.vidref.replay import snapshotDir
 
from rdfdb.syncedgraph import SyncedGraph
 

	
 
 # find replay dirs correctly. show multiple
 
 # replays. trash. reorder/pin. dump takes that are too short; they're
 
 # just from seeking
 
# find replay dirs correctly. show multiple
 
# replays. trash. reorder/pin. dump takes that are too short; they're
 
# just from seeking
 

	
 
parser = optparse.OptionParser()
 
parser.add_option("-v", "--verbose", action="store_true",
 
                  help="logging.DEBUG")
 
parser.add_option("-v", "--verbose", action="store_true", help="logging.DEBUG")
 
(options, args) = parser.parse_args()
 

	
 

	
 
log.setLevel(logging.DEBUG if options.verbose else logging.INFO)
 

	
 

	
 
class Snapshot(cyclone.web.RequestHandler):
 

	
 
    @defer.inlineCallbacks
 
    def post(self):
 
        # save next pic
 
@@ -38,7 +38,7 @@ class Snapshot(cyclone.web.RequestHandle
 
            assert outputFilename.startswith(snapshotDir())
 
            out = networking.vidref.path(
 
                "snapshot/%s" % outputFilename[len(snapshotDir()):].lstrip('/'))
 
            
 

	
 
            self.write(json.dumps({'snapshot': out}))
 
            self.set_header("Location", out)
 
            self.set_status(303)
 
@@ -47,11 +47,13 @@ class Snapshot(cyclone.web.RequestHandle
 
            traceback.print_exc()
 
            raise
 

	
 

	
 
class SnapshotPic(cyclone.web.StaticFileHandler):
 
    pass
 

	
 

	
 
class Time(cyclone.web.RequestHandler):
 

	
 
    def put(self):
 
        body = json.loads(self.request.body)
 
        t = body['t']
 
@@ -65,14 +67,21 @@ graph = SyncedGraph(networking.rdfdb.url
 
gui = Gui(graph)
 

	
 
port = networking.vidref.port
 
reactor.listenTCP(port, cyclone.web.Application(handlers=[
 
    (r'/()', cyclone.web.StaticFileHandler,
 
     {'path': 'light9/vidref', 'default_filename': 'vidref.html'}),
 
    (r'/snapshot', Snapshot),
 
    (r'/snapshot/(.*)', SnapshotPic, {"path": snapshotDir()}),
 
    (r'/time', Time),
 
    ], debug=True, gui=gui))
 
reactor.listenTCP(
 
    port,
 
    cyclone.web.Application(handlers=[
 
        (r'/()', cyclone.web.StaticFileHandler, {
 
            'path': 'light9/vidref',
 
            'default_filename': 'vidref.html'
 
        }),
 
        (r'/snapshot', Snapshot),
 
        (r'/snapshot/(.*)', SnapshotPic, {
 
            "path": snapshotDir()
 
        }),
 
        (r'/time', Time),
 
    ],
 
                            debug=True,
 
                            gui=gui))
 
log.info("serving on %s" % port)
 

	
 
reactor.run()
 

	
bin/vidrefsetup
Show inline comments
 
@@ -16,17 +16,23 @@ from light9 import networking, showconfi
 

	
 
from lib.cycloneerr import PrettyErrorHandler
 

	
 

	
 
class RedirToCamera(PrettyErrorHandler, cyclone.web.RequestHandler):
 

	
 
    def get(self):
 
        return self.redirect(networking.picamserve.path(
 
            'pic?' + self.request.query))
 
        
 
        return self.redirect(
 
            networking.picamserve.path('pic?' + self.request.query))
 

	
 

	
 
class UrlToCamera(PrettyErrorHandler, cyclone.web.RequestHandler):
 

	
 
    def get(self):
 
        self.set_header('Content-Type', 'text/plain')
 
        self.write(networking.picamserve.path('pic'))
 
                   
 

	
 

	
 
class VidrefCamRequest(PrettyErrorHandler, cyclone.web.RequestHandler):
 

	
 
    def get(self):
 
        graph = self.settings.graph
 
        show = showconfig.showUri()
 
@@ -35,7 +41,7 @@ class VidrefCamRequest(PrettyErrorHandle
 
            if ret is None:
 
                self.send_error(404)
 
            self.redirect(ret)
 
            
 

	
 
    def put(self):
 
        graph = self.settings.graph
 
        show = showconfig.showUri()
 
@@ -45,9 +51,12 @@ class VidrefCamRequest(PrettyErrorHandle
 
                          newObject=URIRef(self.get_argument('uri')))
 
        self.send_error(202)
 

	
 

	
 
def main():
 
    parser = optparse.OptionParser()
 
    parser.add_option("-v", "--verbose", action="store_true",
 
    parser.add_option("-v",
 
                      "--verbose",
 
                      action="store_true",
 
                      help="logging.DEBUG")
 
    (options, args) = parser.parse_args()
 

	
 
@@ -55,15 +64,23 @@ def main():
 
    graph = SyncedGraph(networking.rdfdb.url, "vidrefsetup")
 

	
 
    # deliberately conflict with vidref since they can't talk at once to cam
 
    port = networking.vidref.port 
 
    port = networking.vidref.port
 

	
 
    reactor.listenTCP(port, cyclone.web.Application(handlers=[
 
        (r'/pic', RedirToCamera),
 
        (r'/picUrl', UrlToCamera),
 
        (r'/vidrefCamRequest', VidrefCamRequest),
 
        (r'/()', cyclone.web.StaticFileHandler, {'path': 'light9/vidref/', 'default_filename': 'vidref.html'}),
 
        ], debug=True, graph=graph))
 
    reactor.listenTCP(
 
        port,
 
        cyclone.web.Application(handlers=[
 
            (r'/pic', RedirToCamera),
 
            (r'/picUrl', UrlToCamera),
 
            (r'/vidrefCamRequest', VidrefCamRequest),
 
            (r'/()', cyclone.web.StaticFileHandler, {
 
                'path': 'light9/vidref/',
 
                'default_filename': 'vidref.html'
 
            }),
 
        ],
 
                                debug=True,
 
                                graph=graph))
 
    log.info("serving on %s" % port)
 
    reactor.run()
 

	
 

	
 
main()
bin/wavecurve
Show inline comments
 
@@ -3,24 +3,30 @@ import optparse
 
import run_local
 
from light9.wavepoints import simp
 

	
 

	
 
def createCurve(inpath, outpath, t):
 
    print "reading %s, writing %s" % (inpath, outpath)
 
    points = simp(inpath.replace('.ogg', '.wav'), seconds_per_average=t)
 

	
 
    f = file(outpath, 'w')
 
    for time_val in points:
 
        print >>f, "%s %s" % time_val
 
        print >> f, "%s %s" % time_val
 

	
 

	
 
parser = optparse.OptionParser(usage="""%prog inputSong.wav outputCurve
 

	
 
You probably just want -a
 

	
 
""")
 
parser.add_option("-t", type="float", default=.01,
 
parser.add_option("-t",
 
                  type="float",
 
                  default=.01,
 
                  help="seconds per sample (default .01, .07 is smooth)")
 
parser.add_option("-a", "--all", action="store_true",
 
parser.add_option("-a",
 
                  "--all",
 
                  action="store_true",
 
                  help="make standard curves for all songs")
 
options,args = parser.parse_args()
 
options, args = parser.parse_args()
 

	
 
if options.all:
 
    from light9 import showconfig
 
@@ -32,8 +38,7 @@ if options.all:
 
    playlist = Playlist.fromShow(showconfig.getGraph(), showconfig.showUri())
 
    for song in playlist.allSongs():
 
        inpath = showconfig.songOnDisk(song)
 
        for curveName, t in [('music', .01),
 
                             ('smooth_music', .07)]:
 
        for curveName, t in [('music', .01), ('smooth_music', .07)]:
 
            outpath = showconfig.curvesDir() + "/%s-%s" % (
 
                showconfig.songFilenameFromURI(song), curveName)
 
            createCurve(inpath, outpath, t)
bin/webcontrol
Show inline comments
 
@@ -21,14 +21,14 @@ from light9 import showconfig, networkin
 
from light9.namespaces import L9
 
from urllib import urlencode
 

	
 

	
 
# move to web lib
 
def post(url, **args):
 
    return getPage(url,
 
                   method='POST',
 
                   postdata=urlencode(args))
 
    return getPage(url, method='POST', postdata=urlencode(args))
 

	
 

	
 
class Commands(object):
 

	
 
    @staticmethod
 
    def playSong(graph, songUri):
 
        s = xmlrpclib.ServerProxy(networking.musicPlayer.url)
 
@@ -45,17 +45,22 @@ class Commands(object):
 
    @staticmethod
 
    def worklightsOn(graph):
 
        return post(networking.keyboardComposer.path('fadesub'),
 
                    subname='scoop', level=.5, secs=.5)
 
                    subname='scoop',
 
                    level=.5,
 
                    secs=.5)
 

	
 
    @staticmethod
 
    def worklightsOff(graph):
 
        return post(networking.keyboardComposer.path('fadesub'),
 
                    subname='scoop', level=0, secs=.5)
 
                    subname='scoop',
 
                    level=0,
 
                    secs=.5)
 

	
 
    @staticmethod
 
    def dimmerSet(graph, dimmer, value):
 
        raise NotImplementedError("subcomposer doesnt have an http port yet")
 

	
 

	
 
class Main(rend.Page):
 
    docFactory = loaders.xmlfile(sibpath(__file__, "../light9/webcontrol.html"))
 

	
 
@@ -75,9 +80,9 @@ class Main(rend.Page):
 
        out = []
 
        for song in songs:
 
            out.append(
 
                T.form(method="post", action="playSong")[
 
                    T.input(type='hidden', name='songUri', value=song),
 
                    T.button(type='submit')[graph.label(song)]])
 
                T.form(method="post", action="playSong")
 
                [T.input(type='hidden', name='songUri', value=song),
 
                 T.button(type='submit')[graph.label(song)]])
 
        return out
 

	
 
    @inlineCallbacks
 
@@ -85,18 +90,19 @@ class Main(rend.Page):
 
        try:
 
            func = getattr(Commands, segments[0])
 
            req = inevow.IRequest(ctx)
 
            simpleArgDict = dict((k, v[0]) for k,v in req.args.items())
 
            simpleArgDict = dict((k, v[0]) for k, v in req.args.items())
 
            try:
 
                ret = yield robust_apply(func, func, self.graph,
 
                                         **simpleArgDict)
 
            except KeyboardInterrupt: raise
 
            except KeyboardInterrupt:
 
                raise
 
            except Exception, e:
 
                print "Error on command %s" % segments[0]
 
                traceback.print_exc()
 
                returnValue((url.here.up().
 
                             add('status', str(e)).
 
                             add('error', 1), segments[1:]))
 
                
 
                returnValue((url.here.up().add('status',
 
                                               str(e)).add('error',
 
                                                           1), segments[1:]))
 

	
 
            returnValue((url.here.up().add('status', ret), segments[1:]))
 
            #actually return the orig page, with a status message from the func
 
        except AttributeError:
 
@@ -105,7 +111,8 @@ class Main(rend.Page):
 

	
 
    def child_icon(self, ctx):
 
        return static.File("/usr/share/pyshared/elisa/plugins/poblesec/tango")
 
            
 

	
 

	
 
graph = showconfig.getGraph()
 
show = showconfig.showUri()
 

	
light9/Effects.py
Show inline comments
 
@@ -11,21 +11,24 @@ from light9.namespaces import L9
 
log = logging.getLogger()
 

	
 
registered = []
 

	
 

	
 
def register(f):
 
    registered.append(f)
 
    return f
 

	
 

	
 
@register
 
class Strip(object):
 
    """list of r,g,b tuples for sending to an LED strip"""
 
    which = 'L' # LR means both. W is the wide one
 
    which = 'L'  # LR means both. W is the wide one
 
    pixels = []
 

	
 
    def __repr__(self):
 
        return '<Strip which=%r px0=%r>' % (self.which, self.pixels[0])
 
    
 

	
 
    @classmethod
 
    def solid(cls, which='L', color=(1,1,1), hsv=None):
 
    def solid(cls, which='L', color=(1, 1, 1), hsv=None):
 
        """hsv overrides color"""
 
        if hsv is not None:
 
            color = colorsys.hsv_to_rgb(hsv[0] % 1.0, hsv[1], hsv[2])
 
@@ -37,24 +40,34 @@ class Strip(object):
 
    def __mul__(self, f):
 
        if not isinstance(f, (int, float)):
 
            raise TypeError
 
            
 

	
 
        s = Strip()
 
        s.which = self.which
 
        s.pixels = [(r*f, g*f, b*f) for r,g,b in self.pixels]
 
        s.pixels = [(r * f, g * f, b * f) for r, g, b in self.pixels]
 
        return s
 

	
 
    __rmul__ = __mul__
 

	
 

	
 
@register
 
class Blacklight(float):
 
    """a level for the blacklight PWM output"""
 

	
 
    def __mul__(self, f):
 
        return Blacklight(float(self) * f)
 

	
 
    __rmul__ = __mul__
 
    
 

	
 

	
 
@register
 
def chase(t, ontime=0.5, offset=0.2, onval=1.0, 
 
          offval=0.0, names=None, combiner=max, random=False):
 
def chase(t,
 
          ontime=0.5,
 
          offset=0.2,
 
          onval=1.0,
 
          offval=0.0,
 
          names=None,
 
          combiner=max,
 
          random=False):
 
    """names is list of URIs. returns a submaster that chases through
 
    the inputs"""
 
    if random:
 
@@ -69,24 +82,27 @@ def chase(t, ontime=0.5, offset=0.2, onv
 
            dmx = Patch.dmx_from_uri(uri)
 
        except KeyError:
 
            log.info(("chase includes %r, which doesn't resolve to a dmx chan" %
 
                   uri))
 
                      uri))
 
            continue
 
        lev[dmx] = value
 

	
 
    return Submaster.Submaster(name="chase" ,levels=lev)
 
    return Submaster.Submaster(name="chase", levels=lev)
 

	
 

	
 
@register
 
def hsv(h, s, v, light='all', centerScale=.5):
 
    r,g,b = colorsys.hsv_to_rgb(h % 1.0, s, v)
 
    r, g, b = colorsys.hsv_to_rgb(h % 1.0, s, v)
 
    lev = {}
 
    if light in ['left', 'all']:
 
        lev[73], lev[74], lev[75] = r,g,b
 
        lev[73], lev[74], lev[75] = r, g, b
 
    if light in ['right', 'all']:
 
        lev[80], lev[81], lev[82] = r,g,b
 
        lev[80], lev[81], lev[82] = r, g, b
 
    if light in ['center', 'all']:
 
        lev[88], lev[89], lev[90] = r*centerScale,g*centerScale,b*centerScale
 
        lev[88], lev[89], lev[
 
            90] = r * centerScale, g * centerScale, b * centerScale
 
    return Submaster.Submaster(name='hsv', levels=lev)
 
    
 

	
 

	
 
@register
 
def stack(t, names=None, fade=0):
 
    """names is list of URIs. returns a submaster that stacks the the inputs
 
@@ -102,19 +118,22 @@ def stack(t, names=None, fade=0):
 
            try:
 
                dmx = Patch.dmx_from_uri(uri)
 
            except KeyError:
 
                log.info(("stack includes %r, which doesn't resolve to a dmx chan"%
 
                       uri))
 
                log.info(
 
                    ("stack includes %r, which doesn't resolve to a dmx chan" %
 
                     uri))
 
                continue
 
            lev[dmx] = 1
 
        else:
 
            break
 
    
 

	
 
    return Submaster.Submaster(name="stack", levels=lev)
 

	
 

	
 
@register
 
def smoove(x):
 
    return -2 * (x ** 3) + 3 * (x ** 2)
 
    
 
    return -2 * (x**3) + 3 * (x**2)
 

	
 

	
 
def configExprGlobals():
 
    graph = showconfig.getGraph()
 
    ret = {}
 
@@ -130,8 +149,10 @@ def configExprGlobals():
 

	
 
    ret['nsin'] = lambda x: (math.sin(x * (2 * math.pi)) + 1) / 2
 
    ret['ncos'] = lambda x: (math.cos(x * (2 * math.pi)) + 1) / 2
 

	
 
    def nsquare(t, on=.5):
 
        return (t % 1.0) < on
 

	
 
    ret['nsquare'] = nsquare
 

	
 
    _smooth_random_items = [random_mod.random() for x in range(100)]
 
@@ -156,7 +177,4 @@ def configExprGlobals():
 
    ret['noise2'] = smooth_random2
 
    ret['notch2'] = notch_random2
 

	
 

	
 

	
 
    
 
    return ret
light9/Fadable.py
Show inline comments
 
@@ -2,6 +2,7 @@
 
from Tix import *
 
import time
 

	
 

	
 
class Fadable:
 
    """Fading mixin: must mix in with a Tk widget (or something that has
 
    'after' at least) This is currently used by VolumeBox and MixerTk.
 
@@ -21,24 +22,28 @@ class Fadable:
 
    raise or lower the volume.  Shift-mouse wheeling will cause a more
 
    precise volume adjustment.  Control-mouse wheeling will cause a
 
    longer fade."""
 
    def __init__(self, var, wheel_step=5, use_fades=1, key_bindings=1,
 

	
 
    def __init__(self,
 
                 var,
 
                 wheel_step=5,
 
                 use_fades=1,
 
                 key_bindings=1,
 
                 mouse_bindings=1):
 
        self.use_fades = use_fades # whether increase and decrease should fade
 
        self.wheel_step = wheel_step # amount that increase and descrease should
 
                                     # change volume (by default)
 
        
 
        self.use_fades = use_fades  # whether increase and decrease should fade
 
        self.wheel_step = wheel_step  # amount that increase and descrease should
 
        # change volume (by default)
 

	
 
        self.fade_start_level = 0
 
        self.fade_end_level = 0
 
        self.fade_start_time = 0
 
        self.fade_length = 1
 
        self.fade_step_time = 10
 
        self.fade_var = var
 
        self.fading = 0 # whether a fade is in progress
 
        self.fading = 0  # whether a fade is in progress
 

	
 
        if key_bindings:
 
            for k in range(1, 10):
 
                self.bind("<Key-%d>" % k,
 
                    lambda evt, k=k: self.fade(k / 10.0))
 
                self.bind("<Key-%d>" % k, lambda evt, k=k: self.fade(k / 10.0))
 
            self.bind("<Key-0>", lambda evt: self.fade(1.0))
 
            self.bind("<grave>", lambda evt: self.fade(0))
 

	
 
@@ -61,7 +66,7 @@ class Fadable:
 
            self.bind('<Control-4>', lambda evt: self.increase(length=1))
 
            self.bind('<Control-5>', lambda evt: self.decrease(length=1))
 

	
 
        self.last_level = None # used for muting
 
        self.last_level = None  # used for muting
 

	
 
    def set_var_rounded(self, value):
 
        """use this instead of just self.fade_var.set(value) so we can
 
@@ -72,26 +77,27 @@ class Fadable:
 
        # variable's display instead of using Label(textvariable=var)
 
        # and format it there.
 
        self.fade_var.set(round(value, 7))
 
        
 

	
 
    def fade(self, value, length=0.5, step_time=10):
 
        """Fade to value in length seconds with steps every step_time
 
        milliseconds"""
 
        if length == 0: # 0 seconds fades happen right away and prevents
 
                        # and prevents us from entering the fade loop,
 
                        # which would cause a divide by zero
 
        if length == 0:  # 0 seconds fades happen right away and prevents
 
            # and prevents us from entering the fade loop,
 
            # which would cause a divide by zero
 
            self.set_var_rounded(value)
 
            self.fading = 0 # we stop all fades
 
        else: # the general case
 
            self.fading = 0  # we stop all fades
 
        else:  # the general case
 
            self.fade_start_time = time.time()
 
            self.fade_length = length
 

	
 
            self.fade_start_level = self.fade_var.get()
 
            self.fade_end_level = value
 
            
 

	
 
            self.fade_step_time = step_time
 
            if not self.fading:
 
                self.fading = 1
 
                self.do_fade()
 

	
 
    def do_fade(self):
 
        """Actually performs the fade for Fadable.fade.  Shouldn't be called
 
        directly."""
 
@@ -106,6 +112,7 @@ class Fadable:
 
            self.after(self.fade_step_time, self.do_fade)
 
        else:
 
            self.fading = 0
 

	
 
    def increase(self, multiplier=1, length=0.3):
 
        """Increases the volume by multiplier * wheel_step.  If use_fades is
 
        true, it do this as a fade over length time."""
 
@@ -116,6 +123,7 @@ class Fadable:
 
            newlevel = self.fade_var.get() + amount
 
        newlevel = min(100, newlevel)
 
        self.set_volume(newlevel, length)
 

	
 
    def decrease(self, multiplier=1, length=0.3):
 
        """Descreases the volume by multiplier * wheel_step.  If use_fades
 
        is true, it do this as a fade over length time."""
 
@@ -126,6 +134,7 @@ class Fadable:
 
            newlevel = self.fade_var.get() - amount
 
        newlevel = max(0, newlevel)
 
        self.set_volume(newlevel, length)
 

	
 
    def set_volume(self, newlevel, length=0.3):
 
        """Sets the volume to newlevel, performing a fade of length if
 
        use_fades is true."""
 
@@ -133,13 +142,14 @@ class Fadable:
 
            self.fade(newlevel, length=length)
 
        else:
 
            self.set_var_rounded(newlevel)
 

	
 
    def toggle_mute(self):
 
        """Toggles whether the volume is being muted."""
 
        if self.last_level is None:
 
            self.last_level = self.fade_var.get()
 
            if self.last_level == 0: # we don't want last_level to be zero,
 
                                     # since it will make us toggle between 0
 
                                     # and 0
 
            if self.last_level == 0:  # we don't want last_level to be zero,
 
                # since it will make us toggle between 0
 
                # and 0
 
                newlevel = 1
 
            else:
 
                newlevel = 0
 
@@ -148,4 +158,3 @@ class Fadable:
 
            self.last_level = None
 

	
 
        self.set_var_rounded(newlevel)
 

	
light9/FlyingFader.py
Show inline comments
 
from Tix import *
 
from time import time,sleep
 
from time import time, sleep
 
from __future__ import division
 

	
 

	
 
class Mass:
 

	
 
    def __init__(self):
 
        self.x=0 # position
 
        self.xgoal=0 # position goal
 
        
 
        self.v=0 # velocity
 
        self.maxspeed = .8 # maximum speed, in position/second
 
        self.maxaccel = 3 # maximum acceleration, in position/second^2
 
        self.eps = .03 # epsilon - numbers within this much are considered the same
 
        self.x = 0  # position
 
        self.xgoal = 0  # position goal
 

	
 
        self._lastupdate=time()
 
        self._stopped=1
 
        self.v = 0  # velocity
 
        self.maxspeed = .8  # maximum speed, in position/second
 
        self.maxaccel = 3  # maximum acceleration, in position/second^2
 
        self.eps = .03  # epsilon - numbers within this much are considered the same
 

	
 
    def equal(self,a,b):
 
        return abs(a-b)<self.eps
 
        self._lastupdate = time()
 
        self._stopped = 1
 

	
 
    def equal(self, a, b):
 
        return abs(a - b) < self.eps
 

	
 
    def stop(self):
 
        self.v=0
 
        self.xgoal=self.x
 
        self._stopped=1
 
        
 
        self.v = 0
 
        self.xgoal = self.x
 
        self._stopped = 1
 

	
 
    def update(self):
 
        t0 = self._lastupdate
 
        tnow = time()
 
        self._lastupdate = tnow
 

	
 
        dt = tnow-t0
 
        dt = tnow - t0
 

	
 
        self.x += self.v*dt
 
        self.x += self.v * dt
 
        # hitting the ends stops the slider
 
        if self.x>1: self.v=max(self.v,0); self.x=1
 
        if self.x<0: self.v=min(self.v,0); self.x=0
 
            
 
        if self.equal(self.x,self.xgoal):
 
            self.x=self.xgoal # clean up value
 
        if self.x > 1:
 
            self.v = max(self.v, 0)
 
            self.x = 1
 
        if self.x < 0:
 
            self.v = min(self.v, 0)
 
            self.x = 0
 

	
 
        if self.equal(self.x, self.xgoal):
 
            self.x = self.xgoal  # clean up value
 
            self.stop()
 
            return
 
        
 
        self._stopped=0
 
        dir = (-1.0,1,0)[self.xgoal>self.x]
 

	
 
        if abs(self.xgoal-self.x) < abs(self.v*5*dt):
 
        self._stopped = 0
 
        dir = (-1.0, 1, 0)[self.xgoal > self.x]
 

	
 
        if abs(self.xgoal - self.x) < abs(self.v * 5 * dt):
 
            # apply the brakes on the last 5 steps
 
            dir *= -.5
 

	
 
        self.v += dir*self.maxaccel*dt # velocity changes with acceleration in the right direction
 
        self.v = min(max(self.v,-self.maxspeed),self.maxspeed) # clamp velocity
 
        self.v += dir * self.maxaccel * dt  # velocity changes with acceleration in the right direction
 
        self.v = min(max(self.v, -self.maxspeed),
 
                     self.maxspeed)  # clamp velocity
 

	
 
        #print "x=%+.03f v=%+.03f a=%+.03f %f" % (self.x,self.v,self.maxaccel,self.xgoal)
 

	
 
    def goto(self,newx):
 
        self.xgoal=newx
 
    def goto(self, newx):
 
        self.xgoal = newx
 

	
 
    def ismoving(self):
 
        return not self._stopped
 

	
 

	
 
class FlyingFader(Frame):
 
    def __init__(self, master, variable, label, fadedur=1.5, font=('Arial', 8), labelwidth=12,
 

	
 
    def __init__(self,
 
                 master,
 
                 variable,
 
                 label,
 
                 fadedur=1.5,
 
                 font=('Arial', 8),
 
                 labelwidth=12,
 
                 **kw):
 
        Frame.__init__(self, master)
 
        self.name = label
 
        self.variable = variable
 

	
 
        self.mass = Mass()
 
        
 
        self.config({'bd':1, 'relief':'raised'})
 
        scaleopts = {'variable' : variable, 'showvalue' : 0, 'from' : 1.0,
 
                     'to' : 0, 'res' : 0.001, 'width' : 20, 'length' : 200, 'orient':'vert'}
 

	
 
        self.config({'bd': 1, 'relief': 'raised'})
 
        scaleopts = {
 
            'variable': variable,
 
            'showvalue': 0,
 
            'from': 1.0,
 
            'to': 0,
 
            'res': 0.001,
 
            'width': 20,
 
            'length': 200,
 
            'orient': 'vert'
 
        }
 
        scaleopts.update(kw)
 
        if scaleopts['orient']=='vert':
 
            side1=TOP
 
            side2=BOTTOM
 
        if scaleopts['orient'] == 'vert':
 
            side1 = TOP
 
            side2 = BOTTOM
 
        else:
 
            side1=RIGHT
 
            side2=LEFT
 
        
 
            side1 = RIGHT
 
            side2 = LEFT
 

	
 
        self.scale = Scale(self, **scaleopts)
 
        self.vlabel = Label(self, text="0.0", width=6, font=font)
 
        self.label = Label(self, text=label, font=font, anchor='w',width=labelwidth) #wraplength=40, )
 
        self.label = Label(self,
 
                           text=label,
 
                           font=font,
 
                           anchor='w',
 
                           width=labelwidth)  #wraplength=40, )
 

	
 
        self.oldtrough = self.scale['troughcolor']
 

	
 
@@ -89,8 +116,8 @@ class FlyingFader(Frame):
 
        self.label.pack(side=side2, expand=0, fill=X)
 

	
 
        for k in range(1, 10):
 
            self.scale.bind("<Key-%d>" % k, 
 
                lambda evt, k=k: self.newfade(k / 10.0, evt))
 
            self.scale.bind(
 
                "<Key-%d>" % k, lambda evt, k=k: self.newfade(k / 10.0, evt))
 

	
 
        self.scale.bind("<Key-0>", lambda evt: self.newfade(1.0, evt))
 
        self.scale.bind("<grave>", lambda evt: self.newfade(0, evt))
 
@@ -100,10 +127,10 @@ class FlyingFader(Frame):
 
        self.scale.bind("<3>", self.mousefade)
 

	
 
        self.trace_ret = self.variable.trace('w', self.updatelabel)
 
        self.bind("<Destroy>",self.ondestroy)
 
        self.bind("<Destroy>", self.ondestroy)
 

	
 
    def ondestroy(self,*ev):
 
        self.variable.trace_vdelete('w',self.trace_ret)
 
    def ondestroy(self, *ev):
 
        self.variable.trace_vdelete('w', self.trace_ret)
 

	
 
    def cancelfade(self, evt):
 
        self.fadegoal = self.variable.get()
 
@@ -116,16 +143,15 @@ class FlyingFader(Frame):
 
        self.newfade(target, evt)
 

	
 
    def ismoving(self):
 
        return self.fadevel!=0 or self.fadeacc!=0
 
        return self.fadevel != 0 or self.fadeacc != 0
 

	
 
    def newfade(self, newlevel, evt=None, length=None):
 

	
 
        # these are currently unused-- Mass needs to accept a speed input
 
        mult = 1
 
        if evt.state & 8 and evt.state & 4: mult = 0.25 # both
 
        elif evt.state & 8: mult = 0.5 # alt
 
        elif evt.state & 4: mult = 2   # control
 

	
 
        if evt.state & 8 and evt.state & 4: mult = 0.25  # both
 
        elif evt.state & 8: mult = 0.5  # alt
 
        elif evt.state & 4: mult = 2  # control
 

	
 
        self.mass.x = self.variable.get()
 
        self.mass.goto(newlevel)
 
@@ -139,9 +165,9 @@ class FlyingFader(Frame):
 
        if not self.mass.ismoving():
 
            self.scale['troughcolor'] = self.oldtrough
 
            return
 
        
 

	
 
        # blink the trough while the thing's moving
 
        if time()%.4>.2:
 
        if time() % .4 > .2:
 
            # self.scale.config(troughcolor=self.oldtrough)
 
            self.scale.config(troughcolor='orange')
 
        else:
 
@@ -154,6 +180,8 @@ class FlyingFader(Frame):
 
    def updatelabel(self, *args):
 
        if self.variable:
 
            self.vlabel['text'] = "%.3f" % self.variable.get()
 

	
 

	
 
#        if self.fadetimes[1] == 0: # no fade
 
#            self.vlabel['fg'] = 'black'
 
#        elif self.curfade[1] > self.curfade[0]:
 
@@ -167,25 +195,32 @@ class FlyingFader(Frame):
 
    def set(self, val):
 
        self.scale.set(val)
 

	
 

	
 
def colorfade(scale, lev):
 
    low = (255, 255, 255)
 
    high = (0, 0, 0)
 
    out = [int(l+lev*(h-l)) for h, l in zip(high,low)]
 
    col="#%02X%02X%02X" % tuple(out)
 
    out = [int(l + lev * (h - l)) for h, l in zip(high, low)]
 
    col = "#%02X%02X%02X" % tuple(out)
 
    scale.config(troughcolor=col)
 

	
 

	
 
if __name__ == '__main__':
 
    root = Tk()
 
    root.tk_focusFollowsMouse()
 

	
 
    FlyingFader(root, variable=DoubleVar(), label="suck").pack(side=LEFT, 
 
        expand=1, fill=BOTH)
 
    FlyingFader(root, variable=DoubleVar(), label="suck").pack(side=LEFT,
 
                                                               expand=1,
 
                                                               fill=BOTH)
 
    FlyingFader(root, variable=DoubleVar(), label="moof").pack(side=LEFT,
 
        expand=1, fill=BOTH)
 
                                                               expand=1,
 
                                                               fill=BOTH)
 
    FlyingFader(root, variable=DoubleVar(), label="zarf").pack(side=LEFT,
 
        expand=1, fill=BOTH)
 
    FlyingFader(root, variable=DoubleVar(), 
 
        label="long name goes here.  got it?").pack(side=LEFT, expand=1, 
 
        fill=BOTH)
 
                                                               expand=1,
 
                                                               fill=BOTH)
 
    FlyingFader(root,
 
                variable=DoubleVar(),
 
                label="long name goes here.  got it?").pack(side=LEFT,
 
                                                            expand=1,
 
                                                            fill=BOTH)
 

	
 
    root.mainloop()
light9/Patch.py
Show inline comments
 
@@ -8,12 +8,14 @@ def resolve_name(channelname):
 
    "Ensure that we're talking about the primary name of the light."
 
    return get_channel_name(get_dmx_channel(channelname))
 

	
 

	
 
def get_all_channels():
 
    """returns primary names for all channels (sorted)"""
 
    prinames = reverse_patch.values()[:]
 
    prinames.sort()
 
    return prinames
 

	
 

	
 
def get_dmx_channel(name):
 
    if str(name) in patch:
 
        return patch[str(name)]
 
@@ -24,6 +26,7 @@ def get_dmx_channel(name):
 
    except ValueError:
 
        raise ValueError("Invalid channel name: %r" % name)
 

	
 

	
 
def get_channel_name(dmxnum):
 
    """if you pass a name, it will get normalized"""
 
    try:
 
@@ -31,12 +34,15 @@ def get_channel_name(dmxnum):
 
    except KeyError:
 
        return str(dmxnum)
 

	
 

	
 
def get_channel_uri(name):
 
    return uri_map[name]
 

	
 

	
 
def dmx_from_uri(uri):
 
    return uri_patch[uri]
 

	
 

	
 
def reload_data():
 
    global patch, reverse_patch, uri_map, uri_patch
 
    patch = {}
 
@@ -67,6 +73,6 @@ def reload_data():
 
                    else:
 
                        reverse_patch[name] = norm_name
 

	
 

	
 
# importing patch will load initial data
 
reload_data()
 

	
light9/ascoltami/player.py
Show inline comments
 
#!/usr/bin/python
 

	
 
"""
 
alternate to the mpd music player, for ascoltami
 
"""
 
@@ -8,10 +7,11 @@ import time, logging, traceback
 
from gi.repository import GObject, Gst
 
from twisted.internet import reactor, task
 

	
 

	
 
log = logging.getLogger()
 

	
 

	
 
class Player(object):
 

	
 
    def __init__(self, autoStopOffset=4, onEOS=None):
 
        """autoStopOffset is the number of seconds before the end of
 
        song before automatically stopping (which is really pausing).
 
@@ -20,27 +20,27 @@ class Player(object):
 
        It is called with one argument which is the URI of the song that
 
        just finished."""
 
        self.autoStopOffset = autoStopOffset
 
        self.playbin = self.pipeline = Gst.ElementFactory.make('playbin',None)
 
        self.playbin = self.pipeline = Gst.ElementFactory.make('playbin', None)
 

	
 
        self.playStartTime = 0
 
        self.lastWatchTime = 0
 
        self.autoStopTime = 0
 
        self.lastSetSongUri = None
 
        self.onEOS = onEOS
 
        
 

	
 
        task.LoopingCall(self.watchTime).start(.050)
 

	
 
        bus = self.pipeline.get_bus()
 
        # not working- see notes in pollForMessages
 
        #self.watchForMessages(bus)
 
      
 

	
 
    def watchTime(self):
 
        try:
 
            self.pollForMessages()
 
            
 

	
 
            t = self.currentTime()
 
            log.debug("watch %s < %s < %s",
 
                      self.lastWatchTime, self.autoStopTime, t)
 
            log.debug("watch %s < %s < %s", self.lastWatchTime,
 
                      self.autoStopTime, t)
 
            if self.lastWatchTime < self.autoStopTime < t:
 
                log.info("autostop")
 
                self.pause()
 
@@ -58,6 +58,7 @@ class Player(object):
 
            print "onEos", args
 
            if self.onEOS is not None:
 
                self.onEOS(self.getSong())
 

	
 
        bus.connect('message::eos', onEos)
 

	
 
        def onStreamStatus(bus, message):
 
@@ -65,14 +66,16 @@ class Player(object):
 
            (statusType, _elem) = message.parse_stream_status()
 
            if statusType == Gst.StreamStatusType.ENTER:
 
                self.setupAutostop()
 

	
 
        bus.connect('message::stream-status', onStreamStatus)
 
            
 

	
 
    def pollForMessages(self):
 
        """bus.add_signal_watch seems to be having no effect, but this works"""
 
        bus = self.pipeline.get_bus()
 
        mt = Gst.MessageType
 
        msg = bus.poll(mt.EOS | mt.STREAM_STATUS | mt.ERROR,# | mt.ANY,
 
                       0)
 
        msg = bus.poll(
 
            mt.EOS | mt.STREAM_STATUS | mt.ERROR,  # | mt.ANY,
 
            0)
 
        if msg is not None:
 
            log.debug("bus message: %r %r", msg.src, msg.type)
 
            # i'm trying to catch here a case where the pulseaudio
 
@@ -88,7 +91,7 @@ class Player(object):
 
                (statusType, _elem) = msg.parse_stream_status()
 
                if statusType == Gst.StreamStatusType.ENTER:
 
                    self.setupAutostop()
 
            
 

	
 
    def seek(self, t):
 
        isSeekable = self.playbin.seek_simple(
 
            Gst.Format.TIME,
 
@@ -149,9 +152,15 @@ class Player(object):
 
        """json-friendly object describing the interesting states of
 
        the player nodes"""
 
        success, state, pending = self.playbin.get_state(timeout=0)
 
        return {"current": {"name":state.value_nick},
 
                "pending": {"name":state.value_nick}}
 
        
 
        return {
 
            "current": {
 
                "name": state.value_nick
 
            },
 
            "pending": {
 
                "name": state.value_nick
 
            }
 
        }
 

	
 
    def pause(self):
 
        self.pipeline.set_state(Gst.State.PAUSED)
 

	
 
@@ -161,7 +170,8 @@ class Player(object):
 
        """
 
        pos = self.currentTime()
 
        autoStop = self.duration() - self.autoStopOffset
 
        return not self.isPlaying() and abs(pos - autoStop) < 1 # i've seen .4 difference here
 
        return not self.isPlaying() and abs(
 
            pos - autoStop) < 1  # i've seen .4 difference here
 

	
 
    def resume(self):
 
        self.pipeline.set_state(Gst.State.PLAYING)
light9/ascoltami/playlist.py
Show inline comments
 
from light9.showconfig import songOnDisk
 
from light9.namespaces import L9
 

	
 

	
 
class NoSuchSong(ValueError):
 
    """Raised when a song is requested that doesn't exist (e.g. one
 
    after the last song in the playlist)."""
 

	
 

	
 
class Playlist(object):
 

	
 
    def __init__(self, graph, playlistUri):
 
        self.graph = graph
 
        self.songs = list(graph.items(playlistUri))
 
        
 

	
 
    def nextSong(self, currentSong):
 
        """Returns the next song in the playlist or raises NoSuchSong if 
 
        we are at the end of the playlist."""
 
@@ -30,14 +33,14 @@ class Playlist(object):
 
    def allSongs(self):
 
        """Returns a list of all song URIs in order."""
 
        return self.songs
 
    
 

	
 
    def allSongPaths(self):
 
        """Returns a list of the filesystem paths to all songs in order."""
 
        paths = []
 
        for song in self.songs:
 
            paths.append(songOnDisk(song))
 
        return paths
 
    
 

	
 
    def songPath(self, uri):
 
        """filesystem path to a song"""
 
        raise NotImplementedError("see showconfig.songOnDisk")
light9/ascoltami/webapp.py
Show inline comments
 
@@ -9,32 +9,40 @@ render = render_genshi([sibpath(__file__
 

	
 
from lib.cycloneerr import PrettyErrorHandler
 

	
 
_songUris = {} # locationUri : song
 
_songUris = {}  # locationUri : song
 

	
 

	
 
def songLocation(graph, songUri):
 
    loc = URIRef("file://%s" % songOnDisk(songUri))
 
    _songUris[loc] = songUri
 
    return loc
 
    
 

	
 

	
 
def songUri(graph, locationUri):
 
    return _songUris[locationUri]
 

	
 

	
 
class root(PrettyErrorHandler, cyclone.web.RequestHandler):
 

	
 
    def get(self):
 
        self.set_header("Content-Type", "application/xhtml+xml")
 
        # todo: use a template; embed the show name and the intro/post
 
        # times into the page
 
        self.write(render.index(host=socket.gethostname()))
 

	
 

	
 
def playerSongUri(graph, player):
 
    """or None"""
 
    
 

	
 
    playingLocation = player.getSong()
 
    if playingLocation:
 
        return songUri(graph, URIRef(playingLocation))
 
    else:
 
        return None
 

	
 
class timeResource(PrettyErrorHandler,cyclone.web.RequestHandler):
 

	
 
class timeResource(PrettyErrorHandler, cyclone.web.RequestHandler):
 

	
 
    def get(self):
 
        player = self.settings.app.player
 
        graph = self.settings.app.graph
 
@@ -47,14 +55,15 @@ class timeResource(PrettyErrorHandler,cy
 
        else:
 
            nextAction = 'play'
 

	
 
        self.write(json.dumps({
 
            "song" : playerSongUri(graph, player),
 
            "started" : player.playStartTime,
 
            "duration" : player.duration(),
 
            "playing" : player.isPlaying(),
 
            "t" : player.currentTime(),
 
            "state" : player.states(),
 
            "next" : nextAction,
 
        self.write(
 
            json.dumps({
 
                "song": playerSongUri(graph, player),
 
                "started": player.playStartTime,
 
                "duration": player.duration(),
 
                "playing": player.isPlaying(),
 
                "t": player.currentTime(),
 
                "state": player.states(),
 
                "next": nextAction,
 
            }))
 

	
 
    def post(self):
 
@@ -74,28 +83,39 @@ class timeResource(PrettyErrorHandler,cy
 
        self.set_header("Content-Type", "text/plain")
 
        self.write("ok")
 

	
 

	
 
class songs(PrettyErrorHandler, cyclone.web.RequestHandler):
 

	
 
    def get(self):
 
        graph = self.settings.app.graph
 

	
 
        songs = getSongsFromShow(graph, self.settings.app.show)
 

	
 
        self.set_header("Content-Type", "application/json")
 
        self.write(json.dumps({"songs" : [
 
            {"uri" : s,
 
             "path" : graph.value(s, L9['showPath']),
 
             "label" : graph.label(s)} for s in songs]}))
 
        self.write(
 
            json.dumps({
 
                "songs": [{
 
                    "uri": s,
 
                    "path": graph.value(s, L9['showPath']),
 
                    "label": graph.label(s)
 
                } for s in songs]
 
            }))
 

	
 

	
 
class songResource(PrettyErrorHandler, cyclone.web.RequestHandler):
 

	
 
    def post(self):
 
        """post a uri of song to switch to (and start playing)"""
 
        graph = self.settings.app.graph
 

	
 
        self.settings.app.player.setSong(songLocation(graph, URIRef(self.request.body)))
 
        self.settings.app.player.setSong(
 
            songLocation(graph, URIRef(self.request.body)))
 
        self.set_header("Content-Type", "text/plain")
 
        self.write("ok")
 
    
 

	
 

	
 
class seekPlayOrPause(PrettyErrorHandler, cyclone.web.RequestHandler):
 

	
 
    def post(self):
 
        player = self.settings.app.player
 

	
 
@@ -106,12 +126,16 @@ class seekPlayOrPause(PrettyErrorHandler
 
            player.seek(data['t'])
 
            player.resume()
 

	
 

	
 
class output(PrettyErrorHandler, cyclone.web.RequestHandler):
 

	
 
    def post(self):
 
        d = json.loads(self.request.body)
 
        subprocess.check_call(["bin/movesinks", str(d['sink'])])
 

	
 

	
 
class goButton(PrettyErrorHandler, cyclone.web.RequestHandler):
 

	
 
    def post(self):
 
        """
 
        if music is playing, this silently does nothing.
 
@@ -124,10 +148,11 @@ class goButton(PrettyErrorHandler, cyclo
 
            pass
 
        else:
 
            player.resume()
 
            
 

	
 
        self.set_header("Content-Type", "text/plain")
 
        self.write("ok")
 

	
 

	
 
def makeWebApp(app):
 
    return cyclone.web.Application(handlers=[
 
        (r"/", root),
 
@@ -137,5 +162,5 @@ def makeWebApp(app):
 
        (r"/seekPlayOrPause", seekPlayOrPause),
 
        (r"/output", output),
 
        (r"/go", goButton),
 
        ], app=app)
 

	
 
    ],
 
                                   app=app)
light9/chase.py
Show inline comments
 
from __future__ import division
 

	
 
def chase(t, ontime=0.5, offset=0.2, onval=1.0, 
 
          offval=0.0, names=None, combiner=max):
 

	
 
def chase(t,
 
          ontime=0.5,
 
          offset=0.2,
 
          onval=1.0,
 
          offval=0.0,
 
          names=None,
 
          combiner=max):
 
    names = names or []
 
    # maybe this is better:
 
    # period = ontime + ((offset + ontime) * (len(names) - 1))
 
@@ -26,11 +32,16 @@ def chase(t, ontime=0.5, offset=0.2, onv
 
            outputs[name] = value
 
    return outputs
 

	
 

	
 
if __name__ == "__main__":
 
    # a little testing
 
    for x in range(80):
 
        x /= 20.0
 
        output = chase(x, onval='x', offval=' ', ontime=0.1, offset=0.2,
 
        output = chase(x,
 
                       onval='x',
 
                       offval=' ',
 
                       ontime=0.1,
 
                       offset=0.2,
 
                       names=('a', 'b', 'c', 'd'))
 
        output = output.items()
 
        output.sort()
light9/clientsession.py
Show inline comments
 
@@ -6,12 +6,15 @@ from rdflib import URIRef
 
from urllib import quote
 
from light9 import showconfig
 

	
 

	
 
def add_option(parser):
 
    parser.add_option(
 
        '-s', '--session',
 
        '-s',
 
        '--session',
 
        help="name of session used for levels and window position",
 
        default='default')
 

	
 

	
 
def getUri(appName, opts):
 
    return URIRef("%s/sessions/%s/%s" % (showconfig.showUri(), appName,
 
                                         quote(opts.session, safe='')))
 
    return URIRef("%s/sessions/%s/%s" %
 
                  (showconfig.showUri(), appName, quote(opts.session, safe='')))
light9/collector/collector.py
Show inline comments
 
@@ -16,6 +16,7 @@ ClientSessionType = TypeVar('ClientSessi
 

	
 
log = logging.getLogger('collector')
 

	
 

	
 
def outputMap(graph, outputs):
 
    # type: (Graph, List[Output]) -> Dict[Tuple[URIRef, URIRef], Tuple[Output, int]]
 
    """From rdf config graph, compute a map of
 
@@ -46,8 +47,10 @@ def outputMap(graph, outputs):
 
                ret[(dev, outputAttr)] = (output, index)
 
                log.debug('    map %s to %s,%s', outputAttr, output, index)
 
    return ret
 
        
 

	
 

	
 
class Collector(Generic[ClientType, ClientSessionType]):
 

	
 
    def __init__(self, graph, outputs, listeners=None, clientTimeoutSec=10):
 
        # type: (Graph, List[Output], List[Listener], float) -> None
 
        self.graph = graph
 
@@ -60,15 +63,17 @@ class Collector(Generic[ClientType, Clie
 
        self.graph.addHandler(self.rebuildOutputMap)
 

	
 
        # client : (session, time, {(dev,devattr): latestValue})
 
        self.lastRequest = {} # type: Dict[Tuple[ClientType, ClientSessionType], Tuple[float, Dict[Tuple[URIRef, URIRef], float]]]
 
        self.lastRequest = {
 
        }  # type: Dict[Tuple[ClientType, ClientSessionType], Tuple[float, Dict[Tuple[URIRef, URIRef], float]]]
 

	
 
        # (dev, devAttr): value to use instead of 0
 
        self.stickyAttrs = {} # type: Dict[Tuple[URIRef, URIRef], float] 
 
        self.stickyAttrs = {}  # type: Dict[Tuple[URIRef, URIRef], float]
 

	
 
    def rebuildOutputMap(self):
 
        self.outputMap = outputMap(self.graph, self.outputs) # (device, outputattr) : (output, index)
 
        self.deviceType = {} # uri: type that's a subclass of Device
 
        self.remapOut = {} # (device, deviceAttr) : (start, end)
 
        self.outputMap = outputMap(
 
            self.graph, self.outputs)  # (device, outputattr) : (output, index)
 
        self.deviceType = {}  # uri: type that's a subclass of Device
 
        self.remapOut = {}  # (device, deviceAttr) : (start, end)
 
        for dc in self.graph.subjects(RDF.type, L9['DeviceClass']):
 
            for dev in self.graph.subjects(RDF.type, dc):
 
                self.allDevices.add(dev)
 
@@ -93,7 +98,7 @@ class Collector(Generic[ClientType, Clie
 
    # todo: move to settings.py
 
    def resolvedSettingsDict(self, settingsList):
 
        # type: (List[Tuple[URIRef, URIRef, float]]) -> Dict[Tuple[URIRef, URIRef], float]
 
        out = {} # type: Dict[Tuple[URIRef, URIRef], float]
 
        out = {}  # type: Dict[Tuple[URIRef, URIRef], float]
 
        for d, da, v in settingsList:
 
            if (d, da) in out:
 
                out[(d, da)] = resolve(d, da, [out[(d, da)], v])
 
@@ -103,13 +108,15 @@ class Collector(Generic[ClientType, Clie
 

	
 
    def _warnOnLateRequests(self, client, now, sendTime):
 
        requestLag = now - sendTime
 
        if requestLag > .1 and now > self.initTime + 10 and getattr(self, '_lastWarnTime', 0) < now - 3:
 
        if requestLag > .1 and now > self.initTime + 10 and getattr(
 
                self, '_lastWarnTime', 0) < now - 3:
 
            self._lastWarnTime = now
 
            log.warn('collector.setAttrs from %s is running %.1fms after the request was made',
 
                     client, requestLag * 1000)
 
            log.warn(
 
                'collector.setAttrs from %s is running %.1fms after the request was made',
 
                client, requestLag * 1000)
 

	
 
    def _merge(self, lastRequests):
 
        deviceAttrs = {} # device: {deviceAttr: value}       
 
        deviceAttrs = {}  # device: {deviceAttr: value}
 
        for _, lastSettings in lastRequests:
 
            for (device, deviceAttr), value in lastSettings.iteritems():
 
                if (device, deviceAttr) in self.remapOut:
 
@@ -118,7 +125,8 @@ class Collector(Generic[ClientType, Clie
 

	
 
                attrs = deviceAttrs.setdefault(device, {})
 
                if deviceAttr in attrs:
 
                    value = resolve(device, deviceAttr, [attrs[deviceAttr], value])
 
                    value = resolve(device, deviceAttr,
 
                                    [attrs[deviceAttr], value])
 
                attrs[deviceAttr] = value
 
                # list should come from the graph. these are attrs
 
                # that should default to holding the last position,
 
@@ -131,7 +139,7 @@ class Collector(Generic[ClientType, Clie
 
            daDict = deviceAttrs.setdefault(d, {})
 
            if da not in daDict:
 
                daDict[da] = v
 
                    
 

	
 
        return deviceAttrs
 

	
 
    def setAttrs(self, client, clientSession, settings, sendTime):
 
@@ -156,8 +164,8 @@ class Collector(Generic[ClientType, Clie
 
        self.lastRequest[(client, clientSession)] = (now, uniqueSettings)
 

	
 
        deviceAttrs = self._merge(self.lastRequest.itervalues())
 
        
 
        outputAttrs = {} # device: {outputAttr: value}
 

	
 
        outputAttrs = {}  # device: {outputAttr: value}
 
        for d in self.allDevices:
 
            try:
 
                devType = self.deviceType[d]
 
@@ -167,11 +175,12 @@ class Collector(Generic[ClientType, Clie
 
            try:
 
                outputAttrs[d] = toOutputAttrs(devType, deviceAttrs.get(d, {}))
 
                if self.listeners:
 
                    self.listeners.outputAttrsSet(d, outputAttrs[d], self.outputMap)
 
                    self.listeners.outputAttrsSet(d, outputAttrs[d],
 
                                                  self.outputMap)
 
            except Exception as e:
 
                log.error('failing toOutputAttrs on %s: %r', d, e)
 
        
 
        pendingOut = {} # output : values
 

	
 
        pendingOut = {}  # output : values
 
        for out in self.outputs:
 
            pendingOut[out] = [0] * out.numChannels
 

	
 
@@ -183,9 +192,10 @@ class Collector(Generic[ClientType, Clie
 
        self.flush(pendingOut)
 
        dt2 = 1000 * (time.time() - now)
 
        if dt1 > 30:
 
            log.warn("slow setAttrs: %.1fms -> flush -> %.1fms. lr %s da %s oa %s" % (
 
                dt1, dt2, len(self.lastRequest), len(deviceAttrs), len(outputAttrs)
 
            ))
 
            log.warn(
 
                "slow setAttrs: %.1fms -> flush -> %.1fms. lr %s da %s oa %s" %
 
                (dt1, dt2, len(
 
                    self.lastRequest), len(deviceAttrs), len(outputAttrs)))
 

	
 
    def setAttr(self, device, outputAttr, value, pendingOut):
 
        output, index = self.outputMap[(device, outputAttr)]
light9/collector/collector_client.py
Show inline comments
 
@@ -3,38 +3,43 @@ from light9 import networking
 
from light9.effect.settings import DeviceSettings
 
from twisted.internet import defer
 
from txzmq import ZmqEndpoint, ZmqFactory, ZmqPushConnection
 
import json, time,logging
 
import json, time, logging
 
import treq
 

	
 

	
 
log = logging.getLogger('coll_client')
 

	
 
_zmqClient=None
 
_zmqClient = None
 

	
 

	
 
class TwistedZmqClient(object):
 

	
 
    def __init__(self, service):
 
        zf = ZmqFactory()
 
        e = ZmqEndpoint('connect', 'tcp://%s:%s' % (service.host, service.port))
 
        self.conn = ZmqPushConnection(zf, e)
 
        
 

	
 
    def send(self, msg):
 
        self.conn.push(msg)
 

	
 

	
 
def toCollectorJson(client, session, settings):
 
    assert isinstance(settings, DeviceSettings)
 
    return json.dumps({'settings': settings.asList(),
 
                       'client': client,
 
                       'clientSession': session,
 
                       'sendTime': time.time(),
 
                  })
 
        
 
    return json.dumps({
 
        'settings': settings.asList(),
 
        'client': client,
 
        'clientSession': session,
 
        'sendTime': time.time(),
 
    })
 

	
 

	
 
def sendToCollectorZmq(msg):
 
    global _zmqClient
 
    if _zmqClient is None:
 
        _zmqClient = TwistedZmqClient(networking.collectorZmq)
 
    _zmqClient.send(msg)
 
    return defer.succeed(0)
 
        
 

	
 

	
 
def sendToCollector(client, session, settings, useZmq=False):
 
    """deferred to the time in seconds it took to get a response from collector"""
 
    sendTime = time.time()
 
@@ -44,14 +49,17 @@ def sendToCollector(client, session, set
 
        d = sendToCollectorZmq(msg)
 
    else:
 
        d = treq.put(networking.collector.path('attrs'), data=msg, timeout=1)
 
    
 

	
 
    def onDone(result):
 
        dt = time.time() - sendTime
 
        if dt > .1:
 
            log.warn('sendToCollector request took %.1fms', dt * 1000)
 
        return dt
 

	
 
    d.addCallback(onDone)
 

	
 
    def onErr(err):
 
        log.warn('sendToCollector failed: %r', err)
 

	
 
    d.addErrback(onErr)
 
    return d
light9/collector/collector_test.py
Show inline comments
 
@@ -35,9 +35,11 @@ THEATER = '''
 

	
 
'''
 

	
 
t0 = 0 # time
 
t0 = 0  # time
 

	
 

	
 
class MockOutput(object):
 

	
 
    def __init__(self, uri, connections):
 
        self.connections = connections
 
        self.updates = []
 
@@ -53,31 +55,41 @@ class MockOutput(object):
 
    def flush(self):
 
        self.updates.append('flush')
 

	
 
@unittest.skip("outputMap got rewritten and mostly doesn't raise on these cases")
 

	
 
@unittest.skip("outputMap got rewritten and mostly doesn't raise on these cases"
 
              )
 
class TestOutputMap(unittest.TestCase):
 

	
 
    def testWorking(self):
 
        out0 = MockOutput(UDMX, [(0, DMX0['c1'])])
 
        m = outputMap(MockSyncedGraph(PREFIX + '''
 
        m = outputMap(
 
            MockSyncedGraph(PREFIX + '''
 
          dmx0:c1 :connectedTo dev:inst1Brightness .
 
          dev:inst1 a :Device; :brightness dev:inst1Brightness .
 
        '''), [out0])
 
        self.assertEqual({(DEV['inst1'], L9['brightness']): (out0, 0)}, m)
 
        
 

	
 
    def testMissingOutput(self):
 
        out0 = MockOutput(UDMX, [(0, DMX0['c1'])])
 
        self.assertRaises(KeyError, outputMap, MockSyncedGraph(PREFIX + '''
 
        self.assertRaises(
 
            KeyError, outputMap,
 
            MockSyncedGraph(PREFIX + '''
 
          dev:inst1 a :Device; :brightness dev:inst1Brightness .
 
        '''), [out0])
 

	
 
    def testMissingOutputConnection(self):
 
        out0 = MockOutput(UDMX, [(0, DMX0['c1'])])
 
        self.assertRaises(ValueError, outputMap, MockSyncedGraph(PREFIX + '''
 
        self.assertRaises(
 
            ValueError, outputMap,
 
            MockSyncedGraph(PREFIX + '''
 
          dev:inst1 a :Device; :brightness dev:inst1Brightness .
 
        '''), [out0])
 

	
 
    def testMultipleOutputConnections(self):
 
        out0 = MockOutput(UDMX, [(0, DMX0['c1'])])
 
        self.assertRaises(ValueError, outputMap, MockSyncedGraph(PREFIX + '''
 
        self.assertRaises(
 
            ValueError, outputMap,
 
            MockSyncedGraph(PREFIX + '''
 
          dmx0:c1 :connectedTo dev:inst1Brightness .
 
          dmx0:c2 :connectedTo dev:inst1Brightness .
 
          dev:inst1 a :Device; :brightness dev:inst1Brightness .
 
@@ -85,6 +97,7 @@ class TestOutputMap(unittest.TestCase):
 

	
 

	
 
class TestCollector(unittest.TestCase):
 

	
 
    def setUp(self):
 
        self.config = MockSyncedGraph(PREFIX + THEATER + '''
 

	
 
@@ -101,10 +114,8 @@ class TestCollector(unittest.TestCase):
 
        ''')
 

	
 
        self.dmx0 = MockOutput(DMX0[None], [(0, DMX0['c1'])])
 
        self.udmx = MockOutput(UDMX[None], [(0, UDMX['c1']),
 
                                      (1, UDMX['c2']),
 
                                      (2, UDMX['c3']),
 
                                      (3, UDMX['c4'])])
 
        self.udmx = MockOutput(UDMX[None], [(0, UDMX['c1']), (1, UDMX['c2']),
 
                                            (2, UDMX['c3']), (3, UDMX['c4'])])
 

	
 
    def testRoutesColorOutput(self):
 
        c = Collector(self.config, outputs=[self.dmx0, self.udmx])
 
@@ -123,9 +134,9 @@ class TestCollector(unittest.TestCase):
 
        c.setAttrs('client2', 'sess1',
 
                   [(DEV['colorStrip'], L9['color'], '#333333')], t0)
 

	
 
        self.assertEqual([[215, 255, 0, 0], 'flush',
 
                          [215, 255, 51, 51], 'flush'],
 
                         self.udmx.updates)
 
        self.assertEqual(
 
            [[215, 255, 0, 0], 'flush', [215, 255, 51, 51], 'flush'],
 
            self.udmx.updates)
 
        self.assertEqual([[0, 0, 0, 0], 'flush', [0, 0, 0, 0], 'flush'],
 
                         self.dmx0.updates)
 

	
 
@@ -139,36 +150,36 @@ class TestCollector(unittest.TestCase):
 
        c.setAttrs('client1', 'sess1',
 
                   [(DEV['colorStrip'], L9['color'], '#050000')], t0)
 

	
 
        self.assertEqual([[215, 8, 0, 0], 'flush',
 
                          [215, 8, 0, 0], 'flush',
 
                          [215, 6, 0, 0], 'flush'],
 
                         self.udmx.updates)
 
        self.assertEqual([[0, 0, 0, 0], 'flush',
 
                          [0, 0, 0, 0], 'flush',
 
                          [0, 0, 0, 0], 'flush'],
 
                         self.dmx0.updates)
 
        self.assertEqual([[215, 8, 0, 0], 'flush', [215, 8, 0, 0], 'flush',
 
                          [215, 6, 0, 0], 'flush'], self.udmx.updates)
 
        self.assertEqual([[0, 0, 0, 0], 'flush', [0, 0, 0, 0], 'flush',
 
                          [0, 0, 0, 0], 'flush'], self.dmx0.updates)
 

	
 
    def testClientsOnDifferentOutputs(self):
 
        c = Collector(self.config, outputs=[self.dmx0, self.udmx])
 

	
 
        c.setAttrs('client1', 'sess1', [(DEV['colorStrip'], L9['color'], '#aa0000')], t0)
 
        c.setAttrs('client2', 'sess1', [(DEV['inst1'], L9['brightness'], .5)], t0)
 
        c.setAttrs('client1', 'sess1',
 
                   [(DEV['colorStrip'], L9['color'], '#aa0000')], t0)
 
        c.setAttrs('client2', 'sess1', [(DEV['inst1'], L9['brightness'], .5)],
 
                   t0)
 

	
 
        # ok that udmx is flushed twice- it can screen out its own duplicates
 
        self.assertEqual([[215, 170, 0, 0], 'flush',
 
                          [215, 170, 0, 0], 'flush'], self.udmx.updates)
 
        self.assertEqual([[0, 0, 0, 0], 'flush',
 
                          [127, 0, 0, 0], 'flush'], self.dmx0.updates)
 
        self.assertEqual([[215, 170, 0, 0], 'flush', [215, 170, 0, 0], 'flush'],
 
                         self.udmx.updates)
 
        self.assertEqual([[0, 0, 0, 0], 'flush', [127, 0, 0, 0], 'flush'],
 
                         self.dmx0.updates)
 

	
 
    def testNewSessionReplacesPreviousOutput(self):
 
        # ..as opposed to getting max'd with it
 
        c = Collector(self.config, outputs=[self.dmx0, self.udmx])
 

	
 
        c.setAttrs('client1', 'sess1', [(DEV['inst1'], L9['brightness'], .8)], t0)
 
        c.setAttrs('client1', 'sess2', [(DEV['inst1'], L9['brightness'], .5)], t0)
 
        c.setAttrs('client1', 'sess1', [(DEV['inst1'], L9['brightness'], .8)],
 
                   t0)
 
        c.setAttrs('client1', 'sess2', [(DEV['inst1'], L9['brightness'], .5)],
 
                   t0)
 

	
 
        self.assertEqual([[204, 0, 0, 0], 'flush',
 
                          [127, 0, 0, 0], 'flush'], self.dmx0.updates)
 
        self.assertEqual([[204, 0, 0, 0], 'flush', [127, 0, 0, 0], 'flush'],
 
                         self.dmx0.updates)
 

	
 
    def testNewSessionDropsPreviousSettingsOfOtherAttrs(self):
 
        c = Collector(MockSyncedGraph(PREFIX + THEATER + '''
 
@@ -183,15 +194,16 @@ class TestCollector(unittest.TestCase):
 
        dev:inst1 a :Device, :SimpleDimmer;
 
          :dmxUniverse dmx0:; :dmxBase 0;
 
          :level dev:inst1Brightness .
 
        '''), outputs=[self.dmx0, self.udmx])
 
        '''),
 
                      outputs=[self.dmx0, self.udmx])
 

	
 
        c.setAttrs('client1', 'sess1',
 
                   [(DEV['colorStrip'], L9['color'], '#ff0000')], t0)
 
        c.setAttrs('client1', 'sess2',
 
                   [(DEV['colorStrip'], L9['color'], '#00ff00')], t0)
 

	
 
        self.assertEqual([[215, 255, 0, 0], 'flush',
 
                          [215, 0, 255, 0], 'flush'], self.udmx.updates)
 
        self.assertEqual([[215, 255, 0, 0], 'flush', [215, 0, 255, 0], 'flush'],
 
                         self.udmx.updates)
 

	
 
    def testClientIsForgottenAfterAWhile(self):
 
        with freeze_time(datetime.datetime.now()) as ft:
 
@@ -200,37 +212,34 @@ class TestCollector(unittest.TestCase):
 
                       time.time())
 
            ft.tick(delta=datetime.timedelta(seconds=1))
 
            # this max's with cli1's value so we still see .5
 
            c.setAttrs('cli2', 'sess1', [(DEV['inst1'], L9['brightness'], .2)], 
 
            c.setAttrs('cli2', 'sess1', [(DEV['inst1'], L9['brightness'], .2)],
 
                       time.time())
 
            ft.tick(delta=datetime.timedelta(seconds=9.1))
 
            # now cli1 is forgotten, so our value appears
 
            c.setAttrs('cli2', 'sess1', [(DEV['inst1'], L9['brightness'], .4)], 
 
            c.setAttrs('cli2', 'sess1', [(DEV['inst1'], L9['brightness'], .4)],
 
                       time.time())
 
            self.assertEqual([[127, 0, 0, 0], 'flush',
 
                              [127, 0, 0, 0], 'flush',
 
                              [102, 0, 0, 0], 'flush'],
 
                             self.dmx0.updates)
 
            self.assertEqual([[127, 0, 0, 0], 'flush', [127, 0, 0, 0], 'flush',
 
                              [102, 0, 0, 0], 'flush'], self.dmx0.updates)
 

	
 
    def testClientUpdatesAreNotMerged(self):
 
        # second call to setAttrs forgets the first
 
        c = Collector(self.config, outputs=[self.dmx0, self.udmx])
 
        t0 = time.time()
 
        c.setAttrs('client1', 'sess1', [(DEV['inst1'], L9['brightness'], .5)], t0)
 
        c.setAttrs('client1', 'sess1', [(DEV['inst1'], L9['brightness'], 1)], t0)
 
        c.setAttrs('client1', 'sess1', [(DEV['colorStrip'], L9['color'], '#00ff00')], t0)
 
        c.setAttrs('client1', 'sess1', [(DEV['inst1'], L9['brightness'], .5)],
 
                   t0)
 
        c.setAttrs('client1', 'sess1', [(DEV['inst1'], L9['brightness'], 1)],
 
                   t0)
 
        c.setAttrs('client1', 'sess1',
 
                   [(DEV['colorStrip'], L9['color'], '#00ff00')], t0)
 

	
 
        self.assertEqual([[215, 0, 0, 0], 'flush',
 
                          [215, 0, 0, 0], 'flush',
 
                          [215, 0, 255, 0], 'flush'],
 
                         self.udmx.updates)
 
        self.assertEqual([[127, 0, 0, 0], 'flush',
 
                          [255, 0, 0, 0], 'flush',
 
                          [0, 0, 0, 0], 'flush'],
 
                         self.dmx0.updates)
 
        self.assertEqual([[215, 0, 0, 0], 'flush', [215, 0, 0, 0], 'flush',
 
                          [215, 0, 255, 0], 'flush'], self.udmx.updates)
 
        self.assertEqual([[127, 0, 0, 0], 'flush', [255, 0, 0, 0], 'flush',
 
                          [0, 0, 0, 0], 'flush'], self.dmx0.updates)
 

	
 
    def testRepeatedAttributesInOneRequestGetResolved(self):
 
        c = Collector(self.config, outputs=[self.dmx0, self.udmx])
 
        
 

	
 
        c.setAttrs('client1', 'sess1', [
 
            (DEV['inst1'], L9['brightness'], .5),
 
            (DEV['inst1'], L9['brightness'], .3),
 
@@ -241,6 +250,5 @@ class TestCollector(unittest.TestCase):
 
            (DEV['inst1'], L9['brightness'], .3),
 
            (DEV['inst1'], L9['brightness'], .5),
 
        ], t0)
 
        self.assertEqual([[127, 0, 0, 0], 'flush',
 
                          [127, 0, 0, 0], 'flush'], self.dmx0.updates)
 

	
 
        self.assertEqual([[127, 0, 0, 0], 'flush', [127, 0, 0, 0], 'flush'],
 
                         self.dmx0.updates)
light9/collector/device.py
Show inline comments
 
@@ -19,7 +19,8 @@ class ChauvetColorStrip(Device):
 
     device attrs:
 
       color
 
    """
 
        
 

	
 

	
 
class Mini15(Device):
 
    """
 
    plan:
 
@@ -31,14 +32,18 @@ class Mini15(Device):
 
        goboShake
 
        imageAim (configured with a file of calibration data)
 
    """
 

	
 

	
 
def clamp255(x):
 
    return min(255, max(0, x))
 
    
 

	
 

	
 
def _8bit(f):
 
    if not isinstance(f, (int, float)):
 
        raise TypeError(repr(f))
 
    return clamp255(int(f * 255))
 

	
 

	
 
def resolve(deviceType, deviceAttr, values):
 
    """
 
    return one value to use for this attr, given a set of them that
 
@@ -66,6 +71,7 @@ def resolve(deviceType, deviceAttr, valu
 
        return Literal(sum(floatVals) / len(floatVals))
 
    return max(values)
 

	
 

	
 
def toOutputAttrs(deviceType, deviceAttrSettings):
 
    """
 
    Given device attr settings like {L9['color']: Literal('#ff0000')},
 
@@ -74,6 +80,7 @@ def toOutputAttrs(deviceType, deviceAttr
 

	
 
    :outputAttrRange happens before we get here.
 
    """
 

	
 
    def floatAttr(attr, default=0):
 
        out = deviceAttrSettings.get(attr)
 
        if out is None:
 
@@ -86,12 +93,10 @@ def toOutputAttrs(deviceType, deviceAttr
 
        return r, g, b
 

	
 
    def cmyAttr(attr):
 
        rgb = sRGBColor.new_from_rgb_hex(deviceAttrSettings.get(attr, '#000000'))
 
        rgb = sRGBColor.new_from_rgb_hex(deviceAttrSettings.get(
 
            attr, '#000000'))
 
        out = colormath.color_conversions.convert_color(rgb, CMYColor)
 
        return (
 
            _8bit(out.cmy_c),
 
            _8bit(out.cmy_m),
 
            _8bit(out.cmy_y))
 
        return (_8bit(out.cmy_c), _8bit(out.cmy_m), _8bit(out.cmy_y))
 

	
 
    def fine16Attr(attr, scale=1.0):
 
        x = floatAttr(attr) * scale
 
@@ -106,20 +111,15 @@ def toOutputAttrs(deviceType, deviceAttr
 
        if deviceAttrSettings.get(attr) == L9['g2']:
 
            return 10
 
        return 0
 
        
 

	
 
    if deviceType == L9['ChauvetColorStrip']:
 
        r, g, b = rgbAttr(L9['color'])
 
        return {
 
            L9['mode']: 215,
 
            L9['red']: r,
 
            L9['green']: g,
 
            L9['blue']: b
 
            }
 
        return {L9['mode']: 215, L9['red']: r, L9['green']: g, L9['blue']: b}
 
    elif deviceType == L9['SimpleDimmer']:
 
        return {L9['level']: _8bit(floatAttr(L9['brightness']))}
 
    elif deviceType == L9['Mini15']:
 
        out = {
 
            L9['rotationSpeed']: 0, # seems to have no effect
 
            L9['rotationSpeed']: 0,  # seems to have no effect
 
            L9['dimmer']: 255,
 
            L9['colorChange']: 0,
 
            L9['colorSpeed']: 0,
 
@@ -131,17 +131,18 @@ def toOutputAttrs(deviceType, deviceAttr
 
            L9['mini15Gobo1']: 10,
 
            L9['mini15Gobo2']: 20,
 
            L9['mini15Gobo3']: 30,
 
            }[deviceAttrSettings.get(L9['mini15GoboChoice'], L9['open'])]
 
        
 
        }[deviceAttrSettings.get(L9['mini15GoboChoice'], L9['open'])]
 

	
 
        out[L9['red']], out[L9['green']], out[L9['blue']] = rgbAttr(L9['color'])
 
        out[L9['xRotation']], out[L9['xFine']] = fine16Attr(L9['rx'], 1/540)
 
        out[L9['yRotation']], out[L9['yFine']] = fine16Attr(L9['ry'], 1/240)
 
        out[L9['xRotation']], out[L9['xFine']] = fine16Attr(L9['rx'], 1 / 540)
 
        out[L9['yRotation']], out[L9['yFine']] = fine16Attr(L9['ry'], 1 / 240)
 
        # didn't find docs on this, but from tests it looks like 64 fine steps takes you to the next coarse step
 

	
 
        return out
 
    elif deviceType == L9['ChauvetHex12']:
 
        out = {}
 
        out[L9['red']], out[L9['green']], out[L9['blue']] = r, g, b = rgbAttr(L9['color'])
 
        out[L9['red']], out[L9['green']], out[L9['blue']] = r, g, b = rgbAttr(
 
            L9['color'])
 
        out[L9['amber']] = 0
 
        out[L9['white']] = min(r, g, b)
 
        out[L9['uv']] = _8bit(floatAttr(L9['uv']))
 
@@ -153,7 +154,7 @@ def toOutputAttrs(deviceType, deviceAttr
 
        out[L9['fixed255']] = 255
 
        for num in range(7):
 
            out[L9['fixed128_%s' % num]] = 128
 
        return out        
 
        return out
 
    elif deviceType == L9['MacAura']:
 
        out = {
 
            L9['shutter']: 22,
 
@@ -184,21 +185,23 @@ def toOutputAttrs(deviceType, deviceAttr
 
        out = {
 
            L9['dimmerFadeLo']: 0,
 
            L9['fixtureControl']: 0,
 
            L9['fx1Select']:  0,
 
            L9['fx1Adjust']:  0,
 
            L9['fx2Select']:  0,
 
            L9['fx2Adjust']:  0,
 
            L9['fxSync']:  0,            
 
            }
 
            L9['fx1Select']: 0,
 
            L9['fx1Adjust']: 0,
 
            L9['fx2Select']: 0,
 
            L9['fx2Adjust']: 0,
 
            L9['fxSync']: 0,
 
        }
 

	
 
        # note these values are set to 'fade', so they update slowly. Haven't found where to turn that off.
 
        out[L9['cyan']], out[L9['magenta']], out[L9['yellow']] = cmyAttr(L9['color'])
 
        
 
        out[L9['cyan']], out[L9['magenta']], out[L9['yellow']] = cmyAttr(
 
            L9['color'])
 

	
 
        out[L9['focusHi']], out[L9['focusLo']] = fine16Attr(L9['focus'])
 
        out[L9['panHi']], out[L9['panLo']] = fine16Attr(L9['rx'])
 
        out[L9['tiltHi']], out[L9['tiltLo']] = fine16Attr(L9['ry'])
 
        out[L9['zoomHi']], out[L9['zoomLo']] = fine16Attr(L9['zoom'])
 
        out[L9['dimmerFadeHi']] = 0 if deviceAttrSettings.get(L9['color'], '#000000') == '#000000' else 255
 
        out[L9['dimmerFadeHi']] = 0 if deviceAttrSettings.get(
 
            L9['color'], '#000000') == '#000000' else 255
 

	
 
        out[L9['goboChoice']] = {
 
            L9['open']: 0,
 
@@ -208,7 +211,7 @@ def toOutputAttrs(deviceType, deviceAttr
 
            L9['brush']: 51,
 
            L9['whirlpool']: 56,
 
            L9['stars']: 61,
 
            }[deviceAttrSettings.get(L9['quantumGoboChoice'], L9['open'])]
 
        }[deviceAttrSettings.get(L9['quantumGoboChoice'], L9['open'])]
 

	
 
        # my goboSpeed deviceAttr goes 0=stopped to 1=fastest (using one direction only)
 
        x = .5 + .5 * floatAttr(L9['goboSpeed'])
 
@@ -220,13 +223,13 @@ def toOutputAttrs(deviceType, deviceAttr
 
            out[L9['shutter']] = 30
 
        else:
 
            out[L9['shutter']] = 50 + int(150 * (strobe - .1) / .9)
 
        
 
        out.update( {
 

	
 
        out.update({
 
            L9['colorWheel']: 0,
 
            L9['goboStaticRotate']: 0,
 
            L9['prismRotation']: _8bit(floatAttr(L9['prism'])),
 
            L9['iris']: _8bit(floatAttr(L9['iris']) * (200/255)),
 
            })
 
            L9['iris']: _8bit(floatAttr(L9['iris']) * (200 / 255)),
 
        })
 
        return out
 
    else:
 
        raise NotImplementedError('device %r' % deviceType)
light9/collector/device_test.py
Show inline comments
 
@@ -4,50 +4,67 @@ from light9.namespaces import L9
 

	
 
from light9.collector.device import toOutputAttrs, resolve
 

	
 

	
 
class TestUnknownDevice(unittest.TestCase):
 

	
 
    def testFails(self):
 
        self.assertRaises(NotImplementedError, toOutputAttrs, L9['bogus'], {})
 

	
 

	
 
class TestColorStrip(unittest.TestCase):
 

	
 
    def testConvertDeviceToOutputAttrs(self):
 
        out = toOutputAttrs(L9['ChauvetColorStrip'],
 
                            {L9['color']: Literal('#ff0000')})
 
        self.assertEqual({L9['mode']: 215,
 
                          L9['red']: 255,
 
                          L9['green']: 0,
 
                          L9['blue']: 0
 
                      }, out)
 
        
 
        self.assertEqual(
 
            {
 
                L9['mode']: 215,
 
                L9['red']: 255,
 
                L9['green']: 0,
 
                L9['blue']: 0
 
            }, out)
 

	
 

	
 
class TestDimmer(unittest.TestCase):
 

	
 
    def testConvert(self):
 
        self.assertEqual({L9['level']: 127},
 
                         toOutputAttrs(L9['SimpleDimmer'], {L9['brightness']: .5}))
 
                         toOutputAttrs(L9['SimpleDimmer'],
 
                                       {L9['brightness']: .5}))
 

	
 

	
 
class TestMini15(unittest.TestCase):
 

	
 
    def testConvertColor(self):
 
        out = toOutputAttrs(L9['Mini15'], {L9['color']: '#010203'})
 
        self.assertEqual(255, out[L9['dimmer']])
 
        self.assertEqual(1, out[L9['red']])
 
        self.assertEqual(2, out[L9['green']])
 
        self.assertEqual(3, out[L9['blue']])
 

	
 
    def testConvertRotation(self):
 
        out = toOutputAttrs(L9['Mini15'], {L9['rx']: Literal(90), L9['ry']: Literal(45)})
 
        out = toOutputAttrs(L9['Mini15'], {
 
            L9['rx']: Literal(90),
 
            L9['ry']: Literal(45)
 
        })
 
        self.assertEqual(42, out[L9['xRotation']])
 
        self.assertEqual(127, out[L9['xFine']])
 
        self.assertEqual(47, out[L9['yRotation']])
 
        self.assertEqual(207, out[L9['yFine']])
 
        self.assertEqual(0, out[L9['rotationSpeed']])
 
        
 

	
 

	
 
class TestResolve(unittest.TestCase):
 

	
 
    def testMaxes1Color(self):
 
        # do not delete - this one catches a bug in the rgb_to_hex(...) lines
 
        self.assertEqual('#ff0300',
 
                         resolve(None, L9['color'], ['#ff0300']))
 
        self.assertEqual('#ff0300', resolve(None, L9['color'], ['#ff0300']))
 

	
 
    def testMaxes2Colors(self):
 
        self.assertEqual('#ff0400',
 
                         resolve(None, L9['color'], ['#ff0300', '#000400']))
 

	
 
    def testMaxes3Colors(self):
 
        self.assertEqual('#112233',
 
                         resolve(None, L9['color'],
 
                                 ['#110000', '#002200', '#000033']))
 
        
 
        self.assertEqual(
 
            '#112233',
 
            resolve(None, L9['color'], ['#110000', '#002200', '#000033']))
light9/collector/output.py
Show inline comments
 
@@ -8,6 +8,7 @@ from twisted.internet import task, threa
 
from greplin import scales
 
log = logging.getLogger('output')
 

	
 

	
 
# eliminate this: lists are always padded now
 
def setListElem(outList, index, value, fill=0, combine=lambda old, new: new):
 
    if len(outList) < index:
 
@@ -17,6 +18,7 @@ def setListElem(outList, index, value, f
 
    else:
 
        outList[index] = combine(outList[index], value)
 

	
 

	
 
class Output(object):
 
    """
 
    send an array of values to some output device. Call update as
 
@@ -25,9 +27,10 @@ class Output(object):
 
    """
 
    uri = None  # type: URIRef
 
    numChannels = None  # type: int
 

	
 
    def __init__(self):
 
        raise NotImplementedError
 
        
 

	
 
    def allConnections(self):
 
        """
 
        sequence of (index, uri) for the uris we can output, and which
 
@@ -35,7 +38,6 @@ class Output(object):
 
        """
 
        raise NotImplementedError
 

	
 
    
 
    def update(self, values):
 
        """
 
        output takes a flattened list of values, maybe dmx channels, or
 
@@ -53,11 +55,13 @@ class Output(object):
 
        """short string to distinguish outputs"""
 
        raise NotImplementedError
 

	
 

	
 
class DummyOutput(Output):
 

	
 
    def __init__(self, uri, numChannels=1, **kw):
 
        self.uri = uri
 
        self.numChannels = numChannels
 
    
 

	
 
    def update(self, values):
 
        pass
 

	
 
@@ -67,8 +71,9 @@ class DummyOutput(Output):
 
    def shortId(self):
 
        return 'null'
 

	
 
        
 

	
 
class DmxOutput(Output):
 

	
 
    def __init__(self, uri, numChannels):
 
        self.uri = uri
 
        self.numChannels = numChannels
 
@@ -85,16 +90,14 @@ class DmxOutput(Output):
 
                self.countError()
 
            else:
 
                self.lastSentBuffer = sendingBuffer
 
            reactor.callLater(max(0, start + 1/20 - time.time()),
 
                              self._loop)
 
            reactor.callLater(max(0, start + 1 / 20 - time.time()), self._loop)
 

	
 
        d = threads.deferToThread(self.sendDmx, sendingBuffer)
 
        d.addCallback(done)
 
        
 

	
 

	
 
class EnttecDmx(DmxOutput):
 
    stats = scales.collection('/output/enttecDmx',
 
                              scales.PmfStat('write'),
 
    stats = scales.collection('/output/enttecDmx', scales.PmfStat('write'),
 
                              scales.PmfStat('update'))
 

	
 
    def __init__(self, uri, devicePath='/dev/dmx0', numChannels=80):
 
@@ -128,16 +131,16 @@ class EnttecDmx(DmxOutput):
 
    def shortId(self):
 
        return 'enttec'
 

	
 
                                  
 

	
 
class Udmx(DmxOutput):
 
    stats = scales.collection('/output/udmx',
 
                              scales.PmfStat('update'),
 
    stats = scales.collection('/output/udmx', scales.PmfStat('update'),
 
                              scales.PmfStat('write'),
 
                              scales.IntStat('usbErrors'))
 

	
 
    def __init__(self, uri, bus, numChannels):
 
        DmxOutput.__init__(self, uri, numChannels)
 
        self._shortId = self.uri.rstrip('/')[-1]
 
        
 

	
 
        from light9.io.udmx import Udmx
 
        self.dev = Udmx(bus)
 
        self.currentBuffer = ''
 
@@ -173,14 +176,14 @@ class Udmx(DmxOutput):
 
            except usb.core.USBError as e:
 
                # not in main thread
 
                if e.errno != 75:
 
                  msg = 'usb: sending %s bytes to %r; error %r' % (len(buf), self.uri, e)
 
                  print msg
 
                    msg = 'usb: sending %s bytes to %r; error %r' % (
 
                        len(buf), self.uri, e)
 
                    print msg
 
                return False
 

	
 
    def countError(self):
 
        # in main thread
 
        Udmx.stats.usbErrors += 1
 
        
 

	
 
    def shortId(self):
 
        return self._shortId
 

	
light9/collector/output_test.py
Show inline comments
 
@@ -2,38 +2,47 @@ import unittest
 
from light9.namespaces import L9
 
from light9.collector.output import setListElem, DmxOutput
 

	
 

	
 
class TestSetListElem(unittest.TestCase):
 

	
 
    def testSetExisting(self):
 
        x = [0, 1]
 
        setListElem(x, 0, 9)
 
        self.assertEqual([9, 1], x)
 

	
 
    def testSetNext(self):
 
        x = [0, 1]
 
        setListElem(x, 2, 9)
 
        self.assertEqual([0, 1, 9], x)
 

	
 
    def testSetBeyond(self):
 
        x = [0, 1]
 
        setListElem(x, 3, 9)
 
        self.assertEqual([0, 1, 0, 9], x)
 

	
 
    def testArbitraryFill(self):
 
        x = [0, 1]
 
        setListElem(x, 5, 9, fill=8)
 
        self.assertEqual([0, 1, 8, 8, 8, 9], x)
 

	
 
    def testSetZero(self):
 
        x = [0, 1]
 
        setListElem(x, 5, 0)
 
        self.assertEqual([0, 1, 0, 0, 0, 0], x)
 

	
 
    def testCombineMax(self):
 
        x = [0, 1]
 
        setListElem(x, 1, 0, combine=max)
 
        self.assertEqual([0, 1], x)
 

	
 
    def testCombineHasNoEffectOnNewElems(self):
 
        x = [0, 1]
 
        setListElem(x, 2, 1, combine=max)
 
        self.assertEqual([0, 1, 1], x)
 
        
 

	
 

	
 
class TestDmxOutput(unittest.TestCase):
 

	
 
    def testFlushIsNoop(self):
 
        out = DmxOutput(L9['output/udmx/'], 3)
 
        out.flush()
 
        
light9/curvecalc/client.py
Show inline comments
 
@@ -6,16 +6,19 @@ from light9 import networking
 
import urllib
 
from run_local import log
 

	
 

	
 
def sendLiveInputPoint(curve, value):
 
    f = cyclone.httpclient.fetch(
 
        networking.curveCalc.path('liveInputPoint'),
 
        method='POST', timeout=1,
 
        postdata=urllib.urlencode({
 
            'curve': curve,
 
            'value': str(value),
 
        }))
 
    f = cyclone.httpclient.fetch(networking.curveCalc.path('liveInputPoint'),
 
                                 method='POST',
 
                                 timeout=1,
 
                                 postdata=urllib.urlencode({
 
                                     'curve': curve,
 
                                     'value': str(value),
 
                                 }))
 

	
 
    @f.addCallback
 
    def cb(result):
 
        if result.code // 100 != 2:
 
            raise ValueError("curveCalc said %s: %s", result.code, result.body)
 

	
 
    return f
light9/curvecalc/cursors.py
Show inline comments
 

	
 
import logging
 
log = logging.getLogger("cursors")
 

	
 
# accept ascii images, read file images, add hotspots, read xbm as
 
# cursor with @filename form
 

	
 
_pushed = {} # widget : [old, .., newest]
 
def push(widget,new_cursor):
 
_pushed = {}  # widget : [old, .., newest]
 

	
 

	
 
def push(widget, new_cursor):
 
    global _pushed
 
    _pushed.setdefault(widget,[]).append(widget.cget("cursor"))
 
    _pushed.setdefault(widget, []).append(widget.cget("cursor"))
 

	
 

	
 
def pop(widget):
 
    global _pushed
 
@@ -18,5 +20,3 @@ def pop(widget):
 
        log.debug("cursor pop from empty stack")
 
        return
 
    widget.config(cursor=c)
 
    
 
    
light9/curvecalc/curve.py
Show inline comments
 
from __future__ import division
 
import glob, time, logging, ast, os
 
from bisect import bisect_left,bisect
 
from bisect import bisect_left, bisect
 
import louie as dispatcher
 
from twisted.internet import reactor
 
from rdflib import Literal
 
@@ -13,12 +13,14 @@ log = logging.getLogger()
 
introPad = 4
 
postPad = 4
 

	
 

	
 
class Curve(object):
 
    """curve does not know its name. see Curveset"""
 

	
 
    def __init__(self, uri, pointsStorage='graph'):
 
        self.uri = uri
 
        self.pointsStorage = pointsStorage
 
        self.points = [] # x-sorted list of (x,y)
 
        self.points = []  # x-sorted list of (x,y)
 
        self._muted = False
 

	
 
    def __repr__(self):
 
@@ -27,24 +29,28 @@ class Curve(object):
 

	
 
    def muted():
 
        doc = "Whether to currently send levels (boolean, obviously)"
 

	
 
        def fget(self):
 
            return self._muted
 

	
 
        def fset(self, val):
 
            self._muted = val
 
            dispatcher.send('mute changed', sender=self)
 

	
 
        return locals()
 

	
 
    muted = property(**muted())
 

	
 
    def toggleMute(self):
 
        self.muted = not self.muted
 

	
 
    def load(self,filename):
 
        self.points[:]=[]
 
    def load(self, filename):
 
        self.points[:] = []
 
        for line in file(filename):
 
            x, y = line.split()
 
            self.points.append((float(x), ast.literal_eval(y)))
 
        self.points.sort()
 
        dispatcher.send("points changed",sender=self)
 
        dispatcher.send("points changed", sender=self)
 

	
 
    def set_from_string(self, pts):
 
        self.points[:] = []
 
@@ -53,22 +59,24 @@ class Curve(object):
 
        for x, y in pairs:
 
            self.points.append((float(x), ast.literal_eval(y)))
 
        self.points.sort()
 
        dispatcher.send("points changed",sender=self)
 
        dispatcher.send("points changed", sender=self)
 

	
 
    def points_as_string(self):
 

	
 
        def outVal(x):
 
            if isinstance(x, basestring): # markers
 
            if isinstance(x, basestring):  # markers
 
                return x
 
            return "%.4g" % x
 
        return ' '.join("%s %s" % (outVal(p[0]), outVal(p[1]))
 
                        for p in self.points)
 
        
 
    def save(self,filename):
 

	
 
        return ' '.join(
 
            "%s %s" % (outVal(p[0]), outVal(p[1])) for p in self.points)
 

	
 
    def save(self, filename):
 
        # this is just around for markers, now
 
        if filename.endswith('-music') or filename.endswith('_music'):
 
            print "not saving music track"
 
            return
 
        f = file(filename,'w')
 
        f = file(filename, 'w')
 
        for p in self.points:
 
            f.write("%s %r\n" % p)
 
        f.close()
 
@@ -78,25 +86,26 @@ class Curve(object):
 
            return 0
 
        if not self.points:
 
            raise ValueError("curve has no points")
 
        i = bisect_left(self.points,(t,None))-1
 
        i = bisect_left(self.points, (t, None)) - 1
 

	
 
        if i == -1:
 
            return self.points[0][1]
 
        if self.points[i][0]>t:
 
        if self.points[i][0] > t:
 
            return self.points[i][1]
 
        if i>=len(self.points)-1:
 
        if i >= len(self.points) - 1:
 
            return self.points[i][1]
 

	
 
        p1,p2 = self.points[i],self.points[i+1]
 
        frac = (t-p1[0])/(p2[0]-p1[0])
 
        y = p1[1]+(p2[1]-p1[1])*frac
 
        p1, p2 = self.points[i], self.points[i + 1]
 
        frac = (t - p1[0]) / (p2[0] - p1[0])
 
        y = p1[1] + (p2[1] - p1[1]) * frac
 
        return y
 
    __call__=eval
 

	
 
    __call__ = eval
 

	
 
    def insert_pt(self, new_pt):
 
        """returns index of new point"""
 
        i = bisect(self.points, (new_pt[0],None))
 
        self.points.insert(i,new_pt)
 
        i = bisect(self.points, (new_pt[0], None))
 
        self.points.insert(i, new_pt)
 
        # missing a check that this isn't the same X as the neighbor point
 
        dispatcher.send("points changed", sender=self)
 
        return i
 
@@ -109,7 +118,7 @@ class Curve(object):
 
        self.insert_pt(new_pt)
 
        dispatcher.send("points changed", sender=self)
 
        # now simplify to the left
 
        
 

	
 
    def set_points(self, updates):
 
        for i, pt in updates:
 
            self.points[i] = pt
 
@@ -118,7 +127,7 @@ class Curve(object):
 
        # lot. need a new solution.
 
        #self.checkOverlap()
 
        dispatcher.send("points changed", sender=self)
 
            
 

	
 
    def checkOverlap(self):
 
        x = None
 
        for p in self.points:
 
@@ -130,20 +139,20 @@ class Curve(object):
 
        p = self.points.pop(i)
 
        dispatcher.send("points changed", sender=self)
 
        return p
 
            
 

	
 
    def remove_point(self, pt):
 
        self.points.remove(pt)
 
        dispatcher.send("points changed", sender=self)
 
            
 

	
 
    def indices_between(self, x1, x2, beyond=0):
 
        leftidx = max(0, bisect(self.points, (x1,None)) - beyond)
 
        leftidx = max(0, bisect(self.points, (x1, None)) - beyond)
 
        rightidx = min(len(self.points),
 
                       bisect(self.points, (x2,None)) + beyond)
 
                       bisect(self.points, (x2, None)) + beyond)
 
        return range(leftidx, rightidx)
 
        
 

	
 
    def points_between(self, x1, x2):
 
        """returns (x,y) points"""
 
        return [self.points[i] for i in self.indices_between(x1,x2)]
 
        return [self.points[i] for i in self.indices_between(x1, x2)]
 

	
 
    def point_before(self, x):
 
        """(x,y) of the point left of x, or None"""
 
@@ -153,15 +162,17 @@ class Curve(object):
 
        return self.points[leftidx]
 

	
 
    def index_before(self, x):
 
        leftidx = bisect(self.points, (x,None)) - 1
 
        leftidx = bisect(self.points, (x, None)) - 1
 
        if leftidx < 0:
 
            return None
 
        return leftidx
 

	
 

	
 
class CurveResource(object):
 
    """
 
    holds a Curve, deals with graphs
 
    """
 

	
 
    def __init__(self, graph, uri):
 
        # probably newCurve and loadCurve should be the constructors instead.
 
        self.graph, self.uri = graph, uri
 
@@ -177,15 +188,16 @@ class CurveResource(object):
 
        if hasattr(self, 'curve'):
 
            raise ValueError('CurveResource already has a curve %r' %
 
                             self.curve)
 
        self.graph.patch(Patch(addQuads=[
 
            (self.uri, RDF.type, L9['Curve'], ctx),
 
            (self.uri, RDFS.label, label, ctx),
 
        self.graph.patch(
 
            Patch(addQuads=[
 
                (self.uri, RDF.type, L9['Curve'], ctx),
 
                (self.uri, RDFS.label, label, ctx),
 
            ]))
 
        self.curve = Curve(self.uri)
 
        self.curve.points.extend([(0, 0)])
 
        self.saveCurve()
 
        self.watchCurvePointChanges()
 
        
 

	
 
    def loadCurve(self):
 
        if hasattr(self, 'curve'):
 
            raise ValueError('CurveResource already has a curve %r' %
 
@@ -198,7 +210,7 @@ class CurveResource(object):
 
        else:
 
            # given a currentState graph
 
            self.pointsFromGraph()
 
        
 

	
 
    def pointsFromGraph(self):
 
        pts = self.graph.value(self.uri, L9['points'])
 
        if pts is not None:
 
@@ -222,18 +234,20 @@ class CurveResource(object):
 
            #cur.save("%s-%s" % (basename,name))
 
            return []
 
        elif self.curve.pointsStorage == 'graph':
 
            return [self.graph.getObjectPatch(
 
                self.curvePointsContext(),
 
                subject=self.uri,
 
                predicate=L9['points'],
 
                newObject=Literal(self.curve.points_as_string()))]
 
            return [
 
                self.graph.getObjectPatch(self.curvePointsContext(),
 
                                          subject=self.uri,
 
                                          predicate=L9['points'],
 
                                          newObject=Literal(
 
                                              self.curve.points_as_string()))
 
            ]
 
        else:
 
            raise NotImplementedError(self.curve.pointsStorage)
 

	
 
    def watchCurvePointChanges(self):
 
        """start watching and saving changes to the graph"""
 
        dispatcher.connect(self.onChange, 'points changed', sender=self.curve)
 
        
 

	
 
    def onChange(self):
 

	
 
        # Don't write a patch for the edited curve points until they've been
 
@@ -243,31 +257,34 @@ class CurveResource(object):
 
        # this is just the wrong timing algorithm- it should be a max rate,
 
        # not a max-hold-still-time.
 
        HOLD_POINTS_GRAPH_COMMIT_SECS = .1
 
        
 

	
 
        if getattr(self, 'pendingSave', None):
 
            self.pendingSave.cancel()
 
        self.pendingSave = reactor.callLater(HOLD_POINTS_GRAPH_COMMIT_SECS,
 
                                             self.saveCurve)
 
        
 

	
 

	
 
class Markers(Curve):
 
    """Marker is like a point but the y value is a string"""
 

	
 
    def eval(self):
 
        raise NotImplementedError()
 

	
 

	
 
def slope(p1,p2):
 
def slope(p1, p2):
 
    if p2[0] == p1[0]:
 
        return 0
 
    return (p2[1] - p1[1]) / (p2[0] - p1[0])
 

	
 
       
 

	
 
class Curveset(object):
 

	
 
    def __init__(self, graph, session):
 
        self.graph, self.session = graph, session
 

	
 
        self.currentSong = None
 
        self.curveResources = {} # uri : CurveResource
 
        
 
        self.curveResources = {}  # uri : CurveResource
 

	
 
        self.markers = Markers(uri=None, pointsStorage='file')
 

	
 
        graph.addHandler(self.loadCurvesForSong)
 
@@ -285,7 +302,7 @@ class Curveset(object):
 
        dispatcher.send("clear_curves")
 
        self.curveResources.clear()
 
        self.markers = Markers(uri=None, pointsStorage='file')
 
        
 

	
 
        self.currentSong = self.graph.value(self.session, L9['currentSong'])
 
        if self.currentSong is None:
 
            return
 
@@ -298,11 +315,14 @@ class Curveset(object):
 
                curvename = self.graph.label(uri)
 
                if not curvename:
 
                    raise ValueError("curve %r has no label" % uri)
 
                dispatcher.send("add_curve", sender=self,
 
                                uri=uri, label=curvename, curve=cr.curve)
 
                dispatcher.send("add_curve",
 
                                sender=self,
 
                                uri=uri,
 
                                label=curvename,
 
                                curve=cr.curve)
 
            except Exception as e:
 
                log.error("loading %s failed: %s", uri, e)
 
                
 

	
 
        basename = os.path.join(
 
            showconfig.curvesDir(),
 
            showconfig.songFilenameFromURI(self.currentSong))
 
@@ -314,7 +334,7 @@ class Curveset(object):
 
    def save(self):
 
        """writes a file for each curve with a name
 
        like basename-curvename, or saves them to the rdf graph"""
 
        basename=os.path.join(
 
        basename = os.path.join(
 
            showconfig.curvesDir(),
 
            showconfig.songFilenameFromURI(self.currentSong))
 

	
 
@@ -326,23 +346,23 @@ class Curveset(object):
 
        # this will cause reloads that will rebuild our curve list
 
        for p in patches:
 
            self.graph.patch(p)
 
            
 

	
 
    def sorter(self, name):
 
        return self.curves[name].uri
 
        
 

	
 
    def curveUrisInOrder(self):
 
        return sorted(self.curveResources.keys())
 

	
 
    def currentCurves(self):
 
        # deprecated
 
        for uri, cr in sorted(self.curveResources.items()):
 
            with self.graph.currentState(
 
                    tripleFilter=(uri, RDFS['label'], None)) as g:
 
            with self.graph.currentState(tripleFilter=(uri, RDFS['label'],
 
                                                       None)) as g:
 
                yield uri, g.label(uri), cr.curve
 
        
 

	
 
    def globalsdict(self):
 
        raise NotImplementedError('subterm used to get a dict of name:curve')
 
    
 

	
 
    def get_time_range(self):
 
        return 0, dispatcher.send("get max time")[0][1]
 

	
 
@@ -358,8 +378,8 @@ class Curveset(object):
 
        cr.curve.points.extend([(s, 0), (e, 0)])
 

	
 
        ctx = self.currentSong
 
        self.graph.patch(Patch(addQuads=[
 
            (self.currentSong, L9['curve'], uri, ctx),
 
        self.graph.patch(
 
            Patch(addQuads=[
 
                (self.currentSong, L9['curve'], uri, ctx),
 
            ]))
 
        cr.saveCurve()
 

	
light9/curvecalc/curveedit.py
Show inline comments
 
@@ -9,40 +9,46 @@ from lib.cycloneerr import PrettyErrorHa
 
from run_local import log
 
from louie import dispatcher
 

	
 

	
 
def serveCurveEdit(port, hoverTimeResponse, curveset):
 
    """
 
    /hoverTime requests actually are handled by the curvecalc gui
 
    """
 
    curveEdit = CurveEdit(curveset)
 
    
 

	
 
    class HoverTime(PrettyErrorHandler, cyclone.web.RequestHandler):
 

	
 
        def get(self):
 
            hoverTimeResponse(self)
 

	
 
    class LiveInputPoint(PrettyErrorHandler, cyclone.web.RequestHandler):
 

	
 
        def post(self):
 
            params = cgi.parse_qs(self.request.body)
 
            curve = URIRef(params['curve'][0])
 
            value = float(params['value'][0])
 
            curveEdit.liveInputPoint(curve, value)
 
            self.set_status(204)
 
            
 
    reactor.listenTCP(port, cyclone.web.Application(handlers=[
 
        (r'/hoverTime', HoverTime),
 
        (r'/liveInputPoint', LiveInputPoint),
 
        ], debug=True))
 

	
 
    
 
    reactor.listenTCP(
 
        port,
 
        cyclone.web.Application(handlers=[
 
            (r'/hoverTime', HoverTime),
 
            (r'/liveInputPoint', LiveInputPoint),
 
        ],
 
                                debug=True))
 

	
 

	
 
class CurveEdit(object):
 

	
 
    def __init__(self, curveset):
 
        self.curveset = curveset
 
        dispatcher.connect(self.inputTime, "input time")
 
        self.currentTime = 0
 
        
 

	
 
    def inputTime(self, val):
 
        self.currentTime = val
 
        
 

	
 
    def liveInputPoint(self, curveUri, value):
 
        curve = self.curveset.curveFromUri(curveUri)
 
        curve.live_input_point((self.currentTime, value), clear_ahead_secs=.5)
 
        
light9/curvecalc/curveview.py
Show inline comments
 
@@ -13,44 +13,48 @@ from lib.goocanvas_compat import Points,
 

	
 
log = logging.getLogger()
 
print "curveview.py toplevel"
 

	
 

	
 
def vlen(v):
 
    return math.sqrt(v[0]*v[0] + v[1]*v[1])
 
    return math.sqrt(v[0] * v[0] + v[1] * v[1])
 

	
 

	
 
def angle_between(base, p0, p1):
 
    p0 = p0[0] - base[0], p0[1] - base[1]
 
    p1 = p1[0] - base[0], p1[1] - base[1]
 
    p0 = [x/vlen(p0) for x in p0]
 
    p1 = [x/vlen(p1) for x in p1]
 
    dot = p0[0]*p1[0]+p0[1]*p1[1]
 
    dot = max(-1,min(1,dot))
 
    p0 = [x / vlen(p0) for x in p0]
 
    p1 = [x / vlen(p1) for x in p1]
 
    dot = p0[0] * p1[0] + p0[1] * p1[1]
 
    dot = max(-1, min(1, dot))
 
    return math.degrees(math.acos(dot))
 

	
 

	
 
class Sketch:
 
    """a sketch motion on a curveview, with temporary points while you
 
    draw, and simplification when you release"""
 
    
 
    def __init__(self,curveview,ev):
 

	
 
    def __init__(self, curveview, ev):
 
        self.curveview = curveview
 
        self.pts = []
 
        self.last_x = None
 

	
 
    def motion(self,ev):
 
    def motion(self, ev):
 
        p = self.curveview.world_from_screen(ev.x, ev.y)
 
        p = p[0], max(0,min(1,p[1]))
 
        p = p[0], max(0, min(1, p[1]))
 
        if self.last_x is not None and abs(ev.x - self.last_x) < 4:
 
            return
 
        self.last_x = ev.x
 
        self.pts.append(p)
 
        self.curveview.add_point(p)
 

	
 
    def release(self,ev):
 
    def release(self, ev):
 
        pts = self.pts
 
        pts.sort()
 
        finalPoints = pts[:]
 

	
 
        dx = .01
 
        to_remove = []
 
        for i in range(1,len(pts)-1):
 
        for i in range(1, len(pts) - 1):
 
            x = pts[i][0]
 

	
 
            p_left = (x - dx, self.curveview.curve(x - dx))
 
@@ -71,19 +75,21 @@ class Sketch:
 
            if abs(self.curveview.curve(p[0]) - p[1]) > .1:
 
                self.curveview.add_point(p)
 
                finalPoints.append(p)
 
            
 

	
 
        self.curveview.update_curve()
 
        self.curveview.select_points(finalPoints)
 

	
 

	
 
class SelectManip(object):
 
    """
 
    selection manipulator is created whenever you have a selection. It
 
    draws itself on the canvas and edits the points when you drag
 
    various parts
 
    """
 

	
 
    def __init__(self, parent, getSelectedIndices, getWorldPoint,
 
                 getScreenPoint, getCanvasHeight, setPoints,
 
                 getWorldTime, getWorldValue, getDragRange):
 
                 getScreenPoint, getCanvasHeight, setPoints, getWorldTime,
 
                 getWorldValue, getDragRange):
 
        """parent goocanvas group"""
 
        self.getSelectedIndices = getSelectedIndices
 
        self.getWorldPoint = getWorldPoint
 
@@ -94,34 +100,42 @@ class SelectManip(object):
 
        self.getDragRange = getDragRange
 
        self.getWorldValue = getWorldValue
 
        self.grp = GooCanvas.CanvasGroup(parent=parent)
 
        
 
        self.title = GooCanvas.CanvasText(parent=self.grp, text="selectmanip",
 
                                    x=10, y=10, fill_color='white', font="ubuntu 10")
 

	
 
        self.title = GooCanvas.CanvasText(parent=self.grp,
 
                                          text="selectmanip",
 
                                          x=10,
 
                                          y=10,
 
                                          fill_color='white',
 
                                          font="ubuntu 10")
 

	
 
        self.bbox = GooCanvas.CanvasRect(parent=self.grp,
 
                                   fill_color_rgba=0xffff0030,
 
                                   line_width=0)
 
                                         fill_color_rgba=0xffff0030,
 
                                         line_width=0)
 

	
 
        self.xTrans = polyline_new_line(parent=self.grp, close_path=True,
 
                                         fill_color_rgba=0xffffff88,
 
                                         )
 
        self.centerScale = polyline_new_line(parent=self.grp, close_path=True,
 
                                              fill_color_rgba=0xffffff88,
 
                                         )
 
        self.xTrans = polyline_new_line(
 
            parent=self.grp,
 
            close_path=True,
 
            fill_color_rgba=0xffffff88,
 
        )
 
        self.centerScale = polyline_new_line(
 
            parent=self.grp,
 
            close_path=True,
 
            fill_color_rgba=0xffffff88,
 
        )
 

	
 
        thickLine = lambda: polyline_new_line(parent=self.grp,
 
                                               stroke_color_rgba=0xffffccff,
 
                                               line_width=6)
 
        thickLine = lambda: polyline_new_line(
 
            parent=self.grp, stroke_color_rgba=0xffffccff, line_width=6)
 
        self.leftScale = thickLine()
 
        self.rightScale = thickLine()
 
        self.topScale = thickLine()
 
        
 
        for grp, name in [(self.xTrans, 'x'),
 
                          (self.leftScale, 'left'),
 
                          (self.rightScale, 'right'),
 
                          (self.topScale, 'top'),
 
                          (self.centerScale, 'centerScale'),
 
                          ]:
 

	
 
        for grp, name in [
 
            (self.xTrans, 'x'),
 
            (self.leftScale, 'left'),
 
            (self.rightScale, 'right'),
 
            (self.topScale, 'top'),
 
            (self.centerScale, 'centerScale'),
 
        ]:
 
            grp.connect("button-press-event", self.onPress, name)
 
            grp.connect("button-release-event", self.onRelease, name)
 
            grp.connect("motion-notify-event", self.onMotion, name)
 
@@ -133,10 +147,10 @@ class SelectManip(object):
 
    def onEnter(self, item, target_item, event, param):
 
        self.prevColor = item.props.stroke_color_rgba
 
        item.props.stroke_color_rgba = 0xff0000ff
 
        
 

	
 
    def onLeave(self, item, target_item, event, param):
 
        item.props.stroke_color_rgba = self.prevColor
 
        
 

	
 
    def onPress(self, item, target_item, event, param):
 
        self.dragStartTime = self.getWorldTime(event.x)
 
        idxs = self.getSelectedIndices()
 
@@ -147,7 +161,7 @@ class SelectManip(object):
 

	
 
        if param == 'centerScale':
 
            self.maxPointMove = min(moveLeft, moveRight)
 
        
 

	
 
        self.dragRange = (self.dragStartTime - moveLeft,
 
                          self.dragStartTime + moveRight)
 
        return True
 
@@ -165,51 +179,47 @@ class SelectManip(object):
 

	
 
            def clamp(x, lo, hi):
 
                return max(lo, min(hi, x))
 
            
 

	
 
            mouseT = self.getWorldTime(event.x)
 
            clampedT = clamp(mouseT, clampLo + dontCross, clampHi - dontCross)
 

	
 
            dt = clampedT - self.dragStartTime
 

	
 
            if param == 'x':
 
                self.setPoints((i, (orig[0] + dt, orig[1]))
 
                               for i, orig in origPts)
 
                self.setPoints(
 
                    (i, (orig[0] + dt, orig[1])) for i, orig in origPts)
 
            elif param == 'left':
 
                self.setPoints((
 
                    i,
 
                    (left + dt +
 
                     (orig[0] - left) / width *
 
                     clamp(width - dt, dontCross, right - clampLo - dontCross),
 
                     orig[1])) for i, orig in origPts)
 
                self.setPoints(
 
                    (i,
 
                     (left + dt + (orig[0] - left) / width *
 
                      clamp(width - dt, dontCross, right - clampLo - dontCross),
 
                      orig[1])) for i, orig in origPts)
 
            elif param == 'right':
 
                self.setPoints((
 
                    i,
 
                    (left +
 
                     (orig[0] - left) / width *
 
                     clamp(width + dt, dontCross, clampHi - left - dontCross),
 
                     orig[1])) for i, orig in origPts)
 
                self.setPoints(
 
                    (i,
 
                     (left + (orig[0] - left) / width *
 
                      clamp(width + dt, dontCross, clampHi - left - dontCross),
 
                      orig[1])) for i, orig in origPts)
 
            elif param == 'top':
 
                v = self.getWorldValue(event.y)
 
                if self.origMaxValue == 0:
 
                    self.setPoints((i, (orig[0], v)) for i, orig in origPts)
 
                else:
 
                    scl = max(0, min(1 / self.origMaxValue,
 
                                     v / self.origMaxValue))
 
                    self.setPoints((i, (orig[0], orig[1] * scl))
 
                                   for i, orig in origPts)
 
                    scl = max(0,
 
                              min(1 / self.origMaxValue, v / self.origMaxValue))
 
                    self.setPoints(
 
                        (i, (orig[0], orig[1] * scl)) for i, orig in origPts)
 

	
 
            elif param == 'centerScale':
 
                dt = mouseT - self.dragStartTime
 
                rad = width / 2
 
                tMid = left + rad
 
                maxScl = (rad + self.maxPointMove - dontCross) / rad
 
                newWidth = max(dontCross / width,
 
                               min((rad + dt) / rad, maxScl)) * width
 
                self.setPoints((i,
 
                                (tMid +
 
                                 ((orig[0] - left) / width - .5) * newWidth,
 
                                 orig[1])) for i, orig in origPts)
 
                
 
                newWidth = max(dontCross / width, min(
 
                    (rad + dt) / rad, maxScl)) * width
 
                self.setPoints(
 
                    (i, (tMid + ((orig[0] - left) / width - .5) * newWidth,
 
                         orig[1])) for i, orig in origPts)
 

	
 
    def onRelease(self, item, target_item, event, param):
 
        if hasattr(self, 'dragStartTime'):
 
@@ -220,17 +230,18 @@ class SelectManip(object):
 
        change, call this to redo the layout of the manip"""
 
        idxs = self.getSelectedIndices()
 
        pts = [self.getScreenPoint(i) for i in idxs]
 
        
 

	
 
        b = self.bbox.props
 
        b.x = min(p[0] for p in pts) - 5
 
        b.y = min(p[1] for p in pts) - 5
 
        margin = 10 if len(pts) > 1 else 0
 
        b.width = max(p[0] for p in pts) - b.x + margin
 
        b.height = min(max(p[1] for p in pts) - b.y + margin,
 
                       self.getCanvasHeight() - b.y - 1)
 
        b.height = min(
 
            max(p[1] for p in pts) - b.y + margin,
 
            self.getCanvasHeight() - b.y - 1)
 

	
 
        multi = (GooCanvas.CanvasItemVisibility.VISIBLE if len(pts) > 1 else
 
                 GooCanvas.CanvasItemVisibility.INVISIBLE)
 
        multi = (GooCanvas.CanvasItemVisibility.VISIBLE
 
                 if len(pts) > 1 else GooCanvas.CanvasItemVisibility.INVISIBLE)
 
        b.visibility = multi
 
        self.leftScale.props.visibility = multi
 
        self.rightScale.props.visibility = multi
 
@@ -245,24 +256,21 @@ class SelectManip(object):
 
        midY = self.getCanvasHeight() * .5
 
        loY = self.getCanvasHeight() * .8
 

	
 
        self.leftScale.props.points = Points([
 
            (b.x, b.y), (b.x, b.y + b.height)])
 
        self.rightScale.props.points = Points([
 
            (b.x + b.width, b.y), (b.x + b.width, b.y + b.height)])
 
        self.leftScale.props.points = Points([(b.x, b.y),
 
                                              (b.x, b.y + b.height)])
 
        self.rightScale.props.points = Points([(b.x + b.width, b.y),
 
                                               (b.x + b.width, b.y + b.height)])
 

	
 
        self.topScale.props.points = Points([
 
            (b.x, b.y), (b.x + b.width, b.y)])
 
        self.topScale.props.points = Points([(b.x, b.y), (b.x + b.width, b.y)])
 

	
 
        self.updateXTrans(centerX, midY)
 

	
 
        self.centerScale.props.points = Points([
 
            (centerX - 5, loY - 5),
 
            (centerX + 5, loY - 5),
 
            (centerX + 5, loY + 5),
 
            (centerX - 5, loY + 5)])
 
            
 
        self.centerScale.props.points = Points([(centerX - 5, loY - 5),
 
                                                (centerX + 5, loY - 5),
 
                                                (centerX + 5, loY + 5),
 
                                                (centerX - 5, loY + 5)])
 

	
 
    def updateXTrans(self, centerX, midY):       
 
    def updateXTrans(self, centerX, midY):
 
        x1 = centerX - 30
 
        x2 = centerX - 20
 
        x3 = centerX + 20
 
@@ -272,25 +280,24 @@ class SelectManip(object):
 
        y3 = midY + 5
 
        y4 = midY + 10
 
        shape = [
 
            (x1, midY), # left tip
 
            (x1, midY),  # left tip
 
            (x2, y1),
 
            (x2, y2),
 
            
 
            (x3, y2),
 
            (x3, y1),
 
            (x4, midY), # right tip
 
            (x4, midY),  # right tip
 
            (x3, y4),
 
            (x3, y3),
 
            
 
            (x2, y3),
 
            (x2, y4)
 
            ]
 
        ]
 

	
 
        self.xTrans.props.points = Points(shape)
 

	
 
    def destroy(self):
 
        self.grp.remove()
 

	
 

	
 
class Curveview(object):
 
    """
 
    graphical curve widget only. Please pack .widget
 
@@ -306,7 +313,12 @@ class Curveview(object):
 
    The canvas x1/x2/y1/y2 coords are updated to match self.widget.
 
    
 
    """
 
    def __init__(self, curve, markers, knobEnabled=False, isMusic=False,
 

	
 
    def __init__(self,
 
                 curve,
 
                 markers,
 
                 knobEnabled=False,
 
                 isMusic=False,
 
                 zoomControl=None):
 
        """knobEnabled=True highlights the previous key and ties it to a
 
        hardware knob"""
 
@@ -315,46 +327,45 @@ class Curveview(object):
 
        self.knobEnabled = knobEnabled
 
        self._isMusic = isMusic
 
        self.zoomControl = zoomControl
 
        
 

	
 
        self.redrawsEnabled = False
 

	
 
        box = self.createOuterWidgets()
 
        self.canvas = self.createCanvasWidget(box)
 
        self.trackWidgetSize()
 
        self.update_curve()
 
        
 

	
 
        self._time = -999
 
        self.last_mouse_world = None
 
        self.entered = False # is the mouse currently over this widget
 
        self.selected_points=[] # idx of points being dragged
 
        self.entered = False  # is the mouse currently over this widget
 
        self.selected_points = []  # idx of points being dragged
 
        self.dots = {}
 
        # self.bind("<Enter>",self.focus)
 
        dispatcher.connect(self.playPause, "onPlayPause")
 
        dispatcher.connect(self.input_time, "input time")
 
        dispatcher.connect(self.update_curve, "zoom changed")
 
        dispatcher.connect(self.update_curve, "points changed",
 
        dispatcher.connect(self.update_curve,
 
                           "points changed",
 
                           sender=self.curve)
 
        dispatcher.connect(self.update_curve, "mute changed", 
 
                           sender=self.curve)
 
        dispatcher.connect(self.update_curve, "mute changed", sender=self.curve)
 
        dispatcher.connect(self.select_between, "select between")
 
        dispatcher.connect(self.acls, "all curves lose selection")
 
        if self.knobEnabled:
 
            dispatcher.connect(self.knob_in, "knob in")
 
            dispatcher.connect(self.slider_in, "set key")
 

	
 

	
 
        # todo: hold control to get a [+] cursor
 
        #        def curs(ev):
 
        #            print ev.state
 
        #        self.bind("<KeyPress>",curs)
 
        #        self.bind("<KeyRelease-Control_L>",lambda ev: curs(0))
 
      
 

	
 
        # this binds on c-a-b1, etc
 
        if 0: # unported
 
        if 0:  # unported
 
            self.regionzoom = RegionZoom(self, self.world_from_screen,
 
                                         self.screen_from_world)
 

	
 
        self.sketch = None # an in-progress sketch
 
        self.sketch = None  # an in-progress sketch
 

	
 
        self.dragging_dots = False
 
        self.selecting = False
 
@@ -393,6 +404,7 @@ class Curveview(object):
 
            size-allocate seems right but i get into infinite bounces
 
            between two sizes
 
        """
 

	
 
        def sizeEvent(w, alloc):
 
            p = self.canvas.props
 
            if (alloc.width, alloc.height) != (p.x2, p.y2):
 
@@ -401,12 +413,13 @@ class Curveview(object):
 
                # calling update_curve in this event usually doesn't work
 
                reactor.callLater(0, self.update_curve)
 
            return False
 
            
 

	
 
        #self.widget.connect('size-allocate', sizeEvent) # see docstring
 

	
 
        def visEvent(w, alloc):
 
            self.setCanvasToWidgetSize()
 
            return False
 

	
 
        self.widget.add_events(Gdk.EventMask.VISIBILITY_NOTIFY_MASK)
 
        self.widget.connect('visibility-notify-event', visEvent)
 

	
 
@@ -449,15 +462,15 @@ class Curveview(object):
 

	
 
    def onAny(self, w, event):
 
        print "   %s on %s" % (event, w)
 
        
 

	
 
    def onFocusIn(self, *args):
 
        dispatcher.send('curve row focus change')     
 
        dispatcher.send('curve row focus change')
 
        dispatcher.send("all curves lose selection", butNot=self)
 

	
 
        self.widget.modify_bg(Gtk.StateFlags.NORMAL, Gdk.color_parse("red"))
 

	
 
    def onFocusOut(self, widget=None, event=None):
 
        dispatcher.send('curve row focus change')                  
 
        dispatcher.send('curve row focus change')
 
        self.widget.modify_bg(Gtk.StateFlags.NORMAL, Gdk.color_parse("gray30"))
 

	
 
        # you'd think i'm unselecting when we lose focus, but we also
 
@@ -475,8 +488,7 @@ class Curveview(object):
 
    def onDelete(self):
 
        if self.selected_points:
 
            self.remove_point_idx(*self.selected_points)
 
        
 
            
 

	
 
    def onCanvasPress(self, item, target_item, event):
 
        # when we support multiple curves per canvas, this should find
 
        # the close one and add a point to that. Binding to the line
 
@@ -494,7 +506,7 @@ class Curveview(object):
 
        else:
 
            self.select_press(event)
 

	
 
        # this stops some other handler that wants to unfocus 
 
        # this stops some other handler that wants to unfocus
 
        return True
 

	
 
    def playPause(self):
 
@@ -563,13 +575,14 @@ class Curveview(object):
 
                self.canvas.get_root_item(),
 
                getSelectedIndices=lambda: sorted(self.selected_points),
 
                getWorldPoint=lambda i: self.curve.points[i],
 
                getScreenPoint=lambda i: self.screen_from_world(self.curve.points[i]),
 
                getScreenPoint=lambda i: self.screen_from_world(self.curve.
 
                                                                points[i]),
 
                getWorldTime=lambda x: self.world_from_screen(x, 0)[0],
 
                getWorldValue=lambda y: self.world_from_screen(0, y)[1],
 
                getCanvasHeight=lambda: self.canvas.props.y2,
 
                setPoints=self.setPoints,
 
                getDragRange=self.getDragRange,
 
                )
 
            )
 
        if not self.selected_points and self.selectManip:
 
            self.selectManip.destroy()
 
            self.selectManip = None
 
@@ -600,12 +613,12 @@ class Curveview(object):
 

	
 
    def setPoints(self, updates):
 
        self.curve.set_points(updates)
 
        
 

	
 
    def selectionChanged(self):
 
        if self.selectManip:
 
            self.selectManip.update()
 

	
 
    def select_press(self,ev):
 
    def select_press(self, ev):
 
        # todo: these select_ handlers are getting called on c-a-drag
 
        # zooms too. the dispatching should be more selective than
 
        # just calling both handlers all the time
 
@@ -614,37 +627,37 @@ class Curveview(object):
 
            return
 
        if not self.selecting:
 
            self.selecting = True
 
            self.select_start = self.world_from_screen(ev.x,0)[0]
 
            self.select_start = self.world_from_screen(ev.x, 0)[0]
 
            #cursors.push(self,"gumby")
 
        
 
    def select_motion(self,ev):
 

	
 
    def select_motion(self, ev):
 
        if not self.selecting:
 
            return
 
        start = self.select_start
 
        cur = self.world_from_screen(ev.x, 0)[0]
 
        self.select_between(start, cur)
 
        
 
    def select_release(self,ev):
 

	
 
    def select_release(self, ev):
 
        self.print_state("select_release")
 

	
 
        # dotrelease never gets called, but I can clear that state here
 
        self.dragging_dots = False
 
        
 

	
 
        if not self.selecting:
 
            return
 
        #cursors.pop(self)
 
        self.selecting = False
 
        self.select_between(self.select_start,
 
                            self.world_from_screen(ev.x,0)[0])
 
                            self.world_from_screen(ev.x, 0)[0])
 

	
 
    def sketch_press(self,ev):
 
        self.sketch = Sketch(self,ev)
 
    def sketch_press(self, ev):
 
        self.sketch = Sketch(self, ev)
 

	
 
    def sketch_motion(self,ev):
 
    def sketch_motion(self, ev):
 
        if self.sketch:
 
            self.sketch.motion(ev)
 

	
 
    def sketch_release(self,ev):
 
    def sketch_release(self, ev):
 
        if self.sketch:
 
            self.sketch.release(ev)
 
            self.sketch = None
 
@@ -658,13 +671,13 @@ class Curveview(object):
 
        marginBottom = 3 if ht > 40 else 0
 
        marginTop = marginBottom
 
        return z, ht, marginBottom, marginTop
 
        
 
    def screen_from_world(self,p):
 

	
 
    def screen_from_world(self, p):
 
        z, ht, marginBottom, marginTop = self._coords()
 
        return ((p[0] - z.start) / (z.end - z.start) * self.canvas.props.x2,
 
                (ht - marginBottom) - p[1] * (ht - (marginBottom + marginTop)))
 

	
 
    def world_from_screen(self,x,y):
 
    def world_from_screen(self, x, y):
 
        z, ht, marginBottom, marginTop = self._coords()
 
        return (x / self.canvas.props.x2 * (z.end - z.start) + z.start,
 
                ((ht - marginBottom) - y) / (ht - (marginBottom + marginTop)))
 
@@ -681,33 +694,38 @@ class Curveview(object):
 
        # when the widget is gone. Correct solution would be to stop
 
        # and free those handlers when the widget is gone.
 
        return self.canvas.is_visible()
 
        
 

	
 
    def update_time_bar(self, t):
 
        if not self.alive():
 
            return
 
        if not getattr(self, 'timelineLine', None):
 
            self.timelineGroup = GooCanvas.CanvasGroup(
 
                parent=self.canvas.get_root_item())
 
            self.timelineLine = polyline_new_line(
 
                parent=self.timelineGroup,
 
                points=Points([(0,0), (0,0)]),
 
                line_width=2, stroke_color='red')
 
            self.timelineLine = polyline_new_line(parent=self.timelineGroup,
 
                                                  points=Points([(0, 0),
 
                                                                 (0, 0)]),
 
                                                  line_width=2,
 
                                                  stroke_color='red')
 

	
 
        try:
 
            pts = [self.screen_from_world((t, 0)),
 
                   self.screen_from_world((t, 1))]
 
            pts = [
 
                self.screen_from_world((t, 0)),
 
                self.screen_from_world((t, 1))
 
            ]
 
        except ZeroDivisionError:
 
            pts = [(-1, -1), (-1, -1)]
 
        self.timelineLine.set_property('points', Points(pts))
 
        
 

	
 
        self._time = t
 
        if self.knobEnabled:
 
            self.delete('knob')
 
            prevKey = self.curve.point_before(t)
 
            if prevKey is not None:
 
                pos = self.screen_from_world(prevKey)
 
                self.create_oval(pos[0] - 8, pos[1] - 8,
 
                                 pos[0] + 8, pos[1] + 8,
 
                self.create_oval(pos[0] - 8,
 
                                 pos[1] - 8,
 
                                 pos[0] + 8,
 
                                 pos[1] + 8,
 
                                 outline='#800000',
 
                                 tags=('knob',))
 
                dispatcher.send("knob out", value=prevKey[1], curve=self.curve)
 
@@ -716,7 +734,7 @@ class Curveview(object):
 
        if not getattr(self, '_pending_update', False):
 
            self._pending_update = True
 
            reactor.callLater(.01, self._update_curve)
 
        
 

	
 
    def _update_curve(self):
 
        try:
 
            self._update_curve2()
 
@@ -734,16 +752,18 @@ class Curveview(object):
 
            print "no redrawsEnabled, skipping", self
 
            return
 

	
 
        visible_x = (self.world_from_screen(0,0)[0],
 
        visible_x = (self.world_from_screen(0, 0)[0],
 
                     self.world_from_screen(self.canvas.props.x2, 0)[0])
 

	
 
        visible_idxs = self.curve.indices_between(visible_x[0], visible_x[1],
 
        visible_idxs = self.curve.indices_between(visible_x[0],
 
                                                  visible_x[1],
 
                                                  beyond=1)
 
        visible_points = [self.curve.points[i] for i in visible_idxs]
 
        
 

	
 
        if getattr(self, 'curveGroup', None):
 
            self.curveGroup.remove()
 
        self.curveGroup = GooCanvas.CanvasGroup(parent=self.canvas.get_root_item())
 
        self.curveGroup = GooCanvas.CanvasGroup(
 
            parent=self.canvas.get_root_item())
 
        self.curveGroup.lower(None)
 

	
 
        self.canvas.set_property("background-color",
 
@@ -752,15 +772,15 @@ class Curveview(object):
 
        self.update_time_bar(self._time)
 
        self._draw_line(visible_points, area=True)
 
        self._draw_markers(
 
            self.markers.points[i] for i in
 
            self.markers.indices_between(visible_x[0], visible_x[1]))
 
            self.markers.points[i]
 
            for i in self.markers.indices_between(visible_x[0], visible_x[1]))
 
        if self.canvas.props.y2 > 80:
 
            self._draw_time_tics(visible_x)
 

	
 
            self.dots = {} # idx : canvas rectangle
 
            self.dots = {}  # idx : canvas rectangle
 

	
 
            if len(visible_points) < 50 and not self.curve.muted:
 
                self._draw_handle_points(visible_idxs,visible_points)
 
                self._draw_handle_points(visible_idxs, visible_points)
 

	
 
        self.selectionChanged()
 

	
 
@@ -771,63 +791,69 @@ class Curveview(object):
 

	
 
    def _draw_markers(self, pts):
 
        colorMap = {
 
            'q':'#598522',
 
            'w':'#662285',
 
            'e':'#852922',
 
            'r':'#85225C',
 
            't':'#856B22',
 
            'y':'#227085',
 
            }
 
            'q': '#598522',
 
            'w': '#662285',
 
            'e': '#852922',
 
            'r': '#85225C',
 
            't': '#856B22',
 
            'y': '#227085',
 
        }
 
        for t, name in pts:
 
            x = int(self.screen_from_world((t,0))[0]) + .5
 
            x = int(self.screen_from_world((t, 0))[0]) + .5
 
            polyline_new_line(self.curveGroup,
 
                              x, 0, x, self.canvas.props.y2,
 
                              x,
 
                              0,
 
                              x,
 
                              self.canvas.props.y2,
 
                              line_width=.4 if name in 'rty' else .8,
 
                              stroke_color=colorMap.get(name, 'gray'))
 

	
 
    def _draw_time_tics(self,visible_x):
 
    def _draw_time_tics(self, visible_x):
 
        tic = self._draw_one_tic
 

	
 
        tic(0, "0")
 
        t1,t2=visible_x
 
        if t2-t1<30:
 
            for t in range(int(t1),int(t2)+1):
 
                tic(t,str(t))
 
        t1, t2 = visible_x
 
        if t2 - t1 < 30:
 
            for t in range(int(t1), int(t2) + 1):
 
                tic(t, str(t))
 
        tic(introPad, str(introPad))
 

	
 
        endtimes = dispatcher.send("get max time")
 
        if endtimes:
 
            endtime = endtimes[0][1]
 
            tic(endtime, "end %.1f"%endtime)
 
            tic(endtime, "end %.1f" % endtime)
 
            tic(endtime - postPad, "post %.1f" % (endtime - postPad))
 
        
 
    def _draw_one_tic(self,t,label):
 

	
 
    def _draw_one_tic(self, t, label):
 
        try:
 
            x = self.screen_from_world((t,0))[0]
 
            x = self.screen_from_world((t, 0))[0]
 
            if not 0 <= x < self.canvas.props.x2:
 
                return
 
            x = max(5, x) # cheat left-edge stuff onscreen
 
            x = max(5, x)  # cheat left-edge stuff onscreen
 
        except ZeroDivisionError:
 
            x = -100
 
            
 

	
 
        ht = self.canvas.props.y2
 
        polyline_new_line(self.curveGroup,
 
                                    x, ht,
 
                                    x, ht - 20,
 
                                    line_width=.5,
 
                                    stroke_color='gray70')
 
                          x,
 
                          ht,
 
                          x,
 
                          ht - 20,
 
                          line_width=.5,
 
                          stroke_color='gray70')
 
        GooCanvas.CanvasText(parent=self.curveGroup,
 
                       fill_color="white",
 
                       anchor=GooCanvas.CanvasAnchorType.SOUTH,
 
                       font="ubuntu 7",
 
                       x=x+3, y=ht-20,
 
                       text=label)
 
                             fill_color="white",
 
                             anchor=GooCanvas.CanvasAnchorType.SOUTH,
 
                             font="ubuntu 7",
 
                             x=x + 3,
 
                             y=ht - 20,
 
                             text=label)
 

	
 
    def _draw_line(self, visible_points, area=False):
 
        if not visible_points:
 
            return
 
        linepts=[]
 
        step=1
 
        linepts = []
 
        step = 1
 
        linewidth = 1.5
 
        maxPointsToDraw = self.canvas.props.x2 / 2
 
        if len(visible_points) > maxPointsToDraw:
 
@@ -835,7 +861,7 @@ class Curveview(object):
 
            linewidth = .8
 
        for p in visible_points[::step]:
 
            try:
 
                x,y = self.screen_from_world(p)
 
                x, y = self.screen_from_world(p)
 
            except ZeroDivisionError:
 
                x = y = -100
 
            linepts.append((int(x) + .5, int(y) + .5))
 
@@ -855,75 +881,77 @@ class Curveview(object):
 
            if len(areapts) >= 1:
 
                areapts.insert(0, (0, areapts[0][1]))
 
                areapts.append((self.canvas.props.x2, areapts[-1][1]))
 
            polyline_new_line(parent=self.curveGroup,
 
                              points=Points(
 
                                  [(areapts[0][0], base)] +
 
                                  areapts +
 
                                  [(areapts[-1][0], base)]),
 
                              close_path=True,
 
                              line_width=0,
 
                              # transparent as a workaround for
 
                              # covering some selectmanips (which
 
                              # become unclickable)
 
                              fill_color_rgba=0x00800080,
 
            polyline_new_line(
 
                parent=self.curveGroup,
 
                points=Points([(areapts[0][0], base)] + areapts +
 
                              [(areapts[-1][0], base)]),
 
                close_path=True,
 
                line_width=0,
 
                # transparent as a workaround for
 
                # covering some selectmanips (which
 
                # become unclickable)
 
                fill_color_rgba=0x00800080,
 
            )
 

	
 
        self.pl = polyline_new_line(parent=self.curveGroup,
 
                                     points=Points(linepts),
 
                                     line_width=linewidth,
 
                                     stroke_color=fill,
 
                                     )
 
                
 
            
 
    def _draw_handle_points(self,visible_idxs,visible_points):
 
        for i,p in zip(visible_idxs,visible_points):
 
            rad=6
 
        self.pl = polyline_new_line(
 
            parent=self.curveGroup,
 
            points=Points(linepts),
 
            line_width=linewidth,
 
            stroke_color=fill,
 
        )
 

	
 
    def _draw_handle_points(self, visible_idxs, visible_points):
 
        for i, p in zip(visible_idxs, visible_points):
 
            rad = 6
 
            worldp = p
 
            try:
 
                p = self.screen_from_world(p)
 
            except ZeroDivisionError:
 
                p = (-100, -100)
 
            dot = GooCanvas.CanvasRect(parent=self.curveGroup,
 
                                 x=int(p[0] - rad) + .5,
 
                                 y=int(p[1] - rad) + .5,
 
                                 width=rad * 2, height=rad * 2,
 
                                 stroke_color='gray90',
 
                                 fill_color='blue',
 
                                 line_width=1,
 
                                 #tags=('curve','point', 'handle%d' % i)
 
                                 )
 
            dot = GooCanvas.CanvasRect(
 
                parent=self.curveGroup,
 
                x=int(p[0] - rad) + .5,
 
                y=int(p[1] - rad) + .5,
 
                width=rad * 2,
 
                height=rad * 2,
 
                stroke_color='gray90',
 
                fill_color='blue',
 
                line_width=1,
 
                #tags=('curve','point', 'handle%d' % i)
 
            )
 

	
 
            if worldp[1] == 0:
 
                rad += 3
 
                GooCanvas.CanvasEllipse(parent=self.curveGroup,
 
                                  center_x=p[0],
 
                                  center_y=p[1],
 
                                  radius_x=rad,
 
                                  radius_y=rad,
 
                                  line_width=2,
 
                                  stroke_color='#00a000',
 
                                  #tags=('curve','point', 'handle%d' % i)
 
                                  )
 
                GooCanvas.CanvasEllipse(
 
                    parent=self.curveGroup,
 
                    center_x=p[0],
 
                    center_y=p[1],
 
                    radius_x=rad,
 
                    radius_y=rad,
 
                    line_width=2,
 
                    stroke_color='#00a000',
 
                    #tags=('curve','point', 'handle%d' % i)
 
                )
 
            dot.connect("button-press-event", self.dotpress, i)
 
            #self.tag_bind('handle%d' % i,"<ButtonPress-1>",
 
            #              lambda ev,i=i: self.dotpress(ev,i))
 
            #self.tag_bind('handle%d' % i, "<Key-d>",
 
            #              lambda ev, i=i: self.remove_point_idx(i))
 
                      
 
            self.dots[i]=dot
 

	
 
            self.dots[i] = dot
 

	
 
        self.highlight_selected_dots()
 

	
 
    def find_index_near(self,x,y):
 
    def find_index_near(self, x, y):
 
        tags = self.gettags(self.find_closest(x, y))
 
        try:
 
            handletags = [t for t in tags if t.startswith('handle')]
 
            return int(handletags[0][6:])
 
        except IndexError:
 
            raise ValueError("no point found")
 
        
 

	
 
    def new_point_at_mouse(self, ev):
 
        p = self.world_from_screen(ev.x,ev.y)
 
        p = self.world_from_screen(ev.x, ev.y)
 
        x = p[0]
 
        y = self.curve.eval(x)
 
        self.add_point((x, y))
 
@@ -931,13 +959,13 @@ class Curveview(object):
 
    def add_points(self, pts):
 
        idxs = [self.curve.insert_pt(p) for p in pts]
 
        self.select_indices(idxs)
 
        
 

	
 
    def add_point(self, p):
 
        self.add_points([p])
 

	
 
    def add_marker(self, p):
 
        self.markers.insert_pt(p)
 
        
 

	
 
    def remove_point_idx(self, *idxs):
 
        idxs = list(idxs)
 
        while idxs:
 
@@ -960,17 +988,17 @@ class Curveview(object):
 

	
 
            self.select_indices(newsel)
 
            idxs[:] = newidxs
 
            
 

	
 
    def highlight_selected_dots(self):
 
        if not self.redrawsEnabled:
 
            return
 

	
 
        for i,d in self.dots.items():
 
        for i, d in self.dots.items():
 
            if i in self.selected_points:
 
                d.set_property('fill_color', 'red')
 
            else:
 
                d.set_property('fill_color', 'blue')
 
        
 

	
 
    def dotpress(self, r1, r2, ev, dotidx):
 
        self.print_state("dotpress")
 
        if dotidx not in self.selected_points:
 
@@ -979,10 +1007,10 @@ class Curveview(object):
 
        self.last_mouse_world = self.world_from_screen(ev.x, ev.y)
 
        self.dragging_dots = True
 

	
 
    def select_between(self,start,end):
 
    def select_between(self, start, end):
 
        if start > end:
 
            start, end = end, start
 
        self.select_indices(self.curve.indices_between(start,end))
 
        self.select_indices(self.curve.indices_between(start, end))
 

	
 
    def onEnter(self, widget, event):
 
        self.entered = True
 
@@ -993,16 +1021,16 @@ class Curveview(object):
 
    def onMotion(self, widget, event):
 
        self.lastMouseX = event.x
 

	
 
        if event.state & Gdk.ModifierType.SHIFT_MASK and 1: # and B1
 
        if event.state & Gdk.ModifierType.SHIFT_MASK and 1:  # and B1
 
            self.sketch_motion(event)
 
            return
 

	
 
        self.select_motion(event)
 
        
 

	
 
        if not self.dragging_dots:
 
            return
 
        if not event.state & 256:
 
            return # not lmb-down
 
            return  # not lmb-down
 

	
 
        # this way is accumulating error and also making it harder to
 
        # undo (e.g. if the user moves far out of the window or
 
@@ -1013,58 +1041,60 @@ class Curveview(object):
 
            delta = (cur[0] - self.last_mouse_world[0],
 
                     cur[1] - self.last_mouse_world[1])
 
        else:
 
            delta = 0,0
 
            delta = 0, 0
 
        self.last_mouse_world = cur
 

	
 
        self.translate_points(delta)
 
        
 

	
 
    def translate_points(self, delta):
 
        moved = False
 
        
 

	
 
        cp = self.curve.points
 
        updates = []
 
        for idx in self.selected_points:
 

	
 
            newp = [cp[idx][0] + delta[0], cp[idx][1] + delta[1]]
 
            
 
            newp[1] = max(0,min(1,newp[1]))
 
            
 
            if idx>0 and newp[0] <= cp[idx-1][0]:
 

	
 
            newp[1] = max(0, min(1, newp[1]))
 

	
 
            if idx > 0 and newp[0] <= cp[idx - 1][0]:
 
                continue
 
            if idx<len(cp)-1 and newp[0] >= cp[idx+1][0]:
 
            if idx < len(cp) - 1 and newp[0] >= cp[idx + 1][0]:
 
                continue
 
            moved = True
 
            updates.append((idx, tuple(newp)))
 
        self.curve.set_points(updates)
 
        return moved
 
            
 

	
 
    def unselect(self):
 
        self.select_indices([])
 

	
 
    def onScroll(self, widget, event):
 
        t = self.world_from_screen(event.x, 0)[0]
 
        self.zoomControl.zoom_about_mouse(
 
            t, factor=1.5 if event.direction == Gdk.ScrollDirection.DOWN else 1/1.5)
 
            t,
 
            factor=1.5 if event.direction == Gdk.ScrollDirection.DOWN else 1 /
 
            1.5)
 
        # Don't actually scroll the canvas! (it shouldn't have room to
 
        # scroll anyway, but it does because of some coordinate errors
 
        # and borders and stuff)
 
        return True 
 
        
 
        return True
 

	
 
    def onRelease(self, widget, event):
 
        self.print_state("dotrelease")
 

	
 
        if event.state & Gdk.ModifierType.SHIFT_MASK: # relese-B1
 
        if event.state & Gdk.ModifierType.SHIFT_MASK:  # relese-B1
 
            self.sketch_release(event)
 
            return
 

	
 
        self.select_release(event)
 
 
 

	
 
        if not self.dragging_dots:
 
            return
 
        self.last_mouse_world = None
 
        self.dragging_dots = False
 

	
 

	
 
class CurveRow(object):
 
    """
 
    one of the repeating curve rows (including widgets on the left)
 
@@ -1073,6 +1103,7 @@ class CurveRow(object):
 

	
 
    please pack self.box
 
    """
 

	
 
    def __init__(self, graph, name, curve, markers, zoomControl):
 
        self.graph = graph
 
        self.name = name
 
@@ -1081,23 +1112,24 @@ class CurveRow(object):
 

	
 
        self.cols = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 0)
 
        self.box.add(self.cols)
 
        
 

	
 
        controls = Gtk.Frame()
 
        controls.set_size_request(160, -1)
 
        controls.set_shadow_type(Gtk.ShadowType.OUT)
 
        self.cols.pack_start(controls, expand=False, fill=True, padding=0)
 
        self.setupControls(controls, name, curve)
 

	
 
        self.curveView = Curveview(curve, markers,
 
        self.curveView = Curveview(curve,
 
                                   markers,
 
                                   isMusic=name in ['music', 'smooth_music'],
 
                                   zoomControl=zoomControl)
 
        
 

	
 
        self.initCurveView()
 
        dispatcher.connect(self.rebuild, "all curves rebuild")
 

	
 
    def isFocus(self):
 
        return self.curveView.widget.is_focus()
 
        
 

	
 
    def rebuild(self):
 
        raise NotImplementedError('obsolete, if curves are drawing right')
 
        self.curveView.rebuild()
 
@@ -1108,32 +1140,36 @@ class CurveRow(object):
 
        self.curveView.entered = False  # help suppress bad position events
 
        del self.curveView
 
        self.box.destroy()
 
        
 

	
 
    def initCurveView(self):
 
        self.curveView.widget.show()
 
        self.setHeight(100)
 
        self.cols.pack_start(self.curveView.widget, expand=True, fill=True, padding=0)       
 
        self.cols.pack_start(self.curveView.widget,
 
                             expand=True,
 
                             fill=True,
 
                             padding=0)
 

	
 
    def setHeight(self, h):
 
        self.curveView.widget.set_size_request(-1, h)
 

	
 
        # this should have been automatic when the size changed, but
 
        # the signals for that are wrong somehow.
 
        reactor.callLater(0, self.curveView.setCanvasToWidgetSize) 
 
        
 
        reactor.callLater(0, self.curveView.setCanvasToWidgetSize)
 

	
 
    def setupControls(self, controls, name, curve):
 
        box = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 0)
 
        controls.add(box)
 

	
 
        curve_name_label = Gtk.LinkButton()
 
        print "need to truncate this name length somehow"
 

	
 
        def update_label():
 
            # todo: abort if we don't still exist...
 
            p = curve_name_label.props
 
            p.uri = curve.uri
 
            p.label = self.graph.label(curve.uri)
 

	
 
        self.graph.addHandler(update_label)
 
        
 

	
 
        self.muted = Gtk.CheckButton("M")
 
        self.muted.connect("toggled", self.sync_mute_to_curve)
 
@@ -1144,7 +1180,7 @@ class CurveRow(object):
 

	
 
    def onDelete(self):
 
        self.curveView.onDelete()
 
        
 

	
 
    def sync_mute_to_curve(self, *args):
 
        """send value from CheckButton to the master attribute inside Curve"""
 
        new_mute = self.muted.get_active()
 
@@ -1172,6 +1208,7 @@ class Curvesetview(object):
 
    """
 
    
 
    """
 

	
 
    def __init__(self, graph, curvesVBox, zoomControlBox, curveset):
 
        self.graph = graph
 
        self.live = True
 
@@ -1184,13 +1221,13 @@ class Curvesetview(object):
 
        self.zoomControl.redrawzoom()
 

	
 
        for uri, label, curve in curveset.currentCurves():
 
            self.add_curve(uri, label, curve) 
 
            self.add_curve(uri, label, curve)
 

	
 
        dispatcher.connect(self.clear_curves, "clear_curves")
 
        dispatcher.connect(self.add_curve, "add_curve", sender=self.curveset)
 
        dispatcher.connect(self.set_featured_curves, "set_featured_curves")
 
        dispatcher.connect(self.song_has_changed, "song_has_changed")
 
        
 

	
 
        self.newcurvename = Gtk.EntryBuffer.new("", 0)
 

	
 
        eventBox = self.curvesVBox.get_parent()
 
@@ -1200,7 +1237,7 @@ class Curvesetview(object):
 
        self.watchCurveAreaHeight()
 

	
 
    def __del__(self):
 
        print "del curvesetview", id(self) 
 
        print "del curvesetview", id(self)
 

	
 
    def initZoomControl(self, zoomControlBox):
 
        import light9.curvecalc.zoomcontrol
 
@@ -1209,7 +1246,7 @@ class Curvesetview(object):
 
        zoomControlBox.add(zoomControl.widget)
 
        zoomControl.widget.show_all()
 
        return zoomControl
 
        
 

	
 
    def clear_curves(self):
 
        """curveset is about to re-add all new curves"""
 
        while self.allCurveRows:
 
@@ -1217,7 +1254,7 @@ class Curvesetview(object):
 

	
 
    def song_has_changed(self):
 
        self.zoomControl.redrawzoom()
 
        
 

	
 
    def takeFocus(self, *args):
 
        """the whole curveset's eventbox is what gets the focus, currently, so
 
        keys like 'c' can work in it"""
 
@@ -1233,17 +1270,17 @@ class Curvesetview(object):
 
    def set_featured_curves(self, curveNames):
 
        """bring these curves to the top of the stack"""
 
        for n in curveNames[::-1]:
 
            self.curvesVBox.reorder_child(self.curveRow_from_name(n).box,
 
                                          Gtk.PACK_START)
 
        
 
            self.curvesVBox.reorder_child(
 
                self.curveRow_from_name(n).box, Gtk.PACK_START)
 

	
 
    def onKeyPress(self, widget, event):
 
        if not self.live: # workaround for old instances living past reload()
 
        if not self.live:  # workaround for old instances living past reload()
 
            return
 

	
 
        r = self.row_under_mouse()
 
        key = event.string
 
        pass # no handlers right now
 
 
 
        pass  # no handlers right now
 

	
 
    def row_under_mouse(self):
 
        x, y = self.curvesVBox.get_pointer()
 
        for r in self.allCurveRows:
 
@@ -1259,7 +1296,7 @@ class Curvesetview(object):
 
    def new_curve(self, event):
 
        self.curveset.new_curve(self.newcurvename.get())
 
        self.newcurvename.set('')
 
        
 

	
 
    def add_curve(self, uri, label, curve):
 
        if isinstance(label, Literal):
 
            label = str(label)
 
@@ -1273,6 +1310,7 @@ class Curvesetview(object):
 
        f.curveView.goLive()
 

	
 
    def watchCurveAreaHeight(self):
 

	
 
        def sizeEvent(w, size):
 
            # this is firing really often
 
            if self.visibleHeight == size.height:
 
@@ -1285,7 +1323,7 @@ class Curvesetview(object):
 
        visibleArea.connect('size-allocate', sizeEvent)
 

	
 
        dispatcher.connect(self.setRowHeights, "curve row focus change")
 
        
 

	
 
    def setRowHeights(self):
 
        nRows = len(self.allCurveRows)
 
        if not nRows:
 
@@ -1296,14 +1334,13 @@ class Curvesetview(object):
 
        if anyFocus:
 
            focusHeight = max(100, evenHeight)
 
            if nRows > 1:
 
                otherHeight = max(14,
 
                                  (self.visibleHeight - focusHeight) //
 
                otherHeight = max(14, (self.visibleHeight - focusHeight) //
 
                                  (nRows - 1)) - 3
 
        else:
 
            otherHeight = evenHeight
 
        for row in self.allCurveRows:
 
            row.setHeight(focusHeight if row.isFocus() else otherHeight)
 
            
 

	
 
    def row(self, name):
 
        if isinstance(name, Literal):
 
            name = str(name)
 
@@ -1316,13 +1353,10 @@ class Curvesetview(object):
 
    def goLive(self):
 
        """for startup performance, none of the curves redraw
 
        themselves until this is called once (and then they're normal)"""
 
        
 

	
 
        for cr in self.allCurveRows:
 
            cr.curveView.goLive()
 

	
 
    def onDelete(self):
 
        for r in self.allCurveRows:
 
            r.onDelete()
 

	
 

	
 
        
light9/curvecalc/musicaccess.py
Show inline comments
 
@@ -5,14 +5,16 @@ from light9 import networking
 
from twisted.internet import reactor
 
from twisted.web.client import Agent
 
from twisted.internet.protocol import Protocol
 
from twisted.internet.defer import Deferred     
 
from twisted.internet.defer import Deferred
 
from zope.interface import implements
 
from twisted.internet.defer import succeed
 
from twisted.web.iweb import IBodyProducer
 

	
 

	
 
class GatherJson(Protocol):
 
    """calls back the 'finished' deferred with the parsed json data we
 
    received"""
 

	
 
    def __init__(self, finished):
 
        self.finished = finished
 
        self.buf = ""
 
@@ -23,6 +25,7 @@ class GatherJson(Protocol):
 
    def connectionLost(self, reason):
 
        self.finished.callback(json.loads(self.buf))
 

	
 

	
 
class StringProducer(object):
 
    # http://twistedmatrix.com/documents/current/web/howto/client.html
 
    implements(IBodyProducer)
 
@@ -41,12 +44,14 @@ class StringProducer(object):
 
    def stopProducing(self):
 
        pass
 

	
 

	
 
class Music:
 

	
 
    def __init__(self):
 
        self.recenttime=0
 
        self.recenttime = 0
 
        self.player = Agent(reactor)
 
        self.timePath = networking.musicPlayer.path("time")
 
        
 

	
 
    def current_time(self):
 
        """return deferred which gets called with the current
 
        time. This gets called really often"""
 
@@ -65,8 +70,8 @@ class Music:
 
            dispatcher.send("input time", val=data['t'])
 
        if 'song' in data and data['song']:
 
            dispatcher.send("current_player_song", song=URIRef(data['song']))
 
        return data['t'] # pass along to the real receiver
 
    
 
        return data['t']  # pass along to the real receiver
 

	
 
    def playOrPause(self, t=None):
 
        if t is None:
 
            # could be better
 
@@ -74,4 +79,5 @@ class Music:
 
        else:
 
            self.player.request("POST",
 
                                networking.musicPlayer.path("seekPlayOrPause"),
 
                                bodyProducer=StringProducer(json.dumps({"t" : t})))
 
                                bodyProducer=StringProducer(json.dumps({"t":
 
                                                                        t})))
light9/curvecalc/output.py
Show inline comments
 
@@ -7,33 +7,35 @@ from light9.curvecalc.subterm import Sub
 
from louie import dispatcher
 
log = logging.getLogger("output")
 

	
 

	
 
class Output(object):
 
    lastsendtime=0
 
    lastsendlevs=None
 
    lastsendtime = 0
 
    lastsendlevs = None
 

	
 
    def __init__(self, graph, session, music, curveset, currentSubterms):
 
        self.graph, self.session, self.music = graph, session, music
 
        self.currentSubterms = currentSubterms
 
        self.curveset = curveset
 

	
 
        self.recent_t=[]
 
        self.recent_t = []
 
        self.later = None
 

	
 
        self.update()
 
        
 

	
 
    def update(self):
 
        d = self.music.current_time()
 
        d.addCallback(self.update2)
 
        d.addErrback(self.updateerr)
 
        
 
    def updateerr(self,e):
 

	
 
    def updateerr(self, e):
 

	
 
        print e.getTraceback()
 
        dispatcher.send("update status", val=e.getErrorMessage())
 
        if self.later and not self.later.cancelled and not self.later.called:
 
            self.later.cancel()
 
        self.later = reactor.callLater(1, self.update)
 
        
 
    def update2(self,t):
 

	
 
    def update2(self, t):
 
        # spot alsa soundcard offset is always 0, we get times about a
 
        # second ahead of what's really getting played
 
        #t = t - .7
 
@@ -44,28 +46,29 @@ class Output(object):
 

	
 
        self.later = reactor.callLater(.02, self.update)
 

	
 
        self.recent_t = self.recent_t[-50:]+[t]
 
        self.recent_t = self.recent_t[-50:] + [t]
 
        period = (self.recent_t[-1] - self.recent_t[0]) / len(self.recent_t)
 
        dispatcher.send("update period", val=period)
 
        self.send_dmx(t)
 
        
 

	
 
    def send_dmx(self, t):
 
        dispatcher.send("curves to sliders", t=t)
 

	
 
        if not self.currentSubterms:
 
            return
 
            
 
        scaledsubs=[]
 

	
 
        scaledsubs = []
 
        for st in self.currentSubterms:
 
            scl = st.scaled(t)
 
            scaledsubs.append(scl)
 
                
 

	
 
        out = Submaster.sub_maxes(*scaledsubs)
 
        levs = out.get_levels()
 
        now=time.time()
 
        if now-self.lastsendtime>5 or levs!=self.lastsendlevs:
 
            dispatcher.send("output levels",val=levs)
 
        now = time.time()
 
        if now - self.lastsendtime > 5 or levs != self.lastsendlevs:
 
            dispatcher.send("output levels", val=levs)
 
            dmxclient.outputlevels(out.get_dmx_list(),
 
                                   twisted=1,clientid='curvecalc')
 
                                   twisted=1,
 
                                   clientid='curvecalc')
 
            self.lastsendtime = now
 
            self.lastsendlevs = levs
light9/curvecalc/subterm.py
Show inline comments
 
@@ -8,24 +8,26 @@ from rdfdb.patch import Patch
 
from light9.namespaces import L9
 
log = logging.getLogger()
 

	
 

	
 
class Expr(object):
 
    """singleton, provides functions for use in subterm expressions,
 
    e.g. chases"""
 

	
 
    def __init__(self):
 
        self.effectGlobals = light9.Effects.configExprGlobals()
 
    
 

	
 
    def exprGlobals(self, startDict, t):
 
        """globals dict for use by expressions"""
 

	
 
        glo = startDict.copy()
 
        
 

	
 
        # add in functions from Effects
 
        glo.update(self.effectGlobals)
 

	
 
        def chan(name):
 
            return Submaster.Submaster(
 
                name=name,
 
                levels={get_dmx_channel(name) : 1.0})
 
            return Submaster.Submaster(name=name,
 
                                       levels={get_dmx_channel(name): 1.0})
 

	
 
        glo['chan'] = chan
 
        glo['within'] = lambda a, b: a < t < b
 
        glo['bef'] = lambda x: t < x
 
@@ -36,20 +38,24 @@ class Expr(object):
 
            if left < t < right:
 
                return light9.Effects.smoove((t - left) / (right - left))
 
            return t > x
 

	
 
        glo['aft'] = lambda x, smooth=0: aft(t, x, smooth)
 

	
 
        glo['smooth_random'] = lambda speed=1: glo['smooth_random2'](t, speed)
 
        glo['notch_random'] = lambda speed=1: glo['notch_random2'](t, speed)
 
        
 

	
 
        glo['noise'] = glo['smooth_random']
 
        glo['notch'] = glo['notch_random']
 

	
 
        return glo
 

	
 

	
 
exprglo = Expr()
 
        
 

	
 

	
 
class Subterm(object):
 
    """one Submaster and its expression evaluator"""
 

	
 
    def __init__(self, graph, subterm, saveContext, curveset):
 
        self.graph, self.uri = graph, subterm
 
        self.saveContext = saveContext
 
@@ -57,16 +63,19 @@ class Subterm(object):
 
        self.ensureExpression(saveContext)
 

	
 
        self.submasters = Submaster.get_global_submasters(self.graph)
 
        
 

	
 
    def ensureExpression(self, saveCtx):
 
        with self.graph.currentState(tripleFilter=(self.uri, None, None)) as current:
 
        with self.graph.currentState(tripleFilter=(self.uri, None,
 
                                                   None)) as current:
 
            if current.value(self.uri, L9['expression']) is None:
 
                self.graph.patch(Patch(addQuads=[
 
                    (self.uri, L9['expression'], Literal("..."), saveCtx),
 
                self.graph.patch(
 
                    Patch(addQuads=[
 
                        (self.uri, L9['expression'], Literal("..."), saveCtx),
 
                    ]))
 

	
 
    def scaled(self, t):
 
        with self.graph.currentState(tripleFilter=(self.uri, None, None)) as current:
 
        with self.graph.currentState(tripleFilter=(self.uri, None,
 
                                                   None)) as current:
 
            subexpr_eval = self.eval(current, t)
 
            # we prevent any exceptions from escaping, since they cause us to
 
            # stop sending levels
 
@@ -75,7 +84,7 @@ class Subterm(object):
 
                    # if the expression returns a submaster, just return it
 
                    return subexpr_eval
 
                else:
 
                    # otherwise, return our submaster multiplied by the value 
 
                    # otherwise, return our submaster multiplied by the value
 
                    # returned
 
                    if subexpr_eval == 0:
 
                        return Submaster.Submaster("zero", {})
 
@@ -89,7 +98,8 @@ class Subterm(object):
 
    def curves_used_by_expr(self):
 
        """names of curves that are (maybe) used in this expression"""
 

	
 
        with self.graph.currentState(tripleFilter=(self.uri, None, None)) as current:
 
        with self.graph.currentState(tripleFilter=(self.uri, None,
 
                                                   None)) as current:
 
            expr = current.value(self.uri, L9['expression'])
 

	
 
        used = []
 
@@ -106,21 +116,24 @@ class Subterm(object):
 
        if len(objs) > 1:
 
            raise ValueError("found multiple expressions for %s: %s" %
 
                             (self.uri, objs))
 
        
 

	
 
        expr = current.value(self.uri, L9['expression'])
 
        if not expr:
 
            dispatcher.send("expr_error", sender=self.uri, exc="no expr, using 0")
 
            dispatcher.send("expr_error",
 
                            sender=self.uri,
 
                            exc="no expr, using 0")
 
            return 0
 
        glo = self.curveset.globalsdict()
 
        glo['t'] = t
 

	
 
        glo = exprglo.exprGlobals(glo, t)
 
        glo['getsub'] = lambda name: self.submasters.get_sub_by_name(name)
 
        glo['chan'] = lambda name: Submaster.Submaster("chan", {get_dmx_channel(name): 1})
 
        
 
        glo['chan'] = lambda name: Submaster.Submaster(
 
            "chan", {get_dmx_channel(name): 1})
 

	
 
        try:
 
            self.lasteval = eval(expr, glo)
 
        except Exception,e:
 
        except Exception, e:
 
            dispatcher.send("expr_error", sender=self.uri, exc=e)
 
            return Submaster.Submaster("zero", {})
 
        else:
light9/curvecalc/subtermview.py
Show inline comments
 
@@ -9,7 +9,9 @@ log = logging.getLogger()
 
# keeping a ref to the __dict__ of the object stops it from getting zeroed
 
keep = []
 

	
 

	
 
class Subexprview(object):
 

	
 
    def __init__(self, graph, ownerSubterm, saveContext, curveset):
 
        self.graph, self.ownerSubterm = graph, ownerSubterm
 
        self.saveContext = saveContext
 
@@ -30,8 +32,10 @@ class Subexprview(object):
 
        self.entryBuffer.connect("inserted-text", self.entry_changed)
 

	
 
        self.entry.connect("focus-in-event", self.onFocus)
 
        
 
        dispatcher.connect(self.exprError, "expr_error", sender=self.ownerSubterm)
 

	
 
        dispatcher.connect(self.exprError,
 
                           "expr_error",
 
                           sender=self.ownerSubterm)
 
        keep.append(self.__dict__)
 

	
 
    def onFocus(self, *args):
 
@@ -40,30 +44,31 @@ class Subexprview(object):
 

	
 
        usedCurves = [n for n in curveNames if n in currentExpr]
 
        usedCurves.sort()
 
        
 

	
 
        dispatcher.send("set_featured_curves", curveNames=usedCurves)
 
        
 

	
 
    def exprError(self, exc):
 
        self.error.set_text(str(exc))
 
        
 

	
 
    def set_expression_from_graph(self):
 
        e = str(self.graph.value(self.ownerSubterm, L9['expression']))
 
        print "from graph, set to %r" % e
 

	
 
        if e != self.entryBuffer.get_text():
 
            self.entryBuffer.set_text(e, len(e))
 
            
 

	
 
    def entry_changed(self, *args):
 
        log.info("want to patch to %r", self.entryBuffer.get_text())
 
        self.graph.patchObject(self.saveContext,
 
                               self.ownerSubterm,
 
        self.graph.patchObject(self.saveContext, self.ownerSubterm,
 
                               L9['expression'],
 
                               Literal(self.entryBuffer.get_text()))
 
            
 

	
 

	
 
class Subtermview(object):
 
    """
 
    has .label and .exprView widgets for you to put in a table
 
    """
 

	
 
    def __init__(self, st, curveset):
 
        self.subterm = st
 
        self.graph = st.graph
 
@@ -72,19 +77,18 @@ class Subtermview(object):
 
        self.graph.addHandler(self.setName)
 

	
 
        self.label.drag_dest_set(flags=Gtk.DEST_DEFAULT_ALL,
 
                            targets=[('text/uri-list', 0, 0)],
 
                            actions=Gtk.gdk.ACTION_COPY)
 
                                 targets=[('text/uri-list', 0, 0)],
 
                                 actions=Gtk.gdk.ACTION_COPY)
 
        self.label.connect("drag-data-received", self.onDataReceivedOnLabel)
 
        
 
        sev = Subexprview(self.graph, self.subterm.uri, self.subterm.saveContext, curveset)
 

	
 
        sev = Subexprview(self.graph, self.subterm.uri,
 
                          self.subterm.saveContext, curveset)
 
        self.exprView = sev.box
 

	
 
    def onDataReceivedOnLabel(self, widget, context, x, y, selection,
 
                       targetType, time):
 
        self.graph.patchObject(self.subterm.saveContext,
 
                               self.subterm.uri,
 
                               L9['sub'],
 
                               URIRef(selection.data.strip()))
 
                              targetType, time):
 
        self.graph.patchObject(self.subterm.saveContext, self.subterm.uri,
 
                               L9['sub'], URIRef(selection.data.strip()))
 

	
 
    def setName(self):
 
        # some of this could be pushed into Submaster
 
@@ -99,22 +103,24 @@ class Subtermview(object):
 
            return
 
        self.label.set_text(label)
 

	
 

	
 
def add_one_subterm(subterm, curveset, master, show=False):
 
    stv = Subtermview(subterm, curveset)
 
    
 

	
 
    y = master.get_property('n-rows')
 
    master.attach(stv.label, 0, 1, y, y + 1, xoptions=0, yoptions=0)
 
    master.attach(stv.exprView, 1, 2, y, y + 1, yoptions=0)
 
    scrollToRowUponAdd(stv.label)  
 
    scrollToRowUponAdd(stv.label)
 
    if show:
 
        master.show_all()
 

	
 

	
 
def scrollToRowUponAdd(widgetInRow):
 
    """when this table widget is ready, scroll the table so we can see it"""
 
    
 

	
 
    # this doesn't work right, yet
 
    return
 
    
 

	
 
    vp = widgetInRow
 
    while vp.get_name() != 'GtkViewport':
 
        log.info("walk %s", vp.get_name())
 
@@ -125,5 +131,5 @@ def scrollToRowUponAdd(widgetInRow):
 
        log.info("scroll %s", adj.props.value)
 
        adj.props.value = adj.props.upper
 
        widgetInRow.disconnect(handler)
 
        
 

	
 
    handler = widgetInRow.connect('expose-event', firstExpose, adj, widgetInRow)
light9/curvecalc/zoomcontrol.py
Show inline comments
 
@@ -3,10 +3,11 @@ from gi.repository import Gtk
 
from gi.repository import GObject
 
from gi.repository import GooCanvas
 
import louie as dispatcher
 
from light9.curvecalc import cursors 
 
from light9.curvecalc import cursors
 
from lib.goocanvas_compat import Points, polyline_new_line
 
from twisted.internet import reactor
 

	
 

	
 
class ZoomControl(object):
 
    """
 
    please pack .widget
 
@@ -16,43 +17,63 @@ class ZoomControl(object):
 

	
 
    def maxtime():
 
        doc = "seconds at the right edge of the bar"
 
        def fget(self): return self._maxtime
 

	
 
        def fget(self):
 
            return self._maxtime
 

	
 
        def fset(self, value):
 
            self._maxtime = value
 
            self.redrawzoom()
 

	
 
        return locals()
 

	
 
    maxtime = property(**maxtime())
 

	
 
    _end = _start = 0
 

	
 
    def start():
 
        def fget(self): return self._start
 
        def fset(self,v):
 
            v = max(self.mintime,v)
 

	
 
        def fget(self):
 
            return self._start
 

	
 
        def fset(self, v):
 
            v = max(self.mintime, v)
 
            # don't protect for start<end since zooming sometimes sets
 
            # start temporarily after end
 
            self._start = v
 

	
 
        return locals()
 

	
 
    start = property(**start())
 

	
 
    def end():
 
        def fget(self): return self._end
 
        def fset(self,v):
 
            v = min(self.maxtime,v)
 

	
 
        def fget(self):
 
            return self._end
 

	
 
        def fset(self, v):
 
            v = min(self.maxtime, v)
 
            self._end = v
 

	
 
        return locals()
 

	
 
    end = property(**end())
 

	
 
    def offset():
 
        doc = "virtual attr that adjusts start and end together"
 

	
 
        def fget(self):
 
            # work off the midpoint so that "crushing works equally
 
            # well in both directions
 
            return (self.start + self.end) / 2
 

	
 
        def fset(self, value):
 
            d = self.end-self.start
 
            d = self.end - self.start
 
            self.start = value - d / 2
 
            self.end = value + d / 2
 

	
 
        return locals()
 

	
 
    offset = property(**offset())
 

	
 
    def __init__(self, **kw):
 
@@ -72,43 +93,45 @@ class ZoomControl(object):
 

	
 
        self.root = self.widget.get_root_item()
 
        self.leftbrack = polyline_new_line(parent=self.root,
 
                                            line_width=5, stroke_color='black')
 
                                           line_width=5,
 
                                           stroke_color='black')
 
        self.rightbrack = polyline_new_line(parent=self.root,
 
                                             line_width=5, stroke_color='black')
 
                                            line_width=5,
 
                                            stroke_color='black')
 
        self.shade = GooCanvas.CanvasRect(parent=self.root,
 
                                    fill_color='gray70',
 
                                    line_width=.5)
 
                                          fill_color='gray70',
 
                                          line_width=.5)
 
        self.time = polyline_new_line(parent=self.root,
 
                                       line_width=2,
 
                                       stroke_color='red')
 
                                      line_width=2,
 
                                      stroke_color='red')
 

	
 
        self.redrawzoom()
 
        self.widget.connect("size-allocate", self.redrawzoom)
 

	
 
        self.widget.connect("motion-notify-event", self.adjust)
 
        self.widget.connect("button-release-event", self.release)
 
        self.leftbrack.connect("button-press-event",
 
                               lambda i, t, ev: self.press(ev, 'start'))
 
        self.rightbrack.connect("button-press-event",
 
                                lambda i, t, ev: self.press(ev, 'end'))
 
        self.shade.connect("button-press-event",
 
                           lambda i, t, ev: self.press(ev, 'offset'))
 
        
 
        dispatcher.connect(self.input_time,"input time")
 
        self.leftbrack.connect(
 
            "button-press-event", lambda i, t, ev: self.press(ev, 'start'))
 
        self.rightbrack.connect(
 
            "button-press-event", lambda i, t, ev: self.press(ev, 'end'))
 
        self.shade.connect(
 
            "button-press-event", lambda i, t, ev: self.press(ev, 'offset'))
 

	
 
        dispatcher.connect(self.input_time, "input time")
 
        dispatcher.connect(self.max_time, "max time")
 
        dispatcher.connect(self.zoom_about_mouse, "zoom about mouse")
 
        dispatcher.connect(self.see_time, "see time")
 
        dispatcher.connect(self.see_time_until_end, "see time until end")
 
        dispatcher.connect(self.show_all, "show all")
 
        dispatcher.connect(self.zoom_to_range, "zoom to range")
 
        self.created=1
 
        self.created = 1
 
        self.lastTime = 0
 

	
 
    def max_time(self, maxtime):
 
        self.maxtime = maxtime
 
        self.redrawzoom()
 
    
 
    def zoom_to_range(self,start,end):
 

	
 
    def zoom_to_range(self, start, end):
 
        self.start = start
 
        self.end = end
 
        self.redrawzoom()
 
@@ -118,9 +141,9 @@ class ZoomControl(object):
 
        self.end = self.maxtime
 
        self.redrawzoom()
 

	
 
    def zoom_about_mouse(self,t,factor):
 
        self.start = t - factor*(t-self.start)
 
        self.end = t + factor*(self.end-t)
 
    def zoom_about_mouse(self, t, factor):
 
        self.start = t - factor * (t - self.start)
 
        self.end = t + factor * (self.end - t)
 
        self.redrawzoom()
 

	
 
    def see_time(self, t=None):
 
@@ -144,7 +167,7 @@ class ZoomControl(object):
 
        self.end = self.maxtime
 

	
 
        self.redrawzoom()
 
            
 

	
 
    def input_time(self, val):
 
        """move time cursor to this time"""
 
        self.lastTime = val
 
@@ -152,47 +175,48 @@ class ZoomControl(object):
 
            x = self.can_for_t(self.lastTime)
 
        except ZeroDivisionError:
 
            x = -100
 
        self.time.set_property("points",
 
                               Points([(x, 0), (x, self.size.height)]))
 
        
 
    def press(self,ev,attr):
 
        self.time.set_property("points", Points([(x, 0),
 
                                                 (x, self.size.height)]))
 

	
 
    def press(self, ev, attr):
 
        self.adjustingattr = attr
 
        
 

	
 
    def release(self, widget, ev):
 
        if hasattr(self,'adjustingattr'):
 
        if hasattr(self, 'adjustingattr'):
 
            del self.adjustingattr
 
        if hasattr(self,'lastx'):
 
        if hasattr(self, 'lastx'):
 
            del self.lastx
 
        
 

	
 
    def adjust(self, widget, ev):
 

	
 
        if not hasattr(self,'adjustingattr'):
 
        if not hasattr(self, 'adjustingattr'):
 
            return
 
        attr = self.adjustingattr
 
        
 
        if not hasattr(self,'lastx'):
 

	
 
        if not hasattr(self, 'lastx'):
 
            self.lastx = ev.x
 
        new = self.can_for_t(getattr(self,attr)) + (ev.x - self.lastx)
 
        new = self.can_for_t(getattr(self, attr)) + (ev.x - self.lastx)
 
        self.lastx = ev.x
 
        setattr(self,attr,self.t_for_can(new))
 
        setattr(self, attr, self.t_for_can(new))
 
        self.redrawzoom()
 

	
 
    def can_for_t(self,t):
 
    def can_for_t(self, t):
 
        a, b = self.mintime, self.maxtime
 
        return (t - a) / (b - a) * (self.size.width - 30) + 20
 
    def t_for_can(self,x):
 

	
 
    def t_for_can(self, x):
 
        a, b = self.mintime, self.maxtime
 
        return (x - 20) / (self.size.width - 30) * (b - a) + a
 
        
 
    def redrawzoom(self,*args):
 

	
 
    def redrawzoom(self, *args):
 
        # often, this was clearing the zoom widget and not repainting right
 
        reactor.callLater(0, self._redrawzoom)
 
        
 

	
 
    def _redrawzoom(self):
 
        """redraw pieces based on start/end"""
 
        self.size = self.widget.get_allocation()
 
        dispatcher.send("zoom changed")
 
        if not hasattr(self,'created'):
 
        if not hasattr(self, 'created'):
 
            return
 
        y1, y2 = 3, self.size.height - 3
 
        lip = 6
 
@@ -203,46 +227,44 @@ class ZoomControl(object):
 
            # todo: set the zoom to some clear null state
 
            return
 

	
 
        self.leftbrack.set_property("points", Points([
 
            (scan + lip, y1),
 
            (scan, y1),
 
            (scan, y2),
 
            (scan + lip, y2)]))
 
        self.rightbrack.set_property("points", Points([
 
            (ecan - lip, y1),
 
            (ecan, y1),
 
            (ecan, y2),
 
            (ecan - lip, y2)]))
 
        self.shade.set_properties(
 
            x=scan + 5,
 
            y=y1 + lip,
 
            width=max(0, ecan - 5 - (scan + 5)),
 
            height=max(0, y2 - lip - (y1 + lip)))
 
        self.leftbrack.set_property(
 
            "points",
 
            Points([(scan + lip, y1), (scan, y1), (scan, y2),
 
                    (scan + lip, y2)]))
 
        self.rightbrack.set_property(
 
            "points",
 
            Points([(ecan - lip, y1), (ecan, y1), (ecan, y2),
 
                    (ecan - lip, y2)]))
 
        self.shade.set_properties(x=scan + 5,
 
                                  y=y1 + lip,
 
                                  width=max(0, ecan - 5 - (scan + 5)),
 
                                  height=max(0, y2 - lip - (y1 + lip)))
 

	
 
        self.redrawTics()
 
        
 

	
 
    def redrawTics(self):
 
        if hasattr(self, 'ticsGroup'):
 
            self.ticsGroup.remove()
 
        self.ticsGroup = GooCanvas.CanvasGroup(parent=self.root)
 

	
 
        lastx =- 1000
 
        lastx = -1000
 

	
 
        for t in range(0,int(self.maxtime)):
 
        for t in range(0, int(self.maxtime)):
 
            x = self.can_for_t(t)
 
            if 0 < x < self.size.width and x - lastx > 30:
 
                txt = str(t)
 
                if lastx == -1000:
 
                    txt = txt + "sec"
 
                GooCanvas.CanvasPolyline(parent=self.ticsGroup,
 
                                   points=Points([(x, 0), (x, 15)]),
 
                                   line_width=.8,
 
                                   stroke_color='black')
 
                                         points=Points([(x, 0), (x, 15)]),
 
                                         line_width=.8,
 
                                         stroke_color='black')
 
                GooCanvas.CanvasText(parent=self.ticsGroup,
 
                               x=x, y=self.size.height-1,
 
                               anchor=GooCanvas.CanvasAnchorType.SOUTH,
 
                               text=txt,
 
                               font='ubuntu 7')
 
                                     x=x,
 
                                     y=self.size.height - 1,
 
                                     anchor=GooCanvas.CanvasAnchorType.SOUTH,
 
                                     text=txt,
 
                                     font='ubuntu 7')
 
                lastx = x
 

	
 

	
 
@@ -251,22 +273,23 @@ class RegionZoom:
 

	
 
    this is used with Curveview
 
    """
 

	
 
    def __init__(self, canvas, world_from_screen, screen_from_world):
 
        self.canvas, self.world_from_screen = canvas, world_from_screen
 
        self.screen_from_world = screen_from_world
 

	
 
        for evtype, method in [("ButtonPress-1",self.press),
 
                               ("Motion",self.motion),
 
                               ("ButtonRelease-1",self.release)]:
 
        for evtype, method in [("ButtonPress-1", self.press),
 
                               ("Motion", self.motion),
 
                               ("ButtonRelease-1", self.release)]:
 
            #canvas.bind("<Control-Alt-%s>" % evtype, method, add=True)
 
            if 1 or evtype != "ButtonPress-1":
 
                canvas.bind("<%s>" % evtype, method,add=True)
 
                canvas.bind("<%s>" % evtype, method, add=True)
 

	
 
        canvas.bind("<Leave>", self.finish)
 
        self.start_t = self.old_cursor = None
 
        self.state = self.mods = None
 

	
 
    def press(self,ev):
 
    def press(self, ev):
 
        if self.state is not None:
 
            self.finish()
 

	
 
@@ -277,19 +300,24 @@ class RegionZoom:
 
            # sketching handler gets the event
 
            self.mods = "c-s-a"
 
        elif ev.state == 0:
 
            return # no 
 
            return  # no
 
            self.mods = "none"
 
        else:
 
            return
 
        self.state = "buttonpress"
 
            
 
        self.start_t = self.end_t = self.world_from_screen(ev.x,0)[0]
 

	
 
        self.start_t = self.end_t = self.world_from_screen(ev.x, 0)[0]
 
        self.start_x = ev.x
 
        can = self.canvas
 

	
 
        for pos in ('start_t','end_t','hi','lo'):
 
            can.create_line(0,0,50,50, width=3, fill='yellow',
 
                            tags=("regionzoom",pos))
 
        for pos in ('start_t', 'end_t', 'hi', 'lo'):
 
            can.create_line(0,
 
                            0,
 
                            50,
 
                            50,
 
                            width=3,
 
                            fill='yellow',
 
                            tags=("regionzoom", pos))
 
        # if updatelines isn't called here, subsequent updatelines
 
        # will fail for reasons i don't understand
 
        self.updatelines()
 
@@ -297,41 +325,41 @@ class RegionZoom:
 
        # todo: just holding the modifiers ought to turn on the zoom
 
        # cursor (which is not finished)
 
        cursors.push(can, "@light9/cursor1.xbm")
 
        
 

	
 
    def updatelines(self):
 

	
 
        # better would be a gray25 rectangle over the region
 
        
 

	
 
        can = self.canvas
 
        pos_x = {}
 
        height = can.winfo_height()
 
        for pos in ('start_t', 'end_t'):
 
            pos_x[pos] = x = self.screen_from_world((getattr(self,pos),0))[0]
 
            pos_x[pos] = x = self.screen_from_world((getattr(self, pos), 0))[0]
 
            cid = can.find_withtag("regionzoom && %s" % pos)
 
            can.coords(cid, x, 0, x, height)
 
            
 
        for tag,frac in [('hi',.1),('lo',.9)]:
 

	
 
        for tag, frac in [('hi', .1), ('lo', .9)]:
 
            cid = can.find_withtag("regionzoom && %s" % tag)
 
            can.coords(cid, pos_x['start_t'], frac * height,
 
                       pos_x['end_t'], frac * height)
 
            can.coords(cid, pos_x['start_t'], frac * height, pos_x['end_t'],
 
                       frac * height)
 

	
 
    def motion(self,ev):
 
    def motion(self, ev):
 
        if self.state != "buttonpress":
 
            return
 

	
 
        self.end_t = self.world_from_screen(ev.x,0)[0]
 
        self.end_t = self.world_from_screen(ev.x, 0)[0]
 
        self.updatelines()
 

	
 
    def release(self,ev):
 
    def release(self, ev):
 
        if self.state != "buttonpress":
 
            return
 
        
 

	
 
        if abs(self.start_x - ev.x) < 10:
 
            # clicked
 
            if self.mods in ("c-a", "c-s-a"):
 
                factor = 1/1.5
 
                factor = 1 / 1.5
 
                if self.mods == "c-s-a":
 
                    factor = 1.5 # c-s-a-b1 zooms out
 
                    factor = 1.5  # c-s-a-b1 zooms out
 
                dispatcher.send("zoom about mouse",
 
                                t=self.start_t,
 
                                factor=factor)
 
@@ -339,13 +367,14 @@ class RegionZoom:
 
            self.finish()
 
            return
 

	
 
        start,end = min(self.start_t, self.end_t),max(self.start_t, self.end_t)
 
        start, end = min(self.start_t, self.end_t), max(self.start_t,
 
                                                        self.end_t)
 
        if self.mods == "c-a":
 
            dispatcher.send("zoom to range", start=start, end=end)
 
        elif self.mods == "none":
 
            dispatcher.send("select between", start=start, end=end)
 
        self.finish()
 
        
 

	
 
    def finish(self, *ev):
 
        if self.state is not None:
 
            self.state = None
light9/dmxchanedit.py
Show inline comments
 
@@ -16,7 +16,7 @@ proposal for new attribute system:
 
- we have to stop packing these into the names. Names should be like 'b33' or 'blue3' or just '44'. maybe 'blacklight'.
 

	
 
"""
 
from __future__ import nested_scopes,division
 
from __future__ import nested_scopes, division
 
import Tkinter as tk
 
from rdflib import RDF, Literal
 
import math, logging
 
@@ -25,10 +25,12 @@ from light9.namespaces import L9
 
log = logging.getLogger('dmxchanedit')
 
stdfont = ('Arial', 7)
 

	
 
def gradient(lev, low=(80,80,180), high=(255,55,50)):
 
     out = [int(l+lev*(h-l)) for h,l in zip(high,low)]
 
     col="#%02X%02X%02X" % tuple(out)
 
     return col
 

	
 
def gradient(lev, low=(80, 80, 180), high=(255, 55, 50)):
 
    out = [int(l + lev * (h - l)) for h, l in zip(high, low)]
 
    col = "#%02X%02X%02X" % tuple(out)
 
    return col
 

	
 

	
 
class Onelevel(tk.Frame):
 
    """a name/level pair
 
@@ -48,80 +50,102 @@ class Onelevel(tk.Frame):
 
    which I'm doing for what I think is efficiency. Unclear why I
 
    didn't use Observable for that API.
 
    """
 

	
 
    def __init__(self, parent, graph, channelUri, onLevelChange):
 
        tk.Frame.__init__(self,parent, height=20)
 
        tk.Frame.__init__(self, parent, height=20)
 
        self.graph = graph
 
        self.onLevelChange = onLevelChange
 
        self.uri = channelUri
 
        self.currentLevel = 0 # the level we're displaying, 0..1
 
        self.currentLevel = 0  # the level we're displaying, 0..1
 

	
 
        # no statement yet
 
        self.channelnum = int(
 
             self.graph.value(self.uri, L9['output']).rsplit('/c')[-1])
 
            self.graph.value(self.uri, L9['output']).rsplit('/c')[-1])
 

	
 
        # 3 widgets, left-to-right:
 

	
 
        # channel number -- will turn yellow when being altered
 
        self.num_lab = tk.Label(self, text=str(self.channelnum),
 
                                width=3, bg='grey40',
 
        self.num_lab = tk.Label(self,
 
                                text=str(self.channelnum),
 
                                width=3,
 
                                bg='grey40',
 
                                fg='white',
 
                                font=stdfont,
 
                                padx=0, pady=0, bd=0, height=1)
 
                                padx=0,
 
                                pady=0,
 
                                bd=0,
 
                                height=1)
 
        self.num_lab.pack(side='left')
 

	
 
        # text description of channel
 
        self.desc_lab=tk.Label(self,
 
                               width=14,
 
                               font=stdfont,
 
                               anchor='w',
 
                               padx=0, pady=0, bd=0,
 
                 height=1, bg='black', fg='white')
 
        self.desc_lab = tk.Label(self,
 
                                 width=14,
 
                                 font=stdfont,
 
                                 anchor='w',
 
                                 padx=0,
 
                                 pady=0,
 
                                 bd=0,
 
                                 height=1,
 
                                 bg='black',
 
                                 fg='white')
 
        self.graph.addHandler(self.updateLabel)
 
        self.desc_lab.pack(side='left')
 

	
 
        # current level of channel, shows intensity with color
 
        self.level_lab = tk.Label(self, width=3, bg='lightBlue',
 
                                  anchor='e', font=stdfont,
 
                                  padx=1, pady=0, bd=0, height=1)
 
        self.level_lab = tk.Label(self,
 
                                  width=3,
 
                                  bg='lightBlue',
 
                                  anchor='e',
 
                                  font=stdfont,
 
                                  padx=1,
 
                                  pady=0,
 
                                  bd=0,
 
                                  height=1)
 
        self.level_lab.pack(side='left')
 

	
 
        self.setupmousebindings()
 

	
 
    def updateLabel(self):
 
         self.desc_lab.config(text=self.graph.label(self.uri))
 
        self.desc_lab.config(text=self.graph.label(self.uri))
 

	
 
    def setupmousebindings(self):
 

	
 
        def b1down(ev):
 
            self.desc_lab.config(bg='cyan')
 
            self._start_y=ev.y
 
            self._start_lev=self.currentLevel
 
            self._start_y = ev.y
 
            self._start_lev = self.currentLevel
 

	
 
        def b1motion(ev):
 
            delta=self._start_y-ev.y
 
            self.setlevel(max(0, min(1, self._start_lev+delta*.005)))
 
            delta = self._start_y - ev.y
 
            self.setlevel(max(0, min(1, self._start_lev + delta * .005)))
 

	
 
        def b1up(ev):
 
            self.desc_lab.config(bg='black')
 

	
 
        def b3up(ev):
 
            self.setlevel(0.0)
 

	
 
        def b3down(ev):
 
            self.setlevel(1.0)
 
        def b2down(ev): # same thing for now
 

	
 
        def b2down(ev):  # same thing for now
 
            self.setlevel(1.0)
 

	
 
        # make the buttons work in the child windows
 
        for w in self.winfo_children():
 
            for e,func in (('<ButtonPress-1>',b1down),
 
                           ('<B1-Motion>',b1motion),
 
                           ('<ButtonRelease-1>',b1up),
 
                           ('<ButtonPress-2>', b2down),
 
                           ('<ButtonRelease-3>', b3up),
 
                           ('<ButtonPress-3>', b3down)):
 
            for e, func in (('<ButtonPress-1>',
 
                             b1down), ('<B1-Motion>',
 
                                       b1motion), ('<ButtonRelease-1>', b1up),
 
                            ('<ButtonPress-2>',
 
                             b2down), ('<ButtonRelease-3>',
 
                                       b3up), ('<ButtonPress-3>', b3down)):
 

	
 
                w.bind(e,func)
 
                w.bind(e, func)
 

	
 
    def colorlabel(self):
 
        """color the level label based on its own text (which is 0..100)"""
 
        txt=self.level_lab['text'] or "0"
 
        lev=float(txt)/100
 
        txt = self.level_lab['text'] or "0"
 
        lev = float(txt) / 100
 
        self.level_lab.config(bg=gradient(lev))
 

	
 
    def setlevel(self, newlev):
 
@@ -132,7 +156,7 @@ class Onelevel(tk.Frame):
 
        """levelbox saw a change in the graph"""
 
        self.currentLevel = min(1, max(0, newLevel))
 
        newLevel = "%d" % (self.currentLevel * 100)
 
        olddisplay=self.level_lab.cget('text')
 
        olddisplay = self.level_lab.cget('text')
 
        if newLevel != olddisplay:
 
            self.level_lab.config(text=newLevel)
 
            self.colorlabel()
 
@@ -142,37 +166,40 @@ class Levelbox(tk.Frame):
 
    """
 
    this also watches all the levels in the sub and sets the boxes when they change
 
    """
 

	
 
    def __init__(self, parent, graph, currentSub):
 
        """
 
        currentSub is an Observable(PersistentSubmaster)
 
        """
 
        tk.Frame.__init__(self,parent)
 
        tk.Frame.__init__(self, parent)
 

	
 
        self.currentSub = currentSub
 
        self.graph = graph
 
        graph.addHandler(self.updateChannels)
 

	
 
        self.currentSub.subscribe(lambda _: graph.addHandler(self.updateLevelValues))
 
        self.currentSub.subscribe(lambda _: graph.addHandler(self.
 
                                                             updateLevelValues))
 

	
 
    def updateChannels(self):
 
        """(re)make Onelevel boxes for the defined channels"""
 

	
 
        [ch.destroy() for ch in self.winfo_children()]
 
        self.levelFromUri = {} # channel : OneLevel
 
        self.levelFromUri = {}  # channel : OneLevel
 

	
 
        chans = list(self.graph.subjects(RDF.type, L9.Channel))
 
        chans.sort(key=lambda c: int(self.graph.value(c, L9.output).rsplit('/c')[-1]))
 
        chans.sort(
 
            key=lambda c: int(self.graph.value(c, L9.output).rsplit('/c')[-1]))
 
        cols = 2
 
        rows = int(math.ceil(len(chans) / cols))
 

	
 
        def make_frame(parent):
 
             f = tk.Frame(parent, bd=0, bg='black')
 
             f.pack(side='left')
 
             return f
 
            f = tk.Frame(parent, bd=0, bg='black')
 
            f.pack(side='left')
 
            return f
 

	
 
        columnFrames = [make_frame(self) for x in range(cols)]
 

	
 
        for i, channel in enumerate(chans): # sort?
 
        for i, channel in enumerate(chans):  # sort?
 
            # frame for this channel
 
            f = Onelevel(columnFrames[i // rows], self.graph, channel,
 
                         self.onLevelChange)
 
@@ -193,19 +220,19 @@ class Levelbox(tk.Frame):
 
        for ll in self.graph.objects(sub, L9['lightLevel']):
 
            chan = self.graph.value(ll, L9['channel'])
 
            try:
 
                 lev = self.graph.value(ll, L9['level']).toPython()
 
                lev = self.graph.value(ll, L9['level']).toPython()
 
            except AttributeError as e:
 
                 log.error('on lightlevel %r:', ll)
 
                 log.exception(e)
 
                 continue
 
                log.error('on lightlevel %r:', ll)
 
                log.exception(e)
 
                continue
 
            if isinstance(lev, Decimal):
 
                 lev = float(lev)
 
                lev = float(lev)
 
            assert isinstance(lev, (int, long, float)), repr(lev)
 
            try:
 
                 self.levelFromUri[chan].setTo(lev)
 
                 remaining.remove(chan)
 
                self.levelFromUri[chan].setTo(lev)
 
                remaining.remove(chan)
 
            except KeyError as e:
 
                 log.exception(e)
 
                log.exception(e)
 
        for channel in remaining:
 
            self.levelFromUri[channel].setTo(0)
 

	
 
@@ -214,4 +241,3 @@ class Levelbox(tk.Frame):
 
        if self.currentSub() is None:
 
            raise ValueError("no currentSub in Levelbox")
 
        self.currentSub().editLevel(chan, newLevel)
 

	
light9/dmxclient.py
Show inline comments
 
@@ -10,25 +10,34 @@ from txzmq import ZmqEndpoint, ZmqFactor
 
import json
 

	
 
from light9 import networking
 
_dmx=None
 
_dmx = None
 
log = logging.getLogger('dmxclient')
 

	
 
procname = os.path.basename(sys.argv[0])
 
procname = procname.replace('.py', '')
 
_id = "%s-%s-%s" % (procname, socket.gethostname(), os.getpid())
 

	
 

	
 
class TwistedZmqClient(object):
 

	
 
    def __init__(self, service):
 
        zf = ZmqFactory()
 
        e = ZmqEndpoint('connect', 'tcp://%s:%s' % (service.host, service.port))
 

	
 
        class Push(ZmqPushConnection):
 
            pass # highWaterMark = 3000
 
            pass  # highWaterMark = 3000
 

	
 
        self.conn = Push(zf, e)
 
        
 

	
 
    def send(self, clientid, levellist):
 
        self.conn.push(json.dumps({'clientid': clientid, 'levellist': levellist}))
 
        self.conn.push(
 
            json.dumps({
 
                'clientid': clientid,
 
                'levellist': levellist
 
            }))
 

	
 
def outputlevels(levellist,twisted=0,clientid=_id):
 

	
 
def outputlevels(levellist, twisted=0, clientid=_id):
 
    """present a list of dmx channel levels, each scaled from
 
    0..1. list can be any length- it will apply to the first len() dmx
 
    channels.
 
@@ -51,15 +60,17 @@ def outputlevels(levellist,twisted=0,cli
 
        except socket.error, e:
 
            log.error("dmx server error %s, waiting" % e)
 
            time.sleep(1)
 
        except xmlrpclib.Fault,e:
 
        except xmlrpclib.Fault, e:
 
            log.error("outputlevels had xml fault: %s" % e)
 
            time.sleep(1)
 
    else:
 
        _dmx.send(clientid, levellist)
 
        return defer.succeed(None)
 
    
 

	
 

	
 
dummy = os.getenv('DMXDUMMY')
 
if dummy:
 
    print "dmxclient: DMX is in dummy mode."
 

	
 
    def outputlevels(*args, **kw):
 
        pass
light9/editchoice.py
Show inline comments
 
@@ -2,11 +2,13 @@ import Tkinter as tk
 
from rdflib import URIRef
 
from light9.tkdnd import dragSourceRegister, dropTargetRegister
 

	
 

	
 
class Local(object):
 
    """placeholder for the local uri that EditChoice does not
 
    manage. Set resourceObservable to Local to indicate that you're
 
    unlinked"""
 

	
 

	
 
class EditChoice(object):
 
    """
 
    Observable <-> linker UI
 
@@ -52,6 +54,7 @@ class EditChoice(object):
 

	
 
    - list of recent resources that this choice was set to
 
    """
 

	
 
    def __init__(self, parent, graph, resourceObservable, label="Editing:"):
 
        """
 
        getResource is called to get the URI of the currently
 
@@ -63,9 +66,12 @@ class EditChoice(object):
 
        self.currentLinkFrame = tk.Frame(self.frame)
 
        self.currentLinkFrame.pack(side='left')
 

	
 
        self.subIcon = tk.Label(self.currentLinkFrame, text="...",
 
                                borderwidth=2, relief='raised',
 
                                padx=10, pady=10)
 
        self.subIcon = tk.Label(self.currentLinkFrame,
 
                                text="...",
 
                                borderwidth=2,
 
                                relief='raised',
 
                                padx=10,
 
                                pady=10)
 
        self.subIcon.pack()
 

	
 
        self.resourceObservable = resourceObservable
 
@@ -74,12 +80,14 @@ class EditChoice(object):
 
        # when the value is local, this should stop being a drag source
 
        dragSourceRegister(self.subIcon, 'copy', 'text/uri-list',
 
                           self.resourceObservable)
 

	
 
        def onEv(ev):
 
            self.resourceObservable(URIRef(ev.data))
 
            return "link"
 

	
 
        self.onEv = onEv
 

	
 
        b=tk.Button(self.frame, text="Unlink", command=self.switchToLocalSub)
 
        b = tk.Button(self.frame, text="Unlink", command=self.switchToLocalSub)
 
        b.pack(side='left')
 

	
 
        # it would be nice if I didn't receive my own drags here, and
 
@@ -87,7 +95,9 @@ class EditChoice(object):
 
        for target in ([self.frame, self.currentLinkFrame] +
 
                       self.frame.winfo_children() +
 
                       self.currentLinkFrame.winfo_children()):
 
            dropTargetRegister(target, typeList=["*"], onDrop=onEv,
 
            dropTargetRegister(target,
 
                               typeList=["*"],
 
                               onDrop=onEv,
 
                               hoverStyle=dict(background="#555500"))
 

	
 
    def uriChanged(self, newUri):
 
@@ -106,4 +116,3 @@ class EditChoice(object):
 

	
 
    def switchToLocalSub(self):
 
        self.resourceObservable(Local)
 

	
light9/editchoicegtk.py
Show inline comments
 
@@ -4,15 +4,18 @@ from gi.repository import Gdk
 
from rdflib import URIRef
 
log = logging.getLogger('editchoicegtk')
 

	
 

	
 
class Local(object):
 
    """placeholder for the local uri that EditChoice does not
 
    manage. Set resourceObservable to Local to indicate that you're
 
    unlinked"""
 

	
 

	
 
class EditChoice(Gtk.HBox):
 
    """
 
    this is a gtk port of editchoice.EditChoice
 
    """
 

	
 
    def __init__(self, graph, resourceObservable, label="Editing:"):
 
        """
 
        getResource is called to get the URI of the currently
 
@@ -22,23 +25,19 @@ class EditChoice(Gtk.HBox):
 
        # the outer box should have a distinctive border so it's more
 
        # obviously a special drop target
 
        Gtk.HBox.__init__(self)
 
        self.pack_start(Gtk.Label(label),
 
                        False, True, 0) #expand, fill, pad
 
        self.pack_start(Gtk.Label(label), False, True, 0)  #expand, fill, pad
 

	
 
        # this is just a label, but it should look like a physical
 
        # 'thing' (and gtk labels don't work as drag sources)
 
        self.currentLink = Gtk.Button("http://bar")
 

	
 
        self.pack_start(self.currentLink,
 
                        True, True, 0) #expand, fill, pad
 

	
 
        self.pack_start(self.currentLink, True, True, 0)  #expand, fill, pad
 

	
 
        self.unlinkButton = Gtk.Button(label="Unlink")
 
        self.pack_start(self.unlinkButton,
 
                        False, True, 0) #expand, fill pad
 
        self.pack_start(self.unlinkButton, False, True, 0)  #expand, fill pad
 

	
 
        self.unlinkButton.connect("clicked", self.onUnlink)
 
        
 

	
 
        self.show_all()
 

	
 
        self.resourceObservable = resourceObservable
 
@@ -46,8 +45,9 @@ class EditChoice(Gtk.HBox):
 

	
 
        self.makeDragSource()
 
        self.makeDropTarget()
 
         
 

	
 
    def makeDropTarget(self):
 

	
 
        def ddr(widget, drag_context, x, y, selection_data, info, timestamp):
 
            dtype = selection_data.get_data_type()
 
            if dtype.name() not in ['text/uri-list', 'TEXT']:
 
@@ -55,28 +55,30 @@ class EditChoice(Gtk.HBox):
 
            data = selection_data.get_data().strip()
 
            log.debug('drag_data_received data=%r', data)
 
            self.resourceObservable(URIRef(data))
 
        
 

	
 
        self.currentLink.drag_dest_set(
 
            flags=Gtk.DestDefaults.ALL,
 
            targets=[Gtk.TargetEntry.new('text/uri-list', 0, 0),
 
                     Gtk.TargetEntry.new('TEXT', 0, 0), # getting this from chrome :(
 
                 ],
 
            actions=Gdk.DragAction.LINK  | Gdk.DragAction.COPY)
 
            targets=[
 
                Gtk.TargetEntry.new('text/uri-list', 0, 0),
 
                Gtk.TargetEntry.new('TEXT', 0,
 
                                    0),  # getting this from chrome :(
 
            ],
 
            actions=Gdk.DragAction.LINK | Gdk.DragAction.COPY)
 
        self.currentLink.connect("drag_data_received", ddr)
 
                
 

	
 
    def makeDragSource(self):
 
        self.currentLink.drag_source_set(
 
            start_button_mask=Gdk.ModifierType.BUTTON1_MASK,
 
            targets=[Gtk.TargetEntry.new(target='text/uri-list',
 
                                         flags=0, info=0)],
 
            actions=Gdk.DragAction.LINK  | Gdk.DragAction.COPY)
 
            targets=[
 
                Gtk.TargetEntry.new(target='text/uri-list', flags=0, info=0)
 
            ],
 
            actions=Gdk.DragAction.LINK | Gdk.DragAction.COPY)
 

	
 
        def source_drag_data_get(btn, context, selection_data, info, time):
 
            selection_data.set_uris([self.resourceObservable()])
 

	
 
        self.currentLink.connect("drag_data_get", source_drag_data_get)
 

	
 
                
 
    def uriChanged(self, newUri):
 
        # if this resource had a type icon or a thumbnail, those would be
 
        # cool to show in here too
light9/effect/edit.py
Show inline comments
 
@@ -8,22 +8,25 @@ from light9.namespaces import L9, RDF, R
 
from rdfdb.patch import Patch
 
from light9.curvecalc.curve import CurveResource
 

	
 

	
 
def clamp(x, lo, hi):
 
    return max(lo, min(hi, x))
 

	
 

	
 
@inlineCallbacks
 
def getMusicStatus():
 
    returnValue(json.loads((yield cyclone.httpclient.fetch(
 
        networking.musicPlayer.path('time'), timeout=.5)).body))
 
    returnValue(
 
        json.loads(
 
            (yield cyclone.httpclient.fetch(networking.musicPlayer.path('time'),
 
                                            timeout=.5)).body))
 

	
 

	
 
@inlineCallbacks
 
def songEffectPatch(graph, dropped, song, event, ctx):
 
    """
 
    some uri was 'dropped' in the curvecalc timeline. event is 'default' or 'start' or 'end'.
 
    """
 
    with graph.currentState(
 
            tripleFilter=(dropped, None, None)) as g:
 
    with graph.currentState(tripleFilter=(dropped, None, None)) as g:
 
        droppedTypes = list(g.objects(dropped, RDF.type))
 
        droppedLabel = g.label(dropped)
 
        droppedCodes = list(g.objects(dropped, L9['code']))
 
@@ -44,17 +47,17 @@ def songEffectPatch(graph, dropped, song
 
            (song, L9['curve'], curve, ctx),
 
            (effect, RDFS.label, droppedLabel, ctx),
 
            (effect, L9['code'], Literal('env = %s' % curve.n3()), ctx),
 
            ])
 
        ])
 

	
 
        if L9['EffectClass'] in droppedTypes:
 
            quads.extend([
 
                (effect, RDF.type, dropped, ctx),
 
                ] + [(effect, L9['code'], c, ctx) for c in droppedCodes])
 
            ] + [(effect, L9['code'], c, ctx) for c in droppedCodes])
 
        elif L9['Submaster'] in droppedTypes:
 
            quads.extend([
 
                (effect, L9['code'], Literal('out = %s * env' % dropped.n3()),
 
                 ctx),
 
                ])
 
            ])
 
        else:
 
            raise NotImplementedError(
 
                "don't know how to add an effect from %r (types=%r)" %
 
@@ -67,6 +70,7 @@ def songEffectPatch(graph, dropped, song
 
        print qq
 
    returnValue(Patch(addQuads=quads))
 

	
 

	
 
@inlineCallbacks
 
def songNotePatch(graph, dropped, song, event, ctx, note=None):
 
    """
 
@@ -74,8 +78,7 @@ def songNotePatch(graph, dropped, song, 
 

	
 
    ported from timeline.coffee makeNewNote
 
    """
 
    with graph.currentState(
 
            tripleFilter=(dropped, None, None)) as g:
 
    with graph.currentState(tripleFilter=(dropped, None, None)) as g:
 
        droppedTypes = list(g.objects(dropped, RDF.type))
 

	
 
    quads = []
 
@@ -89,7 +92,8 @@ def songNotePatch(graph, dropped, song, 
 
        if L9['Effect'] in droppedTypes:
 
            musicStatus = yield getMusicStatus()
 
            songTime = musicStatus['t']
 
            note = _makeNote(graph, song, note, quads, ctx, dropped, songTime, event, fade)
 
            note = _makeNote(graph, song, note, quads, ctx, dropped, songTime,
 
                             event, fade)
 
        else:
 
            raise NotImplementedError
 

	
 
@@ -97,11 +101,10 @@ def songNotePatch(graph, dropped, song, 
 

	
 

	
 
def _point(ctx, uri, t, v):
 
    return [
 
        (uri, L9['time'], Literal(round(t, 3)), ctx),
 
        (uri, L9['value'], Literal(round(v, 3)), ctx)
 
        ]
 
    
 
    return [(uri, L9['time'], Literal(round(t, 3)), ctx),
 
            (uri, L9['value'], Literal(round(v, 3)), ctx)]
 

	
 

	
 
def _finishCurve(graph, note, quads, ctx, songTime):
 
    with graph.currentState() as g:
 
        origin = g.value(note, L9['originTime']).toPython()
 
@@ -109,11 +112,11 @@ def _finishCurve(graph, note, quads, ctx
 

	
 
    pt2 = graph.sequentialUri(curve + 'p')
 
    pt3 = graph.sequentialUri(curve + 'p')
 
    quads.extend(
 
        [(curve, L9['point'], pt2, ctx)] + _point(ctx, pt2, songTime - origin, 1) +
 
        [(curve, L9['point'], pt3, ctx)] + _point(ctx, pt3, songTime - origin + .5, 0)
 
        )
 
    
 
    quads.extend([(curve, L9['point'], pt2, ctx)] +
 
                 _point(ctx, pt2, songTime - origin, 1) +
 
                 [(curve, L9['point'], pt3, ctx)] +
 
                 _point(ctx, pt3, songTime - origin + .5, 0))
 

	
 

	
 
def _makeNote(graph, song, note, quads, ctx, dropped, songTime, event, fade):
 
    note = graph.sequentialUri(song + '/n')
 
@@ -126,28 +129,34 @@ def _makeNote(graph, song, note, quads, 
 
        (note, L9['originTime'], Literal(songTime), ctx),
 
        (curve, RDF.type, L9['Curve'], ctx),
 
        (curve, L9['attr'], L9['strength'], ctx),
 
        ])
 
    ])
 
    if event == 'default':
 
        coords = [(0 - fade, 0), (0, 1), (20, 1), (20 + fade, 0)]
 
    elif event == 'start':
 
        coords = [(0 - fade, 0), (0, 1), ]
 
    elif event == 'end': # probably unused- goes to _finishCurve instead
 
        coords = [
 
            (0 - fade, 0),
 
            (0, 1),
 
        ]
 
    elif event == 'end':  # probably unused- goes to _finishCurve instead
 
        coords = [(20, 1), (20 + fade, 0)]
 
    else:
 
        raise NotImplementedError(event)
 
    for t,v in coords:
 
    for t, v in coords:
 
        pt = graph.sequentialUri(curve + 'p')
 
        quads.extend([(curve, L9['point'], pt, ctx)] + _point(ctx, pt, t, v))
 
    return note
 
    
 

	
 

	
 
def _songHasEffect(graph, song, uri):
 
    """does this song have an effect of class uri or a sub curve for sub
 
    uri? this should be simpler to look up."""
 
    return False # todo
 
    return False  # todo
 

	
 

	
 
def musicCurveForSong(uri):
 
    return URIRef(uri + 'music')
 
    
 

	
 

	
 
def _newEffect(graph, song, ctx):
 
    effect = graph.sequentialUri(song + "/effect-")
 
    quads = [
 
@@ -156,42 +165,43 @@ def _newEffect(graph, song, ctx):
 
    ]
 
    print "_newEffect", effect, quads
 
    return effect, quads
 
    
 

	
 

	
 
@inlineCallbacks
 
def _newEnvelopeCurve(graph, ctx, uri, label, fade=2):
 
    """this does its own patch to the graph"""
 
    
 

	
 
    cr = CurveResource(graph, uri)
 
    cr.newCurve(ctx, label=Literal(label))
 
    yield _insertEnvelopePoints(cr.curve, fade)
 
    cr.saveCurve()
 

	
 

	
 
@inlineCallbacks
 
def _insertEnvelopePoints(curve, fade=2):
 
    # wrong: we might not be adding to the currently-playing song.
 
    musicStatus = yield getMusicStatus()
 
    songTime=musicStatus['t']
 
    songDuration=musicStatus['duration']
 
    
 
    songTime = musicStatus['t']
 
    songDuration = musicStatus['duration']
 

	
 
    t1 = clamp(songTime - fade, .1, songDuration - .1 * 2) + fade
 
    t2 = clamp(songTime + 20, t1 + .1, songDuration)
 
    
 

	
 
    curve.insert_pt((t1 - fade, 0))
 
    curve.insert_pt((t1, 1))
 
    curve.insert_pt((t2, 1))
 
    curve.insert_pt((t2 + fade, 0))
 
    
 
    
 

	
 

	
 
def _maybeAddMusicLine(quads, effect, song, ctx):
 
    """
 
    add a line getting the current music into 'music' if any code might
 
    be mentioning that var
 
    """
 
    
 

	
 
    for spoc in quads:
 
        if spoc[1] == L9['code'] and 'music' in spoc[2]:
 
            quads.extend([
 
                (effect, L9['code'],
 
                 Literal('music = %s' % musicCurveForSong(song).n3()), ctx)
 
                ])
 
            quads.extend([(effect, L9['code'],
 
                           Literal('music = %s' % musicCurveForSong(song).n3()),
 
                           ctx)])
 
            break
light9/effect/effecteval.py
Show inline comments
 
@@ -17,28 +17,48 @@ print "reload effecteval"
 

	
 
log = logging.getLogger('effecteval')
 

	
 

	
 
def literalColor(rnorm, gnorm, bnorm):
 
    return Literal(rgb_to_hex([int(rnorm * 255), int(gnorm * 255), int(bnorm * 255)]))
 
    return Literal(
 
        rgb_to_hex([int(rnorm * 255),
 
                    int(gnorm * 255),
 
                    int(bnorm * 255)]))
 

	
 

	
 
def literalColorHsv(h, s, v):
 
    return literalColor(*hsv_to_rgb(h, s, v))
 
    
 
def nsin(x): return (math.sin(x * (2 * math.pi)) + 1) / 2
 
def ncos(x): return (math.cos(x * (2 * math.pi)) + 1) / 2
 

	
 

	
 
def nsin(x):
 
    return (math.sin(x * (2 * math.pi)) + 1) / 2
 

	
 

	
 
def ncos(x):
 
    return (math.cos(x * (2 * math.pi)) + 1) / 2
 

	
 

	
 
def nsquare(t, on=.5):
 
    return (t % 1.0) < on
 

	
 

	
 
def lerp(a, b, t):
 
    return a + (b - a) * t
 

	
 

	
 
def noise(t):
 
    return pnoise1(t % 1000.0, 2)
 

	
 

	
 
def clamp(lo, hi, x):
 
    return max(lo, min(hi, x))
 
    
 

	
 

	
 
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, simpleOutputs):
 
        self.graph = graph
 
        self.effect = effect
 
@@ -51,17 +71,21 @@ class EffectEval(object):
 
        the effect code.
 
        """
 
        # both callers need to apply note overrides
 
        effectSettings = dict(effectSettings) # we should make everything into nice float and Color objects too
 
        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, []), {'zero': True}
 

	
 
        report = {}
 
        out = {} # (dev, attr): value
 
        out = {}  # (dev, attr): value
 

	
 
        out.update(self.simpleOutputs.values(
 
            self.effect, strength, effectSettings.get(L9['colorScale'], None)))
 
        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/']):]
 
@@ -74,46 +98,48 @@ class EffectEval(object):
 

	
 
        outList = [(d, a, v) for (d, a), v in out.iteritems()]
 
        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)
 
        for n in range(301,308+1)
 
        }
 
    
 
    return {(L9['device/lowPattern%s' % n], L9['color']):
 
            literalColor(strength, strength, strength)
 
            for n in range(301, 308 + 1)}
 

	
 

	
 
def effect_animRainbow(effectSettings, strength, songTime, noteTime):
 
    out = {}
 
    tint = effectSettings.get(L9['tint'], '#ffffff')
 
    tintStrength = float(effectSettings.get(L9['tintStrength'], 0))
 
    tr, tg, tb = hex_to_rgb(tint)
 
    for n in range(1, 5+1):
 
    for n in range(1, 5 + 1):
 
        scl = strength * nsin(songTime + n * .3)**3
 
        col = literalColor(
 
            scl * lerp(nsin(songTime + n * .2), tr/255, tintStrength),
 
            scl * lerp(nsin(songTime + n * .2 + .3), tg/255, tintStrength),
 
            scl * lerp(nsin(songTime + n * .3 + .6), tb/255, tintStrength))
 
            scl * lerp(nsin(songTime + n * .2), tr / 255, tintStrength),
 
            scl * lerp(nsin(songTime + n * .2 + .3), tg / 255, tintStrength),
 
            scl * lerp(nsin(songTime + n * .3 + .6), tb / 255, tintStrength))
 

	
 
        dev = L9['device/aura%s' % n]
 
        out.update({
 
            (dev, L9['color']): col,
 
            (dev, L9['zoom']): .9,
 
            })
 
        })
 
        ang = songTime * 4
 
        out.update({
 
        (dev, L9['rx']): lerp(.27, .7, (n-1)/4) + .2 * math.sin(ang+n),
 
        (dev, L9['ry']): lerp(.46, .52, (n-1)/4) + .5 * math.cos(ang+n),
 
            })
 
            (dev, L9['rx']):
 
            lerp(.27, .7, (n - 1) / 4) + .2 * math.sin(ang + n),
 
            (dev, L9['ry']):
 
            lerp(.46, .52, (n - 1) / 4) + .5 * math.cos(ang + n),
 
        })
 
    return out
 

	
 

	
 
def effect_auraSparkles(effectSettings, strength, songTime, noteTime):
 
    out = {}
 
    tint = effectSettings.get(L9['tint'], '#ffffff')
 
    tintStrength = float(effectSettings.get(L9['tintStrength'], 0))
 
    print effectSettings
 
    tr, tg, tb = hex_to_rgb(tint)
 
    for n in range(1, 5+1):
 
    for n in range(1, 5 + 1):
 
        scl = strength * ((int(songTime * 10) % n) < 1)
 
        col = literalColorHsv((songTime + (n / 5)) % 1, 1, scl)
 

	
 
@@ -121,14 +147,17 @@ def effect_auraSparkles(effectSettings, 
 
        out.update({
 
            (dev, L9['color']): col,
 
            (dev, L9['zoom']): .95,
 
            })
 
        })
 
        ang = songTime * 4
 
        out.update({
 
        (dev, L9['rx']): lerp(.27, .8, (n-1)/4) + .2 * math.sin(ang+n),
 
        (dev, L9['ry']): lerp(.46, .52, (n-1)/4) + .4 * math.cos(ang+n),
 
            })
 
            (dev, L9['rx']):
 
            lerp(.27, .8, (n - 1) / 4) + .2 * math.sin(ang + n),
 
            (dev, L9['ry']):
 
            lerp(.46, .52, (n - 1) / 4) + .4 * math.cos(ang + n),
 
        })
 
    return out
 

	
 

	
 
def effect_qpan(effectSettings, strength, songTime, noteTime):
 
    dev = L9['device/q2']
 
    dur = 4
 
@@ -142,57 +171,59 @@ def effect_qpan(effectSettings, strength
 
        (dev, L9['zoom']): 0.714,
 
    }
 

	
 

	
 
def effect_pulseRainbow(effectSettings, strength, songTime, noteTime):
 
    out = {}
 
    tint = effectSettings.get(L9['tint'], '#ffffff')
 
    tintStrength = float(effectSettings.get(L9['tintStrength'], 0))
 
    tr, tg, tb = hex_to_rgb(tint)
 
    for n in range(1, 5+1):
 
        scl = strength 
 
    for n in range(1, 5 + 1):
 
        scl = strength
 
        col = literalColor(
 
            scl * lerp(nsin(songTime + n * .2), tr/255, tintStrength),
 
            scl * lerp(nsin(songTime + n * .2 + .3), tg/255, tintStrength),
 
            scl * lerp(nsin(songTime + n * .3 + .6), tb/255, tintStrength))
 
            scl * lerp(nsin(songTime + n * .2), tr / 255, tintStrength),
 
            scl * lerp(nsin(songTime + n * .2 + .3), tg / 255, tintStrength),
 
            scl * lerp(nsin(songTime + n * .3 + .6), tb / 255, tintStrength))
 

	
 
        dev = L9['device/aura%s' % n]
 
        out.update({
 
            (dev, L9['color']): col,
 
            (dev, L9['zoom']): .5,
 
            })
 
        })
 
        out.update({
 
        (dev, L9['rx']): lerp(.27, .7, (n-1)/4),
 
        (dev, L9['ry']): lerp(.46, .52, (n-1)/4),
 
            })
 
            (dev, L9['rx']): lerp(.27, .7, (n - 1) / 4),
 
            (dev, L9['ry']): lerp(.46, .52, (n - 1) / 4),
 
        })
 
    return out
 

	
 

	
 
def effect_aurawash(effectSettings, strength, songTime, noteTime):
 
    out = {}
 
    scl = strength
 
    period = float(effectSettings.get(L9['period'], 125/60/4))
 
    period = float(effectSettings.get(L9['period'], 125 / 60 / 4))
 
    if period < .05:
 
        quantTime = songTime
 
    else:
 
        quantTime = int(songTime / period) * period
 
    noisePos = quantTime * 6.3456
 
    
 

	
 
    col = literalColorHsv(noise(noisePos), 1, scl)
 
    col = scale(col, effectSettings.get(L9['colorScale']) or '#ffffff')
 
                
 

	
 
    print songTime, quantTime, col
 

	
 
    for n in range(1, 5+1):
 
    for n in range(1, 5 + 1):
 
        dev = L9['device/aura%s' % n]
 
        out.update({
 
            (dev, L9['color']): col,
 
            (dev, L9['zoom']): .5,
 
            })
 
        })
 
        out.update({
 
        (dev, L9['rx']): lerp(.27, .7, (n-1)/4),
 
        (dev, L9['ry']): lerp(.46, .52, (n-1)/4),
 
            })
 
            (dev, L9['rx']): lerp(.27, .7, (n - 1) / 4),
 
            (dev, L9['ry']): lerp(.46, .52, (n - 1) / 4),
 
        })
 
    return out
 

	
 

	
 
def effect_qsweep(effectSettings, strength, songTime, noteTime):
 
    out = {}
 
    period = float(effectSettings.get(L9['period'], 2))
 
@@ -200,20 +231,21 @@ def effect_qsweep(effectSettings, streng
 
    col = effectSettings.get(L9['colorScale'], '#ffffff')
 
    col = scale(col, effectSettings.get(L9['strength'], 1))
 

	
 
    
 
    for n in range(1, 3+1):
 
    for n in range(1, 3 + 1):
 
        dev = L9['device/q%s' % n]
 
        out.update({
 
            (dev, L9['color']): col,
 
            (dev, L9['zoom']): effectSettings.get(L9['zoom'], .5),
 
            })
 
        })
 
        out.update({
 
            (dev, L9['rx']):
 
            lerp(.3, .8, nsin(songTime / period + n / 4)),
 
        (dev, L9['ry']): effectSettings.get(L9['ry'], .2),
 
            })
 
            (dev, L9['ry']):
 
            effectSettings.get(L9['ry'], .2),
 
        })
 
    return out
 

	
 

	
 
def effect_qsweepusa(effectSettings, strength, songTime, noteTime):
 
    out = {}
 
    period = float(effectSettings.get(L9['period'], 2))
 
@@ -223,43 +255,48 @@ def effect_qsweepusa(effectSettings, str
 
        2: '#998888',
 
        3: '#0050ff',
 
    }
 
    
 
    for n in range(1, 3+1):
 

	
 
    for n in range(1, 3 + 1):
 
        dev = L9['device/q%s' % n]
 
        out.update({
 
            (dev, L9['color']): scale(colmap[n], effectSettings.get(L9['strength'], 1)),
 
            (dev, L9['zoom']): effectSettings.get(L9['zoom'], .5),
 
            })
 
            (dev, L9['color']):
 
            scale(colmap[n], effectSettings.get(L9['strength'], 1)),
 
            (dev, L9['zoom']):
 
            effectSettings.get(L9['zoom'], .5),
 
        })
 
        out.update({
 
            (dev, L9['rx']):
 
            lerp(.3, .8, nsin(songTime / period + n / 4)),
 
            (dev, L9['ry']): effectSettings.get(L9['ry'], .5),
 
            })
 
            (dev, L9['ry']):
 
            effectSettings.get(L9['ry'], .5),
 
        })
 
    return out
 

	
 

	
 
chase1_members = [
 
        DEV['backlight1'],
 
        DEV['lip1'],
 
        DEV['backlight2'],
 
        DEV['down2'],
 
        DEV['lip2'],
 
        DEV['backlight3'],
 
        DEV['down3'],
 
        DEV['lip3'],
 
        DEV['backlight4'],
 
        DEV['down4'],
 
        DEV['lip4'],
 
        DEV['backlight5'],
 
        DEV['down5Edge'],
 
        DEV['lip5'],
 
        #DEV['upCenter'],
 
        ]
 
    DEV['backlight1'],
 
    DEV['lip1'],
 
    DEV['backlight2'],
 
    DEV['down2'],
 
    DEV['lip2'],
 
    DEV['backlight3'],
 
    DEV['down3'],
 
    DEV['lip3'],
 
    DEV['backlight4'],
 
    DEV['down4'],
 
    DEV['lip4'],
 
    DEV['backlight5'],
 
    DEV['down5Edge'],
 
    DEV['lip5'],
 
    #DEV['upCenter'],
 
]
 
chase2_members = chase1_members * 10
 
random.shuffle(chase2_members)
 

	
 

	
 
def effect_chase1(effectSettings, strength, songTime, noteTime):
 
    members = chase1_members + chase1_members[-2:0:-1]
 
    
 

	
 
    out = {}
 
    period = float(effectSettings.get(L9['period'], 2 / len(members)))
 

	
 
@@ -271,15 +308,16 @@ def effect_chase1(effectSettings, streng
 
            col = effectSettings.get(L9['colorScale'], '#ffffff')
 
            col = scale(col, effectSettings.get(L9['strength'], 1))
 
            col = scale(col, (1 - dist / radius))
 
        
 

	
 
            out.update({
 
                (dev, L9['color']): col,
 
            })
 
    return out
 

	
 

	
 
def effect_chase2(effectSettings, strength, songTime, noteTime):
 
    members = chase2_members
 
    
 

	
 
    out = {}
 
    period = float(effectSettings.get(L9['period'], 0.3))
 

	
 
@@ -291,37 +329,40 @@ def effect_chase2(effectSettings, streng
 
            col = effectSettings.get(L9['colorScale'], '#ffffff')
 
            col = scale(col, effectSettings.get(L9['strength'], 1))
 
            col = scale(col, (1 - dist / radius))
 
        
 

	
 
            out.update({
 
                (dev, L9['color']): col,
 
            })
 
    return out
 
    
 

	
 

	
 
def effect_whirlscolor(effectSettings, strength, songTime, noteTime):
 
    out = {}
 

	
 
    col = effectSettings.get(L9['colorScale'], '#ffffff')
 
    col = scale(col, effectSettings.get(L9['strength'], 1))
 
    
 

	
 
    for n in (1, 3):
 
        dev = L9['device/q%s' % n]
 
        scl = strength
 
        col = literalColorHsv(((songTime / 5) + (n / 5)) % 1, 1, scl)
 
        out.update({
 
            (dev, L9['color']): col,
 
            })
 
        })
 

	
 
    return out
 

	
 

	
 
def effect_orangeSearch(effectSettings, strength, songTime, noteTime):
 
    dev = L9['device/auraStage']
 
    return {(dev, L9['color']): '#a885ff',
 
            (dev, L9['rx']): lerp(.65, 1, nsin(songTime / 2.0)),
 
            (dev, L9['ry']): .6,
 
            (dev, L9['zoom']): 1,
 
            }
 
    
 
    return {
 
        (dev, L9['color']): '#a885ff',
 
        (dev, L9['rx']): lerp(.65, 1, nsin(songTime / 2.0)),
 
        (dev, L9['ry']): .6,
 
        (dev, L9['zoom']): 1,
 
    }
 

	
 

	
 
def effect_Strobe(effectSettings, strength, songTime, noteTime):
 
    rate = 2
 
    duty = .3
 
@@ -331,16 +372,16 @@ def effect_Strobe(effectSettings, streng
 
    col = rgb_to_hex([int(c * 255), int(c * 255), int(c * 255)])
 
    return {(L9['device/colorStrip'], L9['color']): Literal(col)}
 

	
 

	
 
def effect_lightning(effectSettings, strength, songTime, noteTime):
 
    devs = [L9['device/veryLow1'], L9['device/veryLow2'],
 
            L9['device/veryLow3'], L9['device/veryLow4'],
 
            L9['device/veryLow5'], L9['device/backlight1'],
 
            L9['device/backlight2'], L9['device/backlight3'],
 
            L9['device/backlight4'], L9['device/backlight5'],
 
            L9['device/down2'], L9['device/down3'],
 
            L9['device/down4'], L9['device/hexLow3'],
 
            L9['device/hexLow5'],
 
            L9['device/postL1'], L9['device/postR1']]
 
    devs = [
 
        L9['device/veryLow1'], L9['device/veryLow2'], L9['device/veryLow3'],
 
        L9['device/veryLow4'], L9['device/veryLow5'], L9['device/backlight1'],
 
        L9['device/backlight2'], L9['device/backlight3'],
 
        L9['device/backlight4'], L9['device/backlight5'], L9['device/down2'],
 
        L9['device/down3'], L9['device/down4'], L9['device/hexLow3'],
 
        L9['device/hexLow5'], L9['device/postL1'], L9['device/postR1']
 
    ]
 
    out = {}
 
    col = rgb_to_hex([int(255 * strength)] * 3)
 
    for i, dev in enumerate(devs):
light9/effect/scale.py
Show inline comments
 
@@ -10,16 +10,16 @@ def scale(value, strength):
 

	
 
    if isinstance(value, Decimal):
 
        value = float(value)
 
        
 

	
 
    if isinstance(value, basestring):
 
        if value[0] == '#':
 
            if strength == '#ffffff':
 
                return value
 
            r,g,b = hex_to_rgb(value)
 
            r, g, b = hex_to_rgb(value)
 
            if isinstance(strength, Literal):
 
                strength = strength.toPython()
 
            if isinstance(strength, basestring):
 
                sr, sg, sb = [v/255 for v in hex_to_rgb(strength)]
 
                sr, sg, sb = [v / 255 for v in hex_to_rgb(strength)]
 
            else:
 
                sr = sg = sb = strength
 
            return rgb_to_hex([int(r * sr), int(g * sg), int(b * sb)])
 
@@ -27,4 +27,3 @@ def scale(value, strength):
 
        return value * strength
 

	
 
    raise NotImplementedError("%r,%r" % (value, strength))
 
    
light9/effect/sequencer.py
Show inline comments
 
@@ -22,15 +22,17 @@ from light9.effect.simple_outputs import
 
from greplin import scales
 

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

	
 

	
 
class Note(object):
 

	
 
    def __init__(self, graph, uri, effectevalModule, simpleOutputs):
 
        g = self.graph = graph
 
        self.uri = uri
 
@@ -41,7 +43,7 @@ class Note(object):
 
            settingValues = dict(g.predicate_objects(s))
 
            ea = settingValues[L9['effectAttr']]
 
            self.baseEffectSettings[ea] = settingValues[L9['value']]
 
            
 

	
 
        floatVal = lambda s, p: float(g.value(s, p).toPython())
 
        originTime = floatVal(uri, L9['originTime'])
 
        self.points = []
 
@@ -57,11 +59,10 @@ class Note(object):
 
            return []
 
        for point in [row[1] for row in po if row[0] == L9['point']]:
 
            po2 = dict(self.graph.predicate_objects(point))
 
            points.append((
 
                originTime + float(po2[L9['time']]),
 
                float(po2[L9['value']])))
 
            points.append(
 
                (originTime + float(po2[L9['time']]), float(po2[L9['value']])))
 
        return points
 
            
 

	
 
    def activeAt(self, t):
 
        return self.points[0][0] <= t <= self.points[-1][0]
 

	
 
@@ -79,19 +80,19 @@ class Note(object):
 
        frac = (t - p1[0]) / (p2[0] - p1[0])
 
        y = p1[1] + (p2[1] - p1[1]) * frac
 
        return y
 
        
 

	
 
    def outputSettings(self, t):
 
        """
 
        list of (device, attr, value), and a report for web
 
        """
 
        report = {'note': str(self.uri),
 
                  'effectClass': self.effectEval.effect,
 
        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()))
 
            (str(k), str(v)) for k, v in sorted(effectSettings.items()))
 
        report['nonZero'] = effectSettings[L9['strength']] > 0
 
        out, evalReport = self.effectEval.outputFromEffect(
 
            effectSettings.items(),
 
@@ -103,26 +104,29 @@ class Note(object):
 

	
 

	
 
class CodeWatcher(object):
 

	
 
    def __init__(self, onChange):
 
        self.onChange = onChange
 

	
 
        self.notifier = INotify()
 
        self.notifier.startReading()
 
        self.notifier.watch(
 
            FilePath(effecteval.__file__.replace('.pyc', '.py')),
 
            callbacks=[self.codeChange])
 
        self.notifier.watch(FilePath(effecteval.__file__.replace('.pyc',
 
                                                                 '.py')),
 
                            callbacks=[self.codeChange])
 

	
 
    def codeChange(self, watch, path, mask):
 

	
 
        def go():
 
            log.info("reload effecteval")
 
            reload(effecteval)
 
            self.onChange()
 

	
 
        # in case we got an event at the start of the write
 
        reactor.callLater(.1, go) 
 
    
 
        
 
        reactor.callLater(.1, go)
 

	
 

	
 
class Sequencer(object):
 

	
 
    def __init__(self, graph, sendToCollector, fps=40):
 
        self.graph = graph
 
        self.fps = fps
 
@@ -132,7 +136,7 @@ class Sequencer(object):
 
        self.recentUpdateTimes = []
 
        self.lastStatLog = 0
 
        self._compileGraphCall = None
 
        self.notes = {} # song: [notes]
 
        self.notes = {}  # song: [notes]
 
        self.simpleOutputs = SimpleOutputs(self.graph)
 
        self.graph.addHandler(self.compileGraph)
 
        self.updateLoop()
 
@@ -149,29 +153,36 @@ class Sequencer(object):
 
        for song in g.subjects(RDF.type, L9['Song']):
 
            self.graph.addHandler(lambda song=song: self.compileSong(song))
 
        log.info('compileGraph took %.2f ms', 1000 * (time.time() - t1))
 
        
 

	
 
    @stats.compileSong.time()
 
    def compileSong(self, song):
 
        t1 = time.time()
 

	
 
        self.notes[song] = []
 
        for note in self.graph.objects(song, L9['note']):
 
            self.notes[song].append(Note(self.graph, note, effecteval,
 
                                         self.simpleOutputs))
 
            self.notes[song].append(
 
                Note(self.graph, note, effecteval, self.simpleOutputs))
 
        log.info('  compile %s took %.2f ms', song, 1000 * (time.time() - t1))
 

	
 

	
 
    def updateLoop(self):
 
        # print "updateLoop"
 
        now = time.time()
 
        self.recentUpdateTimes = self.recentUpdateTimes[-40:] + [now]
 
        stats.recentFps = len(self.recentUpdateTimes) / (self.recentUpdateTimes[-1] - self.recentUpdateTimes[0] + .0001)
 
        stats.recentFps = len(self.recentUpdateTimes) / (
 
            self.recentUpdateTimes[-1] - self.recentUpdateTimes[0] + .0001)
 
        if now > self.lastStatLog + .2:
 
            dispatcher.send('state', update={
 
                'recentDeltas': sorted([round(t1 - t0, 4) for t0, t1 in
 
                                 zip(self.recentUpdateTimes[:-1],
 
                                     self.recentUpdateTimes[1:])]),
 
                'recentFps': stats.recentFps})
 
            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
 

	
 
        def done(sec):
 
@@ -180,19 +191,21 @@ class Sequencer(object):
 
            # print 'cl', delay
 
            delay = 0.005
 
            reactor.callLater(delay, self.updateLoop)
 

	
 
        def err(e):
 
            log.warn('updateLoop: %r', e)
 
            reactor.callLater(2, self.updateLoop)
 
            
 

	
 
        d = self.update()
 
        d.addCallbacks(done, err)
 
        
 

	
 
    @stats.update.time()
 
    def update(self):
 
        # print "update"
 
        try:
 
            musicState = self.music.getLatest()
 
            song = URIRef(musicState['song']) if musicState.get('song') else None
 
            song = URIRef(
 
                musicState['song']) if musicState.get('song') else None
 
            if 't' not in musicState:
 
                return defer.succeed(0)
 
            t = musicState['t']
 
@@ -206,25 +219,27 @@ class Sequencer(object):
 
                noteReports.append(report)
 
                settings.append(s)
 
            dispatcher.send('state', update={'songNotes': noteReports})
 
            return self.sendToCollector(DeviceSettings.fromList(self.graph, settings))
 
            return self.sendToCollector(
 
                DeviceSettings.fromList(self.graph, settings))
 
        except Exception:
 
            traceback.print_exc()
 
            raise
 

	
 

	
 
class Updates(cyclone.sse.SSEHandler):
 

	
 
    def __init__(self, application, request, **kwargs):
 
        cyclone.sse.SSEHandler.__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):
 
        self.numConnected += 1
 
        
 

	
 
        if self.numConnected == 1:
 
            self.loop()
 

	
 
@@ -233,8 +248,6 @@ class Updates(cyclone.sse.SSEHandler):
 
            return
 
        self.sendEvent(self.state)
 
        reactor.callLater(.1, self.loop)
 
        
 

	
 
    def unbind(self):
 
        self.numConnected -= 1
 

	
 
    
light9/effect/settings.py
Show inline comments
 
@@ -13,15 +13,20 @@ import logging
 
log = logging.getLogger('settings')
 
from light9.collector.device import resolve
 

	
 

	
 
def parseHex(h):
 
    if h[0] != '#': raise ValueError(h)
 
    return [int(h[i:i+2], 16) for i in 1, 3, 5]
 
    return [int(h[i:i + 2], 16) for i in 1, 3, 5]
 

	
 

	
 
def parseHexNorm(h):
 
    return [x / 255 for x in parseHex(h)]
 
    
 

	
 

	
 
def toHex(rgbFloat):
 
    return '#%02x%02x%02x' % tuple(max(0, min(255, int(v * 255))) for v in rgbFloat)
 
    return '#%02x%02x%02x' % tuple(
 
        max(0, min(255, int(v * 255))) for v in rgbFloat)
 

	
 

	
 
def getVal(graph, subj):
 
    lit = graph.value(subj, L9['value']) or graph.value(subj, L9['scaledValue'])
 
@@ -30,26 +35,28 @@ def getVal(graph, subj):
 
        ret = float(ret)
 
    return ret
 

	
 

	
 
class _Settings(object):
 
    """
 
    default values are 0 or '#000000'. Internal rep must not store zeros or some
 
    comparisons will break.
 
    """
 

	
 
    def __init__(self, graph, settingsList):
 
        self.graph = graph # for looking up all possible attrs
 
        self._compiled = {} # dev: { attr: val }; val is number or colorhex
 
        self.graph = graph  # for looking up all possible attrs
 
        self._compiled = {}  # dev: { attr: val }; val is number or colorhex
 
        for row in settingsList:
 
            self._compiled.setdefault(row[0], {})[row[1]] = row[2]
 
        # self._compiled may not be final yet- see _fromCompiled
 
        self._delZeros()
 
        
 

	
 
    @classmethod
 
    def _fromCompiled(cls, graph, compiled):
 
        obj = cls(graph, [])
 
        obj._compiled = compiled
 
        obj._delZeros()
 
        return obj
 
            
 

	
 
    @classmethod
 
    def fromResource(cls, graph, subj):
 
        settingsList = []
 
@@ -67,7 +74,7 @@ class _Settings(object):
 
        i = 0
 
        for (d, a) in cls(graph, [])._vectorKeys(deviceAttrFilter):
 
            if a == L9['color']:
 
                v = toHex(vector[i:i+3])
 
                v = toHex(vector[i:i + 3])
 
                i += 3
 
            else:
 
                v = vector[i]
 
@@ -82,7 +89,7 @@ class _Settings(object):
 
        for s in others:
 
            if not isinstance(s, cls):
 
                raise TypeError(s)
 
            for row in s.asList(): # could work straight from s._compiled
 
            for row in s.asList():  # could work straight from s._compiled
 
                if row[0] is None:
 
                    raise TypeError('bad row %r' % (row,))
 
                dev, devAttr, value = row
 
@@ -100,20 +107,21 @@ class _Settings(object):
 
        for weight, s in others:
 
            if not isinstance(s, cls):
 
                raise TypeError(s)
 
            for row in s.asList(): # could work straight from s._compiled
 
            for row in s.asList():  # could work straight from s._compiled
 
                if row[0] is None:
 
                    raise TypeError('bad row %r' % (row,))
 
                dd = out._compiled.setdefault(row[0], {})
 

	
 
                if isinstance(row[2], basestring):
 
                    prev = parseHexNorm(dd.get(row[1], '#000000'))
 
                    newVal = toHex(prev + weight * numpy.array(parseHexNorm(row[2])))
 
                    newVal = toHex(prev +
 
                                   weight * numpy.array(parseHexNorm(row[2])))
 
                else:
 
                    newVal = dd.get(row[1], 0) + weight * row[2]
 
                dd[row[1]] = newVal
 
        out._delZeros()
 
        return out
 
        
 

	
 
    def _zeroForAttr(self, attr):
 
        if attr == L9['color']:
 
            return '#000000'
 
@@ -126,15 +134,17 @@ class _Settings(object):
 
                    del av[attr]
 
            if not av:
 
                del self._compiled[dev]
 
        
 

	
 
    def __hash__(self):
 
        itemed = tuple([(d, tuple([(a, v) for a, v in sorted(av.items())]))
 
        itemed = tuple([(d, tuple([(a, v)
 
                                   for a, v in sorted(av.items())]))
 
                        for d, av in sorted(self._compiled.items())])
 
        return hash(itemed)
 

	
 
    def __eq__(self, other):
 
        if not issubclass(other.__class__, self.__class__):
 
            raise TypeError("can't compare %r to %r" % (self.__class__, other.__class__))
 
            raise TypeError("can't compare %r to %r" %
 
                            (self.__class__, other.__class__))
 
        return self._compiled == other._compiled
 

	
 
    def __ne__(self, other):
 
@@ -142,18 +152,20 @@ class _Settings(object):
 

	
 
    def __nonzero__(self):
 
        return bool(self._compiled)
 
        
 

	
 
    def __repr__(self):
 
        words = []
 

	
 
        def accum():
 
            for dev, av in self._compiled.iteritems():
 
                for attr, val in sorted(av.iteritems()):
 
                    words.append('%s.%s=%s' % (dev.rsplit('/')[-1],
 
                                               attr.rsplit('/')[-1],
 
                                               val))
 
                    words.append(
 
                        '%s.%s=%s' %
 
                        (dev.rsplit('/')[-1], attr.rsplit('/')[-1], val))
 
                    if len(words) > 5:
 
                        words.append('...')
 
                        return
 

	
 
        accum()
 
        return '<%s %s>' % (self.__class__.__name__, ' '.join(words))
 

	
 
@@ -174,7 +186,7 @@ class _Settings(object):
 

	
 
    def devices(self):
 
        return self._compiled.keys()
 
        
 

	
 
    def toVector(self, deviceAttrFilter=None):
 
        out = []
 
        for dev, attr in self._vectorKeys(deviceAttrFilter):
 
@@ -192,7 +204,7 @@ class _Settings(object):
 
    def ofDevice(self, dev):
 
        return self.__class__._fromCompiled(self.graph,
 
                                            {dev: self._compiled.get(dev, {})})
 
        
 

	
 
    def distanceTo(self, other):
 
        diff = numpy.array(self.toVector()) - other.toVector()
 
        d = numpy.linalg.norm(diff, ord=None)
 
@@ -210,27 +222,29 @@ class _Settings(object):
 
            settingHash = hash((dev, attr, val)) % 9999999
 
            setting = URIRef('%sset%s' % (settingRoot, settingHash))
 
            add.append((subj, L9['setting'], setting, ctx))
 
            if setting in settingsSubgraphCache:              
 
            if setting in settingsSubgraphCache:
 
                continue
 
                
 

	
 
            scaledAttributeTypes = [L9['color'], L9['brightness'], L9['uv']]
 
            settingType = L9['scaledValue'] if attr in scaledAttributeTypes else L9['value']
 
            settingType = L9[
 
                'scaledValue'] if attr in scaledAttributeTypes else L9['value']
 
            if not isinstance(val, URIRef):
 
                val = Literal(val)
 
            add.extend([
 
                (setting, L9['device'], dev, ctx),
 
                (setting, L9['deviceAttr'], attr, ctx),
 
                (setting, settingType, val, ctx),
 
                ])
 
            ])
 
            settingsSubgraphCache.add(setting)
 
            
 

	
 
        return add
 

	
 

	
 
class DeviceSettings(_Settings):
 

	
 
    def _vectorKeys(self, deviceAttrFilter=None):
 
        with self.graph.currentState() as g:
 
            devs = set() # devclass, dev
 
            devs = set()  # devclass, dev
 
            for dc in g.subjects(RDF.type, L9['DeviceClass']):
 
                for dev in g.subjects(RDF.type, dc):
 
                    devs.add((dc, dev))
 
@@ -243,4 +257,3 @@ class DeviceSettings(_Settings):
 
                        continue
 
                    keys.append(key)
 
        return keys
 
    
light9/effect/settings_test.py
Show inline comments
 
@@ -4,12 +4,13 @@ from rdfdb.patch import Patch
 
from rdfdb.localsyncedgraph import LocalSyncedGraph
 
from light9.namespaces import RDF, L9, DEV
 
from light9.effect.settings import DeviceSettings
 
             
 
        
 

	
 

	
 
class TestDeviceSettings(unittest.TestCase):
 

	
 
    def setUp(self):
 
        self.graph = LocalSyncedGraph(files=['test/cam/lightConfig.n3',
 
                                             'test/cam/bg.n3'])
 
        self.graph = LocalSyncedGraph(
 
            files=['test/cam/lightConfig.n3', 'test/cam/bg.n3'])
 

	
 
    def testToVectorZero(self):
 
        ds = DeviceSettings(self.graph, [])
 
@@ -29,50 +30,57 @@ class TestDeviceSettings(unittest.TestCa
 

	
 
    def testMissingFieldsEqZero(self):
 
        self.assertEqual(
 
            DeviceSettings(self.graph, [(L9['aura1'], L9['rx'], 0),]),
 
            DeviceSettings(self.graph, []))
 
            DeviceSettings(self.graph, [
 
                (L9['aura1'], L9['rx'], 0),
 
            ]), DeviceSettings(self.graph, []))
 

	
 
    def testFalseIfZero(self):
 
        self.assertTrue(DeviceSettings(self.graph, [(L9['aura1'], L9['rx'], 0.1)]))
 
        self.assertTrue(
 
            DeviceSettings(self.graph, [(L9['aura1'], L9['rx'], 0.1)]))
 
        self.assertFalse(DeviceSettings(self.graph, []))
 
        
 

	
 
    def testFromResource(self):
 
        ctx = L9['']
 
        self.graph.patch(Patch(addQuads=[
 
            (L9['foo'], L9['setting'], L9['foo_set0'], ctx),
 
            (L9['foo_set0'], L9['device'], L9['light1'], ctx),
 
            (L9['foo_set0'], L9['deviceAttr'], L9['brightness'], ctx),
 
            (L9['foo_set0'], L9['value'], Literal(0.1), ctx),
 
            (L9['foo'], L9['setting'], L9['foo_set1'], ctx),
 
            (L9['foo_set1'], L9['device'], L9['light1'], ctx),
 
            (L9['foo_set1'], L9['deviceAttr'], L9['speed'], ctx),
 
            (L9['foo_set1'], L9['scaledValue'], Literal(0.2), ctx),
 
        ]))
 
        self.graph.patch(
 
            Patch(addQuads=[
 
                (L9['foo'], L9['setting'], L9['foo_set0'], ctx),
 
                (L9['foo_set0'], L9['device'], L9['light1'], ctx),
 
                (L9['foo_set0'], L9['deviceAttr'], L9['brightness'], ctx),
 
                (L9['foo_set0'], L9['value'], Literal(0.1), ctx),
 
                (L9['foo'], L9['setting'], L9['foo_set1'], ctx),
 
                (L9['foo_set1'], L9['device'], L9['light1'], ctx),
 
                (L9['foo_set1'], L9['deviceAttr'], L9['speed'], ctx),
 
                (L9['foo_set1'], L9['scaledValue'], Literal(0.2), ctx),
 
            ]))
 
        s = DeviceSettings.fromResource(self.graph, L9['foo'])
 

	
 
        self.assertEqual(DeviceSettings(self.graph, [
 
            (L9['light1'], L9['brightness'], 0.1),
 
            (L9['light1'], L9['speed'], 0.2),
 
        ]), s)
 
        self.assertEqual(
 
            DeviceSettings(self.graph, [
 
                (L9['light1'], L9['brightness'], 0.1),
 
                (L9['light1'], L9['speed'], 0.2),
 
            ]), s)
 

	
 
    def testToVector(self):
 
        v = DeviceSettings(self.graph, [
 
            (DEV['aura1'], L9['rx'], 0.5),
 
            (DEV['aura1'], L9['color'], '#00ff00'),
 
        ]).toVector()
 
        self.assertEqual(
 
            [0, 1, 0, 0.5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
 
            v)
 
        
 
        self.assertEqual([
 
            0, 1, 0, 0.5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
 
            0, 0, 0, 0, 0, 0, 0, 0
 
        ], v)
 

	
 
    def testFromVector(self):
 
        s = DeviceSettings.fromVector(
 
            self.graph,
 
            [0, 1, 0, 0.5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
 
        
 
        self.assertEqual(DeviceSettings(self.graph, [
 
            (DEV['aura1'], L9['rx'], 0.5),
 
            (DEV['aura1'], L9['color'], '#00ff00'),
 
        ]), s)                            
 
        s = DeviceSettings.fromVector(self.graph, [
 
            0, 1, 0, 0.5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
 
            0, 0, 0, 0, 0, 0, 0, 0
 
        ])
 

	
 
        self.assertEqual(
 
            DeviceSettings(self.graph, [
 
                (DEV['aura1'], L9['rx'], 0.5),
 
                (DEV['aura1'], L9['color'], '#00ff00'),
 
            ]), s)
 

	
 
    def testAsList(self):
 
        sets = [
 
@@ -85,23 +93,23 @@ class TestDeviceSettings(unittest.TestCa
 
        s = DeviceSettings(self.graph, [
 
            (DEV['aura1'], L9['rx'], 0),
 
            (DEV['aura2'], L9['rx'], 0.1),
 
            ])
 
        ])
 
        # aura1 is all defaults (zeros), so it doesn't get listed
 
        self.assertItemsEqual([DEV['aura2']], s.devices())
 

	
 
    def testAddStatements(self):
 
        s = DeviceSettings(self.graph, [
 
            (DEV['aura2'], L9['rx'], 0.1),
 
            ])
 
        ])
 
        stmts = s.statements(L9['foo'], L9['ctx1'], L9['s_'], set())
 
        self.maxDiff=None
 
        self.maxDiff = None
 
        self.assertItemsEqual([
 
            (L9['foo'], L9['setting'], L9['s_set4350023'], L9['ctx1']),
 
            (L9['s_set4350023'], L9['device'], DEV['aura2'], L9['ctx1']),
 
            (L9['s_set4350023'], L9['deviceAttr'], L9['rx'], L9['ctx1']),
 
            (L9['s_set4350023'], L9['value'], Literal(0.1), L9['ctx1']),
 
        ], stmts)
 
        
 

	
 
    def testDistanceTo(self):
 
        s1 = DeviceSettings(self.graph, [
 
            (DEV['aura1'], L9['rx'], 0.1),
 
@@ -112,25 +120,31 @@ class TestDeviceSettings(unittest.TestCa
 
            (DEV['aura1'], L9['ry'], 0.3),
 
        ])
 
        self.assertEqual(0.36055512754639896, s1.distanceTo(s2))
 
        
 

	
 

	
 
L1 = L9['light1']
 
ZOOM = L9['zoom']
 

	
 

	
 
class TestFromBlend(unittest.TestCase):
 

	
 
    def setUp(self):
 
        self.graph = LocalSyncedGraph(files=['test/cam/lightConfig.n3',
 
                                             'test/cam/bg.n3'])
 
        self.graph = LocalSyncedGraph(
 
            files=['test/cam/lightConfig.n3', 'test/cam/bg.n3'])
 

	
 
    def testSingle(self):
 
        self.assertEqual(
 
            DeviceSettings(self.graph, [(L1, ZOOM, 0.5)]),
 
            DeviceSettings.fromBlend(self.graph, [
 
                (1, DeviceSettings(self.graph, [(L1, ZOOM, 0.5)]))]))
 
            DeviceSettings.fromBlend(
 
                self.graph,
 
                [(1, DeviceSettings(self.graph, [(L1, ZOOM, 0.5)]))]))
 

	
 
    def testScale(self):
 
        self.assertEqual(
 
            DeviceSettings(self.graph, [(L1, ZOOM, 0.1)]),
 
            DeviceSettings.fromBlend(self.graph, [
 
                (.2, DeviceSettings(self.graph, [(L1, ZOOM, 0.5)]))]))
 
            DeviceSettings.fromBlend(
 
                self.graph,
 
                [(.2, DeviceSettings(self.graph, [(L1, ZOOM, 0.5)]))]))
 

	
 
    def testMixFloats(self):
 
        self.assertEqual(
light9/effect/simple_outputs.py
Show inline comments
 
@@ -3,15 +3,17 @@ import traceback
 
from light9.namespaces import L9, RDF
 
from light9.effect.scale import scale
 

	
 

	
 
class SimpleOutputs(object):
 

	
 
    def __init__(self, graph):
 
        self.graph = graph
 
        
 

	
 
        # effect : [(dev, attr, value, isScaled)]
 
        self.effectOutputs = {}
 

	
 
        self.graph.addHandler(self.updateEffectsFromGraph)
 
        
 

	
 
    def updateEffectsFromGraph(self):
 
        for effect in self.graph.subjects(RDF.type, L9['Effect']):
 
            settings = []
 
@@ -37,7 +39,6 @@ class SimpleOutputs(object):
 
            if settings:
 
                self.effectOutputs[effect] = settings
 
            # also have to read eff :effectAttr [ :tint x; :tintStrength y ]
 
        
 

	
 
    def values(self, effect, strength, colorScale):
 
        out = {}
 
@@ -48,4 +49,3 @@ class SimpleOutputs(object):
 
                value = scale(value, colorScale)
 
            out[(dev, devAttr)] = value
 
        return out
 
        
light9/effecteval/effect.py
Show inline comments
 
@@ -5,16 +5,19 @@ from light9.namespaces import L9, RDF
 
from light9.curvecalc.curve import CurveResource
 
from light9 import prof
 
from light9 import Submaster
 
from light9 import Effects # gets reload() later
 
from light9 import Effects  # gets reload() later
 
log = logging.getLogger('effect')
 

	
 
# consider http://waxeye.org/ for a parser that can be used in py and js
 

	
 

	
 
class CouldNotConvert(TypeError):
 
    pass
 

	
 

	
 
class CodeLine(object):
 
    """code string is immutable"""
 

	
 
    def __init__(self, graph, code):
 
        self.graph, self.code = graph, code
 

	
 
@@ -36,21 +39,23 @@ class CodeLine(object):
 
        resources = {}
 

	
 
        def alreadyInFunc(prefix, s, i):
 
            return i >= len(prefix) and s[i-len(prefix):i] == prefix
 
            return i >= len(prefix) and s[i - len(prefix):i] == prefix
 

	
 
        def repl(m):
 
            v = '_res%s' % self.uriCounter
 
            self.uriCounter += 1
 
            r = resources[v] = URIRef(m.group(1))
 
            for uriTypeMatches, wrapFuncName, addlArgs in [
 
                    (self._uriIsCurve(r), 'curve', ', t'),
 
                (self._uriIsCurve(r), 'curve', ', t'),
 
                    # I'm pretty sure this shouldn't be auto-applied: it's reasonable to refer to a sub and not want its current value
 
                    #(self._uriIsSub(r), 'currentSubLevel', ''),
 
            ]:
 
                if uriTypeMatches:
 
                    if not alreadyInFunc(wrapFuncName + '(', m.string, m.start()):
 
                    if not alreadyInFunc(wrapFuncName + '(', m.string,
 
                                         m.start()):
 
                        return '%s(%s%s)' % (wrapFuncName, v, addlArgs)
 
            return v
 

	
 
        outExpr = re.sub(r'<(http\S*?)>', repl, expr)
 
        return lname, expr, outExpr, resources
 

	
 
@@ -60,14 +65,14 @@ class CodeLine(object):
 
        tokens = set(re.findall(r'\b([a-zA-Z_]\w*)\b', withoutUris))
 
        tokens.discard('None')
 
        return tokens
 
        
 

	
 
    def _uriIsCurve(self, uri):
 
        # this result could vary with graph changes (rare)
 
        return self.graph.contains((uri, RDF.type, L9['Curve']))
 

	
 
    def _uriIsSub(self, uri):
 
        return self.graph.contains((uri, RDF.type, L9['Submaster']))
 
        
 

	
 
    @prof.logTime
 
    def _resourcesAsPython(self, resources):
 
        """
 
@@ -77,7 +82,7 @@ class CodeLine(object):
 
        out = {}
 
        subs = prof.logTime(Submaster.get_global_submasters)(self.graph)
 
        for localVar, uri in resources.items():
 
            
 

	
 
            for rdfClass in self.graph.objects(uri, RDF.type):
 
                if rdfClass == L9['Curve']:
 
                    cr = CurveResource(self.graph, uri)
 
@@ -95,8 +100,10 @@ class CodeLine(object):
 
                out[localVar] = CouldNotConvert(uri)
 

	
 
        return out
 
        
 

	
 

	
 
class EffectNode(object):
 

	
 
    def __init__(self, graph, uri):
 
        self.graph, self.uri = graph, uri
 
        # this is not expiring at the right time, when an effect goes away
 
@@ -127,7 +134,9 @@ class EffectNode(object):
 
            inNames = c.possibleVars.intersection(codeFromOutput.keys())
 
            inNames.discard(outName)
 
            deps[outName] = inNames
 
        self.codes = [codeFromOutput[n] for n in toposort.toposort_flatten(deps)]
 
        self.codes = [
 
            codeFromOutput[n] for n in toposort.toposort_flatten(deps)
 
        ]
 

	
 
    def _currentSubSettingValues(self, sub):
 
        """what KC subSettings are setting levels right now?"""
 
@@ -149,25 +158,26 @@ class EffectNode(object):
 
            raise TypeError("got %r" % uri)
 

	
 
        foundLevels = list(self._currentSubSettingValues(uri))
 
        
 

	
 
        if not foundLevels:
 
            return 0
 
        
 

	
 
        return max(foundLevels)
 
        
 

	
 
    def eval(self, songTime):
 
        ns = {'t': songTime}
 
        ns.update(self.otherFuncs)
 

	
 
        ns.update(dict(
 
            curve=lambda c, t: c.eval(t),
 
            currentSubLevel=self.currentSubLevel,
 
        ns.update(
 
            dict(
 
                curve=lambda c, t: c.eval(t),
 
                currentSubLevel=self.currentSubLevel,
 
            ))
 

	
 
        # I think this is slowing effecteval. Could we cache results
 
        # that we know haven't changed, like if a curve returns 0
 
        # again, we can skip an eval() call on the line that uses it
 
        
 

	
 
        for c in self.codes:
 
            codeNs = ns.copy()
 
            codeNs.update(c.pyResources)
 
@@ -180,4 +190,3 @@ class EffectNode(object):
 
        if 'out' not in ns:
 
            log.error("ran code for %s, didn't make an 'out' value", self.uri)
 
        return ns['out']
 

	
light9/effecteval/effectloop.py
Show inline comments
 
@@ -16,23 +16,26 @@ from light9 import dmxclient
 
from light9 import prof
 
log = logging.getLogger('effectloop')
 

	
 

	
 
class EffectLoop(object):
 
    """maintains a collection of the current EffectNodes, gets time from
 
    music player, sends dmx"""
 

	
 
    def __init__(self, graph, stats):
 
        self.graph, self.stats = graph, stats
 
        self.currentSong = None
 
        self.currentEffects = [] # EffectNodes for the current song plus the submaster ones
 
        self.currentEffects = [
 
        ]  # EffectNodes for the current song plus the submaster ones
 
        self.lastLogTime = 0
 
        self.lastLogMsg = ""
 
        self.lastErrorLog = 0
 
        self.graph.addHandler(self.setEffects)
 
        self.period = 1 / 30
 
        self.coastSecs = .3 # main reason to keep this low is to notice play/pause
 
        self.coastSecs = .3  # main reason to keep this low is to notice play/pause
 
        self.songTimeFetch = 0
 
        self.songIsPlaying = False
 
        self.songTimeFromRequest = 0
 
        self.requestTime = 0 # unix sec for when we fetched songTime
 
        self.requestTime = 0  # unix sec for when we fetched songTime
 
        self.initOutput()
 

	
 
    def initOutput(self):
 
@@ -49,17 +52,16 @@ class EffectLoop(object):
 
        log.info('setEffects currentSong=%s', self.currentSong)
 
        if self.currentSong is None:
 
            return
 
        
 

	
 
        for effectUri in self.graph.objects(self.currentSong, L9['effect']):
 
            self.currentEffects.append(EffectNode(self.graph, effectUri))
 

	
 

	
 
        for sub in self.graph.subjects(RDF.type, L9['Submaster']):
 
            for effectUri in self.graph.objects(sub, L9['drivesEffect']):
 
                self.currentEffects.append(EffectNode(self.graph, effectUri))
 
                
 

	
 
        log.info('now we have %s effects', len(self.currentEffects))
 
        
 

	
 
    @inlineCallbacks
 
    def getSongTime(self):
 
        now = time.time()
 
@@ -74,15 +76,14 @@ class EffectLoop(object):
 
                self.requestTime = now
 
                self.currentPlaying = response['playing']
 
                self.songTimeFromRequest = response['t']
 
                returnValue(
 
                    (response['t'], (response['song'] and URIRef(response['song']))))
 
                returnValue((response['t'], (response['song'] and
 
                                             URIRef(response['song']))))
 

	
 
        estimated = self.songTimeFromRequest
 
        if self.currentSong is not None and self.currentPlaying:
 
            estimated += now - self.requestTime
 
        returnValue((estimated, self.currentSong))
 

	
 

	
 
    @inlineCallbacks
 
    def updateTimeFromMusic(self):
 
        t1 = time.time()
 
@@ -96,7 +97,8 @@ class EffectLoop(object):
 
            self.graph.addHandler(self.setEffects)
 

	
 
        elapsed = time.time() - t1
 
        reactor.callLater(max(0, self.period - elapsed), self.updateTimeFromMusic)
 
        reactor.callLater(max(0, self.period - elapsed),
 
                          self.updateTimeFromMusic)
 

	
 
    def estimatedSongTime(self):
 
        now = time.time()
 
@@ -108,21 +110,23 @@ class EffectLoop(object):
 
    @inlineCallbacks
 
    def sendLevels(self):
 
        t1 = time.time()
 
        log.debug("time since last call: %.1f ms" % (1000 * (t1 - self.lastSendLevelsTime)))
 
        log.debug("time since last call: %.1f ms" %
 
                  (1000 * (t1 - self.lastSendLevelsTime)))
 
        self.lastSendLevelsTime = t1
 
        try:
 
            with self.stats.sendLevels.time():
 
                if self.currentSong is not None:
 
                    log.debug('allEffectOutputs')
 
                    with self.stats.evals.time():
 
                        outputs = self.allEffectOutputs(self.estimatedSongTime())
 
                        outputs = self.allEffectOutputs(
 
                            self.estimatedSongTime())
 
                    log.debug('combineOutputs')
 
                    combined = self.combineOutputs(outputs)
 
                    self.logLevels(t1, combined)
 
                    log.debug('sendOutput')
 
                    with self.stats.sendOutput.time():
 
                        yield self.sendOutput(combined)
 
                
 

	
 
                elapsed = time.time() - t1
 
                dt = max(0, self.period - elapsed)
 
        except Exception:
 
@@ -138,12 +142,12 @@ class EffectLoop(object):
 
        out = Submaster.sub_maxes(*outputs)
 

	
 
        return out
 
        
 

	
 
    @inlineCallbacks
 
    def sendOutput(self, combined):
 
        dmx = combined.get_dmx_list()
 
        yield dmxclient.outputlevels(dmx, twisted=True)
 
        
 

	
 
    def allEffectOutputs(self, songTime):
 
        outputs = []
 
        for e in self.currentEffects:
 
@@ -160,10 +164,11 @@ class EffectLoop(object):
 
                        log.exception('in expression %r', exc.expr)
 
                    log.error("effect %s: %s" % (e.uri, exc))
 
                    self.lastErrorLog = now
 
        log.debug('eval %s effects, got %s outputs', len(self.currentEffects), len(outputs))
 
                    
 
        log.debug('eval %s effects, got %s outputs', len(self.currentEffects),
 
                  len(outputs))
 

	
 
        return outputs
 
        
 

	
 
    def logLevels(self, now, out):
 
        # this would look nice on the top of the effecteval web pages too
 
        if log.isEnabledFor(logging.DEBUG):
 
@@ -175,23 +180,28 @@ class EffectLoop(object):
 
                    log.info(msg)
 
                    self.lastLogMsg = msg
 
                self.lastLogTime = now
 
                
 

	
 
    def logMessage(self, out):
 
        return ("send dmx: {%s}" %
 
                ", ".join("%r: %.3g" % (str(k), v)
 
                          for k,v in out.get_levels().items()))
 
        return ("send dmx: {%s}" % ", ".join(
 
            "%r: %.3g" % (str(k), v) for k, v in out.get_levels().items()))
 

	
 

	
 
Z = numpy.zeros((50, 3), dtype=numpy.float16)
 

	
 

	
 
class ControlBoard(object):
 
    def __init__(self, dev='/dev/serial/by-id/usb-FTDI_FT232R_USB_UART_A7027NYX-if00-port0'):
 

	
 
    def __init__(
 
            self,
 
            dev='/dev/serial/by-id/usb-FTDI_FT232R_USB_UART_A7027NYX-if00-port0'
 
    ):
 
        log.info('opening %s', dev)
 
        self._dev = serial.Serial(dev, baudrate=115200)
 

	
 
    def _8bitMessage(self, floatArray):
 
        px255 = (numpy.clip(floatArray, 0, 1) * 255).astype(numpy.uint8)
 
        return px255.reshape((-1,)).tostring()
 
        
 

	
 
    def setStrip(self, which, pixels):
 
        """
 
        which: 0 or 1 to pick the strip
 
@@ -209,7 +219,8 @@ class ControlBoard(object):
 
        level: 0 to 1
 
        """
 
        command = {0: '\x02', 1: '\x03'}[which]
 
        self._dev.write('\x60' + command + chr(int(max(0, min(1, level)) * 255)))
 
        self._dev.write('\x60' + command +
 
                        chr(int(max(0, min(1, level)) * 255)))
 
        self._dev.flush()
 

	
 
    def setRgb(self, color):
 
@@ -221,17 +232,22 @@ class ControlBoard(object):
 
        self._dev.write('\x60\x04' + self._8bitMessage(color))
 
        self._dev.flush()
 

	
 
        
 

	
 
class LedLoop(EffectLoop):
 

	
 
    def initOutput(self):
 
        self.board = ControlBoard()
 
        self.lastSent = {} # what's in arduino's memory
 
        
 
        self.lastSent = {}  # what's in arduino's memory
 

	
 
    def combineOutputs(self, outputs):
 
        combined = {'L': Z, 'R': Z,
 
                    'blacklight0': 0, 'blacklight1': 0,
 
                    'W': numpy.zeros((1, 3), dtype=numpy.float16)}
 
        
 
        combined = {
 
            'L': Z,
 
            'R': Z,
 
            'blacklight0': 0,
 
            'blacklight1': 0,
 
            'W': numpy.zeros((1, 3), dtype=numpy.float16)
 
        }
 

	
 
        for out in outputs:
 
            log.debug('combine output %r', out)
 

	
 
@@ -241,12 +257,17 @@ class LedLoop(EffectLoop):
 
            if isinstance(out, Submaster.Submaster) and '*' in out.name:
 
                level = float(out.name.split('*')[1])
 
                n = out.name.split('*')[0]
 
                if n == 'widered': out = Effects.Strip.solid('LRW', (1,0,0)) * level
 
                if n == 'widegreen': out = Effects.Strip.solid('LRW', (0,1,0)) * level
 
                if n == 'wideblue': out = Effects.Strip.solid('LRW', (0,0,1)) * level
 
                if n == 'whiteled': out = Effects.Strip.solid('LRW', (1,.7,.7)) * level
 
                if n == 'blacklight': out = Effects.Blacklight(level) # missing blues!
 
 
 
                if n == 'widered':
 
                    out = Effects.Strip.solid('LRW', (1, 0, 0)) * level
 
                if n == 'widegreen':
 
                    out = Effects.Strip.solid('LRW', (0, 1, 0)) * level
 
                if n == 'wideblue':
 
                    out = Effects.Strip.solid('LRW', (0, 0, 1)) * level
 
                if n == 'whiteled':
 
                    out = Effects.Strip.solid('LRW', (1, .7, .7)) * level
 
                if n == 'blacklight':
 
                    out = Effects.Blacklight(level)  # missing blues!
 

	
 
            if isinstance(out, Effects.Blacklight):
 
                # no picking yet
 
                #key = 'blacklight%s' % out.which
 
@@ -256,34 +277,37 @@ class LedLoop(EffectLoop):
 
                pixels = numpy.array(out.pixels, dtype=numpy.float16)
 
                for w in out.which:
 
                    combined[w] = numpy.maximum(
 
                        combined[w], pixels[:1,:] if w == 'W' else pixels)
 
                
 
                        combined[w], pixels[:1, :] if w == 'W' else pixels)
 

	
 
        return combined
 

	
 
    @inlineCallbacks
 
    def sendOutput(self, combined):
 
        for meth, selectArgs, value in [
 
                ('setStrip', (0,), combined['L']),
 
                ('setStrip', (1,), combined['R']),
 
                ('setUv', (0,), combined['blacklight0']),
 
                ('setUv', (1,), combined['blacklight1']),
 
                ('setRgb', (), combined['W']),
 
            ]:
 
            ('setStrip', (0,), combined['L']),
 
            ('setStrip', (1,), combined['R']),
 
            ('setUv', (0,), combined['blacklight0']),
 
            ('setUv', (1,), combined['blacklight1']),
 
            ('setRgb', (), combined['W']),
 
        ]:
 
            key = (meth, selectArgs)
 
            compValue = value.tolist() if isinstance(value, numpy.ndarray) else value
 
            
 
            compValue = value.tolist() if isinstance(value,
 
                                                     numpy.ndarray) else value
 

	
 
            if self.lastSent.get(key) == compValue:
 
                continue
 

	
 
            log.debug('value changed: %s(%s %s)', meth, selectArgs, value)
 
            
 

	
 
            getattr(self.board, meth)(*(selectArgs + (value,)))
 
            self.lastSent[key] = compValue
 
                
 
        yield succeed(None) # there was an attempt at an async send
 
        
 

	
 
        yield succeed(None)  # there was an attempt at an async send
 

	
 
    def logMessage(self, out):
 
        return str([(w, p.tolist() if isinstance(p, numpy.ndarray) else p) for w,p in out.items()])
 
        return str([(w, p.tolist() if isinstance(p, numpy.ndarray) else p)
 
                    for w, p in out.items()])
 

	
 

	
 
def makeEffectLoop(graph, stats, outputWhere):
 
    if outputWhere == 'dmx':
 
@@ -292,5 +316,3 @@ def makeEffectLoop(graph, stats, outputW
 
        return LedLoop(graph, stats)
 
    else:
 
        raise NotImplementedError("unknown output system %r" % outputWhere)
 

	
 
        
light9/effecteval/test_effect.py
Show inline comments
 
import unittest
 
import mock
 
import sys
 
sys.path.insert(0, 'bin') # for run_local
 
sys.path.insert(0, 'bin')  # for run_local
 

	
 
from effect import CodeLine
 
from rdflib import URIRef
 

	
 

	
 
def isCurve(self, uri):
 
    return 'curve' in uri
 

	
 

	
 
def isSub(self, uri):
 
    return 'sub' in uri
 

	
 

	
 
@mock.patch('light9.effecteval.effect.CodeLine._uriIsCurve', new=isCurve)
 
@mock.patch('light9.effecteval.effect.CodeLine._uriIsSub', new=isSub)
 
@mock.patch('light9.effecteval.effect.CodeLine._resourcesAsPython',
 
            new=lambda self, r: self.expr)
 
class TestAsPython(unittest.TestCase):
 

	
 
    def test_gets_lname(self):
 
        ec = CodeLine(graph=None, code='x = y+1')
 
        self.assertEqual('x', ec.outName)
 
@@ -24,7 +29,7 @@ class TestAsPython(unittest.TestCase):
 
        ec = CodeLine(graph=None, code='x = y+1')
 
        self.assertEqual('y+1', ec._asPython()[2])
 
        self.assertEqual({}, ec._asPython()[3])
 
        
 

	
 
    def test_converts_uri_to_var(self):
 
        ec = CodeLine(graph=None, code='x = <http://example.com/>')
 
        _, inExpr, expr, uris = ec._asPython()
 
@@ -32,18 +37,22 @@ class TestAsPython(unittest.TestCase):
 
        self.assertEqual({'_res0': URIRef('http://example.com/')}, uris)
 

	
 
    def test_converts_multiple_uris(self):
 
        ec = CodeLine(graph=None, code='x = <http://example.com/> + <http://other>')
 
        ec = CodeLine(graph=None,
 
                      code='x = <http://example.com/> + <http://other>')
 
        _, inExpr, expr, uris = ec._asPython()
 
        self.assertEqual('_res0 + _res1', expr)
 
        self.assertEqual({'_res0': URIRef('http://example.com/'),
 
                          '_res1': URIRef('http://other')}, uris)
 
        
 
        self.assertEqual(
 
            {
 
                '_res0': URIRef('http://example.com/'),
 
                '_res1': URIRef('http://other')
 
            }, uris)
 

	
 
    def test_doesnt_fall_for_brackets(self):
 
        ec = CodeLine(graph=None, code='x = 1<2>3< h')
 
        _, inExpr, expr, uris = ec._asPython()
 
        self.assertEqual('1<2>3< h', expr)
 
        self.assertEqual({}, uris)
 
        
 

	
 
    def test_curve_uri_expands_to_curve_eval_func(self):
 
        ec = CodeLine(graph=None, code='x = <http://example/curve1>')
 
        _, inExpr, expr, uris = ec._asPython()
 
@@ -51,19 +60,24 @@ class TestAsPython(unittest.TestCase):
 
        self.assertEqual({'_res0': URIRef('http://example/curve1')}, uris)
 

	
 
    def test_curve_doesnt_double_wrap(self):
 
        ec = CodeLine(graph=None, code='x = curve(<http://example/curve1>, t+.01)')
 
        ec = CodeLine(graph=None,
 
                      code='x = curve(<http://example/curve1>, t+.01)')
 
        _, inExpr, expr, uris = ec._asPython()
 
        self.assertEqual('curve(_res0, t+.01)', expr)
 
        self.assertEqual({'_res0': URIRef('http://example/curve1')}, uris)
 
        
 
        
 

	
 

	
 
@mock.patch('light9.effecteval.effect.CodeLine._uriIsCurve', new=isCurve)
 
@mock.patch('light9.effecteval.effect.CodeLine._resourcesAsPython',
 
            new=lambda self, r: self.expr)
 
class TestPossibleVars(unittest.TestCase):
 

	
 
    def test1(self):
 
        self.assertEqual(set([]), CodeLine(None, 'a1 = 1').possibleVars)
 

	
 
    def test2(self):
 
        self.assertEqual(set(['a2']), CodeLine(None, 'a1 = a2').possibleVars)
 

	
 
    def test3(self):
 
        self.assertEqual(set(['a2', 'a3']), CodeLine(None, 'a1 = a2 + a3').possibleVars)
 
        self.assertEqual(set(['a2', 'a3']),
 
                         CodeLine(None, 'a1 = a2 + a3').possibleVars)
light9/greplin_cyclone.py
Show inline comments
 
@@ -3,26 +3,34 @@ import cyclone.web, cyclone.websocket, c
 
import greplin.scales.twistedweb, greplin.scales.formats
 
from greplin import scales
 

	
 

	
 
# Like scales.twistedweb.StatsResource, but modified for cyclone. May
 
# be missing features.
 
class StatsForCyclone(cyclone.web.RequestHandler):
 

	
 
    def get(self):
 
        parts = []
 
        statDict = greplin.scales.twistedweb.util.lookup(scales.getStats(), parts)
 
        statDict = greplin.scales.twistedweb.util.lookup(
 
            scales.getStats(), parts)
 

	
 
        if statDict is None:
 
          self.set_status(404)
 
          self.write("Path not found.")
 
          return
 
            self.set_status(404)
 
            self.write("Path not found.")
 
            return
 

	
 
        query = self.get_argument('query', default=None)
 

	
 
        if self.get_argument('format', default=None) == 'json':
 
          self.set_header('content-type', 'text/javascript; charset=UTF-8')
 
          greplin.scales.formats.jsonFormat(self, statDict, query)
 
            self.set_header('content-type', 'text/javascript; charset=UTF-8')
 
            greplin.scales.formats.jsonFormat(self, statDict, query)
 
        elif self.get_argument('format', default=None) == 'prettyjson':
 
          self.set_header('content-type', 'text/javascript; charset=UTF-8')
 
          greplin.scales.formats.jsonFormat(self, statDict, query, pretty=True)
 
            self.set_header('content-type', 'text/javascript; charset=UTF-8')
 
            greplin.scales.formats.jsonFormat(self,
 
                                              statDict,
 
                                              query,
 
                                              pretty=True)
 
        else:
 
          greplin.scales.formats.htmlHeader(self, '/' + '/'.join(parts), 'svr', query)
 
          greplin.scales.formats.htmlFormat(self, tuple(parts), statDict, query)
 
            greplin.scales.formats.htmlHeader(self, '/' + '/'.join(parts),
 
                                              'svr', query)
 
            greplin.scales.formats.htmlFormat(self, tuple(parts), statDict,
 
                                              query)
light9/gtkpyconsole.py
Show inline comments
 
@@ -3,6 +3,7 @@ import gi
 
from gi.repository import Gtk
 
from gi.repository import Pango
 

	
 

	
 
def togglePyConsole(self, item, user_ns):
 
    """
 
    toggles a toplevel window with an ipython console inside.
 
@@ -26,11 +27,12 @@ def togglePyConsole(self, item, user_ns)
 
            self.pythonWindow.show_all()
 
            self.pythonWindow.set_size_request(750, 550)
 
            self.pythonWindow.set_resizable(True)
 

	
 
            def onDestroy(*args):
 
                item.set_active(False)
 
                del self.pythonWindow
 

	
 
            self.pythonWindow.connect("destroy", onDestroy)
 
    else:
 
        if hasattr(self, 'pythonWindow'):
 
            self.pythonWindow.destroy()
 

	
light9/io/__init__.py
Show inline comments
 
from __future__ import division
 
import sys
 

	
 

	
 
class BaseIO(object):
 

	
 
    def __init__(self):
 
        self.dummy=1
 
        self.dummy = 1
 
        self.__name__ = 'BaseIO'
 
        # please override and set __name__ to your class name
 

	
 
    def golive(self):
 
        """call this if you want to promote the dummy object becomes a live object"""
 
        print "IO: %s is going live" % self.__name__
 
        self.dummy=0
 
        self.dummy = 0
 
        # you'd override with additional startup stuff here,
 
        # perhaps even loading a module and saving it to a class
 
        # attr so the subclass-specific functions can use it
 

	
 
    def godummy(self):
 
        print "IO: %s is going dummy" % self.__name__
 
        self.dummy=1
 
        self.dummy = 1
 
        # you might override this to close ports, etc
 
        
 

	
 
    def isdummy(self):
 
        return self.dummy
 

	
 
@@ -32,10 +34,12 @@ class BaseIO(object):
 
    # the derived class will have more methods to do whatever it does,
 
    # and they should return dummy values if self.dummy==1.
 

	
 

	
 
class ParportDMX(BaseIO):
 

	
 
    def __init__(self, dimmers=68):
 
        BaseIO.__init__(self)
 
        self.__name__='ParportDMX'
 
        self.__name__ = 'ParportDMX'
 
        self.dimmers = dimmers
 

	
 
    def golive(self):
 
@@ -43,18 +47,20 @@ class ParportDMX(BaseIO):
 
        import parport
 
        self.parport = parport
 
        self.parport.getparport()
 
        
 

	
 
    def sendlevels(self, levels):
 
        if self.dummy:
 
            return
 
        
 

	
 
        levels = list(levels) + [0]
 
        # if levels[14] > 0: levels[14] = 100 # non-dim
 
        self.parport.outstart()
 
        for p in range(1, self.dimmers + 2):
 
            self.parport.outbyte(levels[p-1]*255 / 100)
 
            self.parport.outbyte(levels[p - 1] * 255 / 100)
 

	
 

	
 
class UsbDMX(BaseIO):
 

	
 
    def __init__(self, dimmers=72, port='/dev/dmx0'):
 
        BaseIO.__init__(self)
 
        self.__name__ = "UsbDMX"
 
@@ -79,7 +85,6 @@ class UsbDMX(BaseIO):
 
            return
 
        # I was outputting on 76 and it was turning on the light at
 
        # dmx75. So I added the 0 byte.
 
        packet = '\x00' + ''.join([chr(int(lev * 255 / 100)) 
 
                                  for lev in levels]) + "\x55"
 
        packet = '\x00' + ''.join([chr(int(lev * 255 / 100)) for lev in levels
 
                                  ]) + "\x55"
 
        self._dmx().write(packet)
 
        
light9/io/udmx.py
Show inline comments
 
@@ -4,7 +4,6 @@ import usb.core
 
from usb.util import CTRL_TYPE_VENDOR, CTRL_RECIPIENT_DEVICE, CTRL_OUT
 

	
 
log = logging.getLogger('udmx')
 

	
 
"""
 
Send dmx to one of these:
 
http://www.amazon.com/Interface-Adapter-Controller-Lighting-Freestyler/dp/B00W52VIOS
 
@@ -23,24 +22,29 @@ or https://github.com/markusb/uDMX-linux
 

	
 
cmd_SetChannelRange = 0x0002
 

	
 

	
 
class Udmx(object):
 

	
 
    def __init__(self, bus):
 
        self.dev = None
 
        for dev in usb.core.find(idVendor=0x16c0, idProduct=0x05dc, find_all=True):
 
        for dev in usb.core.find(idVendor=0x16c0,
 
                                 idProduct=0x05dc,
 
                                 find_all=True):
 
            print "udmx device at %r" % dev.bus
 
            if bus is None or bus == dev.bus:
 
                self.dev = dev
 
        if not self.dev:
 
            raise IOError('no matching udmx device found for requested bus %r' % bus)
 
            raise IOError('no matching udmx device found for requested bus %r' %
 
                          bus)
 
        log.info('found udmx at %r', self.dev)
 
        
 

	
 
    def SendDMX(self, buf):
 
        ret = self.dev.ctrl_transfer(
 
           bmRequestType=CTRL_TYPE_VENDOR | CTRL_RECIPIENT_DEVICE | CTRL_OUT,
 
           bRequest=cmd_SetChannelRange,
 
           wValue=len(buf),
 
           wIndex=0,
 
           data_or_wLength=buf)
 
        ret = self.dev.ctrl_transfer(bmRequestType=CTRL_TYPE_VENDOR |
 
                                     CTRL_RECIPIENT_DEVICE | CTRL_OUT,
 
                                     bRequest=cmd_SetChannelRange,
 
                                     wValue=len(buf),
 
                                     wIndex=0,
 
                                     data_or_wLength=buf)
 
        if ret < 0:
 
            raise ValueError("ctrl_transfer returned %r" % ret)
 

	
 
@@ -52,9 +56,8 @@ def demo(chan, fps=44):
 
        nsin = math.sin(time.time() * 6.28) / 2.0 + 0.5
 
        nsin8 = int(255 * nsin)
 
        try:
 
            u.SendDMX('\x00' * (chan - 1) +
 
                      chr(210) +
 
                      chr(nsin8) + chr(nsin8) + chr(nsin8))
 
            u.SendDMX('\x00' * (chan - 1) + chr(210) + chr(nsin8) + chr(nsin8) +
 
                      chr(nsin8))
 
        except usb.core.USBError as e:
 
            print "err", time.time(), repr(e)
 
        time.sleep(1 / fps)
light9/namespaces.py
Show inline comments
 
@@ -3,15 +3,19 @@ from rdflib import Namespace, RDF, RDFS
 

	
 
# Namespace was showing up in profiles
 
class FastNs(object):
 

	
 
    def __init__(self, base):
 
        self.ns = Namespace(base)
 
        self.cache = {}
 

	
 
    def __getitem__(self, term):
 
        if term not in self.cache:
 
            self.cache[term] = self.ns[term]
 
        return self.cache[term]
 

	
 
    __getattr__ = __getitem__
 

	
 

	
 
L9 = FastNs("http://light9.bigasterisk.com/")
 
MUS = Namespace("http://light9.bigasterisk.com/music/")
 
XSD = Namespace("http://www.w3.org/2001/XMLSchema#")
light9/networking.py
Show inline comments
 
@@ -3,7 +3,9 @@ from urllib import splitport
 
from showconfig import getGraph, showUri
 
from namespaces import L9
 

	
 

	
 
class ServiceAddress(object):
 

	
 
    def __init__(self, service):
 
        self.service = service
 

	
 
@@ -12,8 +14,8 @@ class ServiceAddress(object):
 
        net = graph.value(showUri(), L9['networking'])
 
        ret = graph.value(net, self.service)
 
        if ret is None:
 
            raise ValueError("no url for %s -> %s -> %s" % (showUri(), L9['networking'],
 
                                                            self.service))
 
            raise ValueError("no url for %s -> %s -> %s" %
 
                             (showUri(), L9['networking'], self.service))
 
        return str(ret)
 

	
 
    @property
 
@@ -31,11 +33,13 @@ class ServiceAddress(object):
 
    @property
 
    def url(self):
 
        return self._url()
 

	
 
    value = url
 
    
 

	
 
    def path(self, more):
 
        return self.url + str(more)
 

	
 

	
 
captureDevice = ServiceAddress(L9['captureDevice'])
 
curveCalc = ServiceAddress(L9['curveCalc'])
 
dmxServer = ServiceAddress(L9['dmxServer'])
light9/observable.py
Show inline comments
 
import logging
 
log = logging.getLogger('observable')
 

	
 

	
 
class _NoNewVal(object):
 
    pass
 

	
 

	
 
class Observable(object):
 
    """
 
    like knockout's observable. Hopefully this can be replaced by a
 
@@ -13,6 +15,7 @@ class Observable(object):
 
    http://knockoutjs.com/documentation/observables.html
 
    https://github.com/drpancake/python-observable/blob/master/observable/observable.py
 
    """
 

	
 
    def __init__(self, val):
 
        self.val = val
 
        self.subscribers = set()
light9/paint/capture.py
Show inline comments
 
@@ -5,37 +5,46 @@ from rdfdb.patch import Patch
 
from light9.namespaces import L9, RDF
 
from light9.paint.solve import loadNumpy
 

	
 

	
 
def writeCaptureDescription(graph, ctx, session, uri, dev, outPath,
 
                            settingsSubgraphCache, settings):
 
    graph.patch(Patch(addQuads=settings.statements(
 
        uri, ctx=ctx,
 
        settingRoot=URIRef('/'.join([
 
            showconfig.showUri(), 'capture', dev.rsplit('/')[1]])),
 
        settingsSubgraphCache=settingsSubgraphCache)))
 
    graph.patch(Patch(addQuads=[
 
        (dev, L9['capture'], uri, ctx),
 
        (session, L9['capture'], uri, ctx),
 
        (uri, RDF.type, L9['LightSample'], ctx),
 
        (uri, L9['imagePath'], URIRef('/'.join([
 
            showconfig.showUri(), outPath])), ctx),
 
    graph.patch(
 
        Patch(addQuads=settings.statements(
 
            uri,
 
            ctx=ctx,
 
            settingRoot=URIRef('/'.join(
 
                [showconfig.showUri(), 'capture',
 
                 dev.rsplit('/')[1]])),
 
            settingsSubgraphCache=settingsSubgraphCache)))
 
    graph.patch(
 
        Patch(addQuads=[
 
            (dev, L9['capture'], uri, ctx),
 
            (session, L9['capture'], uri, ctx),
 
            (uri, RDF.type, L9['LightSample'], ctx),
 
            (uri, L9['imagePath'],
 
             URIRef('/'.join([showconfig.showUri(), outPath])), ctx),
 
        ]))
 
    graph.suggestPrefixes(ctx, {'cap': uri.rsplit('/', 1)[0] + '/',
 
                                'showcap': showconfig.showUri() + '/capture/'})
 
    
 
    graph.suggestPrefixes(
 
        ctx, {
 
            'cap': uri.rsplit('/', 1)[0] + '/',
 
            'showcap': showconfig.showUri() + '/capture/'
 
        })
 

	
 

	
 
class CaptureLoader(object):
 

	
 
    def __init__(self, graph):
 
        self.graph = graph
 
        
 

	
 
    def loadImage(self, pic, thumb=(100, 100)):
 
        ip = self.graph.value(pic, L9['imagePath'])
 
        if not ip.startswith(showconfig.show()):
 
            raise ValueError(repr(ip))
 
        diskPath = os.path.join(showconfig.root(), ip[len(self.show):])
 
        return loadNumpy(diskPath, thumb)
 
        
 

	
 
    def devices(self):
 
        """devices for which we have any captured data"""
 

	
 
    def capturedSettings(self, device):
 
        """list of (pic, settings) we know for this device"""
 
        
light9/paint/solve.py
Show inline comments
 
@@ -12,43 +12,54 @@ log = logging.getLogger('solve')
 

	
 
# numpy images in this file are (x, y, c) layout.
 

	
 

	
 
def numpyFromCairo(surface):
 
    w, h = surface.get_width(), surface.get_height()
 
    a = numpy.frombuffer(surface.get_data(), numpy.uint8)
 
    a.shape = h, w, 4
 
    a = a.transpose((1, 0, 2))
 
    return a[:w,:h,:3]
 
    return a[:w, :h, :3]
 

	
 

	
 
def numpyFromPil(img):
 
    return scipy.misc.fromimage(img, mode='RGB').transpose((1, 0, 2))
 

	
 

	
 
def loadNumpy(path, thumb=(100, 100)):
 
    img = Image.open(path)
 
    img.thumbnail(thumb)
 
    return numpyFromPil(img)
 

	
 

	
 
def saveNumpy(path, img):
 
    # maybe this should only run if log level is debug?
 
    scipy.misc.imsave(path, img.transpose((1, 0, 2)))
 

	
 

	
 
def scaledHex(h, scale):
 
    rgb = parseHex(h)
 
    rgb8 = (rgb * scale).astype(numpy.uint8)
 
    return '#%02x%02x%02x' % tuple(rgb8)
 
    
 

	
 

	
 
def colorRatio(col1, col2):
 
    rgb1 = parseHex(col1)
 
    rgb2 = parseHex(col2)
 

	
 
    def div(x, y):
 
        if y == 0:
 
            return 0
 
        return round(x / y, 3)
 

	
 
    return tuple([div(a, b) for a, b in zip(rgb1, rgb2)])
 

	
 

	
 
def brightest(img):
 
    return numpy.amax(img, axis=(0, 1))
 
    
 

	
 

	
 
class ImageDist(object):
 

	
 
    def __init__(self, img1):
 
        self.a = img1.reshape((-1,))
 
        self.d = 255 * 255 * self.a.shape[0]
 
@@ -57,27 +68,31 @@ class ImageDist(object):
 
        b = img2.reshape((-1,))
 
        return 1 - numpy.dot(self.a, b) / self.d
 

	
 

	
 
class ImageDistAbs(object):
 

	
 
    def __init__(self, img1):
 
        self.a = img1
 
        self.maxDist = img1.shape[0] * img1.shape[1] * img1.shape[2] * 255
 

	
 
    def distanceTo(self, img2):
 
        return numpy.sum(numpy.absolute(self.a - img2), axis=None) / self.maxDist
 
        return numpy.sum(numpy.absolute(self.a - img2),
 
                         axis=None) / self.maxDist
 

	
 
        
 

	
 
class Solver(object):
 

	
 
    def __init__(self, graph, sessions=None, imgSize=(100, 53)):
 
        self.graph = graph
 
        self.sessions = sessions # URIs of capture sessions to load
 
        self.sessions = sessions  # URIs of capture sessions to load
 
        self.imgSize = imgSize
 
        self.samples = {} # uri: Image array (float 0-255)
 
        self.fromPath = {} # imagePath: image array
 
        self.path = {} # sample: path
 
        self.samples = {}  # uri: Image array (float 0-255)
 
        self.fromPath = {}  # imagePath: image array
 
        self.path = {}  # sample: path
 
        self.blurredSamples = {}
 
        self.sampleSettings = {} # sample: DeviceSettings
 
        self.samplesForDevice = {} # dev : [(sample, img)]
 
        
 
        self.sampleSettings = {}  # sample: DeviceSettings
 
        self.samplesForDevice = {}  # dev : [(sample, img)]
 

	
 
    def loadSamples(self):
 
        """learn what lights do from images"""
 

	
 
@@ -93,7 +108,7 @@ class Solver(object):
 
        pathUri = g.value(samp, L9['imagePath'])
 
        img = loadNumpy(pathUri.replace(L9[''], '')).astype(float)
 
        settings = DeviceSettings.fromResource(self.graph, samp)
 
        
 

	
 
        self.samples[samp] = img
 
        self.fromPath[pathUri] = img
 
        self.blurredSamples[samp] = self._blur(img)
 
@@ -104,30 +119,30 @@ class Solver(object):
 
        devs = settings.devices()
 
        if len(devs) == 1:
 
            self.samplesForDevice.setdefault(devs[0], []).append((samp, img))
 
        
 

	
 
    def _blur(self, img):
 
        return scipy.ndimage.gaussian_filter(img, 10, 0, mode='nearest')
 

	
 
    def draw(self, painting):
 
        return self._draw(painting, self.imgSize[0], self.imgSize[1])
 
        
 

	
 
    def _draw(self, painting, w, h):
 
        surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, w, h)
 
        ctx = cairo.Context(surface)
 
        ctx.rectangle(0, 0, w, h)
 
        ctx.fill()
 
        
 

	
 
        ctx.set_line_cap(cairo.LINE_CAP_ROUND)
 
        ctx.set_line_width(w / 15) # ?
 
        ctx.set_line_width(w / 15)  # ?
 
        for stroke in painting['strokes']:
 
            for pt in stroke['pts']:
 
                op = ctx.move_to if pt is stroke['pts'][0] else ctx.line_to
 
                op(pt[0] * w, pt[1] * h)
 

	
 
            r,g,b = parseHex(stroke['color'])
 
            r, g, b = parseHex(stroke['color'])
 
            ctx.set_source_rgb(r / 255, g / 255, b / 255)
 
            ctx.stroke()
 
        
 

	
 
        #surface.write_to_png('/tmp/surf.png')
 
        return numpyFromCairo(surface)
 

	
 
@@ -150,8 +165,7 @@ class Solver(object):
 
        print 'tops2'
 
        for row in results[:4]:
 
            print '%.5f' % row[0], row[1][-20:], self.sampleSettings[row[1]]
 
        
 
       
 

	
 
        #saveNumpy('/tmp/best_in.png', img)
 
        #saveNumpy('/tmp/best_out.png', topImg)
 
        #saveNumpy('/tmp/mult.png', topImg / 255 * img)
 
@@ -167,32 +181,31 @@ class Solver(object):
 
            for samp, img2 in self.samplesForDevice[dev]:
 
                results.append((dist.distanceTo(img2), samp))
 
            results.sort()
 
            
 
            s = self.blendResults([(d, self.sampleSettings[samp])
 
                                   for d, samp in results[:8]])
 

	
 
            s = self.blendResults([
 
                (d, self.sampleSettings[samp]) for d, samp in results[:8]
 
            ])
 
            devSettings.append(s)
 
        return DeviceSettings.fromList(self.graph, devSettings)
 

	
 
    def blendResults(self, results):
 
        """list of (dist, settings)"""
 
        
 

	
 
        dists = [d for d, sets in results]
 
        hi = max(dists)
 
        lo = min(dists)
 
        n = len(results)
 
        remappedDists = [1 - (d - lo) / (hi - lo) * n / (n + 1)
 
                         for d in dists]
 
        remappedDists = [1 - (d - lo) / (hi - lo) * n / (n + 1) for d in dists]
 
        total = sum(remappedDists)
 
        
 

	
 
        #print 'blend'
 
        #for o,n in zip(dists, remappedDists):
 
        #    print o,n, n / total
 
        blend = DeviceSettings.fromBlend(
 
            self.graph,
 
            [(d / total, sets) for
 
             d, (_, sets) in zip(remappedDists, results)])
 
            [(d / total, sets) for d, (_, sets) in zip(remappedDists, results)])
 
        return blend
 
        
 

	
 
    def solve(self, painting):
 
        """
 
        given strokes of colors on a photo of the stage, figure out the
 
@@ -216,7 +229,7 @@ class Solver(object):
 
        # this is wrong; some wrong-alignments ought to be dimmer than full
 
        brightest0 = brightest(pic0)
 
        brightestSample = brightest(self.samples[sample])
 
        
 

	
 
        if max(brightest0) < 1 / 255:
 
            return DeviceSettings(self.graph, [])
 

	
 
@@ -234,25 +247,29 @@ class Solver(object):
 

	
 
        # use toVector then add ranges
 
        dims = [
 
            (DEV['aura1'], L9['rx'], [slice(.2, .7+.1, .2)]),
 
            (DEV['aura1'], L9['ry'], [slice(.573, .573+1, 1)]),
 
            (DEV['aura1'], L9['color'], [slice(0, 1 + colorStep, colorStep),
 
                                         slice(0, 1 + colorStep, colorStep),
 
                                         slice(0, 1 + colorStep, colorStep)]),
 
            (DEV['aura1'], L9['rx'], [slice(.2, .7 + .1, .2)]),
 
            (DEV['aura1'], L9['ry'], [slice(.573, .573 + 1, 1)]),
 
            (DEV['aura1'], L9['color'], [
 
                slice(0, 1 + colorStep, colorStep),
 
                slice(0, 1 + colorStep, colorStep),
 
                slice(0, 1 + colorStep, colorStep)
 
            ]),
 
        ]
 
        deviceAttrFilter = [(d, a) for d,a,s in dims]
 
        deviceAttrFilter = [(d, a) for d, a, s in dims]
 

	
 
        dist = ImageDist(pic0)
 

	
 
        def drawError(x):
 
            settings = DeviceSettings.fromVector(self.graph, x, deviceAttrFilter=deviceAttrFilter)
 
            settings = DeviceSettings.fromVector(
 
                self.graph, x, deviceAttrFilter=deviceAttrFilter)
 
            preview = self.combineImages(self.simulationLayers(settings))
 
            #saveNumpy('/tmp/x_%s.png' % abs(hash(settings)), preview)
 
            
 

	
 
            out = dist.distanceTo(preview)
 
            
 

	
 
            #print 'measure at', x, 'drawError=', out
 
            return out
 
            
 

	
 
        x0, fval, grid, Jout = scipy.optimize.brute(
 
            func=drawError,
 
            ranges=sum([s for dev, da, s in dims], []),
 
@@ -261,8 +278,10 @@ class Solver(object):
 
            full_output=True)
 
        if fval > 30000:
 
            raise ValueError('solution has error of %s' % fval)
 
        return DeviceSettings.fromVector(self.graph, x0, deviceAttrFilter=deviceAttrFilter)
 
        
 
        return DeviceSettings.fromVector(self.graph,
 
                                         x0,
 
                                         deviceAttrFilter=deviceAttrFilter)
 

	
 
    def combineImages(self, layers):
 
        """make a result image from our self.samples images"""
 
        out = (self.fromPath.itervalues().next() * 0).astype(numpy.uint16)
 
@@ -271,7 +290,7 @@ class Solver(object):
 
            out += colorScaled.astype(numpy.uint16)
 
        numpy.clip(out, 0, 255, out)
 
        return out.astype(numpy.uint8)
 
        
 

	
 
    def simulationLayers(self, settings):
 
        """
 
        how should a simulation preview approximate the light settings
 
@@ -282,7 +301,7 @@ class Solver(object):
 

	
 
        for dev, devSettings in settings.byDevice():
 
            requestedColor = devSettings.getValue(dev, L9['color'])
 
            candidatePics = [] # (distance, path, picColor)
 
            candidatePics = []  # (distance, path, picColor)
 
            for sample, s in self.sampleSettings.items():
 
                path = self.path[sample]
 
                otherDevSettings = s.ofDevice(dev)
 
@@ -295,9 +314,12 @@ class Solver(object):
 
            # we could even blend multiple top candidates, or omit all
 
            # of them if they're too far
 
            bestDist, bestPath, bestPicColor = candidatePics[0]
 
            log.info('  device best d=%g path=%s color=%s', bestDist, bestPath, bestPicColor)
 
            
 
            layers.append({'path': bestPath,
 
                           'color': colorRatio(requestedColor, bestPicColor)})
 
        
 
            log.info('  device best d=%g path=%s color=%s', bestDist, bestPath,
 
                     bestPicColor)
 

	
 
            layers.append({
 
                'path': bestPath,
 
                'color': colorRatio(requestedColor, bestPicColor)
 
            })
 

	
 
        return layers
light9/paint/solve_test.py
Show inline comments
 
@@ -6,11 +6,15 @@ from light9.namespaces import RDF, L9, D
 
from rdfdb.localsyncedgraph import LocalSyncedGraph
 
from light9.effect.settings import DeviceSettings
 

	
 

	
 
class TestSolve(unittest.TestCase):
 

	
 
    def setUp(self):
 
        self.graph = LocalSyncedGraph(files=['test/cam/lightConfig.n3',
 
                                             'test/cam/bg.n3'])
 
        self.solver = solve.Solver(self.graph, imgSize=(100, 48), sessions=[L9['session0']])
 
        self.graph = LocalSyncedGraph(
 
            files=['test/cam/lightConfig.n3', 'test/cam/bg.n3'])
 
        self.solver = solve.Solver(self.graph,
 
                                   imgSize=(100, 48),
 
                                   sessions=[L9['session0']])
 
        self.solver.loadSamples()
 
        self.solveMethod = self.solver.solve
 

	
 
@@ -21,87 +25,132 @@ class TestSolve(unittest.TestCase):
 

	
 
    @unittest.skip("unfinished")
 
    def testSingleLightCloseMatch(self):
 
        devAttrs = self.solveMethod({'strokes': [{'pts': [[224, 141],
 
                                                 [223, 159]],
 
                                         'color': '#ffffff'}]})
 
        self.assertEqual(DeviceSettings(self.graph, [
 
            (DEV['aura1'], L9['color'], u"#ffffff"),
 
            (DEV['aura1'], L9['rx'], 0.5 ),
 
            (DEV['aura1'], L9['ry'], 0.573),
 
        ]), devAttrs)
 
        devAttrs = self.solveMethod({
 
            'strokes': [{
 
                'pts': [[224, 141], [223, 159]],
 
                'color': '#ffffff'
 
            }]
 
        })
 
        self.assertEqual(
 
            DeviceSettings(self.graph, [
 
                (DEV['aura1'], L9['color'], u"#ffffff"),
 
                (DEV['aura1'], L9['rx'], 0.5),
 
                (DEV['aura1'], L9['ry'], 0.573),
 
            ]), devAttrs)
 

	
 

	
 
class TestSolveBrute(TestSolve):
 

	
 
    def setUp(self):
 
        super(TestSolveBrute, self).setUp()
 
        self.solveMethod = self.solver.solveBrute
 

	
 

	
 
CAM_TEST = Namespace('http://light9.bigasterisk.com/test/cam/')
 
        
 

	
 

	
 
class TestSimulationLayers(unittest.TestCase):
 

	
 
    def setUp(self):
 
        self.graph = LocalSyncedGraph(files=['test/cam/lightConfig.n3',
 
                                             'test/cam/bg.n3'])
 
        self.solver = solve.Solver(self.graph, imgSize=(100, 48), sessions=[L9['session0']])
 
        self.graph = LocalSyncedGraph(
 
            files=['test/cam/lightConfig.n3', 'test/cam/bg.n3'])
 
        self.solver = solve.Solver(self.graph,
 
                                   imgSize=(100, 48),
 
                                   sessions=[L9['session0']])
 
        self.solver.loadSamples()
 
        
 

	
 
    def testBlack(self):
 
        self.assertEqual(
 
            [],
 
            self.solver.simulationLayers(settings=DeviceSettings(self.graph, [])))
 
        self.assertEqual([],
 
                         self.solver.simulationLayers(
 
                             settings=DeviceSettings(self.graph, [])))
 

	
 
    def testPerfect1Match(self):
 
        layers = self.solver.simulationLayers(settings=DeviceSettings(self.graph, [
 
            (DEV['aura1'], L9['color'], u"#ffffff"),
 
            (DEV['aura1'], L9['rx'], 0.5 ),
 
            (DEV['aura1'], L9['ry'], 0.573)]))
 
        self.assertEqual([{'path': CAM_TEST['bg2-d.jpg'], 'color': (1., 1., 1.)}], layers)
 
        layers = self.solver.simulationLayers(
 
            settings=DeviceSettings(self.graph, [(
 
                DEV['aura1'], L9['color'],
 
                u"#ffffff"), (DEV['aura1'], L9['rx'],
 
                              0.5), (DEV['aura1'], L9['ry'], 0.573)]))
 
        self.assertEqual([{
 
            'path': CAM_TEST['bg2-d.jpg'],
 
            'color': (1., 1., 1.)
 
        }], layers)
 

	
 
    def testPerfect1MatchTinted(self):
 
        layers = self.solver.simulationLayers(settings=DeviceSettings(self.graph, [
 
            (DEV['aura1'], L9['color'], u"#304050"),
 
            (DEV['aura1'], L9['rx'], 0.5 ),
 
            (DEV['aura1'], L9['ry'], 0.573)]))
 
        self.assertEqual([{'path': CAM_TEST['bg2-d.jpg'], 'color': (.188, .251, .314)}], layers)
 
        
 
        layers = self.solver.simulationLayers(
 
            settings=DeviceSettings(self.graph, [(
 
                DEV['aura1'], L9['color'],
 
                u"#304050"), (DEV['aura1'], L9['rx'],
 
                              0.5), (DEV['aura1'], L9['ry'], 0.573)]))
 
        self.assertEqual([{
 
            'path': CAM_TEST['bg2-d.jpg'],
 
            'color': (.188, .251, .314)
 
        }], layers)
 

	
 
    def testPerfect2Matches(self):
 
        layers = self.solver.simulationLayers(settings=DeviceSettings(self.graph, [
 
            (DEV['aura1'], L9['color'], u"#ffffff"),
 
            (DEV['aura1'], L9['rx'], 0.5 ),
 
            (DEV['aura1'], L9['ry'], 0.573),
 
            (DEV['aura2'], L9['color'], u"#ffffff"),
 
            (DEV['aura2'], L9['rx'], 0.7 ),
 
            (DEV['aura2'], L9['ry'], 0.573),
 
        ]))
 
        layers = self.solver.simulationLayers(
 
            settings=DeviceSettings(self.graph, [
 
                (DEV['aura1'], L9['color'], u"#ffffff"),
 
                (DEV['aura1'], L9['rx'], 0.5),
 
                (DEV['aura1'], L9['ry'], 0.573),
 
                (DEV['aura2'], L9['color'], u"#ffffff"),
 
                (DEV['aura2'], L9['rx'], 0.7),
 
                (DEV['aura2'], L9['ry'], 0.573),
 
            ]))
 
        self.assertItemsEqual([
 
            {'path': CAM_TEST['bg2-d.jpg'], 'color': (1, 1, 1)},
 
            {'path': CAM_TEST['bg2-f.jpg'], 'color': (1, 1, 1)},
 
                      ], layers)
 
            {
 
                'path': CAM_TEST['bg2-d.jpg'],
 
                'color': (1, 1, 1)
 
            },
 
            {
 
                'path': CAM_TEST['bg2-f.jpg'],
 
                'color': (1, 1, 1)
 
            },
 
        ], layers)
 

	
 

	
 
class TestCombineImages(unittest.TestCase):
 

	
 
    def setUp(self):
 
        graph = LocalSyncedGraph(files=['test/cam/lightConfig.n3',
 
                                        'test/cam/bg.n3'])
 
        self.solver = solve.Solver(graph, imgSize=(100, 48), sessions=[L9['session0']])
 
        graph = LocalSyncedGraph(
 
            files=['test/cam/lightConfig.n3', 'test/cam/bg.n3'])
 
        self.solver = solve.Solver(graph,
 
                                   imgSize=(100, 48),
 
                                   sessions=[L9['session0']])
 
        self.solver.loadSamples()
 

	
 
    def test(self):
 
        out = self.solver.combineImages(layers=[
 
            {'path': CAM_TEST['bg2-d.jpg'], 'color': (.2, .2, .3)},
 
            {'path': CAM_TEST['bg2-a.jpg'], 'color': (.888, 0, .3)},
 
            {
 
                'path': CAM_TEST['bg2-d.jpg'],
 
                'color': (.2, .2, .3)
 
            },
 
            {
 
                'path': CAM_TEST['bg2-a.jpg'],
 
                'color': (.888, 0, .3)
 
            },
 
        ])
 
        solve.saveNumpy('/tmp/t.png', out)
 
        golden = solve.loadNumpy('test/cam/layers_out1.png')
 
        numpy.testing.assert_array_equal(golden, out)
 

	
 

	
 
class TestBestMatch(unittest.TestCase):
 

	
 
    def setUp(self):
 
        graph = LocalSyncedGraph(files=['test/cam/lightConfig.n3',
 
                                        'test/cam/bg.n3'])
 
        self.solver = solve.Solver(graph, imgSize=(100, 48), sessions=[L9['session0']])
 
        graph = LocalSyncedGraph(
 
            files=['test/cam/lightConfig.n3', 'test/cam/bg.n3'])
 
        self.solver = solve.Solver(graph,
 
                                   imgSize=(100, 48),
 
                                   sessions=[L9['session0']])
 
        self.solver.loadSamples()
 
        
 

	
 
    def testRightSide(self):
 
        drawingOnRight = {"strokes":[{"pts":[[0.875,0.64],[0.854,0.644]],
 
                                      "color":"#aaaaaa"}]}
 
        drawingOnRight = {
 
            "strokes": [{
 
                "pts": [[0.875, 0.64], [0.854, 0.644]],
 
                "color": "#aaaaaa"
 
            }]
 
        }
 
        drawImg = self.solver.draw(drawingOnRight)
 
        match, dist = self.solver.bestMatch(drawImg)
 
        self.assertEqual(L9['sample5'], match)
light9/prof.py
Show inline comments
 
import sys, traceback, time, logging
 
log = logging.getLogger()
 

	
 

	
 
def run(main, profile=None):
 
    if not profile:
 
        main()
 
@@ -20,7 +21,8 @@ def run(main, profile=None):
 
        finally:
 
            statprof.stop()
 
            statprof.display()
 
    
 

	
 

	
 
def watchPoint(filename, lineno, event="call"):
 
    """whenever we hit this line, print a stack trace. event='call'
 
    for lines that are function definitions, like what a profiler
 
@@ -28,7 +30,8 @@ def watchPoint(filename, lineno, event="
 

	
 
    Switch to 'line' to match lines inside functions. Execution speed
 
    will be much slower."""
 
    seenTraces = {} # trace contents : count
 
    seenTraces = {}  # trace contents : count
 

	
 
    def trace(frame, ev, arg):
 
        if ev == event:
 
            if (frame.f_code.co_filename, frame.f_lineno) == (filename, lineno):
 
@@ -41,17 +44,21 @@ def watchPoint(filename, lineno, event="
 
                    seenTraces[stack] += 1
 

	
 
        return trace
 

	
 
    sys.settrace(trace)
 

	
 
    # atexit, print the frequencies?
 

	
 

	
 
def logTime(func):
 

	
 
    def inner(*args, **kw):
 
        t1 = time.time()
 
        try:
 
            ret = func(*args, **kw)
 
        finally:
 
            log.info("Call to %s took %.1f ms" % (
 
                func.__name__, 1000 * (time.time() - t1)))
 
            log.info("Call to %s took %.1f ms" % (func.__name__, 1000 *
 
                                                  (time.time() - t1)))
 
        return ret
 

	
 
    return inner
light9/subcomposer/subcomposerweb.py
Show inline comments
 
@@ -6,21 +6,29 @@ from rdflib import URIRef, Literal
 
from twisted.internet import reactor
 
log = logging.getLogger('web')
 

	
 

	
 
def init(graph, session, currentSub):
 
    SFH = cyclone.web.StaticFileHandler
 
    app = cyclone.web.Application(handlers=[
 
        (r'/()', SFH,
 
         {'path': 'light9/subcomposer', 'default_filename': 'index.html'}),
 
        (r'/()', SFH, {
 
            'path': 'light9/subcomposer',
 
            'default_filename': 'index.html'
 
        }),
 
        (r'/toggle', Toggle),
 
    ], debug=True, graph=graph, currentSub=currentSub)
 
    ],
 
                                  debug=True,
 
                                  graph=graph,
 
                                  currentSub=currentSub)
 
    reactor.listenTCP(networking.subComposer.port, app)
 
    log.info("listening on %s" % networking.subComposer.port)
 

	
 

	
 
class Toggle(PrettyErrorHandler, cyclone.web.RequestHandler):
 

	
 
    def post(self):
 
        chan = URIRef(self.get_argument('chan'))
 
        sub = self.settings.currentSub()
 
        
 

	
 
        chanKey = Literal(chan.rsplit('/', 1)[1])
 
        old = sub.get_levels().get(chanKey, 0)
 

	
light9/vidref/main.py
Show inline comments
 
#!/usr/bin/python
 

	
 
"""
 

	
 
dvcam test
 
@@ -15,7 +14,9 @@ from light9.vidref.videorecorder import 
 
from light9.vidref import remotepivideo
 
log = logging.getLogger()
 

	
 

	
 
class Gui(object):
 

	
 
    def __init__(self, graph):
 
        wtree = gtk.Builder()
 
        wtree.add_from_file(sibpath(__file__, "vidref.glade"))
 
@@ -28,7 +29,8 @@ class Gui(object):
 
        self.musicScale = wtree.get_object("musicScale")
 
        self.musicScale.connect("value-changed", self.onMusicScaleValue)
 
        # tiny race here if onMusicScaleValue tries to use musicTime right away
 
        self.musicTime = MusicTime(onChange=self.onMusicTimeChange, pollCurvecalc=False)
 
        self.musicTime = MusicTime(onChange=self.onMusicTimeChange,
 
                                   pollCurvecalc=False)
 
        self.ignoreScaleChanges = False
 
        # self.attachLog(wtree.get_object("lastLog")) # disabled due to crashing
 

	
 
@@ -40,33 +42,31 @@ class Gui(object):
 
        vid3 = wtree.get_object("vid3")
 

	
 
        if 0:
 
            self.pipeline = Pipeline(
 
                liveVideoXid=vid3.window.xid,
 
                musicTime=self.musicTime,
 
                recordingTo=self.recordingTo)
 
            self.pipeline = Pipeline(liveVideoXid=vid3.window.xid,
 
                                     musicTime=self.musicTime,
 
                                     recordingTo=self.recordingTo)
 
        else:
 
            self.pipeline = remotepivideo.Pipeline(
 
                liveVideo=vid3,
 
                musicTime=self.musicTime,
 
                recordingTo=self.recordingTo,
 
                graph=graph)
 
            self.pipeline = remotepivideo.Pipeline(liveVideo=vid3,
 
                                                   musicTime=self.musicTime,
 
                                                   recordingTo=self.recordingTo,
 
                                                   graph=graph)
 

	
 
        vid3.props.width_request = 360
 
        vid3.props.height_request = 220
 
        wtree.get_object("frame1").props.height_request = 220
 
        
 

	
 
        self.pipeline.setInput('v4l') # auto seems to not search for dv
 
        self.pipeline.setInput('v4l')  # auto seems to not search for dv
 

	
 
        gobject.timeout_add(1000 // framerate, self.updateLoop)
 

	
 

	
 
    def snapshot(self):
 
        return self.pipeline.snapshot()
 
        
 

	
 
    def attachLog(self, textBuffer):
 
        """write log lines to this gtk buffer"""
 

	
 
        class ToBuffer(logging.Handler):
 

	
 
            def emit(self, record):
 
                textBuffer.set_text(record.getMessage())
 

	
 
@@ -86,10 +86,9 @@ class Gui(object):
 
    def getInputs(self):
 
        return ['auto', 'dv', 'video0']
 

	
 

	
 
    def on_liveVideoEnabled_toggled(self, widget):
 
        self.pipeline.setLiveVideo(widget.get_active())
 
                                                   
 

	
 
    def on_liveFrameRate_value_changed(self, widget):
 
        print widget.get_value()
 

	
light9/vidref/musictime.py
Show inline comments
 
@@ -6,12 +6,17 @@ from restkit.errors import ResourceNotFo
 
import http_parser.http
 
log = logging.getLogger()
 

	
 

	
 
class MusicTime(object):
 
    """
 
    fetch times from ascoltami in a background thread; return times
 
    upon request, adjusted to be more precise with the system clock
 
    """
 
    def __init__(self, period=.2, onChange=lambda position: None, pollCurvecalc=True):
 

	
 
    def __init__(self,
 
                 period=.2,
 
                 onChange=lambda position: None,
 
                 pollCurvecalc=True):
 
        """period is the seconds between http time requests.
 

	
 
        We call onChange with the time in seconds and the total time
 
@@ -28,7 +33,7 @@ class MusicTime(object):
 

	
 
        self.position = {}
 
        # driven by our pollCurvecalcTime and also by Gui.incomingTime
 
        self.lastHoverTime = None # None means "no recent value"
 
        self.lastHoverTime = None  # None means "no recent value"
 
        self.pollMusicTime()
 
        if pollCurvecalc:
 
            self.pollCurvecalcTime()
 
@@ -43,7 +48,7 @@ class MusicTime(object):
 
        Note that this may be called in a gst camera capture thread. Very often.
 
        """
 
        if not hasattr(self, 'position'):
 
            return {'t' : 0, 'song' : None}
 
            return {'t': 0, 'song': None}
 
        pos = self.position.copy()
 
        now = frameTime or time.time()
 
        if pos.get('playing'):
 
@@ -54,6 +59,7 @@ class MusicTime(object):
 
        return pos
 

	
 
    def pollMusicTime(self):
 

	
 
        def cb(response):
 

	
 
            if response.code != 200:
 
@@ -71,14 +77,14 @@ class MusicTime(object):
 
            self.onChange(position)
 

	
 
            reactor.callLater(self.period, self.pollMusicTime)
 
            
 

	
 
        def eb(err):
 
            log.warn("talking to ascoltami: %s", err.getErrorMessage())
 
            reactor.callLater(2, self.pollMusicTime)
 
            
 

	
 
        d = fetch(networking.musicPlayer.path("time"))
 
        d.addCallback(cb)
 
        d.addErrback(eb) # note this includes errors in cb()
 
        d.addErrback(eb)  # note this includes errors in cb()
 

	
 
    def pollCurvecalcTime(self):
 
        """
 
@@ -94,7 +100,7 @@ class MusicTime(object):
 
            self.lastHoverTime = None
 
            reactor.callLater(.2, self.pollCurvecalcTime)
 
            return
 
            
 

	
 
        def cb(response):
 
            if response.code == 404:
 
                # not hovering
 
@@ -115,9 +121,10 @@ class MusicTime(object):
 

	
 
        d = fetch(networking.curveCalc.path("hoverTime"))
 
        d.addCallback(cb)
 
        d.addErrback(eb) # note this includes errors in cb()
 
        
 
        d.addErrback(eb)  # note this includes errors in cb()
 

	
 
    def sendTime(self, t):
 
        """request that the player go to this time"""
 
        self.musicResource.post("time", payload=json.dumps({"t" : t}),
 
                                headers={"content-type" : "application/json"})
 
        self.musicResource.post("time",
 
                                payload=json.dumps({"t": t}),
 
                                headers={"content-type": "application/json"})
light9/vidref/qt_test.py
Show inline comments
 
@@ -4,7 +4,9 @@ from PyQt4 import QtCore, QtGui, uic
 
import gst
 
import gobject
 

	
 

	
 
class Vid(object):
 

	
 
    def __init__(self, windowId):
 
        self.player = gst.Pipeline("player")
 
        self.source = gst.element_factory_make("v4l2src", "vsource")
 
@@ -14,20 +16,24 @@ class Vid(object):
 
        self.window_id = None
 
        self.windowId = windowId
 

	
 
        self.fvidscale_cap = gst.element_factory_make("capsfilter", "fvidscale_cap")
 
        self.fvidscale_cap.set_property('caps', gst.caps_from_string('video/x-raw-yuv, width=320, height=240'))
 
        
 
        self.fvidscale_cap = gst.element_factory_make("capsfilter",
 
                                                      "fvidscale_cap")
 
        self.fvidscale_cap.set_property(
 
            'caps',
 
            gst.caps_from_string('video/x-raw-yuv, width=320, height=240'))
 

	
 
        self.player.add(self.source, self.scaler, self.fvidscale_cap, self.sink)
 
        gst.element_link_many(self.source,self.scaler, self.fvidscale_cap, self.sink)
 
        gst.element_link_many(self.source, self.scaler, self.fvidscale_cap,
 
                              self.sink)
 

	
 
        self.s = MySink()
 
        self.player.add(self.s)
 
#        self.scaler.link(self.s)
 
        #        self.scaler.link(self.s)
 

	
 
        bus = self.player.get_bus()
 
        bus.add_signal_watch()
 
#        bus.enable_sync_message_emission() # with this we segv
 
#        bus.connect("message", self.on_message) # with this we segv
 
        #        bus.enable_sync_message_emission() # with this we segv
 
        #        bus.connect("message", self.on_message) # with this we segv
 
        bus.connect("sync-message::element", self.on_sync_message)
 

	
 
    def on_message(self, bus, message):
 
@@ -36,9 +42,9 @@ class Vid(object):
 
        if t == gst.MESSAGE_EOS:
 
            self.player.set_state(gst.STATE_NULL)
 
        elif t == gst.MESSAGE_ERROR:
 
           err, debug = message.parse_error()
 
           print "Error: %s" % err, debug
 
           self.player.set_state(gst.STATE_NULL)
 
            err, debug = message.parse_error()
 
            print "Error: %s" % err, debug
 
            self.player.set_state(gst.STATE_NULL)
 

	
 
    def on_sync_message(self, bus, message):
 
        print "syncmsg", bus, message
 
@@ -58,9 +64,10 @@ class Vid(object):
 
    def startPrev(self):
 
        self.player.set_state(gst.STATE_PLAYING)
 
        print "should be playing"
 
        
 

	
 

	
 
class MainWin(QtGui.QMainWindow):
 

	
 
    def __init__(self, *args):
 
        super(MainWin, self).__init__(*args)
 

	
 
@@ -71,5 +78,3 @@ class MainWin(QtGui.QMainWindow):
 
    @QtCore.pyqtSlot()
 
    def startLiveView(self):
 
        print "slv"
 

	
 

	
light9/vidref/remotepivideo.py
Show inline comments
 
@@ -13,13 +13,15 @@ from PIL import Image
 
from StringIO import StringIO
 
log = logging.getLogger('remotepi')
 

	
 

	
 
class Pipeline(object):
 

	
 
    def __init__(self, liveVideo, musicTime, recordingTo, graph):
 
        self.musicTime = musicTime
 
        self.recordingTo = recordingTo
 

	
 
        self.liveVideo = self._replaceLiveVideoWidget(liveVideo)
 
        
 

	
 
        self._snapshotRequests = []
 
        self.graph = graph
 
        self.graph.addHandler(self.updateCamUrl)
 
@@ -30,10 +32,10 @@ class Pipeline(object):
 
        log.info("picsUrl now %r", self.picsUrl)
 
        if not self.picsUrl:
 
            return
 
        
 

	
 
        # this cannot yet survive being called a second time
 
        self._startRequest(str(self.picsUrl))
 
        
 

	
 
    def _replaceLiveVideoWidget(self, liveVideo):
 
        aspectFrame = liveVideo.get_parent()
 
        liveVideo.destroy()
 
@@ -42,7 +44,7 @@ class Pipeline(object):
 
        #img.set_size_request(320, 240)
 
        aspectFrame.add(img)
 
        return img
 
        
 

	
 
    def _startRequest(self, url):
 
        self._buffer = ''
 
        log.info('start request to %r', url)
 
@@ -60,10 +62,10 @@ class Pipeline(object):
 
        size = int(size)
 
        if len(self._buffer) - i - 1 < size:
 
            return
 
        jpg = self._buffer[i+1:i+1+size]
 
        jpg = self._buffer[i + 1:i + 1 + size]
 
        self.onFrame(jpg, float(frameTime))
 
        self._buffer = self._buffer[i+1+size:]
 
        
 
        self._buffer = self._buffer[i + 1 + size:]
 

	
 
    def snapshot(self):
 
        """
 
        returns deferred to the path (which is under snapshotDir()) where
 
@@ -93,14 +95,13 @@ class Pipeline(object):
 
                out.write(jpg)
 
            d.callback(filename)
 
        self._snapshotRequests[:] = []
 
            
 
        
 

	
 
        if not position['song']:
 
            self.updateLiveFromTemp(jpg)
 
            return 
 
            return
 
        outDir = takeDir(songDir(position['song']), position['started'])
 
        outFilename = "%s/%08.03f.jpg" % (outDir, position['t'])
 
        if os.path.exists(outFilename): # we're paused on one time
 
        if os.path.exists(outFilename):  # we're paused on one time
 
            self.updateLiveFromTemp(jpg)
 
            return
 
        try:
 
@@ -111,14 +112,14 @@ class Pipeline(object):
 
            out.write(jpg)
 

	
 
        self.updateLiveFromFile(outFilename)
 
            
 

	
 
        # if you're selecting the text while gtk is updating it,
 
        # you can get a crash in xcb_io
 
        if getattr(self, '_lastRecText', None) != outDir:
 
            with gtk.gdk.lock:
 
                self.recordingTo.set_text(outDir)
 
            self._lastRecText = outDir
 
            
 

	
 
    def updateLiveFromFile(self, outFilename):
 
        self.liveVideo.set_from_file(outFilename)
 

	
 
@@ -127,17 +128,14 @@ class Pipeline(object):
 
            img = Image.open(StringIO(jpg))
 
            if not hasattr(self, 'livePixBuf'):
 
                self.livePixBuf = gtk.gdk.pixbuf_new_from_data(
 
                    img.tobytes(),
 
                    gtk.gdk.COLORSPACE_RGB,
 
                    False, 8,
 
                    img.size[0], img.size[1],
 
                    img.size[0]*3)
 
                    img.tobytes(), gtk.gdk.COLORSPACE_RGB, False, 8,
 
                    img.size[0], img.size[1], img.size[0] * 3)
 
                log.info("live images are %r", img.size)
 
            else:
 
                # don't leak pixbufs; update the one we have
 
                a = self.livePixBuf.pixel_array
 
                newImg = numpy.fromstring(img.tobytes(), dtype=numpy.uint8)
 
                a[:,:,:] = newImg.reshape(a.shape)
 
                a[:, :, :] = newImg.reshape(a.shape)
 
            self.liveVideo.set_from_pixbuf(self.livePixBuf)
 

	
 
        except Exception:
light9/vidref/replay.py
Show inline comments
 
@@ -6,31 +6,36 @@ log = logging.getLogger()
 

	
 
framerate = 15
 

	
 

	
 
def songDir(song):
 
    safeUri = song.split('://')[-1].replace('/','_')
 
    safeUri = song.split('://')[-1].replace('/', '_')
 
    return os.path.expanduser("~/light9-vidref/play-%s" % safeUri)
 

	
 

	
 
def takeDir(songDir, startTime):
 
    """
 
    startTime: unix seconds (str ok)
 
    """
 
    return os.path.join(songDir, str(int(startTime)))
 

	
 

	
 
def snapshotDir():
 
    return os.path.expanduser("~/light9-vidref/snapshot")
 
    
 

	
 

	
 
class ReplayViews(object):
 
    """
 
    the whole list of replay windows. parent is the scrolling area for
 
    these windows to be added
 
    """
 

	
 
    def __init__(self, parent):
 
        # today, parent is the vbox the replay windows should appear in
 
        self.parent = parent
 
        self.lastStart = None
 

	
 
        self.views = []
 
     
 

	
 
    def update(self, position):
 
        """
 
        freshen all replay windows. We get called this about every
 
@@ -46,8 +51,8 @@ class ReplayViews(object):
 
            self.lastStart = position['started']
 
        for v in self.views:
 
            v.updatePic(position)
 
        log.debug("update %s views in %.2fms",
 
                  len(self.views), (time.time() - t1) * 1000)
 
        log.debug("update %s views in %.2fms", len(self.views),
 
                  (time.time() - t1) * 1000)
 

	
 
    def loadViewsForSong(self, song):
 
        """
 
@@ -62,7 +67,7 @@ class ReplayViews(object):
 
            takes = sorted(t for t in os.listdir(d) if t.isdigit())
 
        except OSError:
 
            return
 
        
 

	
 
        for take in takes:
 
            td = takeDir(songDir(song), take)
 
            r = Replay(td)
 
@@ -76,10 +81,12 @@ class ReplayViews(object):
 
            rv = ReplayView(self.parent, r)
 
            self.views.append(rv)
 

	
 

	
 
class ReplayView(object):
 
    """
 
    one of the replay widgets
 
    """
 

	
 
    def __init__(self, parent, replay):
 
        self.replay = replay
 
        self.enabled = True
 
@@ -134,16 +141,20 @@ class ReplayView(object):
 
            if True:
 
                en = withLabel(gtk.ToggleButton, "Enabled")
 
                en.set_active(True)
 

	
 
                def tog(w):
 
                    self.enabled = w.get_active()
 

	
 
                en.connect("toggled", tog)
 
                rows.append(en)
 
            if True:
 
                d = withLabel(gtk.Button, "Delete")
 
                d.props.image = delImage
 

	
 
                def onClicked(w):
 
                    self.replay.deleteDir()
 
                    self.destroy()
 

	
 
                d.connect("clicked", onClicked)
 
                rows.append(d)
 
            if True:
 
@@ -156,7 +167,7 @@ class ReplayView(object):
 
            for r in rows:
 
                stack.add(r)
 
                stack.set_child_packing(r, False, False, 0, gtk.PACK_START)
 
            
 

	
 
            replayPanel.pack_start(stack, False, False, 0)
 

	
 
        parent.pack_start(replayPanel, False, False)
 
@@ -166,7 +177,7 @@ class ReplayView(object):
 
    def destroy(self):
 
        self.replayPanel.destroy()
 
        self.enabled = False
 
        
 

	
 
    def updatePic(self, position, lag=.2):
 

	
 
        # this should skip updating off-screen widgets! maybe that is
 
@@ -185,26 +196,30 @@ class ReplayView(object):
 
            self.picWidget.set_from_file(inPic)
 
            if 0:
 
                # force redraw of that widget
 
                self.picWidget.queue_draw_area(0,0,320,240)
 
                self.picWidget.queue_draw_area(0, 0, 320, 240)
 
                self.picWidget.get_window().process_updates(True)
 
        self.showingPic = inPic
 

	
 

	
 
_existingFrames = {}  # takeDir : frames
 
    
 

	
 

	
 
class Replay(object):
 
    """
 
    model for one of the replay widgets
 
    """
 

	
 
    def __init__(self, takeDir):
 
        self.takeDir = takeDir
 
        try:
 
            self.existingFrames = _existingFrames[self.takeDir]
 
        except KeyError:
 
            log.info("scanning %s", self.takeDir)
 
            self.existingFrames = sorted([Decimal(f.split('.jpg')[0])
 
                                          for f in os.listdir(self.takeDir)])
 
            self.existingFrames = sorted(
 
                [Decimal(f.split('.jpg')[0]) for f in os.listdir(self.takeDir)])
 
            if not self.existingFrames:
 
                raise NotImplementedError("suspiciously found no frames in dir %s" % self.takeDir)
 
                raise NotImplementedError(
 
                    "suspiciously found no frames in dir %s" % self.takeDir)
 
            _existingFrames[self.takeDir] = self.existingFrames
 

	
 
    def tooShort(self, minSeconds=5):
 
@@ -238,5 +253,5 @@ class Replay(object):
 
        i = bisect_left(self.existingFrames, Decimal(str(t)))
 
        if i >= len(self.existingFrames):
 
            i = len(self.existingFrames) - 1
 
        return os.path.join(self.takeDir, "%08.03f.jpg" %
 
                            self.existingFrames[i])
 
        return os.path.join(self.takeDir,
 
                            "%08.03f.jpg" % self.existingFrames[i])
light9/vidref/videorecorder.py
Show inline comments
 
@@ -9,7 +9,9 @@ from Queue import Queue, Empty
 
from light9.vidref.replay import framerate, songDir, takeDir, snapshotDir
 
log = logging.getLogger()
 

	
 

	
 
class Pipeline(object):
 

	
 
    def __init__(self, liveVideoXid, musicTime, recordingTo):
 
        self.musicTime = musicTime
 
        self.liveVideoXid = liveVideoXid
 
@@ -28,28 +30,31 @@ class Pipeline(object):
 
        but I haven't noticed that being a problem yet.
 
        """
 
        d = defer.Deferred()
 

	
 
        def req(frame):
 
            filename = "%s/%s.jpg" % (snapshotDir(), time.time())
 
            log.debug("received snapshot; saving in %s", filename)
 
            frame.save(filename)
 
            d.callback(filename)
 

	
 
        log.debug("requesting snapshot")
 
        self.snapshotRequests.put(req)
 
        return d
 
        
 

	
 
    def setInput(self, name):
 
        sourcePipe = {
 
            "auto": "autovideosrc name=src1",
 
            "testpattern" : "videotestsrc name=src1",
 
            "testpattern": "videotestsrc name=src1",
 
            "dv": "dv1394src name=src1 ! dvdemux ! dvdec",
 
            "v4l": "v4l2src device=/dev/video0 name=src1" ,
 
            }[name]
 
            "v4l": "v4l2src device=/dev/video0 name=src1",
 
        }[name]
 

	
 
        cam = (sourcePipe + " ! "
 
              "videorate ! video/x-raw-yuv,framerate=%s/1 ! "
 
              "videoscale ! video/x-raw-yuv,width=640,height=480;video/x-raw-rgb,width=320,height=240 ! "
 
              "videocrop left=160 top=180 right=120 bottom=80 ! "
 
              "queue name=vid" % framerate)
 
        cam = (
 
            sourcePipe + " ! "
 
            "videorate ! video/x-raw-yuv,framerate=%s/1 ! "
 
            "videoscale ! video/x-raw-yuv,width=640,height=480;video/x-raw-rgb,width=320,height=240 ! "
 
            "videocrop left=160 top=180 right=120 bottom=80 ! "
 
            "queue name=vid" % framerate)
 

	
 
        print cam
 
        self.pipeline = gst.parse_launch(cam)
 
@@ -58,8 +63,9 @@ class Pipeline(object):
 
            e = gst.element_factory_make(t, n)
 
            self.pipeline.add(e)
 
            return e
 
        
 

	
 
        sink = makeElem("xvimagesink")
 

	
 
        def setRec(t):
 
            # if you're selecting the text while gtk is updating it,
 
            # you can get a crash in xcb_io
 
@@ -68,21 +74,22 @@ class Pipeline(object):
 
            with gtk.gdk.lock:
 
                self.recordingTo.set_text(t)
 
            self._lastRecText = t
 

	
 
        recSink = VideoRecordSink(self.musicTime, setRec, self.snapshotRequests)
 
        self.pipeline.add(recSink)
 

	
 
        tee = makeElem("tee")
 
        
 

	
 
        caps = makeElem("capsfilter")
 
        caps.set_property('caps', gst.caps_from_string('video/x-raw-rgb'))
 

	
 
        gst.element_link_many(self.pipeline.get_by_name("vid"), tee, sink)
 
        gst.element_link_many(tee, makeElem("ffmpegcolorspace"), caps, recSink)
 
        sink.set_xwindow_id(self.liveVideoXid)
 
        self.pipeline.set_state(gst.STATE_PLAYING)        
 
        self.pipeline.set_state(gst.STATE_PLAYING)
 

	
 
    def setLiveVideo(self, on):
 
        
 

	
 
        if on:
 
            self.pipeline.set_state(gst.STATE_PLAYING)
 
            # this is an attempt to bring the dv1394 source back, but
 
@@ -91,13 +98,11 @@ class Pipeline(object):
 
                gst.FORMAT_TIME, gst.SEEK_FLAG_FLUSH, 0 * gst.SECOND)
 
        else:
 
            self.pipeline.set_state(gst.STATE_READY)
 
                                                   
 

	
 

	
 
class VideoRecordSink(gst.Element):
 
    _sinkpadtemplate = gst.PadTemplate ("sinkpadtemplate",
 
                                        gst.PAD_SINK,
 
                                        gst.PAD_ALWAYS,
 
                                        gst.caps_new_any())
 
    _sinkpadtemplate = gst.PadTemplate("sinkpadtemplate", gst.PAD_SINK,
 
                                       gst.PAD_ALWAYS, gst.caps_new_any())
 

	
 
    def __init__(self, musicTime, updateRecordingTo, snapshotRequests):
 
        gst.Element.__init__(self)
 
@@ -107,14 +112,15 @@ class VideoRecordSink(gst.Element):
 
        self.add_pad(self.sinkpad)
 
        self.sinkpad.set_chain_function(self.chainfunc)
 
        self.lastTime = 0
 
        
 

	
 
        self.musicTime = musicTime
 

	
 
        self.imagesToSave = Queue()
 
        self.startBackgroundImageSaver(self.imagesToSave)
 
        
 

	
 
    def startBackgroundImageSaver(self, imagesToSave):
 
        """do image saves in another thread to not block gst"""
 

	
 
        def imageSaver():
 
            while True:
 
                args = imagesToSave.get()
 
@@ -135,7 +141,7 @@ class VideoRecordSink(gst.Element):
 
                else:
 
                    req(args[1])
 
                    self.snapshotRequests.task_done()
 
        
 

	
 
        t = Thread(target=imageSaver)
 
        t.setDaemon(True)
 
        t.start()
 
@@ -159,14 +165,14 @@ class VideoRecordSink(gst.Element):
 

	
 
    def saveImg(self, position, img, bufferTimestamp):
 
        if not position['song']:
 
            return 
 
        
 
            return
 

	
 
        t1 = time.time()
 
        outDir = takeDir(songDir(position['song']), position['started'])
 
        outFilename = "%s/%08.03f.jpg" % (outDir, position['t'])
 
        if os.path.exists(outFilename): # we're paused on one time
 
        if os.path.exists(outFilename):  # we're paused on one time
 
            return
 
        
 

	
 
        try:
 
            os.makedirs(outDir)
 
        except OSError:
 
@@ -175,11 +181,10 @@ class VideoRecordSink(gst.Element):
 
        img.save(outFilename)
 

	
 
        now = time.time()
 
        log.info("wrote %s delay of %.2fms, took %.2fms",
 
                  outFilename,
 
                  (now - self.lastTime) * 1000,
 
                  (now - t1) * 1000)
 
        log.info("wrote %s delay of %.2fms, took %.2fms", outFilename,
 
                 (now - self.lastTime) * 1000, (now - t1) * 1000)
 
        self.updateRecordingTo(outDir)
 
        self.lastTime = now
 

	
 

	
 
gobject.type_register(VideoRecordSink)
0 comments (0 inline, 0 general)