Changeset - 7772cc48e016
[Not reviewed]
default
! ! !
drewp@bigasterisk.com - 6 years ago 2019-05-21 23:56:12
drewp@bigasterisk.com
reformat all python
Ignore-this: 1135b78893f8b3d31badddda7f45678f
69 files changed:
Changeset was too big and was cut off... Show full diff anyway
0 comments (0 inline, 0 general)
bin/ascoltami2
Show inline comments
 
@@ -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)
 
@@ -37,16 +39,22 @@ class App(object):
 

	
 
        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()
 

	
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
 
@@ -9,8 +9,10 @@ from light9.TLUtility import make_attrib
 

	
 
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)
 
@@ -33,38 +35,54 @@ class pad(tk.Frame):
 
            sub = Submaster(subname)
 
            self.levs[sub]=0
 
            
 
            l = tk.Label(self,font="arial 12 bold",anchor='w',height=2,
 
                         relief='groove',bd=5,
 
            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(
 
                "<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()
 
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')
 
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)
 
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,14 +26,17 @@ 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):
 
@@ -46,14 +49,17 @@ 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
 
@@ -82,7 +88,10 @@ class Capture(object):
 
            row += 1
 
            for rx in xSteps:
 
                for zoom in zoomSteps:
 
                    self.toGather.append(DeviceSettings(graph, [
 
                    self.toGather.append(
 
                        DeviceSettings(
 
                            graph,
 
                            [
 
                        (dev, L9['rx'], rx),
 
                        (dev, L9['ry'], ry),
 
                        (dev, L9['color'], '#ffffff'),
 
@@ -91,11 +100,12 @@ class Capture(object):
 
                    ]))
 

	
 
        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.graph.patch(
 
            Patch(addQuads=[
 
            (self.session, RDF.type, L9['CaptureSession'], self.ctx),
 
        ]))
 
                
 
@@ -104,7 +114,8 @@ class Capture(object):
 
        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
 
@@ -117,59 +128,71 @@ class Capture(object):
 
        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()),
 
        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,13 +57,15 @@ 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)
 
@@ -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],
 
            attrRows.append({
 
                'attr': attr.rsplit('/')[-1],
 
                             'val': val,
 
                             'chan': (output.shortId(), index + 1)})
 
                '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,17 +145,21 @@ 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
 
@@ -165,12 +180,16 @@ def launch(graph, doLoadTest=False):
 
    
 
    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,18 +199,26 @@ 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)
 
@@ -200,8 +227,10 @@ def main():
 
    
 
    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,6 +16,7 @@ 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)
 
@@ -28,9 +31,12 @@ def loadTest():
 
                     [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.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
 
@@ -46,15 +45,19 @@ 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()
 
@@ -67,10 +70,12 @@ class Main(object):
 
        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
 
@@ -95,15 +100,18 @@ class Main(object):
 
        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')
 
@@ -117,10 +125,10 @@ class Main(object):
 
                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)
 

	
 
@@ -129,13 +137,16 @@ 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():
 
@@ -157,19 +168,20 @@ class Main(object):
 
        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:
 
@@ -179,8 +191,8 @@ class Main(object):
 
            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
 
            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,
 
@@ -189,9 +201,12 @@ class Main(object):
 
        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):
 
@@ -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):
 
@@ -339,8 +354,7 @@ class Main(object):
 
        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
 
@@ -396,11 +410,10 @@ class Main(object):
 
        """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)),
 
            ('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)),
 
            ]):
 
@@ -412,19 +425,23 @@ 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 [
 
        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):
 
@@ -432,8 +449,7 @@ class Main(object):
 
            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:
 
@@ -501,7 +519,6 @@ 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")
 
        
 
    if opts.startup_only:
 
@@ -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)
 
        
 
    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
 
@@ -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
 
@@ -70,10 +75,11 @@ 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):
 

	
 
        xmlrpc.XMLRPC.__init__(self)
 
@@ -98,7 +104,6 @@ class XMLRPCServe(xmlrpc.XMLRPC):
 
        else:
 
            self.parportdmx.golive()
 
            
 

	
 
        self.updatefreq=Updatefreq() # freq of actual dmx sends
 
        self.num_unshown_updates=None
 
        self.lastshownlevels=None
 
@@ -109,7 +114,6 @@ class XMLRPCServe(xmlrpc.XMLRPC):
 
        self.purgeclients()
 
        
 
    def purgeclients(self):
 
        
 
        """forget about any clients who haven't sent levels in a while.
 
        this runs in a loop"""
 

	
 
@@ -132,7 +136,6 @@ class XMLRPCServe(xmlrpc.XMLRPC):
 
                del self.lastseen[cid]
 
        
 
    def sendlevels(self):
 
        
 
        """sends to dmx if levels have changed, or if we havent sent
 
        in a while"""
 

	
 
@@ -145,7 +148,8 @@ class XMLRPCServe(xmlrpc.XMLRPC):
 
         
 
            if (self.num_unshown_updates is None or # first time
 
                self.options.fast_updates or # show always
 
                (self.combinedlevels!=self.lastshownlevels and # changed
 
                (
 
                    self.combinedlevels != self.lastshownlevels and  # changed
 
                 self.num_unshown_updates>5)): # not too frequent
 
                self.num_unshown_updates=0
 
                self.printlevels()
 
@@ -159,7 +163,8 @@ class XMLRPCServe(xmlrpc.XMLRPC):
 

	
 
        # 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:
 
        if self.clientschanged or time.time(
 
        ) > self.lastupdate + self.calldelay:
 
            self.lastupdate=time.time()
 
            self.sendlevels_dmx()
 

	
 
@@ -179,14 +184,14 @@ class XMLRPCServe(xmlrpc.XMLRPC):
 

	
 
    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"),
 
        sys.stdout.write("dmxserver up at %s, [polls %s] " % (
 
            time.strftime("%H:%M:%S"),
 
                          str(self.updatefreq),
 
                          ))
 
        for cid,freq in self.clientfreq.items():
 
@@ -239,14 +244,23 @@ class XMLRPCServe(xmlrpc.XMLRPC):
 
        
 

	
 
parser=OptionParser()
 
parser.add_option("-f","--fast-updates",action='store_true',
 
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()
 

	
 
@@ -255,7 +269,6 @@ print options
 
if options.dummy:
 
    os.environ['DMXDUMMY'] = "1"
 

	
 

	
 
port = networking.dmxServer.port
 
print "starting xmlrpc server on port %s" % port
 
xmlrpcServe = XMLRPCServe(options)
 
@@ -266,4 +279,3 @@ startZmq(networking.dmxServerZmq.port, x
 
oscApp = ReceiverApplication(9051, xmlrpcServe)
 

	
 
reactor.run()
 

	
bin/effecteval
Show inline comments
 
@@ -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.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=[
 
        self.settings.graph.patch(
 
            Patch(delQuads=[
 
            (song, L9['effect'], uri, ctx),
 
            ]))
 
        
 

	
 
@inlineCallbacks
 
def currentSong():
 
    s = (yield getMusicStatus())['song']
 
@@ -42,12 +47,16 @@ 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()
 
@@ -70,13 +79,20 @@ class SongEffects(PrettyErrorHandler, cy
 
            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)
 
@@ -88,7 +104,10 @@ 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})
 
        
 
        
 
@@ -96,6 +115,7 @@ class EffectUpdates(cyclone.websocket.We
 
    """
 
    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'))
 
@@ -121,9 +141,9 @@ 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,
 
    patch = graph.getObjectPatch(context=c,
 
        subject=s,
 
        predicate=p,
 
        newObject=newObjs[0])
 
@@ -137,6 +157,7 @@ def replaceObjects(graph, c, s, p, newOb
 

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

	
 
    def put(self):
 
        effect = URIRef(self.get_argument('uri'))
 
        codeLines = []
 
@@ -150,8 +171,8 @@ 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)
 
@@ -159,7 +180,9 @@ class Code(PrettyErrorHandler, cyclone.w
 
        # 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,14 +207,18 @@ 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('/',
 
        self.stats = scales.collection(
 
            '/',
 
                                       scales.PmfStat('sendLevels'),
 
                                       scales.PmfStat('getMusic'),
 
                                       scales.PmfStat('evals'),
 
@@ -206,11 +234,17 @@ class App(object):
 
        
 
        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),
 
@@ -225,24 +259,32 @@ class App(object):
 
        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",
 
    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()
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('/',
 
        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",
 
    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()
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
 
@@ -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,7 +31,9 @@ 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()
 
@@ -39,7 +42,9 @@ class App(object):
 
        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
 
@@ -25,7 +25,9 @@ curves = {
 
    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()
 
@@ -48,7 +50,8 @@ class WatchMidi(object):
 
        
 
    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")
 
@@ -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,7 +85,6 @@ 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)]:
 
@@ -99,10 +103,12 @@ class WatchMidi(object):
 
                            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
 
@@ -15,5 +14,3 @@ if len(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
 
@@ -72,21 +87,30 @@ class SubmasterBox(tk.Frame):
 
        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)]))
 
        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"""
 
@@ -142,13 +170,17 @@ class SubmasterBox(tk.Frame):
 
                    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
 
@@ -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']),
 
            withgroups.append((self.graph.value(effect, L9['group']),
 
                self.graph.value(effect, L9['order']),
 
                self.graph.label(effect),
 
                effect))
 
                               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:
 
@@ -421,11 +467,14 @@ 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)
 
@@ -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
 
@@ -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,18 +552,22 @@ 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):
 
        self.name_to_subbox = name_to_subbox
 

	
 
@@ -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')
 
            
 

	
 
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")
 
@@ -604,8 +668,8 @@ if __name__ == "__main__":
 

	
 
    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)
 

	
bin/lightsim
Show inline comments
 
@@ -21,19 +21,26 @@ 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:
 
@@ -54,11 +61,14 @@ 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()
 
@@ -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)
 
@@ -98,6 +110,7 @@ class Window(QMainWindow):
 
        status = StatusKeys(mainLayout)
 
        mainLayout.addWidget(status)      
 

	
 

	
 
def requiredImages(graph):
 
    """filenames that we'll need to show, based on a config structure
 
    like this:
 
@@ -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
 

	
bin/musicPad
Show inline comments
 
@@ -35,5 +35,3 @@ for p in playlist.allSongPaths():
 
    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,25 +23,37 @@ 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()
bin/paintserver
Show inline comments
 
@@ -17,21 +17,26 @@ 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},
 
        self.write(
 
            json.dumps({
 
                'bestMatch': {
 
                    'uri': sample,
 
                    'path': bestPath,
 
                    'dist': sampleDist
 
                },
 
        #    'layers': layers,
 
        #    'out': out,
 
        }))
 
@@ -41,7 +46,9 @@ class Solve(PrettyErrorHandler, cyclone.
 
        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,24 +56,29 @@ 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.graph.initiallySynced.addCallback(self.launch).addErrback(
 
            log.error)
 
        
 
        self.stats = scales.collection('/', scales.PmfStat('solve'),
 
        self.stats = scales.collection(
 
            '/',
 
            scales.PmfStat('solve'),
 
                                       )
 
       
 
    def launch(self, *args):
 

	
 
        self.solver = light9.paint.solve.Solver(self.graph, sessions=[
 
        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'],
 
@@ -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",
 
    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()
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 = {
 
@@ -38,9 +47,10 @@ def setCameraParams(c, arg):
 
    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 ]
 
@@ -54,6 +64,7 @@ def setupCrop(c, arg):
 
        rw = int(scl2 * c.crop[2] * c.resolution[0])
 
    return rw, rh
 
    
 

	
 
@prof.logTime
 
def getFrame(c, arg):
 
    setCameraParams(c, arg)
 
@@ -62,7 +73,9 @@ def getFrame(c, arg):
 
    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,16 +83,20 @@ 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",
 
@@ -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,9 +117,12 @@ 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
 
@@ -116,14 +136,16 @@ class FpsReport(object):
 
            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:
 
@@ -146,6 +168,7 @@ class Pics(cyclone.web.RequestHandler):
 
                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
 
@@ -158,16 +181,25 @@ class Pics(cyclone.web.RequestHandler):
 
        log.info("connection closed")
 
        self.running = False
 
            
 

	
 
log.setLevel(logging.INFO)
 

	
 
with cameraCls() as camera:
 
    port = 8208
 
    reactor.listenTCP(port, cyclone.web.Application(handlers=[
 
    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))
 
            (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/',
 
@@ -18,4 +19,3 @@ rdfdb.service.main(
 
    },
 
    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
 
@@ -74,7 +84,9 @@ 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
 

	
 

	
 
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')
 
@@ -92,8 +105,11 @@ def setTerminalTitle(s):
 
    if os.environ.get('TERM', '') in ['xterm', 'rxvt', 'rxvt-unicode-256color']:
 
        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,6 +107,7 @@ 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()
 
@@ -117,8 +120,7 @@ class Subcomposer(tk.Frame):
 
        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:
 
@@ -130,13 +132,15 @@ class Subcomposer(tk.Frame):
 
        self.relocateSub(newUri, newName)
 

	
 
        # these are in separate patches for clarity as i'm debugging this
 
        self.graph.patch(Patch(addQuads=[
 
        self.graph.patch(
 
            Patch(addQuads=[
 
            (newUri, RDFS.label, Literal(newName), newUri),
 
        ], delQuads=[
 
            ],
 
                  delQuads=[
 
            (newUri, RDF.type, L9['LocalSubmaster'], newUri),
 
                           ]))
 
        self.graph.patchObject(self.session,
 
                               self.session, L9['currentSub'], newUri)
 
        self.graph.patchObject(self.session, self.session, L9['currentSub'],
 
                               newUri)
 
        
 
    def relocateSub(self, newUri, newName):
 
        # maybe this goes in Submaster
 
@@ -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]
 
        # 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())
 
@@ -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:
 
@@ -203,7 +208,8 @@ 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):
 
@@ -220,7 +226,8 @@ 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=[
 
        self.graph.patch(
 
            Patch(addQuads=[
 
            (new, RDF.type, L9['Submaster'], self.session),
 
            (new, RDF.type, L9['LocalSubmaster'], self.session),
 
        ]))
 
@@ -231,7 +238,9 @@ class Subcomposer(tk.Frame):
 
        self.levelbox = Levelbox(self, self.graph, self.currentSub)
 
        self.levelbox.pack(side='top')
 

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

	
 
    def savenewsub(self, subname):
 
@@ -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")
 

	
 
@@ -297,7 +307,8 @@ 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)
bin/subserver
Show inline comments
 
@@ -17,7 +17,9 @@ from light9 import networking, showconfi
 

	
 
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" %
 
@@ -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
 
@@ -51,7 +57,8 @@ class Snapshot(PrettyErrorHandler, cyclo
 

	
 
        ctx = showconfig.showUri() + "/snapshots"
 
        
 
        self.settings.graph.patch(Patch(addQuads=[
 
        self.settings.graph.patch(
 
            Patch(addQuads=[
 
            (about, L9['image'], snapUri, ctx),
 
            (snapUri, DCTERMS['created'],
 
             Literal(datetime.datetime.now(tzlocal())), ctx),
 
@@ -59,6 +66,7 @@ class Snapshot(PrettyErrorHandler, cyclo
 
        
 
        self.write(json.dumps({'snapshot': snapUri}))
 

	
 

	
 
def newestImage(subject):
 
    newest = (None, None)
 
    for img in graph.objects(subject, L9['image']):
 
@@ -67,9 +75,12 @@ def newestImage(subject):
 
            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=[
 
    reactor.listenTCP(
 
        port,
 
        cyclone.web.Application(handlers=[
 
        (r'/snapshot', Snapshot),
 
        (r'/(.*)', Static,
 
         {"path" : "light9/subserver",
 
          "default_filename" : "index.jade"}),
 
        ], debug=True, graph=graph))
 
            (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,18 +21,33 @@ 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)
 

	
 

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

	
bin/tracker
Show inline comments
 
@@ -18,25 +18,30 @@ import Tkinter as tk
 

	
 
defaultfont="arial 8"
 

	
 

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

	
 

	
 
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):
 
        """light/sub name"""
 
        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"""
 
@@ -44,7 +49,6 @@ class Field(xmlnodeclass):
 
                    self._getorsettypedattr("y",float,y))
 

	
 
    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)
 
@@ -58,9 +62,12 @@ class Field(xmlnodeclass):
 
        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"""
 
@@ -78,13 +85,13 @@ class Fieldset(collectiveelement):
 
        if active>0:
 
            print
 
        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()
 
        dmxclient.outputlevels(dmxlist)
 
        
 
        
 
    def getbounds(self):
 
        """returns xmin,xmax,ymin,ymax for the non-zero areas of this field"""
 
        r=None
 
@@ -98,17 +105,23 @@ class Fieldset(collectiveelement):
 
                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 fieldset(self):
 
        return self._gettoplevel()
 

	
 

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

	
 

	
 
class FieldDisplay:
 
    """the view for a Field."""
 

	
 
    def __init__(self,canvas,field):
 
        self.canvas=canvas
 
        self.field=field
 
@@ -145,27 +158,42 @@ class FieldDisplay:
 
        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,
 
            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',
 
        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
 

	
 
        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
 

	
 
        def release(ev):
 
            if hasattr(self,'_lastmouse'):
 
                del self._lastmouse
 
@@ -177,20 +205,25 @@ class FieldDisplay:
 

	
 
        # radius drag bindings
 
        outerring=self.rings[0]
 
        canvashighlighter(c,outerring,
 
                          'outline',normalval='#000080',highlightval='#4040ff')
 
        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())
 
            self.field.falloff(currentdist)
 
            self.setcoords()
 

	
 
        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"""
 

	
 
@@ -214,11 +247,13 @@ class Tracker(tk.Frame):
 
        # 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("<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")
 

	
 
@@ -237,8 +272,7 @@ class Tracker(tk.Frame):
 
        # 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.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))
 
@@ -246,7 +280,8 @@ class Tracker(tk.Frame):
 
    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()
 
        
 
@@ -255,11 +290,15 @@ class Tracker(tk.Frame):
 
        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,
 
                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()]
 

	
 

	
 
########################################################################
 
########################################################################
 
                
bin/vidref
Show inline comments
 
@@ -20,14 +20,14 @@ from rdfdb.syncedgraph import SyncedGrap
 
 # 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
 
@@ -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'}),
 
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'/snapshot/(.*)', SnapshotPic, {
 
            "path": snapshotDir()
 
        }),
 
    (r'/time', Time),
 
    ], debug=True, gui=gui))
 
    ],
 
                            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()
 
@@ -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()
 

	
 
@@ -57,13 +66,21 @@ def main():
 
    # deliberately conflict with vidref since they can't talk at once to cam
 
    port = networking.vidref.port 
 

	
 
    reactor.listenTCP(port, cyclone.web.Application(handlers=[
 
    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))
 
            (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,6 +3,7 @@ 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)
 
@@ -11,14 +12,19 @@ def createCurve(inpath, outpath, t):
 
    for time_val in points:
 
        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()
 

	
 
@@ -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,8 +80,8 @@ 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.form(method="post", action="playSong")
 
                [T.input(type='hidden', name='songUri', value=song),
 
                    T.button(type='submit')[graph.label(song)]])
 
        return out
 

	
 
@@ -89,13 +94,14 @@ class Main(rend.Page):
 
            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
 
@@ -106,6 +112,7 @@ 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,10 +11,13 @@ 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"""
 
@@ -45,16 +48,26 @@ class Strip(object):
 

	
 
    __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:
 
@@ -75,6 +88,7 @@ def chase(t, ontime=0.5, offset=0.2, onv
 

	
 
    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)
 
@@ -84,9 +98,11 @@ def hsv(h, s, v, light='all', centerScal
 
    if light in ['right', 'all']:
 
        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,7 +118,8 @@ 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"%
 
                log.info(
 
                    ("stack includes %r, which doesn't resolve to a dmx chan" %
 
                       uri))
 
                continue
 
            lev[dmx] = 1
 
@@ -111,10 +128,12 @@ def stack(t, names=None, fade=0):
 
    
 
    return Submaster.Submaster(name="stack", levels=lev)
 

	
 

	
 
@register
 
def smoove(x):
 
    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,7 +22,12 @@ 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
 
@@ -37,8 +43,7 @@ class Fadable:
 

	
 
        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))
 

	
 
@@ -92,6 +97,7 @@ class Fadable:
 
            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,6 +142,7 @@ 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:
 
@@ -148,4 +158,3 @@ class Fadable:
 
            self.last_level = None
 

	
 
        self.set_var_rounded(newlevel)
 

	
light9/FlyingFader.py
Show inline comments
 
@@ -2,7 +2,9 @@ from Tix import *
 
from time import time,sleep
 
from __future__ import division
 

	
 

	
 
class Mass:
 

	
 
    def __init__(self):
 
        self.x=0 # position
 
        self.xgoal=0 # position goal
 
@@ -32,8 +34,12 @@ class Mass:
 

	
 
        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.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
 
@@ -48,7 +54,8 @@ class Mass:
 
            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 = 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)
 

	
 
@@ -58,8 +65,16 @@ class Mass:
 
    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
 
@@ -68,8 +83,16 @@ class FlyingFader(Frame):
 
        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'}
 
        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
 
@@ -80,7 +103,11 @@ class FlyingFader(Frame):
 
        
 
        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))
 
@@ -126,7 +153,6 @@ class FlyingFader(Frame):
 
        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)
 

	
 
@@ -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,6 +195,7 @@ class FlyingFader(Frame):
 
    def set(self, val):
 
        self.scale.set(val)
 

	
 

	
 
def colorfade(scale, lev):
 
    low = (255, 255, 255)
 
    high = (0, 0, 0)
 
@@ -174,18 +203,24 @@ def colorfade(scale, lev):
 
    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)
 
                                                               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, 
 
                                                               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).
 
@@ -39,8 +39,8 @@ class Player(object):
 
            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,13 +66,15 @@ 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,
 
        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)
 
@@ -149,8 +152,14 @@ 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))
light9/ascoltami/webapp.py
Show inline comments
 
@@ -10,21 +10,27 @@ render = render_genshi([sibpath(__file__
 
from lib.cycloneerr import PrettyErrorHandler
 

	
 
_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"""
 
    
 
@@ -34,7 +40,9 @@ def playerSongUri(graph, player):
 
    else:
 
        return None
 

	
 

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

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

	
 
        self.write(json.dumps({
 
        self.write(
 
            json.dumps({
 
            "song" : playerSongUri(graph, player),
 
            "started" : player.playStartTime,
 
            "duration" : player.duration(),
 
@@ -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,
 
        self.write(
 
            json.dumps({
 
                "songs": [{
 
                    "uri": s,
 
             "path" : graph.value(s, L9['showPath']),
 
             "label" : graph.label(s)} for s in songs]}))
 
                    "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.
 
@@ -128,6 +152,7 @@ class goButton(PrettyErrorHandler, cyclo
 
        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
 
@@ -47,7 +48,9 @@ def outputMap(graph, outputs):
 
                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,13 +63,15 @@ 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] 
 

	
 
    def rebuildOutputMap(self):
 
        self.outputMap = outputMap(self.graph, self.outputs) # (device, outputattr) : (output, index)
 
        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']):
 
@@ -103,9 +108,11 @@ 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',
 
            log.warn(
 
                'collector.setAttrs from %s is running %.1fms after the request was made',
 
                     client, requestLag * 1000)
 

	
 
    def _merge(self, lastRequests):
 
@@ -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,
 
@@ -167,7 +175,8 @@ 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)
 
        
 
@@ -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
 
@@ -6,11 +6,13 @@ from txzmq import ZmqEndpoint, ZmqFactor
 
import json, time,logging
 
import treq
 

	
 

	
 
log = logging.getLogger('coll_client')
 

	
 
_zmqClient=None
 

	
 

	
 
class TwistedZmqClient(object):
 

	
 
    def __init__(self, service):
 
        zf = ZmqFactory()
 
        e = ZmqEndpoint('connect', 'tcp://%s:%s' % (service.host, service.port))
 
@@ -22,12 +24,14 @@ class TwistedZmqClient(object):
 

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

	
 
def sendToCollectorZmq(msg):
 
    global _zmqClient
 
    if _zmqClient is None:
 
@@ -35,6 +39,7 @@ def sendToCollectorZmq(msg):
 
    _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()
 
@@ -50,8 +55,11 @@ def sendToCollector(client, session, set
 
        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
 
@@ -37,7 +37,9 @@ THEATER = '''
 

	
 
t0 = 0 # time
 

	
 

	
 
class MockOutput(object):
 

	
 
    def __init__(self, uri, connections):
 
        self.connections = connections
 
        self.updates = []
 
@@ -53,11 +55,15 @@ 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])
 
@@ -65,19 +71,25 @@ class TestOutputMap(unittest.TestCase):
 
        
 
    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,8 +134,8 @@ 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.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:
 
@@ -206,27 +218,24 @@ class TestCollector(unittest.TestCase):
 
            # now cli1 is forgotten, so our value appears
 
            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])
 
@@ -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
 
@@ -20,6 +20,7 @@ class ChauvetColorStrip(Device):
 
       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
 
@@ -109,12 +114,7 @@ def toOutputAttrs(deviceType, deviceAttr
 
        
 
    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']:
 
@@ -141,7 +141,8 @@ def toOutputAttrs(deviceType, deviceAttr
 
        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']))
 
@@ -192,13 +193,15 @@ def toOutputAttrs(deviceType, deviceAttr
 
            }
 

	
 
        # 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,
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,
 
        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,6 +27,7 @@ class Output(object):
 
    """
 
    uri = None  # type: URIRef
 
    numChannels = None  # type: int
 

	
 
    def __init__(self):
 
        raise NotImplementedError
 
        
 
@@ -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,7 +55,9 @@ 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
 
@@ -69,6 +73,7 @@ class DummyOutput(Output):
 

	
 
        
 
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):
 
@@ -130,10 +133,10 @@ class EnttecDmx(DmxOutput):
 

	
 
                                  
 
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]
 
@@ -173,7 +176,8 @@ 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)
 
                    msg = 'usb: sending %s bytes to %r; error %r' % (
 
                        len(buf), self.uri, e)
 
                  print msg
 
                return False
 

	
 
@@ -183,4 +187,3 @@ class Udmx(DmxOutput):
 
        
 
    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,
 
    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")
 

	
 
@@ -6,10 +5,13 @@ log = logging.getLogger("cursors")
 
# cursor with @filename form
 

	
 
_pushed = {} # widget : [old, .., newest]
 

	
 

	
 
def push(widget,new_cursor):
 
    global _pushed
 
    _pushed.setdefault(widget,[]).append(widget.cget("cursor"))
 

	
 

	
 
def pop(widget):
 
    global _pushed
 
    try:
 
@@ -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
 
@@ -13,8 +13,10 @@ 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
 
@@ -27,12 +29,16 @@ 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):
 
@@ -56,12 +62,14 @@ class Curve(object):
 
        dispatcher.send("points changed",sender=self)
 

	
 
    def points_as_string(self):
 

	
 
        def outVal(x):
 
            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)
 

	
 
        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
 
@@ -91,6 +99,7 @@ class Curve(object):
 
        frac = (t-p1[0])/(p2[0]-p1[0])
 
        y = p1[1]+(p2[1]-p1[1])*frac
 
        return y
 

	
 
    __call__=eval
 

	
 
    def insert_pt(self, new_pt):
 
@@ -158,10 +167,12 @@ class Curve(object):
 
            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,7 +188,8 @@ class CurveResource(object):
 
        if hasattr(self, 'curve'):
 
            raise ValueError('CurveResource already has a curve %r' %
 
                             self.curve)
 
        self.graph.patch(Patch(addQuads=[
 
        self.graph.patch(
 
            Patch(addQuads=[
 
            (self.uri, RDF.type, L9['Curve'], ctx),
 
            (self.uri, RDFS.label, label, ctx),
 
            ]))
 
@@ -222,11 +234,13 @@ class CurveResource(object):
 
            #cur.save("%s-%s" % (basename,name))
 
            return []
 
        elif self.curve.pointsStorage == 'graph':
 
            return [self.graph.getObjectPatch(
 
                self.curvePointsContext(),
 
            return [
 
                self.graph.getObjectPatch(self.curvePointsContext(),
 
                subject=self.uri,
 
                predicate=L9['points'],
 
                newObject=Literal(self.curve.points_as_string()))]
 
                                          newObject=Literal(
 
                                              self.curve.points_as_string()))
 
            ]
 
        else:
 
            raise NotImplementedError(self.curve.pointsStorage)
 

	
 
@@ -249,8 +263,10 @@ class CurveResource(object):
 
        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()
 

	
 
@@ -262,6 +278,7 @@ def slope(p1,p2):
 

	
 
       
 
class Curveset(object):
 

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

	
 
@@ -298,8 +315,11 @@ 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)
 
                
 
@@ -336,8 +356,8 @@ class Curveset(object):
 
    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):
 
@@ -358,8 +378,8 @@ class Curveset(object):
 
        cr.curve.points.extend([(s, 0), (e, 0)])
 

	
 
        ctx = self.currentSong
 
        self.graph.patch(Patch(addQuads=[
 
        self.graph.patch(
 
            Patch(addQuads=[
 
            (self.currentSong, L9['curve'], uri, ctx),
 
            ]))
 
        cr.saveCurve()
 

	
light9/curvecalc/curveedit.py
Show inline comments
 
@@ -9,6 +9,7 @@ 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
 
@@ -16,10 +17,12 @@ def serveCurveEdit(port, hoverTimeRespon
 
    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])
 
@@ -27,13 +30,17 @@ def serveCurveEdit(port, hoverTimeRespon
 
            curveEdit.liveInputPoint(curve, value)
 
            self.set_status(204)
 
            
 
    reactor.listenTCP(port, cyclone.web.Application(handlers=[
 
    reactor.listenTCP(
 
        port,
 
        cyclone.web.Application(handlers=[
 
        (r'/hoverTime', HoverTime),
 
        (r'/liveInputPoint', LiveInputPoint),
 
        ], debug=True))
 
        ],
 
                                debug=True))
 

	
 
    
 
class CurveEdit(object):
 

	
 
    def __init__(self, curveset):
 
        self.curveset = curveset
 
        dispatcher.connect(self.inputTime, "input time")
 
@@ -45,4 +52,3 @@ class CurveEdit(object):
 
    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,9 +13,12 @@ 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])
 

	
 

	
 
def angle_between(base, p0, p1):
 
    p0 = p0[0] - base[0], p0[1] - base[1]
 
    p1 = p1[0] - base[0], p1[1] - base[1]
 
@@ -25,6 +28,7 @@ def angle_between(base, p0, p1):
 
    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"""
 
@@ -75,15 +79,17 @@ class Sketch:
 
        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
 
@@ -95,28 +101,36 @@ class SelectManip(object):
 
        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)
 

	
 
        self.xTrans = polyline_new_line(parent=self.grp, close_path=True,
 
        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,
 
        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'),
 
        for grp, name in [
 
            (self.xTrans, 'x'),
 
                          (self.leftScale, 'left'),
 
                          (self.rightScale, 'right'),
 
                          (self.topScale, 'top'),
 
@@ -172,20 +186,18 @@ class SelectManip(object):
 
            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 *
 
                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 *
 
                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':
 
@@ -193,24 +205,22 @@ class SelectManip(object):
 
                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,
 
                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'):
 
            del self.dragStartTime
 
@@ -226,11 +236,12 @@ class SelectManip(object):
 
        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,
 
        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,23 +256,20 @@ 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),
 
        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):       
 
        x1 = centerX - 30
 
        x2 = centerX - 20
 
@@ -275,13 +283,11 @@ class SelectManip(object):
 
            (x1, midY), # left tip
 
            (x2, y1),
 
            (x2, y2),
 
            
 
            (x3, y2),
 
            (x3, y1),
 
            (x4, midY), # right tip
 
            (x3, y4),
 
            (x3, y3),
 
            
 
            (x2, y3),
 
            (x2, y4)
 
            ]
 
@@ -291,6 +297,7 @@ class SelectManip(object):
 
    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"""
 
@@ -332,17 +344,16 @@ class Curveview(object):
 
        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
 
@@ -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):
 
@@ -407,6 +419,7 @@ class Curveview(object):
 
        def visEvent(w, alloc):
 
            self.setCanvasToWidgetSize()
 
            return False
 

	
 
        self.widget.add_events(Gdk.EventMask.VISIBILITY_NOTIFY_MASK)
 
        self.widget.connect('visibility-notify-event', visEvent)
 

	
 
@@ -476,7 +489,6 @@ class Curveview(object):
 
        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
 
@@ -563,7 +575,8 @@ 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,
 
@@ -688,14 +701,17 @@ class Curveview(object):
 
        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))
 
@@ -706,8 +722,10 @@ class Curveview(object):
 
            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)
 
@@ -737,13 +755,15 @@ class Curveview(object):
 
        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,8 +772,8 @@ 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)
 

	
 
@@ -781,7 +801,10 @@ class Curveview(object):
 
        for t, name in pts:
 
            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'))
 

	
 
@@ -812,15 +835,18 @@ class Curveview(object):
 
            
 
        ht = self.canvas.props.y2
 
        polyline_new_line(self.curveGroup,
 
                                    x, ht,
 
                                    x, ht - 20,
 
                          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,
 
                             x=x + 3,
 
                             y=ht - 20,
 
                       text=label)
 

	
 
    def _draw_line(self, visible_points, area=False):
 
@@ -855,10 +881,9 @@ 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 +
 
            polyline_new_line(
 
                parent=self.curveGroup,
 
                points=Points([(areapts[0][0], base)] + areapts +
 
                                  [(areapts[-1][0], base)]),
 
                              close_path=True,
 
                              line_width=0,
 
@@ -868,13 +893,13 @@ class Curveview(object):
 
                              fill_color_rgba=0x00800080,
 
            )
 

	
 
        self.pl = polyline_new_line(parent=self.curveGroup,
 
        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
 
@@ -883,10 +908,12 @@ class Curveview(object):
 
                p = self.screen_from_world(p)
 
            except ZeroDivisionError:
 
                p = (-100, -100)
 
            dot = GooCanvas.CanvasRect(parent=self.curveGroup,
 
            dot = GooCanvas.CanvasRect(
 
                parent=self.curveGroup,
 
                                 x=int(p[0] - rad) + .5,
 
                                 y=int(p[1] - rad) + .5,
 
                                 width=rad * 2, height=rad * 2,
 
                width=rad * 2,
 
                height=rad * 2,
 
                                 stroke_color='gray90',
 
                                 fill_color='blue',
 
                                 line_width=1,
 
@@ -895,7 +922,8 @@ class Curveview(object):
 

	
 
            if worldp[1] == 0:
 
                rad += 3
 
                GooCanvas.CanvasEllipse(parent=self.curveGroup,
 
                GooCanvas.CanvasEllipse(
 
                    parent=self.curveGroup,
 
                                  center_x=p[0],
 
                                  center_y=p[1],
 
                                  radius_x=rad,
 
@@ -1018,7 +1046,6 @@ class Curveview(object):
 

	
 
        self.translate_points(delta)
 
        
 

	
 
    def translate_points(self, delta):
 
        moved = False
 
        
 
@@ -1045,7 +1072,9 @@ class Curveview(object):
 
    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)
 
@@ -1065,6 +1094,7 @@ class Curveview(object):
 
        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
 
@@ -1088,7 +1119,8 @@ class CurveRow(object):
 
        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)
 
        
 
@@ -1112,7 +1144,10 @@ class CurveRow(object):
 
    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)
 
@@ -1127,14 +1162,15 @@ class CurveRow(object):
 

	
 
        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)
 
        dispatcher.connect(self.mute_changed, 'mute changed', sender=curve)
 
@@ -1172,6 +1208,7 @@ class Curvesetview(object):
 
    """
 
    
 
    """
 

	
 
    def __init__(self, graph, curvesVBox, zoomControlBox, curveset):
 
        self.graph = graph
 
        self.live = True
 
@@ -1233,8 +1270,8 @@ 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()
 
@@ -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:
 
@@ -1296,8 +1334,7 @@ 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
 
@@ -1323,6 +1360,3 @@ class Curvesetview(object):
 
    def onDelete(self):
 
        for r in self.allCurveRows:
 
            r.onDelete()
 

	
 

	
 
        
light9/curvecalc/musicaccess.py
Show inline comments
 
@@ -10,9 +10,11 @@ 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,7 +44,9 @@ class StringProducer(object):
 
    def stopProducing(self):
 
        pass
 

	
 

	
 
class Music:
 

	
 
    def __init__(self):
 
        self.recenttime=0
 
        self.player = Agent(reactor)
 
@@ -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,9 +7,11 @@ from light9.curvecalc.subterm import Sub
 
from louie import dispatcher
 
log = logging.getLogger("output")
 

	
 

	
 
class Output(object):
 
    lastsendtime=0
 
    lastsendlevs=None
 

	
 
    def __init__(self, graph, session, music, curveset, currentSubterms):
 
        self.graph, self.session, self.music = graph, session, music
 
        self.currentSubterms = currentSubterms
 
@@ -66,6 +68,7 @@ class Output(object):
 
        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,9 +8,11 @@ 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()
 
    
 
@@ -23,9 +25,9 @@ class Expr(object):
 
        glo.update(self.effectGlobals)
 

	
 
        def chan(name):
 
            return Submaster.Submaster(
 
                name=name,
 
            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,6 +38,7 @@ 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)
 
@@ -46,10 +49,13 @@ class Expr(object):
 

	
 
        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
 
@@ -59,14 +65,17 @@ class Subterm(object):
 
        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.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
 
@@ -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 = []
 
@@ -109,14 +119,17 @@ class Subterm(object):
 
        
 
        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)
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
 
@@ -31,7 +33,9 @@ class Subexprview(object):
 

	
 
        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):
 
@@ -55,15 +59,16 @@ class Subexprview(object):
 
            
 
    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
 
@@ -76,15 +81,14 @@ class Subtermview(object):
 
                            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()))
 
        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,6 +103,7 @@ class Subtermview(object):
 
            return
 
        self.label.set_text(label)
 

	
 

	
 
def add_one_subterm(subterm, curveset, master, show=False):
 
    stv = Subtermview(subterm, curveset)
 
    
 
@@ -109,6 +114,7 @@ def add_one_subterm(subterm, curveset, m
 
    if show:
 
        master.show_all()
 

	
 

	
 
def scrollToRowUponAdd(widgetInRow):
 
    """when this table widget is ready, scroll the table so we can see it"""
 
    
light9/curvecalc/zoomcontrol.py
Show inline comments
 
@@ -7,6 +7,7 @@ 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 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 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
 
            self.start = value - d / 2
 
            self.end = value + d / 2
 

	
 
        return locals()
 

	
 
    offset = property(**offset())
 

	
 
    def __init__(self, **kw):
 
@@ -72,9 +93,11 @@ 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)
 
@@ -87,12 +110,12 @@ class ZoomControl(object):
 

	
 
        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'))
 
        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")
 
@@ -152,8 +175,8 @@ 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)]))
 
        self.time.set_property("points", Points([(x, 0),
 
                                                 (x, self.size.height)]))
 
        
 
    def press(self,ev,attr):
 
        self.adjustingattr = attr
 
@@ -180,6 +203,7 @@ class ZoomControl(object):
 
    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):
 
        a, b = self.mintime, self.maxtime
 
        return (x - 20) / (self.size.width - 30) * (b - a) + a
 
@@ -203,18 +227,15 @@ 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),
 
        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),
 
        self.rightbrack.set_property(
 
            "points",
 
            Points([(ecan - lip, y1), (ecan, y1), (ecan, y2),
 
            (ecan - lip, y2)]))
 
        self.shade.set_properties(
 
            x=scan + 5,
 
        self.shade.set_properties(x=scan + 5,
 
            y=y1 + lip,
 
            width=max(0, ecan - 5 - (scan + 5)),
 
            height=max(0, y2 - lip - (y1 + lip)))
 
@@ -239,7 +260,8 @@ class ZoomControl(object):
 
                                   line_width=.8,
 
                                   stroke_color='black')
 
                GooCanvas.CanvasText(parent=self.ticsGroup,
 
                               x=x, y=self.size.height-1,
 
                                     x=x,
 
                                     y=self.size.height - 1,
 
                               anchor=GooCanvas.CanvasAnchorType.SOUTH,
 
                               text=txt,
 
                               font='ubuntu 7')
 
@@ -251,6 +273,7 @@ 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
 
@@ -288,7 +311,12 @@ class RegionZoom:
 
        can = self.canvas
 

	
 
        for pos in ('start_t','end_t','hi','lo'):
 
            can.create_line(0,0,50,50, width=3, fill='yellow',
 
            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
 
@@ -312,8 +340,8 @@ class RegionZoom:
 
            
 
        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):
 
        if self.state != "buttonpress":
 
@@ -339,7 +367,8 @@ 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":
light9/dmxchanedit.py
Show inline comments
 
@@ -25,11 +25,13 @@ 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
 

	
 

	
 
class Onelevel(tk.Frame):
 
    """a name/level pair
 

	
 
@@ -48,6 +50,7 @@ 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)
 
        self.graph = graph
 
@@ -62,11 +65,16 @@ class Onelevel(tk.Frame):
 
        # 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
 
@@ -74,15 +82,25 @@ class Onelevel(tk.Frame):
 
                               width=14,
 
                               font=stdfont,
 
                               anchor='w',
 
                               padx=0, pady=0, bd=0,
 
                 height=1, bg='black', fg='white')
 
                                 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()
 
@@ -91,30 +109,36 @@ class Onelevel(tk.Frame):
 
         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
 

	
 
        def b1motion(ev):
 
            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
 
            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)
 

	
 
@@ -142,6 +166,7 @@ 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)
 
@@ -152,7 +177,8 @@ class Levelbox(tk.Frame):
 
        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"""
 
@@ -161,7 +187,8 @@ class Levelbox(tk.Frame):
 
        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))
 

	
 
@@ -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
 
@@ -17,16 +17,25 @@ 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
 

	
 
        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):
 
    """present a list of dmx channel levels, each scaled from
 
@@ -58,8 +67,10 @@ def outputlevels(levellist,twisted=0,cli
 
        _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,9 +80,11 @@ 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)
 
@@ -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,20 +25,16 @@ 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)
 
        
 
@@ -48,6 +47,7 @@ class EditChoice(Gtk.HBox):
 
        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']:
 
@@ -58,8 +58,10 @@ class EditChoice(Gtk.HBox):
 
        
 
        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 :(
 
            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)
 
@@ -67,8 +69,9 @@ class EditChoice(Gtk.HBox):
 
    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)],
 
            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):
 
@@ -76,7 +79,6 @@ class EditChoice(Gtk.HBox):
 

	
 
        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']))
 
@@ -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,10 +101,9 @@ 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:
 
@@ -109,10 +112,10 @@ 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):
 
@@ -130,7 +133,10 @@ def _makeNote(graph, song, note, quads, 
 
    if event == 'default':
 
        coords = [(0 - fade, 0), (0, 1), (20, 1), (20 + fade, 0)]
 
    elif event == 'start':
 
        coords = [(0 - fade, 0), (0, 1), ]
 
        coords = [
 
            (0 - fade, 0),
 
            (0, 1),
 
        ]
 
    elif event == 'end': # probably unused- goes to _finishCurve instead
 
        coords = [(20, 1), (20 + fade, 0)]
 
    else:
 
@@ -140,14 +146,17 @@ def _makeNote(graph, song, note, quads, 
 
        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
 

	
 

	
 
def musicCurveForSong(uri):
 
    return URIRef(uri + 'music')
 
    
 

	
 
def _newEffect(graph, song, ctx):
 
    effect = graph.sequentialUri(song + "/effect-")
 
    quads = [
 
@@ -157,6 +166,7 @@ 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"""
 
@@ -166,6 +176,7 @@ def _newEnvelopeCurve(graph, ctx, uri, l
 
    yield _insertEnvelopePoints(cr.curve, fade)
 
    cr.saveCurve()
 

	
 

	
 
@inlineCallbacks
 
def _insertEnvelopePoints(curve, fade=2):
 
    # wrong: we might not be adding to the currently-playing song.
 
@@ -190,8 +201,7 @@ def _maybeAddMusicLine(quads, effect, so
 
    
 
    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,7 +71,9 @@ 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:
 
@@ -60,8 +82,10 @@ class EffectEval(object):
 
        report = {}
 
        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/']):]
 
@@ -77,11 +101,10 @@ class EffectEval(object):
 
                  
 

	
 
def effect_Curtain(effectSettings, strength, songTime, noteTime):
 
    return {
 
        (L9['device/lowPattern%s' % n], L9['color']):
 
    return {(L9['device/lowPattern%s' % n], L9['color']):
 
        literalColor(strength, strength, strength)
 
        for n in range(301,308+1)
 
        }
 
            for n in range(301, 308 + 1)}
 

	
 
    
 
def effect_animRainbow(effectSettings, strength, songTime, noteTime):
 
    out = {}
 
@@ -102,11 +125,14 @@ def effect_animRainbow(effectSettings, s
 
            })
 
        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')
 
@@ -124,11 +150,14 @@ def effect_auraSparkles(effectSettings, 
 
            })
 
        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,6 +171,7 @@ def effect_qpan(effectSettings, strength
 
        (dev, L9['zoom']): 0.714,
 
    }
 

	
 

	
 
def effect_pulseRainbow(effectSettings, strength, songTime, noteTime):
 
    out = {}
 
    tint = effectSettings.get(L9['tint'], '#ffffff')
 
@@ -193,6 +223,7 @@ def effect_aurawash(effectSettings, stre
 
            })
 
    return out
 

	
 

	
 
def effect_qsweep(effectSettings, strength, songTime, noteTime):
 
    out = {}
 
    period = float(effectSettings.get(L9['period'], 2))
 
@@ -200,7 +231,6 @@ 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):
 
        dev = L9['device/q%s' % n]
 
        out.update({
 
@@ -210,10 +240,12 @@ def effect_qsweep(effectSettings, streng
 
        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))
 
@@ -227,16 +259,20 @@ def effect_qsweepusa(effectSettings, str
 
    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'],
 
@@ -257,6 +293,7 @@ chase1_members = [
 
chase2_members = chase1_members * 10
 
random.shuffle(chase2_members)
 

	
 

	
 
def effect_chase1(effectSettings, strength, songTime, noteTime):
 
    members = chase1_members + chase1_members[-2:0:-1]
 
    
 
@@ -277,6 +314,7 @@ def effect_chase1(effectSettings, streng
 
            })
 
    return out
 

	
 

	
 
def effect_chase2(effectSettings, strength, songTime, noteTime):
 
    members = chase2_members
 
    
 
@@ -297,6 +335,7 @@ def effect_chase2(effectSettings, streng
 
            })
 
    return out
 
    
 

	
 
def effect_whirlscolor(effectSettings, strength, songTime, noteTime):
 
    out = {}
 

	
 
@@ -316,12 +355,14 @@ def effect_whirlscolor(effectSettings, s
 

	
 
def effect_orangeSearch(effectSettings, strength, songTime, noteTime):
 
    dev = L9['device/auraStage']
 
    return {(dev, L9['color']): '#a885ff',
 
    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'],
 
    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']]
 
        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
 
@@ -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,7 +22,8 @@ from light9.effect.simple_outputs import
 
from greplin import scales
 

	
 
log = logging.getLogger('sequencer')
 
stats = scales.collection('/sequencer/',
 
stats = scales.collection(
 
    '/sequencer/',
 
                          scales.PmfStat('update'),
 
                          scales.PmfStat('compileGraph'),
 
                          scales.PmfStat('compileSong'),
 
@@ -31,6 +32,7 @@ stats = scales.collection('/sequencer/',
 

	
 

	
 
class Note(object):
 

	
 
    def __init__(self, graph, uri, effectevalModule, simpleOutputs):
 
        g = self.graph = graph
 
        self.uri = uri
 
@@ -57,9 +59,8 @@ 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):
 
@@ -84,14 +85,14 @@ class Note(object):
 
        """
 
        list of (device, attr, value), and a report for web
 
        """
 
        report = {'note': str(self.uri),
 
        report = {
 
            'note': str(self.uri),
 
                  'effectClass': self.effectEval.effect,
 
        }
 
        effectSettings = self.baseEffectSettings.copy()
 
        effectSettings[L9['strength']] = self.evalCurve(t)
 
        report['effectSettings'] = dict(
 
            (str(k), str(v))
 
            for k,v in sorted(effectSettings.items()))
 
            (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')),
 
        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) 
 
    
 
        
 
class Sequencer(object):
 

	
 
class Sequencer(object):
 
    def __init__(self, graph, sendToCollector, fps=40):
 
        self.graph = graph
 
        self.fps = fps
 
@@ -156,22 +160,29 @@ class Sequencer(object):
 

	
 
        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,6 +191,7 @@ 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)
 
@@ -192,7 +204,8 @@ class Sequencer(object):
 
        # 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,15 +219,17 @@ 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
 
@@ -236,5 +251,3 @@ class Updates(cyclone.sse.SSEHandler):
 
        
 
    def unbind(self):
 
        self.numConnected -= 1
 

	
 
    

Changeset was too big and was cut off... Show full diff anyway

0 comments (0 inline, 0 general)