Changeset - f066d6e874db
[Not reviewed]
default
! ! !
drewp@bigasterisk.com - 6 years ago 2019-05-22 00:08:22
drewp@bigasterisk.com
2to3 with these fixers: all idioms set_literal
Ignore-this: cbd28518218c2f0ddce8c4f92d3b8b33
54 files changed:
0 comments (0 inline, 0 general)
bin/ascoltami2
Show inline comments
 
#!bin/python
 
from run_local import log
 
from twisted.internet import reactor
 
import web, thread, sys, optparse, logging
 
import web, _thread, sys, optparse, logging
 
from rdflib import URIRef
 
sys.path.append(".")
 
sys.path.append('/usr/lib/python2.7/dist-packages')  # For gi
 

	
 
import gi
 
gi.require_version('Gst', '1.0')
 
gi.require_version('Gtk', '3.0')
 

	
 
from light9.ascoltami.player import Player
 
from light9.ascoltami.playlist import Playlist, NoSuchSong
 
from light9.ascoltami.webapp import makeWebApp, songUri, songLocation
 
from light9 import networking, showconfig
 

	
 
from gi.repository import GObject, Gst, Gtk
 

	
 

	
 
class App(object):
 

	
 
    def __init__(self, graph, show):
 
        self.graph = graph
 
        self.player = Player(onEOS=self.onEOS)
 
        self.show = show
 
        self.playlist = Playlist.fromShow(graph, show)
 

	
 
    def onEOS(self, song):
 
        self.player.pause()
 
        self.player.seek(0)
 

	
 
        thisSongUri = songUri(graph, URIRef(song))
 

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

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

	
 

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

	
 
    parser = optparse.OptionParser()
 
    parser.add_option(
 
        '--show',
 
        help='show URI, like http://light9.bigasterisk.com/show/dance2008',
 
        default=showconfig.showUri())
 
    parser.add_option("-v",
bin/bumppad
Show inline comments
 
#!bin/python
 
from __future__ import division, nested_scopes
 

	
 
import sys, time, math
 
import Tkinter as tk
 
import tkinter as tk
 

	
 
import run_local
 
import light9.dmxclient as dmxclient
 
from light9.TLUtility import make_attributes_from_args
 

	
 
from light9.Submaster import Submaster, sub_maxes
 

	
 

	
 
class pad(tk.Frame):
 
    levs = None  # Submaster : level
 

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

	
 
            sub = Submaster(subname)
 
            self.levs[sub] = 0
 

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

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

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

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

	
 

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

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

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

	
 
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
 
#!bin/python
 
from __future__ import division
 

	
 
from rdflib import URIRef
 
from twisted.internet import reactor
 
from twisted.internet.defer import inlineCallbacks, Deferred
 

	
 
import logging
 
import optparse
 
import os
 
import time
 
import treq
 
import cyclone.web, cyclone.websocket, cyclone.httpclient
 
from greplin import scales
 

	
 
from run_local import log
 
from lib.cycloneerr import PrettyErrorHandler
 

	
 
from light9.namespaces import L9, RDF
 
from light9 import networking, showconfig
 
from rdfdb.syncedgraph import SyncedGraph
 
from light9.paint.capture import writeCaptureDescription
 
from light9.greplin_cyclone import StatsForCyclone
 
from light9.effect.settings import DeviceSettings
 
from light9.collector.collector_client import sendToCollector
 
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)
 

	
 
    @inlineCallbacks
 
    def _done(self, writePath, response):
 
        jpg = yield response.content()
 
        try:
 
            os.makedirs(os.path.dirname(writePath))
 
        except OSError:
 
            pass
 
        with open(writePath, 'w') as out:
 
            out.write(jpg)
 
        log.info('wrote %s', writePath)
bin/clientdemo
Show inline comments
 
#!bin/python
 

	
 
import os, sys
 
sys.path.append(".")
 
from twisted.internet import reactor
 
import cyclone.web, cyclone.httpclient, logging
 
from rdflib import Namespace, Literal, URIRef
 
from light9 import networking
 
from rdfdb.patch import Patch
 
from rdfdb.syncedgraph import SyncedGraph
 

	
 
if __name__ == "__main__":
 
    logging.basicConfig(level=logging.DEBUG)
 
    log = logging.getLogger()
 

	
 
    g = SyncedGraph(networking.rdfdb.url, "clientdemo")
 

	
 
    from light9.Submaster import PersistentSubmaster
 
    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/")
 

	
 
    def updateDemoValue():
 
        v = list(g.objects(L9['demo'], L9['is']))
 
        print "demo value is %r" % v
 
        print("demo value is %r" % v)
 

	
 
    g.addHandler(updateDemoValue)
 

	
 
    def adj():
 
        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
 
#!bin/python
 
"""
 
Collector receives device attrs from multiple senders, combines
 
them, and sends output attrs to hardware. The combining part has
 
custom code for some attributes.
 

	
 
Input can be over http or zmq.
 
"""
 

	
 
from __future__ import division
 

	
 
from run_local import log
 

	
 
from rdflib import URIRef, Literal
 
from twisted.internet import reactor, utils
 
from txzmq import ZmqEndpoint, ZmqFactory, ZmqPullConnection
 
import json
 
import logging
 
import optparse
 
import time
 
import traceback
 
import cyclone.web, cyclone.websocket
 
from greplin import scales
 

	
 
from lib.cycloneerr import PrettyErrorHandler
 
from light9.collector.output import EnttecDmx, Udmx, DummyOutput
 
from light9.collector.collector import Collector
 
from light9.namespaces import L9
 
from light9 import networking
 
from rdfdb.syncedgraph import SyncedGraph
 
from light9.greplin_cyclone import StatsForCyclone
 

	
 

	
 
def parseJsonMessage(msg):
 
    body = json.loads(msg)
 
    settings = []
 
    for device, attr, value in body['settings']:
 
        if isinstance(value, basestring) and value.startswith('http'):
 
        if isinstance(value, str) and value.startswith('http'):
 
            value = URIRef(value)
 
        else:
 
            value = Literal(value)
 
        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'))
 

	
 
    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):
 
            with stats.setAttr.time():
 
                # 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])
 
                collector.setAttrs(client, clientSession, settings, sendTime)
 

	
 
    s = Pull(zf, e)
 

	
 

	
 
class WebListeners(object):
 

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

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

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

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

	
 
        self.pendingMessageForDev[dev] = (attrs, outputMap)
 
        try:
 
            self._flush()
 
        except Exception:
 
            traceback.print_exc()
 
            raise
 

	
 
    def _flush(self):
 
        now = time.time()
 
        if now < self.lastFlush + .05 or not self.clients:
 
            return
 
        self.lastFlush = now
 

	
 
        while self.pendingMessageForDev:
 
            dev, (attrs, outputMap) = self.pendingMessageForDev.popitem()
 

	
 
            msg = None  # lazy, since makeMsg is slow
 

	
 
            # this omits repeats, but can still send many
 
            # messages/sec. Not sure if piling up messages for the browser
 
            # could lead to slowdowns in the real dmx output.
 
            for client, seen in self.clients:
 
                if seen.get(dev) == attrs:
 
                    continue
 
                if msg is None:
 
                    msg = self.makeMsg(dev, attrs, outputMap)
 

	
 
                seen[dev] = attrs
 
                client.sendMessage(msg)
 

	
 
    def makeMsg(self, dev, attrs, outputMap):
 
        attrRows = []
 
        for attr, val in attrs.items():
 
        for attr, val in list(attrs.items()):
 
            output, index = outputMap[(dev, attr)]
 
            attrRows.append({
 
                'attr': attr.rsplit('/')[-1],
 
                'val': val,
 
                'chan': (output.shortId(), index + 1)
 
            })
 
        attrRows.sort(key=lambda r: r['chan'])
 
        for row in attrRows:
 
            row['chan'] = '%s %s' % (row['chan'][0], row['chan'][1])
 

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

	
 

	
 
class Updates(cyclone.websocket.WebSocketHandler):
 

	
 
    def connectionMade(self, *args, **kwargs):
 
        log.info('socket connect %s', self)
 
        self.settings.listeners.addClient(self)
 

	
 
    def connectionLost(self, reason):
 
        self.settings.listeners.delClient(self)
 

	
 
    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)
 
            self.set_status(202)
 

	
 

	
 
def launch(graph, doLoadTest=False):
 
    try:
 
        # todo: drive outputs with config files
bin/collector_loadtest.py
Show inline comments
 
import sys
 
sys.path.append('bin')
 
from run_local import log
 
from light9.collector.collector_client import sendToCollector, sendToCollectorZmq
 
from light9.namespaces import L9, DEV
 
from twisted.internet import reactor
 
import time
 
import logging
 
log.setLevel(logging.DEBUG)
 

	
 

	
 
def loadTest():
 
    print "scheduling loadtest"
 
    print("scheduling loadtest")
 
    n = 2500
 
    times = [None] * n
 
    session = "loadtest%s" % time.time()
 
    offset = 0
 
    for i in range(n):
 

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

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

	
 
            d.addCallback(ontime)
 

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

	
 
    def done():
 
        print "loadtest done"
 
        print("loadtest done")
 
        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
 

	
 

	
 

	
 
todo: curveview should preserve more objects, for speed maybe
 

	
 
"""
 
from __future__ import division
 

	
 

	
 
import sys
 
import imp
 
sys.path.append('/usr/lib/python2.7/dist-packages')  # For gtk
 
from twisted.internet import gtk3reactor
 
gtk3reactor.install()
 
from twisted.internet import reactor
 

	
 
import time, textwrap, os, optparse, linecache, signal, traceback, json
 
import gi
 
from gi.repository import Gtk
 
from gi.repository import GObject
 
from gi.repository import Gdk
 

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

	
 
from run_local import log
 
from light9 import showconfig, networking
 
from light9.curvecalc import curveview
 
from light9.curvecalc.curve import Curveset
 
from light9.curvecalc.curveedit import serveCurveEdit
 
from light9.curvecalc.musicaccess import Music
 
from light9.curvecalc.output import Output
 
from light9.curvecalc.subterm import Subterm
 
from light9.curvecalc.subtermview import add_one_subterm
 
from light9.editchoicegtk import EditChoice, Local
 
from light9.gtkpyconsole import togglePyConsole
 
from light9.namespaces import L9
 
from light9.observable import Observable
 
from light9 import clientsession
 
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.setTheme()
 
        wtree = self.wtree = Gtk.Builder()
 
        wtree.add_from_file("light9/curvecalc/curvecalc.glade")
 
        mainwin = wtree.get_object("MainWindow")
 

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

	
 
        mainwin.show_all()
 

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

	
 
@@ -126,116 +127,116 @@ class Main(object):
 
            self.muteSongChoiceUntil = now + 1
 

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

	
 
        songChoice.subscribe(songChoiceToGraph)
 

	
 
    def registerCurrentPlayerSongToUi(self, wtree, graph, songChoice):
 
        """current_player_song 'song' param -> playerSong ui
 
        and
 
        current_player_song 'song' param -> songChoice, if you're in autofollow
 
        """
 

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

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

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

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

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

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

	
 
        def recv(widget, context, x, y, selection, targetType, time):
 
            subUri = URIRef(selection.data.strip())
 
            print "into curves", subUri
 
            print("into curves", subUri)
 
            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,
 
                                     sub=subUri,
 
                                     expr="%s(t)" % subName)
 
                except SubtermExists:
 
                    # we're not making sure the expression/etc are
 
                    # correct-- user mihgt need to fix things
 
                    pass
 
            curveView = self.curvesetView.row(subName).curveView
 
            t = self.lastSeenInputTime  # curveView.current_time() # new curve hasn't heard the time yet. this has gotten too messy- everyone just needs to be able to reach the time source
 
            print "time", t
 
            print("time", t)
 
            curveView.add_points([(t - .5, 0), (t, 1)])
 

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

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

	
 
    def handleSubtermDrop(self, data):
 
        params = parse_qsl(data.split('?')[1])
 
        flattened = dict(params)
 
        self.makeSubterm(Literal(flattened['subtermName']),
 
                         expr=flattened['subtermExpr'])
 

	
 
        for cmd, name in params:
 
            if cmd == 'curve':
 
                self.curveset.new_curve(name)
 
        return name
 

	
 
    def onNewCurve(self, *args):
 
        dialog = self.wtree.get_object("newCurve")
 
        entry = self.wtree.get_object("newCurveName")
 
        # if you don't have songx, that should be the suggested name
 
        entry.set_text("")
 
        if dialog.run() == 1:
 
            self.curveset.new_curve(entry.get_text())
 
        dialog.hide()
 

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

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

	
 
@@ -367,154 +368,154 @@ class Main(object):
 
        # there's a hang after this, maybe in sem_wait in two
 
        # threads. I don't know whose they are.
 
        # This fix affects profilers who want to write output at the end.
 
        os.kill(os.getpid(), signal.SIGKILL)
 

	
 
    def onCollapseAll(self, *args):
 
        self.curvesetView.collapseAll()
 

	
 
    def onCollapseNone(self, *args):
 
        self.curvesetView.collapseNone()
 

	
 
    def onDelete(self, *args):
 
        self.curvesetView.onDelete()
 

	
 
    def onPythonConsole(self, item):
 
        ns = dict()
 
        ns.update(globals())
 
        ns.update(self.__dict__)
 
        togglePyConsole(self, item, ns)
 

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

	
 
    def onSeeTimeUntilEnd(self, item):
 
        dispatcher.send("see time until end")
 

	
 
    def onZoomAll(self, item):
 
        dispatcher.send("show all")
 

	
 
    def onPlayPause(self, item):
 
        # since the X coord in a curveview affects the handling, one
 
        # of them may be able to pick this up
 
        results = dispatcher.send("onPlayPause")
 
        times = [t for listener, t in results if t is not None]
 
        self.music.playOrPause(t=times[0] if times else None)
 

	
 
    def onSave(self, *args):
 
        # only doing curves still. I hope to eliminate all this.
 
        log.info("saving curves")
 
        self.curveset.save()
 
        log.info("saved")
 

	
 
    def makeStatusLines(self, master):
 
        """various labels that listen for dispatcher signals"""
 
        for row, (signame, textfilter) in enumerate([
 
            ('input time', lambda t: "%.2fs" % t),
 
            ('output levels', lambda levels: textwrap.fill(
 
                "; ".join([
 
                    "%s:%.2f" % (n, v) for n, v in levels.items()[:2] if v > 0
 
                    "%s:%.2f" % (n, v) for n, v in list(levels.items())[:2] if v > 0
 
                ]), 70)),
 
            ('update period', lambda t: "%.1fms" % (t * 1000)),
 
            ('update status', lambda x: str(x)),
 
        ]):
 
            key = Gtk.Label("%s:" % signame)
 
            value = Gtk.Label("")
 
            master.resize(row + 1, 2)
 
            master.attach(key, 0, 1, row, row + 1)
 
            master.attach(value, 1, 2, row, row + 1)
 
            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: setattr(self, 'lastSeenInputTime', val),
 
                           'input time',
 
                           weak=False)
 
        master.show_all()
 

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

	
 
        if (not hasattr(self, 'curvesetView') or
 
                self.curvesetView._mtimes != mtimes):
 
            print "reload curveview.py"
 
            print("reload curveview.py")
 
            curvesVBox = wtree.get_object("curves")
 
            zoomControlBox = wtree.get_object("zoomControlBox")
 
            [curvesVBox.remove(c) for c in curvesVBox.get_children()]
 
            [zoomControlBox.remove(c) for c in zoomControlBox.get_children()]
 
            try:
 
                linecache.clearcache()
 
                reload(curveview)
 
                imp.reload(curveview)
 

	
 
                # old ones are not getting deleted right
 
                if hasattr(self, 'curvesetView'):
 
                    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._mtimes = mtimes
 

	
 
                # this is scheduled after some tk shuffling, to
 
                # try to minimize the number of times we redraw
 
                # the curve at startup. If tk is very slow, it's
 
                # ok. You'll just get some wasted redraws.
 
                self.curvesetView.goLive()
 
            except Exception:
 
                print "reload failed:"
 
                print("reload failed:")
 
                traceback.print_exc()
 
        if self.opts.reload:
 
            reactor.callLater(1, self.refreshCurveView)
 

	
 

	
 
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)
 

	
 
    def update(self):
 
        song = self.graph.value(self.session, L9['currentSong'])
 
        if song is None:
 
            self.maxtime = 0
 
            return
 
        musicfilename = showconfig.songOnDisk(song)
 
        self.maxtime = wavelength(musicfilename)
 
        log.info("new max time %r", self.maxtime)
 
        dispatcher.send("max time", maxtime=self.maxtime)
 

	
 
    def get(self):
 
        return self.maxtime
 

	
 

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

	
 
    try:
 
        song = URIRef(args[0])
 
        graph.patchObject(context=session,
 
                          subject=session,
 
                          predicate=L9['currentSong'],
 
                          newObject=song)
 
    except IndexError:
 
        pass
 

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

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

	
 
    mt = MaxTime(graph, session)
 
    dispatcher.connect(lambda: mt.get(), "get max time", weak=False)
 

	
 
    start = Main(graph, opts, session, curveset, music)
 
    out = Output(graph, session, music, curveset, start.currentSubterms)
bin/dmxserver
Show inline comments
 
#!bin/python
 
"""
 
Replaced by bin/collector
 

	
 

	
 
this is the only process to talk to the dmx hardware. other clients
 
can connect to this server and present dmx output, and this server
 
will max ('pile-on') all the client requests.
 

	
 
this server has a level display which is the final set of values that
 
goes to the hardware.
 

	
 
clients shall connect to the xmlrpc server and send:
 

	
 
  their PID (or some other cookie)
 

	
 
  a length-n list of 0..1 levels which will represent the channel
 
    values for the n first dmx channels.
 

	
 
server is port 8030; xmlrpc method is called outputlevels(pid,levellist).
 

	
 
todo:
 
  save dmx on quit and restore on restart
 
  if parport fails, run in dummy mode (and make an option for that too)
 
"""
 

	
 
from __future__ import division
 

	
 
from twisted.internet import reactor
 
from twisted.web import xmlrpc, server
 
import sys, time, os
 
from optparse import OptionParser
 
import run_local
 
import txosc.dispatch, txosc. async
 
from light9.io import ParportDMX, UsbDMX
 

	
 
from light9.updatefreq import Updatefreq
 
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
 
        self.receiver = txosc.dispatch.Receiver()
 
        self.receiver.addCallback("/dmx/*", self.pixel_handler)
 
        self._server_port = reactor.listenUDP(
 
            self.port,
 
            txosc. async .DatagramServerProtocol(self.receiver),
 
            interface='0.0.0.0')
 
        print "Listening OSC on udp port %s" % (self.port)
 
        print("Listening OSC on udp port %s" % (self.port))
 

	
 
    def pixel_handler(self, message, address):
 
        # this is already 1-based though I don't know why
 
        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)
 

	
 

	
 
class XMLRPCServe(xmlrpc.XMLRPC):
 

	
 
    def __init__(self, options):
 

	
 
        xmlrpc.XMLRPC.__init__(self)
 

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

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

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

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

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

	
 
        # the other loop
 
        self.purgeclients()
 

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

	
 
        purge_age = 10  # seconds
 

	
 
        reactor.callLater(1, self.purgeclients)
 

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

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

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

	
 
        if self.clientschanged:
 
            # recalc levels
 

	
 
            self.calclevels()
 

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

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

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

	
 
        self.clientschanged = 0  # clear the flag
 

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

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

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

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

	
 
    def sendlevels_dmx(self):
 
        """output self.combinedlevels to dmx, and keep the updates/sec stats"""
 
        # they'll get divided by 100
 
        if self.parportdmx:
 
            self.parportdmx.sendlevels([l * 100 for l in self.combinedlevels])
 
        self.updatefreq.update()
 

	
 
    def xmlrpc_echo(self, x):
 
        return x
 

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

	
 
    def xmlrpc_currentlevels(self, cid):
 
        """get a list of levels we're currently sending out. All
 
        channels beyond the list you get back, they're at zero."""
 
        # if this is still too slow, it might help to return a single
 
        # pickled string
 
        self.trackClientFreq(cid)
 
        trunc = self.combinedlevels[:]
 
        i = len(trunc) - 1
 
        if i < 0:
 
            return []
 
        while trunc[i] == 0 and i >= 0:
 
            i -= 1
 
        if i < 0:
 
            return []
 
        trunc = trunc[:i + 1]
 
        return trunc
 

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

	
 

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

	
 
print options
 
print(options)
 

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

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

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

	
 
oscApp = ReceiverApplication(9051, xmlrpcServe)
 

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

	
 
from __future__ import division
 

	
 
from run_local import log
 
from twisted.internet import reactor
 
from twisted.internet.defer import inlineCallbacks, returnValue
 
import cyclone.web, cyclone.websocket, cyclone.httpclient
 
import sys, optparse, logging, subprocess, json, itertools
 
from rdflib import URIRef, Literal
 

	
 
sys.path.append('/usr/lib/pymodules/python2.7/')  # for numpy, on rpi
 
sys.path.append('/usr/lib/python2.7/dist-packages')  # For numpy
 
from light9 import networking, showconfig
 
from light9.effecteval.effect import EffectNode
 
from light9.effect.edit import getMusicStatus, songNotePatch
 
from light9.effecteval.effectloop import makeEffectLoop
 
from light9.greplin_cyclone import StatsForCyclone
 
from light9.namespaces import L9
 
from rdfdb.patch import Patch
 
from rdfdb.syncedgraph import SyncedGraph
 
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=[
 
                (song, L9['effect'], uri, ctx),
 
            ]))
 

	
 

	
 
@inlineCallbacks
 
def currentSong():
 
    s = (yield getMusicStatus())['song']
 
    if s is None:
 
        raise ValueError("no current song")
 
    returnValue(URIRef(s))
 

	
 

	
 
class SongEffects(PrettyErrorHandler, cyclone.web.RequestHandler):
 
@@ -128,97 +128,97 @@ class EffectUpdates(cyclone.websocket.We
 
        # todo: if client has dropped, abort and don't get any more
 
        # graph updates
 

	
 
        # EffectNode knows how to put them in order. Somehow this is
 
        # not triggering an update when the order changes.
 
        en = EffectNode(self.graph, self.uri)
 
        codeLines = [c.code for c in en.codes]
 
        self.sendMessage({'codeLines': codeLines})
 

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

	
 
    def messageReceived(self, message):
 
        log.info("got message %s" % message)
 
        # write a patch back to the graph
 

	
 

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

	
 
    moreAdds = []
 
    for line in newObjs[1:]:
 
        moreAdds.append((s, p, line, c))
 
    fullPatch = Patch(delQuads=patch.delQuads,
 
                      addQuads=patch.addQuads + moreAdds)
 
    graph.patch(fullPatch)
 

	
 

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

	
 
    def put(self):
 
        effect = URIRef(self.get_argument('uri'))
 
        codeLines = []
 
        for i in itertools.count(0):
 
            k = 'codeLines[%s][text]' % i
 
            v = self.get_argument(k, None)
 
            if v is not None:
 
                codeLines.append(Literal(v))
 
            else:
 
                break
 
        if not codeLines:
 
            log.info("no codelines received on PUT /code")
 
            return
 
        with self.settings.graph.currentState(tripleFilter=(None, L9['effect'],
 
                                                            effect)) as g:
 
            song = g.subjects(L9['effect'], effect).next()
 
            song = next(g.subjects(L9['effect'], effect))
 

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

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

	
 

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

	
 
    @inlineCallbacks
 
    def get(self):
 
        # return dmx list for that effect
 
        uri = URIRef(self.get_argument('uri'))
 
        response = yield cyclone.httpclient.fetch(
 
            networking.musicPlayer.path('time'))
 
        songTime = json.loads(response.body)['t']
 

	
 
        node = EffectNode(self.settings.graph, uri)
 
        outSub = node.eval(songTime)
 
        self.write(json.dumps(outSub.get_dmx_list()))
 

	
 

	
 
# 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)
 
        raise NotImplementedError
 
        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.stats = scales.collection(
 
            '/',
 
            scales.PmfStat('sendLevels'),
 
            scales.PmfStat('getMusic'),
 
            scales.PmfStat('evals'),
bin/effectsequencer
Show inline comments
 
#!bin/python
 
"""
 
plays back effect notes from the timeline
 
"""
 
from __future__ import division
 

	
 
from run_local import log
 
from twisted.internet import reactor
 
from light9.greplin_cyclone import StatsForCyclone
 
from rdfdb.syncedgraph import SyncedGraph
 
from light9 import networking, showconfig
 
from greplin import scales
 
import optparse, sys, logging
 
import cyclone.web
 
from rdflib import URIRef
 
from light9.effect.sequencer import Sequencer, Updates
 
from light9.collector.collector_client import sendToCollector
 

	
 
from light9 import clientsession
 

	
 

	
 
class App(object):
 

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

	
 
        self.graph = SyncedGraph(networking.rdfdb.url, "effectSequencer")
 
        self.graph.initiallySynced.addCallback(self.launch)
 

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

	
 
    def launch(self, *args):
 
        self.seq = Sequencer(
 
            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'/updates', Updates),
 
            (r'/stats', StatsForCyclone),
 
        ],
 
                                                  debug=True,
 
                                                  seq=self.seq,
bin/homepageConfig
Show inline comments
 
#!bin/python
 
from run_local import log
 
from rdflib import RDF, URIRef
 
from light9 import networking, showconfig
 
from light9.namespaces import L9
 
from urlparse import urlparse
 
from urllib import splitport
 
from urllib.parse import urlparse
 
from urllib.parse import splitport
 

	
 
from rdfdb.syncedgraph import SyncedGraph
 
from twisted.internet import reactor
 

	
 
graph = showconfig.getGraph()
 

	
 
netHome = graph.value(showconfig.showUri(), L9['networking'])
 
webServer = graph.value(netHome, L9['webServer'])
 
if not webServer:
 
    raise ValueError('no %r :webServer' % netHome)
 
print "listen %s;" % splitport(urlparse(webServer).netloc)[1]
 
print("listen %s;" % splitport(urlparse(webServer).netloc)[1])
 

	
 

	
 
def location(path, server):
 
    print """
 
    print("""
 
    location /%(path)s/ {
 

	
 
      # for websocket
 
      proxy_http_version 1.1;
 
      proxy_set_header Upgrade $http_upgrade;
 
      proxy_set_header Connection "upgrade";
 
      proxy_set_header Host $host;
 

	
 
      proxy_pass %(server)s;
 
      proxy_buffering off;
 
      rewrite /[^/]+/(.*) /$1 break;
 
    }""" % vars()
 
    }""" % vars())
 

	
 

	
 
for role, server in sorted(graph.predicate_objects(netHome)):
 
    if not server.startswith('http') or role == L9['webServer']:
 
        continue
 
    path = graph.value(role, L9['urlPath'])
 
    if not path:
 
        continue
 
    server = server.rstrip('/')
 
    location(path, server)
 

	
 
showPath = showconfig.showUri().split('/', 3)[-1]
 
print """
 
print("""
 
    location /%(path)s {
 
      root %(root)s;
 
    }""" % {
 
    'path': showPath,
 
    'root': showconfig.root()[:-len(showPath)]
 
}
 
})
bin/inputdemo
Show inline comments
 
#!bin/python
 
import sys
 
sys.path.append('/usr/lib/python2.7/dist-packages')  # For gtk
 
from twisted.internet import gtk3reactor
 
gtk3reactor.install()
 
from twisted.internet import reactor
 
from rdflib import URIRef
 
import optparse, logging, urllib, time
 
import optparse, logging, urllib.request, urllib.parse, urllib.error, time
 
from gi.repository import Gtk
 
from run_local import log
 
from light9 import showconfig, networking
 
from light9 import clientsession
 
from rdfdb.syncedgraph import SyncedGraph
 
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")
 
        clientsession.add_option(parser)
 
        opts, args = parser.parse_args()
 

	
 
        log.setLevel(logging.DEBUG if opts.debug else logging.INFO)
 

	
 
        self.session = clientsession.getUri('inputdemo', opts)
 
        self.graph = SyncedGraph(networking.rdfdb.url, "inputdemo")
 

	
 
        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'
 
        )
 
        print "sending points on curve %s" % self.curve
 
        print("sending points on curve %s" % self.curve)
 

	
 
        reactor.run()
 

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

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

	
 
        win.add(slider)
 
        win.parse_geometry('50x250')
 
        win.connect("delete-event", lambda *a: reactor.crash())
 
        win.connect("destroy", lambda *a: reactor.crash())
 
        win.show_all()
 

	
 
    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))
 
            print("posted in %.1f ms" % (1000 * (time.time() - t1)))
 

	
 

	
 
App()
bin/inputquneo
Show inline comments
 
#!bin/python
 
"""
 
read Quneo midi events, write to curvecalc and maybe to effects
 
"""
 
from __future__ import division
 

	
 
from run_local import log
 
import logging, urllib
 
import logging, urllib.request, urllib.parse, urllib.error
 
import cyclone.web, cyclone.httpclient
 
from rdflib import URIRef
 
from twisted.internet import reactor, task
 
from light9.curvecalc.client import sendLiveInputPoint
 
from light9.namespaces import L9, RDF, RDFS
 
from rdfdb.syncedgraph import SyncedGraph
 
from light9 import networking
 

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

	
 
curves = {
 
    23: URIRef('http://light9.bigasterisk.com/show/dance2014/song1/curve/c-2'),
 
    24: URIRef('http://light9.bigasterisk.com/show/dance2014/song1/curve/c-3'),
 
    25: URIRef('http://light9.bigasterisk.com/show/dance2014/song1/curve/c-4'),
 
    6: URIRef('http://light9.bigasterisk.com/show/dance2014/song1/curve/c-5'),
 
    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()
 

	
 
        dev = self.findQuneo()
 
        self.inp = pygame.midi.Input(dev)
 
        task.LoopingCall(self.step).start(.05)
 

	
 
        self.noteIsOn = {}
 

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

	
 
    def setupNotes(self):
 
        for e in self.graph.subjects(RDF.type, L9['EffectClass']):
 
            qn = self.graph.value(e, L9['quneoNote'])
 
            if qn:
 
                self.effectMap[int(qn)] = e
 
        log.info("setup with %s effects", len(self.effectMap))
 

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

	
 
    def step(self):
 
        if not self.inp.poll():
 
            return
 
        NOTEON, NOTEOFF = 144, 128
 
        for ev in self.inp.read(999):
 
            (status, d1, d2, _), _ = ev
 
            if status in [NOTEON, NOTEOFF]:
 
                print status, d1, d2
 
                print(status, d1, d2)
 

	
 
            if status == NOTEON:
 
                if not self.noteIsOn.get(d1):
 
                    self.noteIsOn[d1] = True
 
                    try:
 
                        e = self.effectMap[d1]
 
                        cyclone.httpclient.fetch(
 
                            url=networking.effectEval.path('songEffects'),
 
                            method='POST',
 
                            headers={
 
                                'Content-Type':
 
                                ['application/x-www-form-urlencoded']
 
                            },
 
                            postdata=urllib.urlencode([('drop', e)]),
 
                            postdata=urllib.parse.urlencode([('drop', e)]),
 
                        )
 
                    except KeyError:
 
                        pass
 

	
 
            if status == NOTEOFF:
 
                self.noteIsOn[d1] = False
 

	
 
            if 0:
 
                # curve editing mode, not done yet
 
                for group in [(23, 24, 25), (6, 18)]:
 
                    if d1 in group:
 
                        if not self.noteIsOn.get(group):
 
                            print "start zero"
 
                            print("start zero")
 

	
 
                            for d in group:
 
                                sendLiveInputPoint(curves[d], 0)
 
                            self.noteIsOn[group] = True
 
                        else:  # miss first update
 
                            sendLiveInputPoint(curves[d1], d2 / 127)
 

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

	
 

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

	
 

	
 
main()
bin/keyboardcomposer
Show inline comments
 
#!bin/python
 

	
 
from __future__ import division, nested_scopes
 

	
 
from run_local import log
 
import cgi, time, logging
 
from optparse import OptionParser
 
import webcolors, colorsys
 
from louie import dispatcher
 
from twisted.internet import reactor, tksupport
 
from twisted.web import resource
 
from rdflib import URIRef, Literal
 
import Tix as tk
 
import tkinter.tix as tk
 

	
 
from light9.Fadable import Fadable
 
from light9.subclient import SubClient
 
from light9 import showconfig, networking, prof
 
from light9.uihelpers import toplevelat
 
from light9.namespaces import L9, RDF, RDFS
 
from light9.tkdnd import initTkdnd, dragSourceRegister, dropTargetRegister
 
from light9 import clientsession
 
from rdfdb.syncedgraph import SyncedGraph
 
from light9.effect.sequencer import CodeWatcher
 
import light9.effect.effecteval
 
from light9.effect.settings import DeviceSettings
 
from rdfdb.patch import Patch
 
from light9.effect.simple_outputs import SimpleOutputs
 

	
 
from bcf2000 import BCF2000
 
import imp
 

	
 
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'
 
        })
 
        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):
 
@@ -205,185 +206,185 @@ class KeyboardComposer(tk.Frame, SubClie
 

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

	
 
    def make_buttons(self):
 
        self.buttonframe = tk.Frame(self, bg='black')
 
        self.buttonframe.pack(side=tk.BOTTOM)
 

	
 
        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,
 
            command=lambda: self.toggle_slider_connectedness(),
 
            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.pack(side='left')
 

	
 
        self.save_stage_button = tk.Button(
 
            self.buttonframe,
 
            text="Save",
 
            command=lambda: self.save_current_stage(self.sub_name.get()),
 
            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)
 

	
 
    def redraw_sliders(self):
 
        self.draw_sliders()
 
        if len(self.rows):
 
            self.change_row(self.current_row)
 
            self.rows[self.current_row].focus()
 

	
 
        self.stop_frequent_update_time = 0
 

	
 
    def draw_sliders(self):
 
        for r in self.rows:
 
            r.destroy()
 
        self.rows = []
 
        for b in self.subbox.values():
 
        for b in list(self.subbox.values()):
 
            b.cleanup()
 
        self.subbox.clear()
 
        self.slider_table.clear()
 

	
 
        self.tk_focusFollowsMouse()
 

	
 
        rowcount = -1
 
        col = 0
 
        last_group = None
 

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

	
 
        log.info("withgroups %s", withgroups)
 

	
 
        self.effectEval = {}
 
        reload(light9.effect.effecteval)
 
        imp.reload(light9.effect.effecteval)
 
        simpleOutputs = SimpleOutputs(self.graph)
 
        for group, order, sortLabel, effect in withgroups:
 
            if col == 0 or group != last_group:
 
                row = self.make_row(group)
 
                rowcount += 1
 
                col = 0
 

	
 
            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)
 

	
 
            col = (col + 1) % 8
 
            last_group = group
 

	
 
    def toggle_slider_connectedness(self):
 
        self.use_hw_sliders = not self.use_hw_sliders
 
        if self.use_hw_sliders:
 
            self.sliders.reopen()
 
        else:
 
            self.sliders.close()
 
        self.change_row(self.current_row)
 
        self.rows[self.current_row].focus()
 

	
 
    def connect_to_hw(self, hw_sliders):
 
        if hw_sliders:
 
            try:
 
                self.sliders = Sliders(self)
 
                log.info("connected to sliders")
 
            except IOError:
 
                log.info("no hardware sliders")
 
                self.sliders = DummySliders()
 
                self.use_hw_sliders = False
 
            dispatcher.connect(self.send_to_hw, 'send_to_hw')
 
        else:
 
            self.sliders = DummySliders()
 

	
 
    def make_key_hints(self):
 
        keyhintrow = tk.Frame(self)
 

	
 
        col = 0
 
        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.pack(side=tk.LEFT, expand=1, fill=tk.X)
 
            col += 1
 

	
 
        keyhintrow.pack(fill=tk.X, expand=0)
 
        self.keyhints = keyhintrow
 

	
 
    def setup_key_nudgers(self, tkobject):
 
        for d, keys in nudge_keys.items():
 
        for d, keys in list(nudge_keys.items()):
 
            for key in keys:
 
                # lowercase makes full=0
 
                keysym = "<KeyPress-%s>" % key
 
                tkobject.bind(keysym, \
 
                    lambda evt, num=keys.index(key), d=d: \
 
                        self.got_nudger(num, d))
 

	
 
                # uppercase makes full=1
 
                keysym = "<KeyPress-%s>" % key.upper()
 
                keysym = keysym.replace('SEMICOLON', 'colon')
 
                tkobject.bind(keysym, \
 
                    lambda evt, num=keys.index(key), d=d: \
 
                        self.got_nudger(num, d, full=1))
 

	
 
        # Row changing:
 
        # Page dn, C-n, and ] do down
 
        # Page up, C-p, and ' do up
 
        for key in '<Prior> <Next> <Control-n> <Control-p> ' \
 
                   '<Key-bracketright> <Key-apostrophe>'.split():
 
            tkobject.bind(key, self.change_row_cb)
 

	
 
    def change_row_cb(self, event):
 
        diff = 1
 
        if event.keysym in ('Prior', 'p', 'bracketright'):
 
            diff = -1
 
        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)
 

	
 
    def change_row(self, row, fromGraph=False):
 
        old_row = self.current_row
 
        self.current_row = row
 
        self.current_row = max(0, self.current_row)
 
        self.current_row = min(len(self.rows) - 1, self.current_row)
 
        try:
 
            row = self.rows[self.current_row]
 
        except IndexError:
 
            # if we're mid-load, this row might still appear soon. If
 
            # we changed interactively, the user is out of bounds and
 
            # needs to be brought back in
 
            if fromGraph:
 
                return
 
            raise
 

	
 
        self.unhighlight_row(old_row)
 
@@ -456,136 +457,136 @@ class KeyboardComposer(tk.Frame, SubClie
 
        chan = "slider%s" % hwCol
 

	
 
        # workaround for some rounding issue, where we receive one
 
        # value and then decide to send back a value that's one step
 
        # lower.  -5 is a fallback for having no last value.  hopefully
 
        # we won't really see it
 
        if abs(v - self.sliders.lastValue.get(chan, -5)) <= 1:
 
            return
 
        self.sliders.valueOut(chan, v)
 

	
 
    def make_row(self, group):
 
        """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=['*'],
 
                           hoverStyle=dict(background="#555500"))
 

	
 
        row.pack(expand=1, fill=tk.BOTH)
 
        self.setup_key_nudgers(row)
 
        self.rows.append(row)
 
        return row
 

	
 
    def change_group(self, sub, row):
 
        """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)
 

	
 
    def highlight_row(self, row):
 
        row = self.rows[row]
 
        row['bg'] = 'red'
 

	
 
    def unhighlight_row(self, row):
 
        row = self.rows[row]
 
        row['bg'] = 'black'
 

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

	
 
    def get_output_settings(self, _graph=None):
 
        _graph = _graph or self.graph
 
        outputSettings = []
 
        for setting in _graph.objects(self.session, L9['subSetting']):
 
            effect = _graph.value(setting, L9['sub'])
 
            strength = _graph.value(setting, L9['level'])
 
            if strength:
 
                now = time.time()
 
                out, report = self.effectEval[effect].outputFromEffect(
 
                    [(L9['strength'], strength)],
 
                    songTime=now,
 
                    # should be counting from when you bumped up from 0
 
                    noteTime=now)
 
                outputSettings.append(out)
 

	
 
        return DeviceSettings.fromList(_graph, outputSettings)
 

	
 
    def save_current_stage(self, subname):
 
        log.info("saving current levels as %s", subname)
 
        with self.graph.currentState() as g:
 
            ds = self.get_output_settings(_graph=g)
 
        effect = L9['effect/%s' % subname]
 
        ctx = URIRef(showconfig.showUri() + '/effect/' + subname)
 
        stmts = ds.statements(effect, ctx, effect + '/', set())
 
        stmts.extend([
 
            (effect, RDF.type, L9['Effect'], ctx),
 
            (effect, RDFS.label, Literal(subname), ctx),
 
            (effect, L9['publishAttr'], L9['strength'], ctx),
 
        ])
 

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

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

	
 
    def alltozero(self):
 
        for uri, subbox in self.subbox.items():
 
        for uri, subbox in list(self.subbox.items()):
 
            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
 
    values. Supports args encoded in the url or in postdata. No
 
    support for repeated args."""
 
    # this is something nevow normally does for me
 
    request.content.seek(0)
 
    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
 

	
 
    def render_POST(self, request):
 
        arg = postArgGetter(request)
 

	
 
        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')))
 
            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'
 
        ]
bin/lightsim
Show inline comments
 
#!bin/python
 

	
 
from __future__ import division
 

	
 
import run_local
 
import sys, logging
 

	
 
sys.path.append("lib")
 
import qt4reactor
 
qt4reactor.install()
 

	
 
from twisted.internet import reactor
 
from twisted.internet.task import LoopingCall
 
from twisted.web.xmlrpc import Proxy
 
from louie import dispatcher
 
from PyQt4.QtGui import QWidget, QLabel, QVBoxLayout, QHBoxLayout, QMainWindow
 
from OpenGL.GL import *
 
from OpenGL.GLU import *
 

	
 
from light9 import networking, Patch, showconfig, dmxclient, updatefreq, prof
 
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")
 
log.setLevel(logging.DEBUG)
 

	
 

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

	
 

	
 
_lastLevels = None
 

	
 

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

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

	
 
        level = {}  # filename : level
 
        for i, lev in enumerate(dmxLevels):
bin/listsongs
Show inline comments
 
#!bin/python
 
"""for completion, print the available song uris on stdout
 

	
 
in .zshrc:
 

	
 
function _songs { local expl;  _description files expl 'songs';  compadd "$expl[@]" - `${LIGHT9_SHOW}/../../bin/listsongs` }
 
compdef _songs curvecalc
 
"""
 

	
 
from run_local import log
 
from twisted.internet import reactor
 
from rdflib import RDF
 
from light9 import networking
 
from light9.namespaces import L9
 
from rdfdb.syncedgraph import SyncedGraph
 

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

	
 

	
 
@graph.initiallySynced.addCallback
 
def printSongs(result):
 
    with graph.currentState() as current:
 
        for song in current.subjects(RDF.type, L9['Song']):
 
            print song
 
            print(song)
 
    reactor.stop()
 

	
 

	
 
reactor.run()
bin/mpd_timing_test
Show inline comments
 
#!/usr/bin/python
 
"""
 
records times coming out of ascoltami
 

	
 
for example:
 

	
 
 % mpd_timing_test > timing
 
 # play some music in ascoltami, then ctrl-c
 
 % gnuplot
 
 > plot "timing" with lines
 

	
 
"""
 

	
 
import xmlrpclib, time
 
import xmlrpc.client, time
 

	
 
s = xmlrpclib.ServerProxy("http://localhost:8040")
 
s = xmlrpc.client.ServerProxy("http://localhost:8040")
 
start = time.time()
 
while 1:
 
    print time.time() - start, s.gettime()
 
while True:
 
    print(time.time() - start, s.gettime())
 
    time.sleep(.01)
bin/musictime
Show inline comments
 
#!/usr/bin/env python
 
import run_local
 
import light9.networking
 

	
 
import Tkinter as tk
 
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:
 
            try:
 
                playtime = jsonlib.read(self.player.get("time").body_string(),
 
                                        use_float=True)['t']
 
            except restkit.RequestError, e:
 
                print "Server error %s, waiting" % e
 
            except restkit.RequestError as e:
 
                print("Server error %s, waiting" % e)
 
                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.pack(expand=1, fill='both')
 

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

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

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

	
 

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

	
 
    root = tk.Tk()
 
    root.title("Time")
 
    MusicTimeTk(root, options.url).pack(expand=1, fill='both')
 
    try:
 
        tk.mainloop()
 
    except KeyboardInterrupt:
 
        root.destroy()
bin/paintserver
Show inline comments
 
#!bin/python
 

	
 
from __future__ import division
 

	
 
from run_local import log
 
import json
 
from twisted.internet import reactor
 
from light9.greplin_cyclone import StatsForCyclone
 
from rdfdb.syncedgraph import SyncedGraph
 
from light9 import networking, showconfig
 
from greplin import scales
 
import optparse, sys, logging
 
import cyclone.web
 
from rdflib import URIRef
 
from light9 import clientsession
 
import light9.paint.solve
 
from lib.cycloneerr import PrettyErrorHandler
 
from light9.namespaces import RDF, L9, DEV
 
import imp
 

	
 

	
 
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'])
 
            with self.settings.graph.currentState() as g:
 
                bestPath = g.value(sample, L9['imagePath']).replace(L9[''], '')
 
            #out = solver.solve(painting)
 
            #layers = solver.simulationLayers(out)
 

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

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

	
 

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

	
 
    def post(self):
 
        body = json.loads(self.request.body)
 
        painting = body['painting']
 
        devs = [URIRef(d) for d in body['devices']]
 
        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()}))
 

	
 

	
 
class App(object):
 

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

	
 
        self.graph = SyncedGraph(networking.rdfdb.url, "paintServer")
 
        self.graph.initiallySynced.addCallback(self.launch).addErrback(
 
            log.error)
 

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

	
 
    def launch(self, *args):
 

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

	
 
        self.cycloneApp = cyclone.web.Application(handlers=[
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 io, logging, traceback, time
 
import cyclone.web
 
from twisted.internet import reactor, threads
 
from twisted.internet.defer import inlineCallbacks
 
from light9 import prof
 

	
 
try:
 
    import picamera
 
    cameraCls = picamera.PiCamera
 
except ImportError:
 

	
 
    class cameraCls(object):
 

	
 
        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 = {
 
        480: (640, 480),
 
        1080: (1920, 1080),
 
        1944: (2592, 1944),
 
    }[res]
 
    c.shutter_speed = int(arg('shutter', 50000))
 
    c.exposure_mode = arg('exposure_mode', 'fixedfps')
 
    c.awb_mode = arg('awb_mode', 'off')
 
    c.brightness = int(arg('brightness', 50))
 
    c.exposure_compensation = int(arg('exposure_compensation', 0))
 
    c.awb_gains = (float(arg('redgain', 1)), float(arg('bluegain', 1)))
 
    c.ISO = int(arg('iso', 250))
 
    c.rotation = int(arg('rotation', '0'))
 

	
 

	
bin/run_local.py
Show inline comments
 
@@ -14,102 +14,102 @@ def fixSysPath():
 
    import types
 
    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')
 
    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__', [])
 
    (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__', [])
 
    (p not in mp) and mp.append(p)
 

	
 
    sys.path = [
 
        root,
 
        root + 'env/lib/python2.7',
 
        root + 'env/lib/python2.7/plat-x86_64-linux-gnu',
 
        root + 'env/lib/python2.7/lib-tk',
 
        root + 'env/lib/python2.7/lib-old',
 
        root + 'env/lib/python2.7/lib-dynload',
 
        '/usr/lib/python2.7',
 
        '/usr/lib/python2.7/plat-x86_64-linux-gnu',
 
        '/usr/lib/python2.7/lib-tk',
 
        root + 'env/local/lib/python2.7/site-packages',
 
        root + 'env/local/lib/python2.7/site-packages/gtk-2.0',
 
        root + 'env/lib/python2.7/site-packages',
 
        root + 'env/lib/python2.7/site-packages/gtk-2.0',
 
    ]
 

	
 

	
 
fixSysPath()
 

	
 
from twisted.python.failure import Failure
 

	
 
try:
 
    import Tkinter
 
    import tkinter
 
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
 
    tkinter.Tk.report_callback_exception = rce
 

	
 
import coloredlogs, logging, time
 
try:
 
    import faulthandler
 
    faulthandler.enable()
 
except ImportError:
 
    pass
 

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

	
 

	
 
class FractionTimeFilter(logging.Filter):
 

	
 
    def filter(self, record):
 
        record.fractionTime = (
 
            time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(record.created)) +
 
            ("%.3f" % (record.created % 1)).lstrip('0'))
 
        # Don't filter the record.
 
        return 1
 

	
 

	
 
coloredlogs.install(
 
    level='DEBUG',
 
    fmt='%(fractionTime)s %(name)s[%(process)d] %(levelname)s %(message)s')
 
logging.getLogger().handlers[0].addFilter(FractionTimeFilter())
 

	
 

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

	
 

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

	
 
# see http://www.youtube.com/watch?v=3cIOT9kM--g for commands that make
 
# profiles and set background images
bin/staticclient
Show inline comments
 
#!bin/python
 
"""
 
push a dmx level forever
 
"""
 
from __future__ import division, nested_scopes
 

	
 
import time, logging
 
from optparse import OptionParser
 
import logging, urllib
 
import logging, urllib.request, urllib.parse, urllib.error
 
from twisted.internet import reactor, tksupport, task
 
from rdflib import URIRef, RDF, RDFS, Literal
 

	
 
from run_local import log
 
log.setLevel(logging.DEBUG)
 

	
 
from light9 import dmxclient, showconfig, networking
 

	
 
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('--level', help='0..1', type=float)
 
    parser.add_option('-v', action='store_true', help="log debug level")
 

	
 
    opts, args = parser.parse_args()
 

	
 
    log.setLevel(logging.DEBUG if opts.v else logging.INFO)
 

	
 
    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
 
#!bin/python
 
"""
 
subcomposer
 
  session
 
  observable(currentSub), a Submaster which tracks the graph
 

	
 
    EditChoice widget
 
      can change currentSub to another sub
 

	
 
    Levelbox widget
 
      watch observable(currentSub) for a new sub, and also watch currentSub for edits to push to the OneLevel widgets
 

	
 
        OneLevel widget
 
          UI edits are caught here and go all the way back to currentSub
 

	
 

	
 
"""
 
from __future__ import division, nested_scopes
 

	
 

	
 
from run_local import log
 
import time, logging
 

	
 
log.setLevel(logging.DEBUG)
 

	
 
from optparse import OptionParser
 
import logging, urllib
 
import Tkinter as tk
 
import logging, urllib.request, urllib.parse, urllib.error
 
import tkinter as tk
 
import louie as dispatcher
 
from twisted.internet import reactor, tksupport, task
 
from rdflib import URIRef, RDF, RDFS, Literal
 

	
 
from light9.dmxchanedit import Levelbox
 
from light9 import dmxclient, Submaster, prof, showconfig, networking
 
from light9.Patch import get_channel_name
 
from light9.uihelpers import toplevelat
 
from rdfdb.syncedgraph import SyncedGraph
 
from light9 import clientsession
 
from light9.tkdnd import initTkdnd
 
from light9.namespaces import L9
 
from rdfdb.patch import Patch
 
from light9.observable import Observable
 
from light9.editchoice import EditChoice, Local
 
from light9.subcomposer import subcomposerweb
 

	
 

	
 
class Subcomposer(tk.Frame):
 
    """
 
    <session> l9:currentSub ?sub is the URI of the sub we're tied to for displaying and
 
    editing. If we don't have a currentSub, then we're actually
 
    editing a session-local sub called <session> l9:currentSub <sessionLocalSub>
 

	
 
    I'm not sure that Locals should even be PersistentSubmaster with
 
    uri and graph storage, but I think that way is making fewer
 
    special cases.
 

	
 
    Contains an EditChoice widget
 

	
 
    Dependencies:
 

	
 
      graph (?session :currentSub ?s) -> self.currentSub
 
      self.currentSub -> graph
 
      self.currentSub -> self._currentChoice (which might be Local)
 
      self._currentChoice (which might be Local) -> self.currentSub
 

	
 
      inside the current sub:
 
      graph -> Submaster levels (handled in Submaster)
 
      Submaster levels -> OneLevel widget
 
      OneLevel widget -> Submaster.editLevel
 
      Submaster.editLevel -> graph (handled in Submaster)
 

	
 
    """
 

	
 
    def __init__(self, master, graph, session):
 
        tk.Frame.__init__(self, master, bg='black')
 
        self.graph = graph
 
        self.session = session
 
        self.launchTime = time.time()
 
        self.localSerial = 0
 

	
 
        # this is a URIRef or Local. Strangely, the Local case still
 
        # has a uri, which you can get from
 
        # self.currentSub.uri. Probably that should be on the Local
 
        # object too, or maybe Local should be a subclass of URIRef
 
        self._currentChoice = Observable(Local)
 

	
 
        # this is a PersistentSubmaster (even for local)
 
        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)
 
        ec.frame.pack(side='top')
 

	
 
        ec.subIcon.bind("<ButtonPress-1>", self.clickSubIcon)
 
        self.setupSubChoiceLinks()
 
        self.setupLevelboxUi()
 

	
 
    def clickSubIcon(self, *args):
 
        box = tk.Toplevel(self.editChoice.frame)
 
        box.wm_transient(self.editChoice.frame)
 
        tk.Label(box, text="Name this sub:").pack()
 
        e = tk.Entry(box)
 
        e.pack()
 
        b = tk.Button(box, text="Make global")
 
        b.pack()
 

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

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

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

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

	
 
        self.relocateSub(newUri, newName)
 

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

	
 
    def relocateSub(self, newUri, newName):
 
        # maybe this goes in Submaster
 
        uri = self.currentSub().uri
 

	
 
        def repl(u):
 
            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())
 
            print("currently: session=%s currentSub=%r _currentChoice=%r" % (
 
                self.session, self.currentSub(), self._currentChoice()))
 

	
 
        @graph.addHandler
 
        def graphChanged():
 
            # some bug where SC is making tons of graph edits and many
 
            # are failing. this calms things down.
 
            log.warn('skip graphChanged')
 
            return
 

	
 
            s = graph.value(self.session, L9['currentSub'])
 
            log.debug('HANDLER getting session currentSub from graph: %s', s)
 
            if s is None:
 
                s = self.switchToLocal()
 
            self.currentSub(Submaster.PersistentSubmaster(graph, s))
 

	
 
        @self.currentSub.subscribe
 
        def subChanged(newSub):
 
            log.debug('HANDLER currentSub changed to %s', newSub)
 
            if newSub is None:
 
                graph.patchObject(self.session, self.session, L9['currentSub'],
 
                                  None)
 
                return
 
            self.sendupdate()
 
            graph.patchObject(self.session, self.session, L9['currentSub'],
 
                              newSub.uri)
 

	
 
            localStmt = (newSub.uri, RDF.type, L9['LocalSubmaster'])
 
            with graph.currentState(tripleFilter=localStmt) as current:
 
                if newSub and localStmt in current:
 
                    log.debug('  HANDLER set _currentChoice to Local')
 
                    self._currentChoice(Local)
 
                else:
 
                    # i think right here is the point that the last local
 
                    # becomes garbage, and we could clean it up.
 
                    log.debug('  HANDLER set _currentChoice to newSub.uri')
 
                    self._currentChoice(newSub.uri)
 

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

	
 
        @self._currentChoice.subscribe
 
        def choiceChanged(newChoice):
 
            log.debug('HANDLER choiceChanged to %s', newChoice)
 
            if newChoice is Local:
 
                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)
 
                self.currentSub(newSub)
 

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

	
 
    def switchToLocal(self):
 
        """
 
        change our display to a local submaster
 
        """
 
        # todo: where will these get stored, or are they local to this
 
        # subcomposer process and don't use PersistentSubmaster at all?
 
        localId = "%s-%s" % (self.launchTime, self.localSerial)
 
        self.localSerial += 1
 
        new = URIRef("http://light9.bigasterisk.com/sub/local/%s" % localId)
 
        log.debug('making up a local sub %s', new)
 
        self.graph.patch(
 
            Patch(addQuads=[
 
                (new, RDF.type, L9['Submaster'], self.session),
 
                (new, RDF.type, L9['LocalSubmaster'], self.session),
 
            ]))
 

	
 
        return new
 

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

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

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

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

	
 
    def sendupdate(self):
 
        d = self.currentSub().get_dmx_list()
 
        dmxclient.outputlevels(d, twisted=True)
 

	
 

	
 
def launch(opts, args, root, graph, session):
 
    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')
 

	
 
    if len(args) == 1:
 
        # it might be a little too weird that cmdline arg to this
 
        # process changes anything else in the same session. But also
 
        # I'm not sure who would ever make 2 subcomposers of the same
 
        # session (except when quitting and restarting, to get the
 
        # 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]))
 

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

	
 

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

	
 
if __name__ == "__main__":
 
    parser = OptionParser(usage="%prog [suburi]")
 
    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")
 

	
 
    clientsession.add_option(parser)
bin/tkdnd_minimal_drop.py
Show inline comments
 
#!bin/python
 
from run_local import log
 
import Tkinter as tk
 
import tkinter as tk
 
from light9.tkdnd import initTkdnd, dropTargetRegister
 
from twisted.internet import reactor, tksupport
 

	
 
root = tk.Tk()
 
initTkdnd(root.tk, "tkdnd/trunk/")
 
label = tk.Label(root, borderwidth=2, relief='groove', padx=10, pady=10)
 
label.pack()
 
label.config(text="drop target %s" % label._w)
 

	
 
frame1 = tk.Frame()
 
frame1.pack()
 

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

	
 

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

	
 

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

	
 

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

	
 

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

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

	
 

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

	
 

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

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

	
 

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

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

	
 
import dmxclient
 

	
 
import Tkinter as tk
 
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}))
 

	
 

	
 
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"""
 
        return Pair(self._getorsettypedattr("x", float, x),
 
                    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)
 

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

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

	
 

	
 
class Fieldset(collectiveelement):
 
    """group of fields. persistent."""
 

	
 
    def childtype(self):
 
        return Field
 

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

	
 
    def report(self, x, y):
 
        """reports active fields and their intensities"""
 
        active = 0
 
        for f in self.getall():
 
            name = f.name()
 
            intens = f.calc(x, y)
 
            if intens > 0:
 
                print name, intens,
 
                print(name, intens, end=' ')
 
                active += 1
 
        if active > 0:
 
            print
 
            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
 
        for f in self.getall():
 
            rad = f.getdistforintensity(0)
 
            fx, fy = f.center()
 
            fieldrect = Rect(fx - rad, fy - rad, rad * 2, rad * 2)
 
            if r is None:
 
                r = fieldrect
 
            else:
 
                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
 
        self.tags = [str(id(self))]  # canvas tag to id our objects
 

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

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

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

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

	
 
        w2c = self.canvas.world2canvas
 

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

	
 
        # make text
 
        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')
 

	
 
        # position drag bindings
 
@@ -206,107 +206,107 @@ class FieldDisplay:
 
        # radius drag bindings
 
        outerring = self.rings[0]
 
        canvashighlighter(c,
 
                          outerring,
 
                          'outline',
 
                          normalval='#000080',
 
                          highlightval='#4040ff')
 

	
 
        def motion(ev):
 
            worldmouse = self.canvas.canvas2world(ev.x, ev.y)
 
            currentdist = pairdist(worldmouse, self.field.center())
 
            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"""
 

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

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

	
 
    def __init__(self, master):
 
        tk.Frame.__init__(self, master)
 

	
 
        self.displays = {}
 

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

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

	
 
        def save(ev):
 
            print "saving"
 
            print("saving")
 
            self.fieldsetfile.save()
 

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

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

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

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

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

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

	
 

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

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

	
 
tra.load("fieldsets/demo")
 

	
 
root.mainloop()
bin/wavecurve
Show inline comments
 
#!bin/python
 
import optparse
 
import run_local
 
from light9.wavepoints import simp
 

	
 

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

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

	
 

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

	
 
You probably just want -a
 

	
 
""")
 
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",
 
                  help="make standard curves for all songs")
 
options, args = parser.parse_args()
 

	
 
if options.all:
 
    from light9 import showconfig
 
    from light9.namespaces import L9
 
    from rdflib import RDF
 
    from light9.ascoltami.playlist import Playlist
 
    graph = showconfig.getGraph()
 

	
 
    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)]:
 
            outpath = showconfig.curvesDir() + "/%s-%s" % (
 
                showconfig.songFilenameFromURI(song), curveName)
 
            createCurve(inpath, outpath, t)
 
else:
 
    inpath, outpath = args
 
    createCurve(inpath, outpath, options.t)
bin/webcontrol
Show inline comments
 
#!bin/python
 
"""
 
web UI for various commands that we might want to run from remote
 
computers and phones
 

	
 
todo:
 
disable buttons that don't make sense
 
"""
 
import sys, xmlrpclib, traceback
 
import sys, xmlrpc.client, traceback
 
from twisted.internet import reactor
 
from twisted.python import log
 
from twisted.python.util import sibpath
 
from twisted.internet.defer import inlineCallbacks, returnValue
 
from twisted.web.client import getPage
 
from nevow.appserver import NevowSite
 
from nevow import rend, static, loaders, inevow, url, tags as T
 
from rdflib import URIRef
 
from louie.robustapply import robust_apply
 
sys.path.append(".")
 
from light9 import showconfig, networking
 
from light9.namespaces import L9
 
from urllib import urlencode
 
from urllib.parse import urlencode
 

	
 

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

	
 

	
 
class Commands(object):
 

	
 
    @staticmethod
 
    def playSong(graph, songUri):
 
        s = xmlrpclib.ServerProxy(networking.musicPlayer.url)
 
        s = xmlrpc.client.ServerProxy(networking.musicPlayer.url)
 
        songPath = graph.value(URIRef(songUri), L9.showPath)
 
        if songPath is None:
 
            raise ValueError("unknown song %s" % songUri)
 
        return s.playfile(songPath.encode('ascii'))
 

	
 
    @staticmethod
 
    def stopMusic(graph):
 
        s = xmlrpclib.ServerProxy(networking.musicPlayer.url)
 
        s = xmlrpc.client.ServerProxy(networking.musicPlayer.url)
 
        return s.stop()
 

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

	
 
    @staticmethod
 
    def worklightsOff(graph):
 
        return post(networking.keyboardComposer.path('fadesub'),
 
                    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"))
 

	
 
    def __init__(self, graph):
 
        self.graph = graph
 
        rend.Page.__init__(self)
 

	
 
    def render_status(self, ctx, data):
 
        pic = T.img(src="icon/enabled.png")
 
        if ctx.arg('error'):
 
            pic = T.img(src="icon/warning.png")
 
        return [pic, ctx.arg('status') or 'ready']
 

	
 
    def render_songButtons(self, ctx, data):
 
        playList = graph.value(show, L9['playList'])
 
        songs = list(graph.items(playList))
 
        out = []
 
        for song in songs:
 
            out.append(
 
                T.form(method="post", action="playSong")
 
                [T.input(type='hidden', name='songUri', value=song),
 
                 T.button(type='submit')[graph.label(song)]])
 
        return out
 

	
 
    @inlineCallbacks
 
    def locateChild(self, ctx, segments):
 
        try:
 
            func = getattr(Commands, segments[0])
 
            req = inevow.IRequest(ctx)
 
            simpleArgDict = dict((k, v[0]) for k, v in req.args.items())
 
            simpleArgDict = dict((k, v[0]) for k, v in list(req.args.items()))
 
            try:
 
                ret = yield robust_apply(func, func, self.graph,
 
                                         **simpleArgDict)
 
            except KeyboardInterrupt:
 
                raise
 
            except Exception, e:
 
                print "Error on command %s" % segments[0]
 
            except Exception as 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', ret), segments[1:]))
 
            #actually return the orig page, with a status message from the func
 
        except AttributeError:
 
            pass
 
        returnValue(rend.Page.locateChild(self, ctx, segments))
 

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

	
 

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

	
 
log.startLogging(sys.stdout)
 

	
 
reactor.listenTCP(9000, NevowSite(Main(graph)))
 
reactor.run()
light9/Effects.py
Show inline comments
 
from __future__ import division
 

	
 
import random as random_mod
 
import math
 
import logging, colorsys
 
import light9.Submaster as Submaster
 
from chase import chase as chase_logic
 
import showconfig
 
from .chase import chase as chase_logic
 
from . import showconfig
 
from rdflib import RDF
 
from light9 import Patch
 
from light9.namespaces import L9
 
log = logging.getLogger()
 

	
 
registered = []
 

	
 

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

	
 

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

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

	
 
    @classmethod
 
    def solid(cls, which='L', color=(1, 1, 1), hsv=None):
 
        """hsv overrides color"""
 
        if hsv is not None:
 
            color = colorsys.hsv_to_rgb(hsv[0] % 1.0, hsv[1], hsv[2])
 
        x = cls()
 
        x.which = which
 
        x.pixels = [tuple(color)] * 50
 
        return x
 

	
 
    def __mul__(self, f):
 
        if not isinstance(f, (int, float)):
 
            raise TypeError
 

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

	
 
    __rmul__ = __mul__
 

	
 

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

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

	
 
    __rmul__ = __mul__
 

	
 

	
 
@register
 
def chase(t,
 
          ontime=0.5,
 
          offset=0.2,
 
          onval=1.0,
 
          offval=0.0,
 
          names=None,
 
          combiner=max,
 
          random=False):
 
    """names is list of URIs. returns a submaster that chases through
 
    the inputs"""
 
    if random:
 
        r = random_mod.Random(random)
 
        names = names[:]
 
        r.shuffle(names)
 

	
 
    chase_vals = chase_logic(t, ontime, offset, onval, offval, names, combiner)
 
    lev = {}
 
    for uri, value in chase_vals.items():
 
    for uri, value in list(chase_vals.items()):
 
        try:
 
            dmx = Patch.dmx_from_uri(uri)
 
        except KeyError:
 
            log.info(("chase includes %r, which doesn't resolve to a dmx chan" %
 
                      uri))
 
            continue
 
        lev[dmx] = value
 

	
 
    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)
 
    lev = {}
 
    if light in ['left', 'all']:
 
        lev[73], lev[74], lev[75] = r, g, b
 
    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
 
    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
 

	
 
    fade=0 makes steps, fade=1 means each one gets its full fraction
 
    of the time to fade in. Fades never...
 
    """
 
    frac = 1.0 / len(names)
 

	
 
    lev = {}
 
    for i, uri in enumerate(names):
 
        if t >= (i + 1) * frac:
 
            try:
 
                dmx = Patch.dmx_from_uri(uri)
 
            except KeyError:
 
                log.info(
 
                    ("stack includes %r, which doesn't resolve to a dmx chan" %
 
                     uri))
 
                continue
 
            lev[dmx] = 1
 
        else:
 
            break
 

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

	
 

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

	
 

	
 
def configExprGlobals():
 
    graph = showconfig.getGraph()
 
    ret = {}
 

	
 
    for chaseUri in graph.subjects(RDF.type, L9['Chase']):
 
        shortName = chaseUri.rsplit('/')[-1]
 
        chans = graph.value(chaseUri, L9['channels'])
 
        ret[shortName] = list(graph.items(chans))
 
        print "%r is a chase" % shortName
 
        print("%r is a chase" % shortName)
 

	
 
    for f in registered:
 
        ret[f.__name__] = f
 

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

	
 
    # suffix '2' to keep backcompat with the versions that magically knew time
 
    def smooth_random2(t, speed=1):
 
        """1 = new stuff each second, <1 is slower, fade-ier"""
 
        x = (t * speed) % len(_smooth_random_items)
 
        x1 = int(x)
 
        x2 = (int(x) + 1) % len(_smooth_random_items)
 
        y1 = _smooth_random_items[x1]
 
        y2 = _smooth_random_items[x2]
 
        return y1 + (y2 - y1) * ((x - x1))
 

	
 
    def notch_random2(t, speed=1):
 
        """1 = new stuff each second, <1 is slower, notch-ier"""
 
        x = (t * speed) % len(_smooth_random_items)
 
        x1 = int(x)
 
        y1 = _smooth_random_items[x1]
 
        return y1
 

	
 
    ret['noise2'] = smooth_random2
 
    ret['notch2'] = notch_random2
 

	
 
    return ret
light9/Fadable.py
Show inline comments
 
# taken from SnackMix -- now that's reusable code
 
from Tix import *
 
from tkinter.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.
 
    It's probably too specialized to be used elsewhere, but could possibly
 
    work with an Entry or a Meter, I guess.  (Actually, this is used by
 
    KeyboardComposer and KeyboardRecorder now too.)
 

	
 
    var is a Tk variable that should be used to set and get the levels.
 
    If use_fades is true, it will use fades to move between levels.
 
    If key_bindings is true, it will install these keybindings:
 

	
 
    Press a number to fade to that amount (e.g. '5' = 50%).  Also,
 
    '`' (grave) will fade to 0 and '0' will fade to 100%.
 

	
 
    If mouse_bindings is true, the following mouse bindings will be
 
    installed: Right clicking toggles muting.  The mouse wheel will
 
    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,
 
                 mouse_bindings=1):
 
        self.use_fades = use_fades  # whether increase and decrease should fade
 
        self.wheel_step = wheel_step  # amount that increase and descrease should
 
        # change volume (by default)
 

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

	
 
        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-0>", lambda evt: self.fade(1.0))
 
            self.bind("<grave>", lambda evt: self.fade(0))
 

	
 
            # up / down arrows
light9/FlyingFader.py
Show inline comments
 
from Tix import *
 
from tkinter.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
 

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

	
 
        self._lastupdate = time()
 
        self._stopped = 1
 

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

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

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

	
 
        dt = tnow - t0
 

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

	
 
        if self.equal(self.x, self.xgoal):
 
            self.x = self.xgoal  # clean up value
 
            self.stop()
 
            return
 

	
 
        self._stopped = 0
 
        dir = (-1.0, 1, 0)[self.xgoal > self.x]
 

	
light9/Patch.py
Show inline comments
 
import os
 
from rdflib import RDF
 
from light9.namespaces import L9
 
from light9 import showconfig
 

	
 

	
 
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()
 
    prinames = sorted(list(reverse_patch.values())[:])
 
    return prinames
 

	
 

	
 
def get_dmx_channel(name):
 
    if str(name) in patch:
 
        return patch[str(name)]
 

	
 
    try:
 
        i = int(name)
 
        return i
 
    except ValueError:
 
        raise ValueError("Invalid channel name: %r" % name)
 

	
 

	
 
def get_channel_name(dmxnum):
 
    """if you pass a name, it will get normalized"""
 
    try:
 
        return reverse_patch[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 = {}
 
    reverse_patch = {}
 
    uri_map = {}
 
    uri_patch = {}
 

	
 
    graph = showconfig.getGraph()
 

	
 
    for chan in graph.subjects(RDF.type, L9['Channel']):
 
        for which, name in enumerate([graph.label(chan)] +
 
                                     list(graph.objects(chan, L9['altName']))):
 
            name = str(name)
 
            uri_map[name] = chan
 

	
 
            if name in patch:
 
                raise ValueError("channel name %r used multiple times" % name)
 
            for output in graph.objects(chan, L9['output']):
light9/Submaster.py
Show inline comments
 
from __future__ import division
 

	
 
import os, logging, time
 
from rdflib import Graph, RDF
 
from rdflib import RDFS, Literal, BNode
 
from light9.namespaces import L9, XSD
 
from light9.TLUtility import dict_scale, dict_max
 
from light9 import showconfig
 
from light9.Patch import resolve_name, get_dmx_channel, get_channel_uri, reload_data
 
from louie import dispatcher
 
from rdfdb.patch import Patch
 
from .rdfdb.patch import Patch
 
log = logging.getLogger('submaster')
 

	
 
class Submaster(object):
 
    """mapping of channels to levels"""
 
    def __init__(self, name, levels):
 
        """this sub has a name just for debugging. It doesn't get persisted.
 
        See PersistentSubmaster.
 

	
 
        levels is a dict
 
        """
 
        self.name = name
 
        self.levels = levels
 

	
 
        self.temporary = True
 

	
 
        if not self.temporary:
 
            # obsolete
 
            dispatcher.connect(log.error, 'reload all subs')
 

	
 
        #log.debug("%s initial levels %s", self.name, self.levels)
 

	
 
    def _editedLevels(self):
 
        pass
 

	
 
    def set_level(self, channelname, level, save=True):
 
        self.levels[resolve_name(channelname)] = level
 
        self._editedLevels()
 

	
 
    def set_all_levels(self, leveldict):
 
        self.levels.clear()
 
        for k, v in leveldict.items():
 
        for k, v in list(leveldict.items()):
 
            # this may call _editedLevels too many times
 
            self.set_level(k, v, save=0)
 

	
 
    def get_levels(self):
 
        return self.levels
 

	
 
    def no_nonzero(self):
 
        return all(v == 0 for v in self.levels.itervalues())
 
        return all(v == 0 for v in self.levels.values())
 

	
 
    def __mul__(self, scalar):
 
        return Submaster("%s*%s" % (self.name, scalar),
 
                         levels=dict_scale(self.levels, scalar))
 
    __rmul__ = __mul__
 
    def max(self, *othersubs):
 
        return sub_maxes(self, *othersubs)
 

	
 
    def __add__(self, other):
 
        return self.max(other)
 

	
 
    def ident(self):
 
        return (self.name, tuple(sorted(self.levels.items())))
 

	
 
    def __repr__(self):
 
        items = getattr(self, 'levels', {}).items()
 
        items.sort()
 
        items = sorted(list(getattr(self, 'levels', {}).items()))
 
        levels = ' '.join(["%s:%.2f" % item for item in items])
 
        return "<'%s': [%s]>" % (getattr(self, 'name', 'no name yet'), levels)
 

	
 
    def __cmp__(self, other):
 
        # not sure how useful this is
 
        if not isinstance(other, Submaster):
 
            return -1
 
        return cmp(self.ident(), other.ident())
 

	
 
    def __hash__(self):
 
        return hash(self.ident())
 

	
 
    def get_dmx_list(self):
 
        leveldict = self.get_levels() # gets levels of sub contents
 

	
 
        levels = []
 
        for k, v in leveldict.items():
 
        for k, v in list(leveldict.items()):
 
            if v == 0:
 
                continue
 
            try:
 
                dmxchan = get_dmx_channel(k) - 1
 
            except ValueError:
 
                log.error("error trying to compute dmx levels for submaster %s"
 
                          % self.name)
 
                raise
 
            if dmxchan >= len(levels):
 
                levels.extend([0] * (dmxchan - len(levels) + 1))
 
            levels[dmxchan] = max(v, levels[dmxchan])
 

	
 
        return levels
 

	
 
    def normalize_patch_names(self):
 
        """Use only the primary patch names."""
 
        # possibly busted -- don't use unless you know what you're doing
 
        self.set_all_levels(self.levels.copy())
 

	
 
    def get_normalized_copy(self):
 
        """Get a copy of this sumbaster that only uses the primary patch
 
        names.  The levels will be the same."""
 
        newsub = Submaster("%s (normalized)" % self.name, {})
 
        newsub.set_all_levels(self.levels)
 
        return newsub
 

	
 
    def crossfade(self, othersub, amount):
 
        """Returns a new sub that is a crossfade between this sub and
 
        another submaster.
 

	
 
        NOTE: You should only crossfade between normalized submasters."""
 
        otherlevels = othersub.get_levels()
 
        keys_set = {}
 
        for k in self.levels.keys() + otherlevels.keys():
 
        for k in list(self.levels.keys()) + list(otherlevels.keys()):
 
            keys_set[k] = 1
 
        all_keys = keys_set.keys()
 
        all_keys = list(keys_set.keys())
 

	
 
        xfaded_sub = Submaster("xfade", {})
 
        for k in all_keys:
 
            xfaded_sub.set_level(k,
 
                                 linear_fade(self.levels.get(k, 0),
 
                                             otherlevels.get(k, 0),
 
                                             amount))
 

	
 
        return xfaded_sub
 

	
 
class PersistentSubmaster(Submaster):
 
    def __init__(self, graph, uri):
 
        if uri is None:
 
            raise TypeError("uri must be URIRef")
 
        self.graph, self.uri = graph, uri
 
        self.graph.addHandler(self.setName)
 
        self.graph.addHandler(self.setLevels)
 
        Submaster.__init__(self, self.name, self.levels)
 
        self.temporary = False
 

	
 
    def ident(self):
 
        return self.uri
 

	
 
    def _editedLevels(self):
 
        self.save()
 

	
 
    def changeName(self, newName):
 
        self.graph.patchObject(self.uri, self.uri, RDFS.label, Literal(newName))
 
        
 
    def setName(self):
 
        log.info("sub update name %s %s", self.uri, self.graph.label(self.uri))
 
        self.name = self.graph.label(self.uri)
 

	
 
    def setLevels(self):
 
        log.debug("sub update levels")
 
        oldLevels = getattr(self, 'levels', {}).copy()
 
        self.setLevelsFromGraph()
 
        if oldLevels != self.levels:
 
            log.debug("sub %s changed" % self.name)
 
            # dispatcher too? this would help subcomposer
 
            dispatcher.send("sub levels changed", sub=self)
 

	
 
    def setLevelsFromGraph(self):
 
        if hasattr(self, 'levels'):
 
            self.levels.clear()
 
        else:
 
            self.levels = {}
 
        for lev in self.graph.objects(self.uri, L9['lightLevel']):
 
@@ -196,171 +195,170 @@ class PersistentSubmaster(Submaster):
 
            try:
 
                log.debug("submaster's type statement is in %r so we save there" %
 
                          list(current.contextsForStatement(typeStmt)))
 
                ctx = current.contextsForStatement(typeStmt)[0]
 
            except IndexError:
 
                log.info("declaring %s to be a submaster" % self.uri)
 
                ctx = self.uri
 
                self.graph.patch(Patch(addQuads=[
 
                    (self.uri, RDF.type, L9['Submaster'], ctx),
 
                    ]))
 

	
 
        return ctx
 

	
 
    def editLevel(self, chan, newLevel):
 
        self.graph.patchMapping(self._saveContext(),
 
                                subject=self.uri, predicate=L9['lightLevel'],
 
                                nodeClass=L9['ChannelSetting'],
 
                                keyPred=L9['channel'], newKey=chan,
 
                                valuePred=L9['level'],
 
                                newValue=Literal(newLevel))
 

	
 
    def clear(self):
 
        """set all levels to 0"""
 
        with self.graph.currentState() as g:
 
            levs = list(g.objects(self.uri, L9['lightLevel']))
 
        for lev in levs:
 
            self.graph.removeMappingNode(self._saveContext(), lev)
 

	
 
    def allQuads(self):
 
        """all the quads for this sub"""
 
        quads = []
 
        with self.graph.currentState() as current:
 
            quads.extend(current.quads((self.uri, None, None)))
 
            for s,p,o,c in quads:
 
                if p == L9['lightLevel']:
 
                    quads.extend(current.quads((o, None, None)))
 
        return quads
 

	
 

	
 
    def save(self):
 
        raise NotImplementedError("obsolete?")
 
        if self.temporary:
 
            log.info("not saving temporary sub named %s",self.name)
 
            return
 

	
 
        graph = Graph()
 
        subUri = L9['sub/%s' % self.name]
 
        graph.add((subUri, RDFS.label, Literal(self.name)))
 
        for chan in self.levels.keys():
 
        for chan in list(self.levels.keys()):
 
            try:
 
                chanUri = get_channel_uri(chan)
 
            except KeyError:
 
                log.error("saving dmx channels with no :Channel node "
 
                          "is not supported yet. Give channel %s a URI "
 
                          "for it to be saved. Omitting this channel "
 
                          "from the sub." % chan)
 
                continue
 
            lev = BNode()
 
            graph.add((subUri, L9['lightLevel'], lev))
 
            graph.add((lev, L9['channel'], chanUri))
 
            graph.add((lev, L9['level'],
 
                       Literal(self.levels[chan], datatype=XSD['decimal'])))
 

	
 
        graph.serialize(showconfig.subFile(self.name), format="nt")
 

	
 

	
 
def linear_fade(start, end, amount):
 
    """Fades between two floats by an amount.  amount is a float between
 
    0 and 1.  If amount is 0, it will return the start value.  If it is 1,
 
    the end value will be returned."""
 
    level = start + (amount * (end - start))
 
    return level
 

	
 
def sub_maxes(*subs):
 
    nonzero_subs = [s for s in subs if not s.no_nonzero()]
 
    name = "max(%s)" % ", ".join([repr(s) for s in nonzero_subs])
 
    return Submaster(name,
 
                     levels=dict_max(*[sub.levels for sub in nonzero_subs]))
 

	
 
def combine_subdict(subdict, name=None, permanent=False):
 
    """A subdict is { Submaster objects : levels }.  We combine all
 
    submasters first by multiplying the submasters by their corresponding
 
    levels and then max()ing them together.  Returns a new Submaster
 
    object.  You can give it a better name than the computed one that it
 
    will get or make it permanent if you'd like it to be saved to disk.
 
    Serves 8."""
 
    scaledsubs = [sub * level for sub, level in subdict.items()]
 
    scaledsubs = [sub * level for sub, level in list(subdict.items())]
 
    maxes = sub_maxes(*scaledsubs)
 
    if name:
 
        maxes.name = name
 
    if permanent:
 
        maxes.temporary = False
 

	
 
    return maxes
 

	
 
class Submasters(object):
 
    "Collection o' Submaster objects"
 
    def __init__(self, graph):
 
        self.submasters = {} # uri : Submaster
 
        self.graph = graph
 

	
 
        graph.addHandler(self.findSubs)
 

	
 
    def findSubs(self):
 
        current = set()
 

	
 
        for s in self.graph.subjects(RDF.type, L9['Submaster']):
 
            if self.graph.contains((s, RDF.type, L9['LocalSubmaster'])):
 
                continue
 
            log.debug("found sub %s", s)
 
            if s not in self.submasters:
 
                sub = self.submasters[s] = PersistentSubmaster(self.graph, s)
 
                dispatcher.send("new submaster", sub=sub)
 
            current.add(s)
 
        for s in set(self.submasters.keys()) - current:
 
            del self.submasters[s]
 
            dispatcher.send("lost submaster", subUri=s)
 
        log.info("findSubs finished, %s subs", len(self.submasters))
 

	
 
    def get_all_subs(self):
 
        "All Submaster objects"
 
        l = self.submasters.items()
 
        l.sort()
 
        l = sorted(list(self.submasters.items()))
 
        l = [x[1] for x in l]
 
        songs = []
 
        notsongs = []
 
        for s in l:
 
            if s.name and s.name.startswith('song'):
 
                songs.append(s)
 
            else:
 
                notsongs.append(s)
 
        combined = notsongs + songs
 

	
 
        return combined
 

	
 
    def get_sub_by_uri(self, uri):
 
        return self.submasters[uri]
 

	
 
    def get_sub_by_name(self, name):
 
        return get_sub_by_name(name, self)
 

	
 
# a global instance of Submasters, created on demand
 
_submasters = None
 

	
 
def get_global_submasters(graph):
 
    """
 
    Get (and make on demand) the global instance of
 
    Submasters. Cached, but the cache is not correctly using the graph
 
    argument. The first graph you pass will stick in the cache.
 
    """
 
    global _submasters
 
    if _submasters is None:
 
        _submasters = Submasters(graph)
 
    return _submasters
 

	
 
def get_sub_by_name(name, submasters=None):
 
    """name is a channel or sub nama, submasters is a Submasters object.
 
    If you leave submasters empty, it will use the global instance of
 
    Submasters."""
 
    if not submasters:
 
        submasters = get_global_submasters()
 

	
 
    # get_all_sub_names went missing. needs rework
 
    #if name in submasters.get_all_sub_names():
 
    #    return submasters.get_sub_by_name(name)
 

	
 
    try:
 
        val = int(name)
 
        s = Submaster("#%d" % val, levels={val : 1.0})
 
        return s
 
    except ValueError:
light9/TLUtility.py
Show inline comments
 
"""Collected utility functions, many are taken from Drew's utils.py in
 
Cuisine CVS and Hiss's Utility.py."""
 

	
 
from __future__ import generators
 

	
 
import sys
 

	
 
__author__ = "David McClosky <dmcc@bigasterisk.com>, " + \
 
             "Drew Perttula <drewp@bigasterisk.com>"
 
__cvsid__ = "$Id: TLUtility.py,v 1.1 2003/05/25 08:25:35 dmcc Exp $"
 
__version__ = "$Revision: 1.1 $"[11:-2]
 

	
 
def make_attributes_from_args(*argnames):
 
    """
 
    This function simulates the effect of running
 
      self.foo=foo
 
    for each of the given argument names ('foo' in the example just
 
    now). Now you can write:
 
        def __init__(self,foo,bar,baz):
 
            copy_to_attributes('foo','bar','baz')
 
            ...
 
    instead of:
 
        def __init__(self,foo,bar,baz):
 
            self.foo=foo
 
            self.bar=bar
 
            self.baz=baz
 
            ... 
 
    """
 
    
 
    callerlocals=sys._getframe(1).f_locals
 
    callerself=callerlocals['self']
 
    for a in argnames:
 
        try:
 
            setattr(callerself,a,callerlocals[a])
 
        except KeyError:
 
            raise KeyError, "Function has no argument '%s'" % a
 
            raise KeyError("Function has no argument '%s'" % a)
 

	
 
def enumerate(*collections):
 
    """Generates an indexed series:  (0,coll[0]), (1,coll[1]) ...
 
    
 
    this is a multi-list version of the code from the PEP:
 
    enumerate(a,b) gives (0,a[0],b[0]), (1,a[1],b[1]) ...
 
    """
 
    i = 0
 
    iters = [iter(collection) for collection in collections]
 
    while 1:
 
        yield [i,] + [iterator.next() for iterator in iters]
 
    while True:
 
        yield [i,] + [next(iterator) for iterator in iters]
 
        i += 1
 

	
 
def dumpobj(o):
 
    """Prints all the object's non-callable attributes"""
 
    print repr(o)
 
    print(repr(o))
 
    for a in [x for x in dir(o) if not callable(getattr(o, x))]:
 
        try:
 
            print "  %20s: %s " % (a, getattr(o, a))
 
            print("  %20s: %s " % (a, getattr(o, a)))
 
        except:
 
            pass
 
    print ""
 
    print("")
 

	
 
def dict_filter_update(d, **newitems):
 
    """Adds a set of new keys and values to dictionary 'd' if the values are
 
    true:
 

	
 
    >>> some_dict = {}
 
    >>> dict_filter_update(some_dict, a=None, b=0, c=1, e={}, s='hello')
 
    >>> some_dict
 
    {'c': 1, 's': 'hello'}
 
    """
 
    for k, v in newitems.items():
 
    for k, v in list(newitems.items()):
 
        if v: d[k] = v
 

	
 
def try_get_logger(channel):
 
    """Tries to get a logger with the channel 'channel'.  Will return a
 
    silent DummyClass if logging is not available."""
 
    try:
 
        import logging
 
        log = logging.getLogger(channel)
 
    except ImportError:
 
        log = DummyClass()
 
    return log
 

	
 
class DummyClass:
 
    """A class that can be instantiated but never used.  It is intended to
 
    be replaced when information is available.
 
    
 
    Usage:
 
    >>> d = DummyClass(1, 2, x="xyzzy")
 
    >>> d.someattr
 
    Traceback (most recent call last):
 
      File "<stdin>", line 1, in ?
 
      File "Utility.py", line 33, in __getattr__
 
        raise AttributeError, "Attempted usage of a DummyClass: %s" % key
 
    AttributeError: Attempted usage of a DummyClass: someattr
 
    >>> d.somefunction()
 
    Traceback (most recent call last):
 
      File "<stdin>", line 1, in ?
 
      File "Utility.py", line 33, in __getattr__
 
        raise AttributeError, "Attempted usage of a DummyClass: %s" % key
 
    AttributeError: Attempted usage of a DummyClass: somefunction"""
 
    def __init__(self, use_warnings=1, raise_exceptions=0, **kw):
 
        """Constructs a DummyClass"""
 
        make_attributes_from_args('use_warnings', 'raise_exceptions')
 
    def __getattr__(self, key):
 
        """Raises an exception to warn the user that a Dummy is not being
 
        replaced in time."""
 
        if key == "__del__":
 
            return
 
        msg = "Attempted usage of '%s' on a DummyClass" % key
 
        if self.use_warnings:
 
            import warnings
 
            warnings.warn(msg)
 
        if self.raise_exceptions:
 
            raise AttributeError, msg
 
            raise AttributeError(msg)
 
        return lambda *args, **kw: self.bogus_function()
 
    def bogus_function(self):
 
        pass
 

	
 
class ClassyDict(dict):
 
    """A dict that accepts attribute-style access as well (for keys
 
    that are legal names, obviously). I used to call this Struct, but
 
    chose the more colorful name to avoid confusion with the struct
 
    module."""
 
    def __getattr__(self, a):
 
        return self[a]
 
    def __setattr__(self, a, v):
 
        self[a] = v
 
    def __delattr__(self, a):
 
        del self[a]
 

	
 
def trace(func):
 
    """Good old fashioned Lisp-style tracing.  Example usage:
 
    
 
    >>> def f(a, b, c=3):
 
    >>>     print a, b, c
 
    >>>     return a + b
 
    >>>
 
    >>>
 
    >>> f = trace(f)
 
    >>> f(1, 2)
 
    |>> f called args: [1, 2]
 
    1 2 3
 
    <<| f returned 3
 
    3
 

	
 
    TODO: print out default keywords (maybe)
 
          indent for recursive call like the lisp version (possible use of 
 
              generators?)"""
 
    name = func.func_name
 
    name = func.__name__
 
    def tracer(*args, **kw):
 
        s = '|>> %s called' % name
 
        if args:
 
            s += ' args: %r' % list(args)
 
        if kw:
 
            s += ' kw: %r' % kw
 
        print s
 
        print(s)
 
        ret = func(*args, **kw)
 
        print '<<| %s returned %s' % (name, ret)
 
        print('<<| %s returned %s' % (name, ret))
 
        return ret
 
    return tracer
 

	
 
# these functions taken from old light8 code
 
def dict_max(*dicts):
 
    """
 
    ({'a' : 5, 'b' : 9}, {'a' : 10, 'b' : 4})
 
      returns ==> {'a' : 10, 'b' : 9}
 
    """
 
    newdict = {}
 
    for d in dicts:
 
        for k,v in d.items():
 
        for k,v in list(d.items()):
 
            newdict[k] = max(v, newdict.get(k, 0))
 
    return newdict
 

	
 
def dict_scale(d,scl):
 
    """scales all values in dict and returns a new dict"""
 
    return dict([(k,v*scl) for k,v in d.items()])
 
    return dict([(k,v*scl) for k,v in list(d.items())])
 
    
 
def dict_subset(d, dkeys, default=0):
 
    """Subset of dictionary d: only the keys in dkeys.  If you plan on omitting
 
    keys, make sure you like the default."""
 
    newd = {} # dirty variables!
 
    for k in dkeys:
 
        newd[k] = d.get(k, default)
 
    return newd
 

	
 
# functions specific to Timeline
 
# TBD
 
def last_less_than(array, x):
 
    """array must be sorted"""
 
    best = None
 
    for elt in array:
 
        if elt <= x:
 
            best = elt
 
        elif best is not None:
 
            return best
 
    return best
 

	
 
# TBD
 
def first_greater_than(array, x):
 
    """array must be sorted"""
 
    array_rev = array[:]
 
    array_rev.reverse()
 
    best = None
 
    for elt in array_rev:
 
        if elt >= x:
 
            best = elt
 
        elif best is not None:
 
            return best
 
    return best
 

	
 

	
light9/ascoltami/player.py
Show inline comments
 
#!/usr/bin/python
 
"""
 
alternate to the mpd music player, for ascoltami
 
"""
 
from __future__ import division
 

	
 
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).
 
        onEOS is an optional function to be called when we reach the
 
        end of a stream (for example, can be used to advance the song).
 
        It is called with one argument which is the URI of the song that
 
        just finished."""
 
        self.autoStopOffset = autoStopOffset
 
        self.playbin = self.pipeline = Gst.ElementFactory.make('playbin', None)
 

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

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

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

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

	
 
            t = self.currentTime()
 
            log.debug("watch %s < %s < %s", self.lastWatchTime,
 
                      self.autoStopTime, t)
 
            if self.lastWatchTime < self.autoStopTime < t:
 
                log.info("autostop")
 
                self.pause()
 

	
 
            self.lastWatchTime = t
 
        except:
 
            traceback.print_exc()
 

	
 
    def watchForMessages(self, bus):
 
        """this would be nicer than pollForMessages but it's not working for
 
        me. It's like add_signal_watch isn't running."""
 
        bus.add_signal_watch()
 

	
 
        def onEos(*args):
 
            print "onEos", args
 
            print("onEos", args)
 
            if self.onEOS is not None:
 
                self.onEOS(self.getSong())
 

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

	
 
        def onStreamStatus(bus, message):
 
            print "streamstatus", bus, message
 
            print("streamstatus", bus, message)
 
            (statusType, _elem) = message.parse_stream_status()
 
            if statusType == Gst.StreamStatusType.ENTER:
 
                self.setupAutostop()
 

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

	
 
    def pollForMessages(self):
 
        """bus.add_signal_watch seems to be having no effect, but this works"""
 
        bus = self.pipeline.get_bus()
 
        mt = Gst.MessageType
 
        msg = bus.poll(
 
            mt.EOS | mt.STREAM_STATUS | mt.ERROR,  # | mt.ANY,
 
            0)
 
        if msg is not None:
 
            log.debug("bus message: %r %r", msg.src, msg.type)
 
            # i'm trying to catch here a case where the pulseaudio
 
            # output has an error, since that's otherwise kind of
 
            # mysterious to diagnose. I don't think this is exactly
 
            # working.
 
            if msg.type == mt.ERROR:
 
                log.error(repr(msg.parse_error()))
 
            if msg.type == mt.EOS:
 
                if self.onEOS is not None:
 
                    self.onEOS(self.getSong())
 
            if msg.type == mt.STREAM_STATUS:
 
                (statusType, _elem) = msg.parse_stream_status()
 
                if statusType == Gst.StreamStatusType.ENTER:
 
                    self.setupAutostop()
 

	
 
    def seek(self, t):
 
        isSeekable = self.playbin.seek_simple(
 
            Gst.Format.TIME,
 
            Gst.SeekFlags.FLUSH | Gst.SeekFlags.ACCURATE | Gst.SeekFlags.SKIP,
 
            t * Gst.SECOND)
 
        if not isSeekable:
 
            raise ValueError('seek_simple failed')
 
        self.playStartTime = time.time()
 

	
 
    def setSong(self, songLoc, play=True):
 
        """
 
        uri like file:///my/proj/light9/show/dance2010/music/07.wav
 
        """
 
        log.info("set song to %r" % songLoc)
 
        self.pipeline.set_state(Gst.State.READY)
 
        self.preload(songLoc)
 
        self.pipeline.set_property("uri", songLoc)
 
        self.lastSetSongUri = songLoc
 
        # todo: don't have any error report yet if the uri can't be read
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):
 
    names = names or []
 
    # maybe this is better:
 
    # period = ontime + ((offset + ontime) * (len(names) - 1))
 
    period = (offset + ontime) * len(names)
 
    outputs = {}
 
    for index, name in enumerate(names):
 
        # normalize our time
 
        local_offset = (offset + ontime) * index
 
        local_t = t - local_offset
 
        local_t %= period
 

	
 
        # see if we're still in the on part
 
        if local_t <= ontime:
 
            value = onval
 
        else:
 
            value = offval
 

	
 
        # it could be in there twice (in a bounce like (1, 2, 3, 2)
 
        if name in outputs:
 
            outputs[name] = combiner(value, outputs[name])
 
        else:
 
            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,
 
                       names=('a', 'b', 'c', 'd'))
 
        output = output.items()
 
        output.sort()
 
        print "%.2f\t%s" % (x, ' '.join([str(x) for x in output]))
 
        output = sorted(list(output.items()))
 
        print("%.2f\t%s" % (x, ' '.join([str(x) for x in output])))
light9/clientsession.py
Show inline comments
 
"""
 
some clients will support the concept of a named session that keeps
 
multiple instances of that client separate
 
"""
 
from rdflib import URIRef
 
from urllib import quote
 
from urllib.parse import quote
 
from light9 import showconfig
 

	
 

	
 
def add_option(parser):
 
    parser.add_option(
 
        '-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='')))
light9/collector/collector.py
Show inline comments
 
from __future__ import division
 

	
 
import time
 
import logging
 
from rdflib import Literal
 
from light9.namespaces import L9, RDF
 
from light9.collector.output import setListElem
 
from light9.collector.device import toOutputAttrs, resolve
 

	
 
# types only
 
from rdflib import Graph, URIRef
 
from typing import List, Dict, Tuple, Any, TypeVar, Generic
 
from light9.collector.output import Output
 

	
 
ClientType = TypeVar('ClientType')
 
ClientSessionType = TypeVar('ClientSessionType')
 

	
 
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
 
       (device, outputattr) : (output, index)
 
    that explains which output index to set for any device update.
 
    """
 
    ret = {}
 

	
 
    outputByUri = {}  # universeUri : output
 
    for out in outputs:
 
        outputByUri[out.uri] = out
 

	
 
    for dc in graph.subjects(RDF.type, L9['DeviceClass']):
 
        log.info('mapping DeviceClass %s', dc)
 
        for dev in graph.subjects(RDF.type, dc):
 
            log.info('  mapping device %s', dev)
 
            universe = graph.value(dev, L9['dmxUniverse'])
 
            try:
 
                output = outputByUri[universe]
 
            except Exception:
 
                log.warn('dev %r :dmxUniverse %r', dev, universe)
 
                raise
 
            dmxBase = int(graph.value(dev, L9['dmxBase']).toPython())
 
            for row in graph.objects(dc, L9['attr']):
 
                outputAttr = graph.value(row, L9['outputAttr'])
 
                offset = int(graph.value(row, L9['dmxOffset']).toPython())
 
                index = dmxBase + offset - 1
 
                ret[(dev, outputAttr)] = (output, index)
 
                log.debug('    map %s to %s,%s', outputAttr, output, index)
 
    return ret
 

	
 

	
 
class Collector(Generic[ClientType, ClientSessionType]):
 

	
 
    def __init__(self, graph, outputs, listeners=None, clientTimeoutSec=10):
 
        # type: (Graph, List[Output], List[Listener], float) -> None
 
        self.graph = graph
 
        self.outputs = outputs
 
        self.listeners = listeners
 
        self.clientTimeoutSec = clientTimeoutSec
 
        self.initTime = time.time()
 
        self.allDevices = set()
 

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

	
 
        # (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.deviceType = {}  # uri: type that's a subclass of Device
 
        self.remapOut = {}  # (device, deviceAttr) : (start, end)
 
        for dc in self.graph.subjects(RDF.type, L9['DeviceClass']):
 
            for dev in self.graph.subjects(RDF.type, dc):
 
                self.allDevices.add(dev)
 
                self.deviceType[dev] = dc
 

	
 
                for remap in self.graph.objects(dev, L9['outputAttrRange']):
 
                    attr = self.graph.value(remap, L9['outputAttr'])
 
                    start = float(self.graph.value(remap, L9['start']))
 
                    end = float(self.graph.value(remap, L9['end']))
 
                    self.remapOut[(dev, attr)] = start, end
 

	
 
    def _forgetStaleClients(self, now):
 
        # type: (float) -> None
 
        staleClientSessions = []
 
        for c, (t, _) in self.lastRequest.iteritems():
 
        for c, (t, _) in self.lastRequest.items():
 
            if t < now - self.clientTimeoutSec:
 
                staleClientSessions.append(c)
 
        for c in staleClientSessions:
 
            log.info('forgetting stale client %r', c)
 
            del self.lastRequest[c]
 

	
 
    # todo: move to settings.py
 
    def resolvedSettingsDict(self, settingsList):
 
        # type: (List[Tuple[URIRef, URIRef, float]]) -> Dict[Tuple[URIRef, URIRef], float]
 
        out = {}  # type: Dict[Tuple[URIRef, URIRef], float]
 
        for d, da, v in settingsList:
 
            if (d, da) in out:
 
                out[(d, da)] = resolve(d, da, [out[(d, da)], v])
 
            else:
 
                out[(d, da)] = v
 
        return out
 

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

	
 
    def _merge(self, lastRequests):
 
        deviceAttrs = {}  # device: {deviceAttr: value}
 
        for _, lastSettings in lastRequests:
 
            for (device, deviceAttr), value in lastSettings.iteritems():
 
            for (device, deviceAttr), value in lastSettings.items():
 
                if (device, deviceAttr) in self.remapOut:
 
                    start, end = self.remapOut[(device, deviceAttr)]
 
                    value = Literal(start + float(value) * (end - start))
 

	
 
                attrs = deviceAttrs.setdefault(device, {})
 
                if deviceAttr in attrs:
 
                    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,
 
                # not going to 0.
 
                if deviceAttr in [L9['rx'], L9['ry'], L9['zoom'], L9['focus']]:
 
                    self.stickyAttrs[(device, deviceAttr)] = value
 

	
 
        # e.g. don't let an unspecified rotation go to 0
 
        for (d, da), v in self.stickyAttrs.iteritems():
 
        for (d, da), v in self.stickyAttrs.items():
 
            daDict = deviceAttrs.setdefault(d, {})
 
            if da not in daDict:
 
                daDict[da] = v
 

	
 
        return deviceAttrs
 

	
 
    def setAttrs(self, client, clientSession, settings, sendTime):
 
        """
 
        settings is a list of (device, attr, value). These attrs are
 
        device attrs. We resolve conflicting values, process them into
 
        output attrs, and call Output.update/Output.flush to send the
 
        new outputs.
 

	
 
        client is a string naming the type of client. (client,
 
        clientSession) is a unique client instance.
 

	
 
        Each client session's last settings will be forgotten after
 
        clientTimeoutSec.
 
        """
 
        now = time.time()
 
        self._warnOnLateRequests(client, now, sendTime)
 

	
 
        self._forgetStaleClients(now)
 

	
 
        uniqueSettings = self.resolvedSettingsDict(settings)
 
        self.lastRequest[(client, clientSession)] = (now, uniqueSettings)
 

	
 
        deviceAttrs = self._merge(self.lastRequest.itervalues())
 
        deviceAttrs = self._merge(iter(self.lastRequest.values()))
 

	
 
        outputAttrs = {}  # device: {outputAttr: value}
 
        for d in self.allDevices:
 
            try:
 
                devType = self.deviceType[d]
 
            except KeyError:
 
                log.warn("request for output to unconfigured device %s" % d)
 
                continue
 
            try:
 
                outputAttrs[d] = toOutputAttrs(devType, deviceAttrs.get(d, {}))
 
                if self.listeners:
 
                    self.listeners.outputAttrsSet(d, outputAttrs[d],
 
                                                  self.outputMap)
 
            except Exception as e:
 
                log.error('failing toOutputAttrs on %s: %r', d, e)
 

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

	
 
        for device, attrs in outputAttrs.iteritems():
 
            for outputAttr, value in attrs.iteritems():
 
        for device, attrs in outputAttrs.items():
 
            for outputAttr, value in attrs.items():
 
                self.setAttr(device, outputAttr, value, pendingOut)
 

	
 
        dt1 = 1000 * (time.time() - now)
 
        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)))
 

	
 
    def setAttr(self, device, outputAttr, value, pendingOut):
 
        output, index = self.outputMap[(device, outputAttr)]
 
        outList = pendingOut[output]
 
        setListElem(outList, index, value, combine=max)
 

	
 
    def flush(self, pendingOut):
 
        """write any changed outputs"""
 
        for out, vals in pendingOut.iteritems():
 
        for out, vals in pendingOut.items():
 
            out.update(vals)
 
            out.flush()
light9/collector/collector_client.py
Show inline comments
 
from __future__ import division
 

	
 
from light9 import networking
 
from light9.effect.settings import DeviceSettings
 
from twisted.internet import defer
 
from txzmq import ZmqEndpoint, ZmqFactory, ZmqPushConnection
 
import json, time, logging
 
import 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))
 
        self.conn = ZmqPushConnection(zf, e)
 

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

	
 

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

	
 

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

	
 

	
 
def sendToCollector(client, session, settings, useZmq=False):
 
    """deferred to the time in seconds it took to get a response from collector"""
 
    sendTime = time.time()
 
    msg = toCollectorJson(client, session, settings)
 

	
 
    if useZmq:
 
        d = sendToCollectorZmq(msg)
light9/collector/device.py
Show inline comments
 
from __future__ import division
 

	
 
import logging
 
import math
 
from light9.namespaces import L9, RDF, DEV
 
from rdflib import Literal
 
from webcolors import hex_to_rgb, rgb_to_hex
 
from colormath.color_objects import sRGBColor, CMYColor
 
import colormath.color_conversions
 

	
 
log = logging.getLogger('device')
 

	
 

	
 
class Device(object):
 
    pass
 

	
 

	
 
class ChauvetColorStrip(Device):
 
    """
 
     device attrs:
 
       color
 
    """
 

	
 

	
 
class Mini15(Device):
 
    """
 
    plan:
 

	
 
      device attrs
 
        rx, ry
 
        color
 
        gobo
 
        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
light9/collector/output.py
Show inline comments
 
from __future__ import division
 

	
 
from rdflib import URIRef
 
import sys
 
import time
 
import usb.core
 
import logging
 
from twisted.internet import task, threads, reactor
 
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:
 
        outList.extend([fill] * (index - len(outList)))
 
    if len(outList) <= index:
 
        outList.append(value)
 
    else:
 
        outList[index] = combine(outList[index], value)
 

	
 

	
 
class Output(object):
 
    """
 
    send an array of values to some output device. Call update as
 
    often as you want- the result will be sent as soon as possible,
 
    and with repeats as needed to outlast hardware timeouts.
 
    """
 
    uri = None  # type: URIRef
 
    numChannels = None  # type: int
 

	
 
    def __init__(self):
 
        raise NotImplementedError
 

	
 
    def allConnections(self):
 
        """
 
        sequence of (index, uri) for the uris we can output, and which
 
        index in 'values' to use for them
 
        """
 
        raise NotImplementedError
 

	
 
    def update(self, values):
 
        """
 
        output takes a flattened list of values, maybe dmx channels, or
 
        pin numbers, etc
 
        """
 
        raise NotImplementedError
 

	
 
    def flush(self):
 
        """
 
@@ -124,66 +124,66 @@ class EnttecDmx(DmxOutput):
 
    @stats.write.time()
 
    def sendDmx(self, buf):
 
        self.dev.write(self.currentBuffer)
 

	
 
    def countError(self):
 
        pass
 

	
 
    def shortId(self):
 
        return 'enttec'
 

	
 

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

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

	
 
        from light9.io.udmx import Udmx
 
        self.dev = Udmx(bus)
 
        self.currentBuffer = ''
 
        self.lastSentBuffer = None
 
        self.lastLog = 0
 

	
 
        # Doesn't actually need to get called repeatedly, but we do
 
        # need these two things:
 
        #   1. A throttle so we don't lag behind sending old updates.
 
        #   2. Retries if there are usb errors.
 
        # Copying the LoopingCall logic accomplishes those with a
 
        # little wasted time if there are no updates.
 
        #task.LoopingCall(self._loop).start(0.050)
 
        self._loop()
 

	
 
    @stats.update.time()
 
    def update(self, values):
 
        now = time.time()
 
        if now > self.lastLog + 1:
 
            log.debug('%s %s', self.shortId(), ' '.join(map(str, values)))
 
            self.lastLog = now
 

	
 
        self.currentBuffer = ''.join(map(chr, values))
 

	
 
    def sendDmx(self, buf):
 
        with Udmx.stats.write.time():
 
            try:
 
                if not buf:
 
                    print "skip empty msg"
 
                    print("skip empty msg")
 
                    return True
 
                self.dev.SendDMX(buf)
 
                return True
 
            except usb.core.USBError as e:
 
                # not in main thread
 
                if e.errno != 75:
 
                    msg = 'usb: sending %s bytes to %r; error %r' % (
 
                        len(buf), self.uri, e)
 
                    print msg
 
                    print(msg)
 
                return False
 

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

	
 
    def shortId(self):
 
        return self._shortId
light9/curvecalc/client.py
Show inline comments
 
"""
 
client code for talking to curvecalc
 
"""
 
import cyclone.httpclient
 
from light9 import networking
 
import urllib
 
import urllib.request, urllib.parse, urllib.error
 
from run_local import log
 

	
 

	
 
def sendLiveInputPoint(curve, value):
 
    f = cyclone.httpclient.fetch(networking.curveCalc.path('liveInputPoint'),
 
                                 method='POST',
 
                                 timeout=1,
 
                                 postdata=urllib.urlencode({
 
                                 postdata=urllib.parse.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/curve.py
Show inline comments
 
from __future__ import division
 

	
 
import glob, time, logging, ast, os
 
from bisect import bisect_left, bisect
 
import louie as dispatcher
 
from twisted.internet import reactor
 
from rdflib import Literal
 
from light9 import showconfig
 
from light9.namespaces import L9, RDF, RDFS
 
from rdfdb.patch import Patch
 

	
 
log = logging.getLogger()
 
# todo: move to config, consolidate with ascoltami, musicPad, etc
 
introPad = 4
 
postPad = 4
 

	
 

	
 
class Curve(object):
 
    """curve does not know its name. see Curveset"""
 

	
 
    def __init__(self, uri, pointsStorage='graph'):
 
        self.uri = uri
 
        self.pointsStorage = pointsStorage
 
        self.points = []  # x-sorted list of (x,y)
 
        self._muted = False
 

	
 
    def __repr__(self):
 
        return "<%s %s (%s points)>" % (self.__class__.__name__, self.uri,
 
                                        len(self.points))
 

	
 
    def muted():
 
        doc = "Whether to currently send levels (boolean, obviously)"
 

	
 
        def fget(self):
 
            return self._muted
 

	
 
        def fset(self, val):
 
            self._muted = val
 
            dispatcher.send('mute changed', sender=self)
 

	
 
        return locals()
 

	
 
    muted = property(**muted())
 

	
 
    def toggleMute(self):
 
        self.muted = not self.muted
 

	
 
    def load(self, filename):
 
        self.points[:] = []
 
        for line in file(filename):
 
            x, y = line.split()
 
            self.points.append((float(x), ast.literal_eval(y)))
 
        self.points.sort()
 
        dispatcher.send("points changed", sender=self)
 

	
 
    def set_from_string(self, pts):
 
        self.points[:] = []
 
        vals = pts.split()
 
        pairs = zip(vals[0::2], vals[1::2])
 
        pairs = list(zip(vals[0::2], vals[1::2]))
 
        for x, y in pairs:
 
            self.points.append((float(x), ast.literal_eval(y)))
 
        self.points.sort()
 
        dispatcher.send("points changed", sender=self)
 

	
 
    def points_as_string(self):
 

	
 
        def outVal(x):
 
            if isinstance(x, basestring):  # markers
 
            if isinstance(x, str):  # markers
 
                return x
 
            return "%.4g" % x
 

	
 
        return ' '.join(
 
            "%s %s" % (outVal(p[0]), outVal(p[1])) for p in self.points)
 

	
 
    def save(self, filename):
 
        # this is just around for markers, now
 
        if filename.endswith('-music') or filename.endswith('_music'):
 
            print "not saving music track"
 
            print("not saving music track")
 
            return
 
        f = file(filename, 'w')
 
        for p in self.points:
 
            f.write("%s %r\n" % p)
 
        f.close()
 

	
 
    def eval(self, t, allow_muting=True):
 
        if self.muted and allow_muting:
 
            return 0
 
        if not self.points:
 
            raise ValueError("curve has no points")
 
        i = bisect_left(self.points, (t, None)) - 1
 

	
 
        if i == -1:
 
            return self.points[0][1]
 
        if self.points[i][0] > t:
 
            return self.points[i][1]
 
        if i >= len(self.points) - 1:
 
            return self.points[i][1]
 

	
 
        p1, p2 = self.points[i], self.points[i + 1]
 
        frac = (t - p1[0]) / (p2[0] - p1[0])
 
        y = p1[1] + (p2[1] - p1[1]) * frac
 
        return y
 

	
 
    __call__ = eval
 

	
 
    def insert_pt(self, new_pt):
 
        """returns index of new point"""
 
        i = bisect(self.points, (new_pt[0], None))
 
        self.points.insert(i, new_pt)
 
        # missing a check that this isn't the same X as the neighbor point
 
        dispatcher.send("points changed", sender=self)
 
        return i
 

	
 
    def live_input_point(self, new_pt, clear_ahead_secs=.01):
 
        x, y = new_pt
 
        exist = self.points_between(x, x + clear_ahead_secs)
 
        for pt in exist:
 
            self.remove_point(pt)
 
        self.insert_pt(new_pt)
 
        dispatcher.send("points changed", sender=self)
 
        # now simplify to the left
 

	
 
    def set_points(self, updates):
 
        for i, pt in updates:
 
            self.points[i] = pt
 

	
 
        # this should be on, but live_input_point made it fail a
 
        # lot. need a new solution.
 
        #self.checkOverlap()
 
        dispatcher.send("points changed", sender=self)
 

	
 
    def checkOverlap(self):
 
        x = None
 
        for p in self.points:
 
            if p[0] <= x:
 
                raise ValueError("overlapping points")
 
            x = p[0]
 

	
 
    def pop_point(self, i):
 
        p = self.points.pop(i)
 
        dispatcher.send("points changed", sender=self)
 
        return p
 

	
 
    def remove_point(self, pt):
 
        self.points.remove(pt)
 
        dispatcher.send("points changed", sender=self)
 

	
 
    def indices_between(self, x1, x2, beyond=0):
 
        leftidx = max(0, bisect(self.points, (x1, None)) - beyond)
 
        rightidx = min(len(self.points),
 
                       bisect(self.points, (x2, None)) + beyond)
 
        return range(leftidx, rightidx)
 
        return list(range(leftidx, rightidx))
 

	
 
    def points_between(self, x1, x2):
 
        """returns (x,y) points"""
 
        return [self.points[i] for i in self.indices_between(x1, x2)]
 

	
 
    def point_before(self, x):
 
        """(x,y) of the point left of x, or None"""
 
        leftidx = self.index_before(x)
 
        if leftidx is None:
 
            return None
 
        return self.points[leftidx]
 

	
 
    def index_before(self, x):
 
        leftidx = bisect(self.points, (x, None)) - 1
 
        if leftidx < 0:
 
            return None
 
        return leftidx
 

	
 

	
 
class CurveResource(object):
 
    """
 
    holds a Curve, deals with graphs
 
    """
 

	
 
    def __init__(self, graph, uri):
 
        # probably newCurve and loadCurve should be the constructors instead.
 
        self.graph, self.uri = graph, uri
 

	
 
    def curvePointsContext(self):
 
        return self.uri
 

	
 
    def newCurve(self, ctx, label):
 
        """
 
        Save type/label for a new :Curve resource.
 
        Pass the ctx where the main curve data (not the points) will go.
 
        """
 
        if hasattr(self, 'curve'):
 
            raise ValueError('CurveResource already has a curve %r' %
 
                             self.curve)
 
        self.graph.patch(
 
            Patch(addQuads=[
 
                (self.uri, RDF.type, L9['Curve'], ctx),
 
                (self.uri, RDFS.label, label, ctx),
 
            ]))
 
        self.curve = Curve(self.uri)
 
        self.curve.points.extend([(0, 0)])
 
        self.saveCurve()
 
        self.watchCurvePointChanges()
 
@@ -284,102 +284,102 @@ class Curveset(object):
 

	
 
        self.currentSong = None
 
        self.curveResources = {}  # uri : CurveResource
 

	
 
        self.markers = Markers(uri=None, pointsStorage='file')
 

	
 
        graph.addHandler(self.loadCurvesForSong)
 

	
 
    def curveFromUri(self, uri):
 
        return self.curveResources[uri].curve
 

	
 
    def loadCurvesForSong(self):
 
        """
 
        current curves will track song's curves.
 
        
 
        This fires 'add_curve' dispatcher events to announce the new curves.
 
        """
 
        log.info('loadCurvesForSong')
 
        dispatcher.send("clear_curves")
 
        self.curveResources.clear()
 
        self.markers = Markers(uri=None, pointsStorage='file')
 

	
 
        self.currentSong = self.graph.value(self.session, L9['currentSong'])
 
        if self.currentSong is None:
 
            return
 

	
 
        for uri in sorted(self.graph.objects(self.currentSong, L9['curve'])):
 
            try:
 
                cr = self.curveResources[uri] = CurveResource(self.graph, uri)
 
                cr.loadCurve()
 

	
 
                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)
 
            except Exception as e:
 
                log.error("loading %s failed: %s", uri, e)
 

	
 
        basename = os.path.join(
 
            showconfig.curvesDir(),
 
            showconfig.songFilenameFromURI(self.currentSong))
 
        try:
 
            self.markers.load("%s.markers" % basename)
 
        except IOError:
 
            print "no marker file found"
 
            print("no marker file found")
 

	
 
    def save(self):
 
        """writes a file for each curve with a name
 
        like basename-curvename, or saves them to the rdf graph"""
 
        basename = os.path.join(
 
            showconfig.curvesDir(),
 
            showconfig.songFilenameFromURI(self.currentSong))
 

	
 
        patches = []
 
        for cr in self.curveResources.values():
 
        for cr in list(self.curveResources.values()):
 
            patches.extend(cr.getSavePatches())
 

	
 
        self.markers.save("%s.markers" % basename)
 
        # this will cause reloads that will rebuild our curve list
 
        for p in patches:
 
            self.graph.patch(p)
 

	
 
    def sorter(self, name):
 
        return self.curves[name].uri
 

	
 
    def curveUrisInOrder(self):
 
        return sorted(self.curveResources.keys())
 

	
 
    def currentCurves(self):
 
        # deprecated
 
        for uri, cr in sorted(self.curveResources.items()):
 
            with self.graph.currentState(tripleFilter=(uri, RDFS['label'],
 
                                                       None)) as g:
 
                yield uri, g.label(uri), cr.curve
 

	
 
    def globalsdict(self):
 
        raise NotImplementedError('subterm used to get a dict of name:curve')
 

	
 
    def get_time_range(self):
 
        return 0, dispatcher.send("get max time")[0][1]
 

	
 
    def new_curve(self, name):
 
        if isinstance(name, Literal):
 
            name = str(name)
 

	
 
        uri = self.graph.sequentialUri(self.currentSong + '/curve-')
 

	
 
        cr = self.curveResources[uri] = CurveResource(self.graph, uri)
 
        cr.newCurve(ctx=self.currentSong, label=Literal(name))
 
        s, e = self.get_time_range()
 
        cr.curve.points.extend([(s, 0), (e, 0)])
 

	
 
        ctx = self.currentSong
 
        self.graph.patch(
 
            Patch(addQuads=[
 
                (self.currentSong, L9['curve'], uri, ctx),
 
            ]))
 
        cr.saveCurve()
light9/curvecalc/curveview.py
Show inline comments
 
from __future__ import division
 

	
 
import math, logging, traceback
 
from gi.repository import Gtk
 
from gi.repository import Gdk
 
from gi.repository import GooCanvas
 
import louie as dispatcher
 
from rdflib import Literal
 
from twisted.internet import reactor
 
from light9.curvecalc.zoomcontrol import RegionZoom
 
from light9.curvecalc import cursors
 
from light9.curvecalc.curve import introPad, postPad
 
from lib.goocanvas_compat import Points, polyline_new_line
 
import imp
 

	
 
log = logging.getLogger()
 
print "curveview.py toplevel"
 
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]
 
    p0 = [x / vlen(p0) for x in p0]
 
    p1 = [x / vlen(p1) for x in p1]
 
    dot = p0[0] * p1[0] + p0[1] * p1[1]
 
    dot = max(-1, min(1, dot))
 
    return math.degrees(math.acos(dot))
 

	
 

	
 
class Sketch:
 
    """a sketch motion on a curveview, with temporary points while you
 
    draw, and simplification when you release"""
 

	
 
    def __init__(self, curveview, ev):
 
        self.curveview = curveview
 
        self.pts = []
 
        self.last_x = None
 

	
 
    def motion(self, ev):
 
        p = self.curveview.world_from_screen(ev.x, ev.y)
 
        p = p[0], max(0, min(1, p[1]))
 
        if self.last_x is not None and abs(ev.x - self.last_x) < 4:
 
            return
 
        self.last_x = ev.x
 
        self.pts.append(p)
 
        self.curveview.add_point(p)
 

	
 
    def release(self, ev):
 
        pts = self.pts
 
        pts.sort()
 
        pts = sorted(self.pts)
 
        finalPoints = pts[:]
 

	
 
        dx = .01
 
        to_remove = []
 
        for i in range(1, len(pts) - 1):
 
            x = pts[i][0]
 

	
 
            p_left = (x - dx, self.curveview.curve(x - dx))
 
            p_right = (x + dx, self.curveview.curve(x + dx))
 

	
 
            if angle_between(pts[i], p_left, p_right) > 160:
 
                to_remove.append(i)
 

	
 
        for i in to_remove:
 
            self.curveview.curve.remove_point(pts[i])
 
            finalPoints.remove(pts[i])
 

	
 
        # the simplified curve may now be too far away from some of
 
        # the points, so we'll put them back. this has an unfortunate
 
        # bias toward reinserting the earlier points
 
        for i in to_remove:
 
            p = pts[i]
 
            if abs(self.curveview.curve(p[0]) - p[1]) > .1:
 
                self.curveview.add_point(p)
 
                finalPoints.append(p)
 

	
 
        self.curveview.update_curve()
 
        self.curveview.select_points(finalPoints)
 

	
 

	
 
class SelectManip(object):
 
    """
 
    selection manipulator is created whenever you have a selection. It
 
    draws itself on the canvas and edits the points when you drag
 
    various parts
 
    """
 

	
 
    def __init__(self, parent, getSelectedIndices, getWorldPoint,
 
                 getScreenPoint, getCanvasHeight, setPoints, getWorldTime,
 
                 getWorldValue, getDragRange):
 
        """parent goocanvas group"""
 
        self.getSelectedIndices = getSelectedIndices
 
        self.getWorldPoint = getWorldPoint
 
        self.getScreenPoint = getScreenPoint
 
        self.getCanvasHeight = getCanvasHeight
 
        self.setPoints = setPoints
 
        self.getWorldTime = getWorldTime
 
        self.getDragRange = getDragRange
 
@@ -123,97 +123,97 @@ class SelectManip(object):
 
            fill_color_rgba=0xffffff88,
 
        )
 

	
 
        thickLine = lambda: polyline_new_line(
 
            parent=self.grp, stroke_color_rgba=0xffffccff, line_width=6)
 
        self.leftScale = thickLine()
 
        self.rightScale = thickLine()
 
        self.topScale = thickLine()
 

	
 
        for grp, name in [
 
            (self.xTrans, 'x'),
 
            (self.leftScale, 'left'),
 
            (self.rightScale, 'right'),
 
            (self.topScale, 'top'),
 
            (self.centerScale, 'centerScale'),
 
        ]:
 
            grp.connect("button-press-event", self.onPress, name)
 
            grp.connect("button-release-event", self.onRelease, name)
 
            grp.connect("motion-notify-event", self.onMotion, name)
 
            grp.connect("enter-notify-event", self.onEnter, name)
 
            grp.connect("leave-notify-event", self.onLeave, name)
 
            # and hover highlight
 
        self.update()
 

	
 
    def onEnter(self, item, target_item, event, param):
 
        self.prevColor = item.props.stroke_color_rgba
 
        item.props.stroke_color_rgba = 0xff0000ff
 

	
 
    def onLeave(self, item, target_item, event, param):
 
        item.props.stroke_color_rgba = self.prevColor
 

	
 
    def onPress(self, item, target_item, event, param):
 
        self.dragStartTime = self.getWorldTime(event.x)
 
        idxs = self.getSelectedIndices()
 

	
 
        self.origPoints = [self.getWorldPoint(i) for i in idxs]
 
        self.origMaxValue = max(p[1] for p in self.origPoints)
 
        moveLeft, moveRight = self.getDragRange(idxs)
 

	
 
        if param == 'centerScale':
 
            self.maxPointMove = min(moveLeft, moveRight)
 

	
 
        self.dragRange = (self.dragStartTime - moveLeft,
 
                          self.dragStartTime + moveRight)
 
        return True
 

	
 
    def onMotion(self, item, target_item, event, param):
 
        if hasattr(self, 'dragStartTime'):
 
            origPts = zip(self.getSelectedIndices(), self.origPoints)
 
            origPts = list(zip(self.getSelectedIndices(), self.origPoints))
 
            left = origPts[0][1][0]
 
            right = origPts[-1][1][0]
 
            width = right - left
 
            dontCross = .001
 

	
 
            clampLo = left if param == 'right' else self.dragRange[0]
 
            clampHi = right if param == 'left' else self.dragRange[1]
 

	
 
            def clamp(x, lo, hi):
 
                return max(lo, min(hi, x))
 

	
 
            mouseT = self.getWorldTime(event.x)
 
            clampedT = clamp(mouseT, clampLo + dontCross, clampHi - dontCross)
 

	
 
            dt = clampedT - self.dragStartTime
 

	
 
            if param == 'x':
 
                self.setPoints(
 
                    (i, (orig[0] + dt, orig[1])) for i, orig in origPts)
 
            elif param == 'left':
 
                self.setPoints(
 
                    (i,
 
                     (left + dt + (orig[0] - left) / width *
 
                      clamp(width - dt, dontCross, right - clampLo - dontCross),
 
                      orig[1])) for i, orig in origPts)
 
            elif param == 'right':
 
                self.setPoints(
 
                    (i,
 
                     (left + (orig[0] - left) / width *
 
                      clamp(width + dt, dontCross, clampHi - left - dontCross),
 
                      orig[1])) for i, orig in origPts)
 
            elif param == 'top':
 
                v = self.getWorldValue(event.y)
 
                if self.origMaxValue == 0:
 
                    self.setPoints((i, (orig[0], v)) for i, orig in origPts)
 
                else:
 
                    scl = max(0,
 
                              min(1 / self.origMaxValue, v / self.origMaxValue))
 
                    self.setPoints(
 
                        (i, (orig[0], orig[1] * scl)) for i, orig in origPts)
 

	
 
            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
 
@@ -416,192 +416,192 @@ class Curveview(object):
 

	
 
        #self.widget.connect('size-allocate', sizeEvent) # see docstring
 

	
 
        def visEvent(w, alloc):
 
            self.setCanvasToWidgetSize()
 
            return False
 

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

	
 
    def setCanvasToWidgetSize(self):
 
        p = self.canvas.props
 
        w = self.widget.get_allocated_width()
 
        h = self.widget.get_allocated_height()
 
        if (w, h) != (p.x2, p.y2):
 
            p.x1, p.x2 = 0, w
 
            p.y1, p.y2 = 0, h
 
            self.update_curve()
 

	
 
    def createCanvasWidget(self, parent):
 
        # this is only separate from createOuterWidgets because in the
 
        # past, i worked around display bugs by recreating the whole
 
        # canvas widget. If that's not necessary, this could be more
 
        # clearly combined with createOuterWidgets since there's no
 
        # time you'd want that one but not this one.
 
        canvas = GooCanvas.Canvas()
 
        parent.pack_start(canvas, expand=True, fill=True, padding=0)
 
        canvas.show()
 

	
 
        p = canvas.props
 
        p.background_color = 'black'
 
        root = canvas.get_root_item()
 

	
 
        canvas.connect("leave-notify-event", self.onLeave)
 
        canvas.connect("enter-notify-event", self.onEnter)
 
        canvas.connect("motion-notify-event", self.onMotion)
 
        canvas.connect("scroll-event", self.onScroll)
 
        canvas.connect("button-release-event", self.onRelease)
 
        root.connect("button-press-event", self.onCanvasPress)
 

	
 
        self.widget.connect("key-press-event", self.onKeyPress)
 

	
 
        self.widget.connect("focus-in-event", self.onFocusIn)
 
        self.widget.connect("focus-out-event", self.onFocusOut)
 
        #self.widget.connect("event", self.onAny)
 
        return canvas
 

	
 
    def onAny(self, w, event):
 
        print "   %s on %s" % (event, w)
 
        print("   %s on %s" % (event, w))
 

	
 
    def onFocusIn(self, *args):
 
        dispatcher.send('curve row focus change')
 
        dispatcher.send("all curves lose selection", butNot=self)
 

	
 
        self.widget.modify_bg(Gtk.StateFlags.NORMAL, Gdk.color_parse("red"))
 

	
 
    def onFocusOut(self, widget=None, event=None):
 
        dispatcher.send('curve row focus change')
 
        self.widget.modify_bg(Gtk.StateFlags.NORMAL, Gdk.color_parse("gray30"))
 

	
 
        # you'd think i'm unselecting when we lose focus, but we also
 
        # lose focus when the user moves off the toplevel window, and
 
        # that's not a time to forget the selection. See the 'all
 
        # curves lose selection' signal for the fix.
 

	
 
    def onKeyPress(self, widget, event):
 
        if event.string in list('12345'):
 
            x = int(event.string)
 
            self.add_point((self.current_time(), (x - 1) / 4.0))
 
        if event.string in list('qwerty'):
 
            self.add_marker((self.current_time(), event.string))
 

	
 
    def onDelete(self):
 
        if self.selected_points:
 
            self.remove_point_idx(*self.selected_points)
 

	
 
    def onCanvasPress(self, item, target_item, event):
 
        # when we support multiple curves per canvas, this should find
 
        # the close one and add a point to that. Binding to the line
 
        # itself is probably too hard to hit. Maybe a background-color
 
        # really thick line would be a nice way to allow a sloppier
 
        # click
 

	
 
        self.widget.grab_focus()
 

	
 
        _, flags = event.get_state()
 
        if flags & Gdk.ModifierType.CONTROL_MASK:
 
            self.new_point_at_mouse(event)
 
        elif flags & Gdk.ModifierType.SHIFT_MASK:
 
            self.sketch_press(event)
 
        else:
 
            self.select_press(event)
 

	
 
        # this stops some other handler that wants to unfocus
 
        return True
 

	
 
    def playPause(self):
 
        """
 
        user has pressed ctrl-p over a curve view, possibly this
 
        one. Returns the time under the mouse if we know it, or else
 
        None
 

	
 
        todo: there should be a faint timecursor line under the mouse
 
        so it's more obvious that we use that time for some
 
        events. Rt-click should include Ctrl+P as 'play/pause from
 
        here'
 
        """
 
        # maybe self.canvas.get_pointer would be ok for this? i didn't try it
 
        if self.entered and hasattr(self, 'lastMouseX'):
 
            t = self.world_from_screen(self.lastMouseX, 0)[0]
 
            return t
 
        return None
 

	
 
    def goLive(self):
 
        """this is for startup performance only, since the curves were
 
        getting redrawn many times. """
 
        self.redrawsEnabled = True
 
        self.update_curve()
 

	
 
    def knob_in(self, curve, value):
 
        """user turned a hardware knob, which edits the point to the
 
        left of the current time"""
 
        if curve != self.curve:
 
            return
 
        idx = self.curve.index_before(self.current_time())
 
        if idx is not None:
 
            pos = self.curve.points[idx]
 
            self.curve.set_points([(idx, (pos[0], value))])
 

	
 
    def slider_in(self, curve, value=None):
 
        """user pushed on a slider. make a new key.  if value is None,
 
        the value will be the same as the last."""
 
        if curve != self.curve:
 
            return
 

	
 
        if value is None:
 
            value = self.curve.eval(self.current_time())
 

	
 
        self.curve.insert_pt((self.current_time(), value))
 

	
 
    def print_state(self, msg=""):
 
        if 0:
 
            print "%s: dragging_dots=%s selecting=%s" % (
 
                msg, self.dragging_dots, self.selecting)
 
            print("%s: dragging_dots=%s selecting=%s" % (
 
                msg, self.dragging_dots, self.selecting))
 

	
 
    def select_points(self, pts):
 
        """set selection to the given point values (tuples, not indices)"""
 
        idxs = []
 
        for p in pts:
 
            idxs.append(self.curve.points.index(p))
 
        self.select_indices(idxs)
 

	
 
    def select_indices(self, idxs):
 
        """set selection to these point indices. This is the only
 
        writer to self.selected_points"""
 
        self.selected_points = idxs
 
        self.highlight_selected_dots()
 
        if self.selected_points and not self.selectManip:
 
            self.selectManip = SelectManip(
 
                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]),
 
                getWorldTime=lambda x: self.world_from_screen(x, 0)[0],
 
                getWorldValue=lambda y: self.world_from_screen(0, y)[1],
 
                getCanvasHeight=lambda: self.canvas.props.y2,
 
                setPoints=self.setPoints,
 
                getDragRange=self.getDragRange,
 
            )
 
        if not self.selected_points and self.selectManip:
 
            self.selectManip.destroy()
 
            self.selectManip = None
 

	
 
        self.selectionChanged()
 

	
 
    def getDragRange(self, idxs):
 
        """
 
        if you're dragging these points, what's the most time you can move
 
        left and right before colliding (exactly) with another
 
        point
 
        """
 
        maxLeft = maxRight = 99999
 
        cp = self.curve.points
 
        for i in idxs:
 
            nextStatic = i
 
            while nextStatic >= 0 and nextStatic in idxs:
 
                nextStatic -= 1
 
            if nextStatic >= 0:
 
                maxLeft = min(maxLeft, cp[i][0] - cp[nextStatic][0])
 

	
 
            nextStatic = i
 
@@ -704,97 +704,97 @@ class Curveview(object):
 
            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))
 
            ]
 
        except ZeroDivisionError:
 
            pts = [(-1, -1), (-1, -1)]
 
        self.timelineLine.set_property('points', Points(pts))
 

	
 
        self._time = t
 
        if self.knobEnabled:
 
            self.delete('knob')
 
            prevKey = self.curve.point_before(t)
 
            if prevKey is not None:
 
                pos = self.screen_from_world(prevKey)
 
                self.create_oval(pos[0] - 8,
 
                                 pos[1] - 8,
 
                                 pos[0] + 8,
 
                                 pos[1] + 8,
 
                                 outline='#800000',
 
                                 tags=('knob',))
 
                dispatcher.send("knob out", value=prevKey[1], curve=self.curve)
 

	
 
    def update_curve(self, *args):
 
        if not getattr(self, '_pending_update', False):
 
            self._pending_update = True
 
            reactor.callLater(.01, self._update_curve)
 

	
 
    def _update_curve(self):
 
        try:
 
            self._update_curve2()
 
        except Exception:
 
            log.error("in update_curve on %s", self.curve.uri)
 
            raise
 

	
 
    def _update_curve2(self):
 
        if not getattr(self, '_pending_update', False):
 
            return
 
        self._pending_update = False
 
        if not self.alive():
 
            return
 
        if not self.redrawsEnabled:
 
            print "no redrawsEnabled, skipping", self
 
            print("no redrawsEnabled, skipping", self)
 
            return
 

	
 
        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],
 
                                                  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.lower(None)
 

	
 
        self.canvas.set_property("background-color",
 
                                 "gray20" if self.curve.muted else "black")
 

	
 
        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]))
 
        if self.canvas.props.y2 > 80:
 
            self._draw_time_tics(visible_x)
 

	
 
            self.dots = {}  # idx : canvas rectangle
 

	
 
            if len(visible_points) < 50 and not self.curve.muted:
 
                self._draw_handle_points(visible_idxs, visible_points)
 

	
 
        self.selectionChanged()
 

	
 
    def is_music(self):
 
        """are we one of the music curves (which might be drawn a bit
 
        differently)"""
 
        return self._isMusic
 

	
 
    def _draw_markers(self, pts):
 
        colorMap = {
 
            'q': '#598522',
 
            'w': '#662285',
 
            'e': '#852922',
 
            'r': '#85225C',
 
            't': '#856B22',
 
            'y': '#227085',
 
        }
 
@@ -948,97 +948,97 @@ class Curveview(object):
 
            handletags = [t for t in tags if t.startswith('handle')]
 
            return int(handletags[0][6:])
 
        except IndexError:
 
            raise ValueError("no point found")
 

	
 
    def new_point_at_mouse(self, ev):
 
        p = self.world_from_screen(ev.x, ev.y)
 
        x = p[0]
 
        y = self.curve.eval(x)
 
        self.add_point((x, y))
 

	
 
    def add_points(self, pts):
 
        idxs = [self.curve.insert_pt(p) for p in pts]
 
        self.select_indices(idxs)
 

	
 
    def add_point(self, p):
 
        self.add_points([p])
 

	
 
    def add_marker(self, p):
 
        self.markers.insert_pt(p)
 

	
 
    def remove_point_idx(self, *idxs):
 
        idxs = list(idxs)
 
        while idxs:
 
            i = idxs.pop()
 

	
 
            self.curve.pop_point(i)
 
            newsel = []
 
            newidxs = []
 
            for si in range(len(self.selected_points)):
 
                sp = self.selected_points[si]
 
                if sp == i:
 
                    continue
 
                if sp > i:
 
                    sp -= 1
 
                newsel.append(sp)
 
            for ii in range(len(idxs)):
 
                if ii > i:
 
                    ii -= 1
 
                newidxs.append(idxs[ii])
 

	
 
            self.select_indices(newsel)
 
            idxs[:] = newidxs
 

	
 
    def highlight_selected_dots(self):
 
        if not self.redrawsEnabled:
 
            return
 

	
 
        for i, d in self.dots.items():
 
        for i, d in list(self.dots.items()):
 
            if i in self.selected_points:
 
                d.set_property('fill_color', 'red')
 
            else:
 
                d.set_property('fill_color', 'blue')
 

	
 
    def dotpress(self, r1, r2, ev, dotidx):
 
        self.print_state("dotpress")
 
        if dotidx not in self.selected_points:
 
            self.select_indices([dotidx])
 

	
 
        self.last_mouse_world = self.world_from_screen(ev.x, ev.y)
 
        self.dragging_dots = True
 

	
 
    def select_between(self, start, end):
 
        if start > end:
 
            start, end = end, start
 
        self.select_indices(self.curve.indices_between(start, end))
 

	
 
    def onEnter(self, widget, event):
 
        self.entered = True
 

	
 
    def onLeave(self, widget, event):
 
        self.entered = False
 

	
 
    def onMotion(self, widget, event):
 
        self.lastMouseX = event.x
 

	
 
        if event.state & Gdk.ModifierType.SHIFT_MASK and 1:  # and B1
 
            self.sketch_motion(event)
 
            return
 

	
 
        self.select_motion(event)
 

	
 
        if not self.dragging_dots:
 
            return
 
        if not event.state & 256:
 
            return  # not lmb-down
 

	
 
        # this way is accumulating error and also making it harder to
 
        # undo (e.g. if the user moves far out of the window or
 
        # presses esc or something). Instead, we should be resetting
 
        # the points to their start pos plus our total offset.
 
        cur = self.world_from_screen(event.x, event.y)
 
        if self.last_mouse_world:
 
            delta = (cur[0] - self.last_mouse_world[0],
 
                     cur[1] - self.last_mouse_world[1])
 
        else:
 
            delta = 0, 0
 
@@ -1116,177 +1116,177 @@ class CurveRow(object):
 
        controls = Gtk.Frame()
 
        controls.set_size_request(160, -1)
 
        controls.set_shadow_type(Gtk.ShadowType.OUT)
 
        self.cols.pack_start(controls, expand=False, fill=True, padding=0)
 
        self.setupControls(controls, name, curve)
 

	
 
        self.curveView = Curveview(curve,
 
                                   markers,
 
                                   isMusic=name in ['music', 'smooth_music'],
 
                                   zoomControl=zoomControl)
 

	
 
        self.initCurveView()
 
        dispatcher.connect(self.rebuild, "all curves rebuild")
 

	
 
    def isFocus(self):
 
        return self.curveView.widget.is_focus()
 

	
 
    def rebuild(self):
 
        raise NotImplementedError('obsolete, if curves are drawing right')
 
        self.curveView.rebuild()
 
        self.initCurveView()
 
        self.update_ui_to_collapsed_state()
 

	
 
    def destroy(self):
 
        self.curveView.entered = False  # help suppress bad position events
 
        del self.curveView
 
        self.box.destroy()
 

	
 
    def initCurveView(self):
 
        self.curveView.widget.show()
 
        self.setHeight(100)
 
        self.cols.pack_start(self.curveView.widget,
 
                             expand=True,
 
                             fill=True,
 
                             padding=0)
 

	
 
    def setHeight(self, h):
 
        self.curveView.widget.set_size_request(-1, h)
 

	
 
        # this should have been automatic when the size changed, but
 
        # the signals for that are wrong somehow.
 
        reactor.callLater(0, self.curveView.setCanvasToWidgetSize)
 

	
 
    def setupControls(self, controls, name, curve):
 
        box = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 0)
 
        controls.add(box)
 

	
 
        curve_name_label = Gtk.LinkButton()
 
        print "need to truncate this name length somehow"
 
        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)
 

	
 
        box.pack_start(curve_name_label, expand=True, fill=True, padding=0)
 
        box.pack_start(self.muted, expand=True, fill=True, padding=0)
 

	
 
    def onDelete(self):
 
        self.curveView.onDelete()
 

	
 
    def sync_mute_to_curve(self, *args):
 
        """send value from CheckButton to the master attribute inside Curve"""
 
        new_mute = self.muted.get_active()
 
        self.curveView.curve.muted = new_mute
 

	
 
    def update_mute_look(self):
 
        """set colors on the widgets in the row according to self.muted.get()"""
 
        # not yet ported for gtk
 
        return
 
        if self.curveView.curve.muted:
 
            new_bg = 'grey20'
 
        else:
 
            new_bg = 'normal'
 

	
 
        for widget in self.widgets:
 
            widget['bg'] = new_bg
 

	
 
    def mute_changed(self):
 
        """call this if curve.muted changed"""
 
        self.muted.set_active(self.curveView.curve.muted)
 
        #self.update_mute_look()
 

	
 

	
 
class Curvesetview(object):
 
    """
 
    
 
    """
 

	
 
    def __init__(self, graph, curvesVBox, zoomControlBox, curveset):
 
        self.graph = graph
 
        self.live = True
 
        self.curvesVBox = curvesVBox
 
        self.curveset = curveset
 
        self.allCurveRows = set()
 
        self.visibleHeight = 1000
 

	
 
        self.zoomControl = self.initZoomControl(zoomControlBox)
 
        self.zoomControl.redrawzoom()
 

	
 
        for uri, label, curve in curveset.currentCurves():
 
            self.add_curve(uri, label, curve)
 

	
 
        dispatcher.connect(self.clear_curves, "clear_curves")
 
        dispatcher.connect(self.add_curve, "add_curve", sender=self.curveset)
 
        dispatcher.connect(self.set_featured_curves, "set_featured_curves")
 
        dispatcher.connect(self.song_has_changed, "song_has_changed")
 

	
 
        self.newcurvename = Gtk.EntryBuffer.new("", 0)
 

	
 
        eventBox = self.curvesVBox.get_parent()
 
        eventBox.connect("key-press-event", self.onKeyPress)
 
        eventBox.connect("button-press-event", self.takeFocus)
 

	
 
        self.watchCurveAreaHeight()
 

	
 
    def __del__(self):
 
        print "del curvesetview", id(self)
 
        print("del curvesetview", id(self))
 

	
 
    def initZoomControl(self, zoomControlBox):
 
        import light9.curvecalc.zoomcontrol
 
        reload(light9.curvecalc.zoomcontrol)
 
        imp.reload(light9.curvecalc.zoomcontrol)
 
        zoomControl = light9.curvecalc.zoomcontrol.ZoomControl()
 
        zoomControlBox.add(zoomControl.widget)
 
        zoomControl.widget.show_all()
 
        return zoomControl
 

	
 
    def clear_curves(self):
 
        """curveset is about to re-add all new curves"""
 
        while self.allCurveRows:
 
            self.allCurveRows.pop().destroy()
 

	
 
    def song_has_changed(self):
 
        self.zoomControl.redrawzoom()
 

	
 
    def takeFocus(self, *args):
 
        """the whole curveset's eventbox is what gets the focus, currently, so
 
        keys like 'c' can work in it"""
 
        dispatcher.send("all curves lose selection")
 
        self.curvesVBox.get_parent().grab_focus()
 

	
 
    def curveRow_from_name(self, name):
 
        for cr in self.allCurveRows:
 
            if cr.name == name:
 
                return cr
 
        raise ValueError("couldn't find curveRow named %r" % name)
 

	
 
    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)
 

	
 
    def onKeyPress(self, widget, event):
 
        if not self.live:  # workaround for old instances living past reload()
 
            return
 

	
 
        r = self.row_under_mouse()
 
        key = event.string
 
        pass  # no handlers right now
 

	
 
    def row_under_mouse(self):
 
        x, y = self.curvesVBox.get_pointer()
 
        for r in self.allCurveRows:
 
            inRowX, inRowY = self.curvesVBox.translate_coordinates(r.box, x, y)
 
            alloc = r.box.get_allocation()
 
            if 0 <= inRowX < alloc.width and 0 <= inRowY < alloc.height:
 
                return r
 
        raise ValueError("no curveRow is under the mouse")
 

	
light9/curvecalc/output.py
Show inline comments
 
import time, logging
 
from twisted.internet import reactor
 
from light9 import Submaster, dmxclient
 
from light9.namespaces import L9
 
from light9.curvecalc.subterm import Subterm
 

	
 
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
 
        self.curveset = curveset
 

	
 
        self.recent_t = []
 
        self.later = None
 

	
 
        self.update()
 

	
 
    def update(self):
 
        d = self.music.current_time()
 
        d.addCallback(self.update2)
 
        d.addErrback(self.updateerr)
 

	
 
    def updateerr(self, e):
 

	
 
        print e.getTraceback()
 
        print(e.getTraceback())
 
        dispatcher.send("update status", val=e.getErrorMessage())
 
        if self.later and not self.later.cancelled and not self.later.called:
 
            self.later.cancel()
 
        self.later = reactor.callLater(1, self.update)
 

	
 
    def update2(self, t):
 
        # spot alsa soundcard offset is always 0, we get times about a
 
        # second ahead of what's really getting played
 
        #t = t - .7
 
        dispatcher.send("update status",
 
                        val="ok: receiving time from music player")
 
        if self.later and not self.later.cancelled and not self.later.called:
 
            self.later.cancel()
 

	
 
        self.later = reactor.callLater(.02, self.update)
 

	
 
        self.recent_t = self.recent_t[-50:] + [t]
 
        period = (self.recent_t[-1] - self.recent_t[0]) / len(self.recent_t)
 
        dispatcher.send("update period", val=period)
 
        self.send_dmx(t)
 

	
 
    def send_dmx(self, t):
 
        dispatcher.send("curves to sliders", t=t)
 

	
 
        if not self.currentSubterms:
 
            return
 

	
 
        scaledsubs = []
 
        for st in self.currentSubterms:
 
            scl = st.scaled(t)
 
            scaledsubs.append(scl)
 

	
 
        out = Submaster.sub_maxes(*scaledsubs)
 
        levs = out.get_levels()
 
        now = time.time()
 
        if now - self.lastsendtime > 5 or levs != self.lastsendlevs:
 
            dispatcher.send("output levels", val=levs)
 
            dmxclient.outputlevels(out.get_dmx_list(),
 
                                   twisted=1,
 
                                   clientid='curvecalc')
 
            self.lastsendtime = now
 
            self.lastsendlevs = levs
light9/curvecalc/subterm.py
Show inline comments
 
@@ -46,99 +46,99 @@ class Expr(object):
 

	
 
        glo['noise'] = glo['smooth_random']
 
        glo['notch'] = glo['notch_random']
 

	
 
        return glo
 

	
 

	
 
exprglo = Expr()
 

	
 

	
 
class Subterm(object):
 
    """one Submaster and its expression evaluator"""
 

	
 
    def __init__(self, graph, subterm, saveContext, curveset):
 
        self.graph, self.uri = graph, subterm
 
        self.saveContext = saveContext
 
        self.curveset = curveset
 
        self.ensureExpression(saveContext)
 

	
 
        self.submasters = Submaster.get_global_submasters(self.graph)
 

	
 
    def ensureExpression(self, saveCtx):
 
        with self.graph.currentState(tripleFilter=(self.uri, None,
 
                                                   None)) as current:
 
            if current.value(self.uri, L9['expression']) is None:
 
                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:
 
            subexpr_eval = self.eval(current, t)
 
            # we prevent any exceptions from escaping, since they cause us to
 
            # stop sending levels
 
            try:
 
                if isinstance(subexpr_eval, Submaster.Submaster):
 
                    # if the expression returns a submaster, just return it
 
                    return subexpr_eval
 
                else:
 
                    # otherwise, return our submaster multiplied by the value
 
                    # returned
 
                    if subexpr_eval == 0:
 
                        return Submaster.Submaster("zero", {})
 
                    subUri = current.value(self.uri, L9['sub'])
 
                    sub = self.submasters.get_sub_by_uri(subUri)
 
                    return sub * subexpr_eval
 
            except Exception, e:
 
            except Exception as e:
 
                dispatcher.send("expr_error", sender=self.uri, exc=repr(e))
 
                return Submaster.Submaster(name='Error: %s' % str(e), levels={})
 

	
 
    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:
 
            expr = current.value(self.uri, L9['expression'])
 

	
 
        used = []
 
        for name in self.curveset.curveNamesInOrder():
 
            if name in expr:
 
                used.append(name)
 
        return used
 

	
 
    def eval(self, current, t):
 
        """current graph is being passed as an optimization. It should be
 
        equivalent to use self.graph in here."""
 

	
 
        objs = list(current.objects(self.uri, L9['expression']))
 
        if len(objs) > 1:
 
            raise ValueError("found multiple expressions for %s: %s" %
 
                             (self.uri, objs))
 

	
 
        expr = current.value(self.uri, L9['expression'])
 
        if not expr:
 
            dispatcher.send("expr_error",
 
                            sender=self.uri,
 
                            exc="no expr, using 0")
 
            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})
 

	
 
        try:
 
            self.lasteval = eval(expr, glo)
 
        except Exception, e:
 
        except Exception as e:
 
            dispatcher.send("expr_error", sender=self.uri, exc=e)
 
            return Submaster.Submaster("zero", {})
 
        else:
 
            dispatcher.send("expr_error", sender=self.uri, exc="ok")
 
        return self.lasteval
 

	
 
    def __repr__(self):
 
        return "<Subterm %s>" % self.uri
light9/curvecalc/subtermview.py
Show inline comments
 
import logging
 
from gi.repository import Gtk
 
from louie import dispatcher
 
from rdflib import Literal, URIRef
 
from light9.namespaces import L9
 
log = logging.getLogger()
 

	
 
# inspired by http://www.daa.com.au/pipermail/pygtk/2008-August/015772.html
 
# 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
 
        self.curveset = curveset
 

	
 
        self.box = Gtk.HBox()
 

	
 
        self.entryBuffer = Gtk.EntryBuffer("", -1)
 
        self.entry = Gtk.Entry()
 
        self.error = Gtk.Label("")
 

	
 
        self.box.pack_start(self.entry, expand=True)
 
        self.box.pack_start(self.error, expand=False)
 

	
 
        self.entry.set_buffer(self.entryBuffer)
 
        self.graph.addHandler(self.set_expression_from_graph)
 
        self.entryBuffer.connect("deleted-text", self.entry_changed)
 
        self.entryBuffer.connect("inserted-text", self.entry_changed)
 

	
 
        self.entry.connect("focus-in-event", self.onFocus)
 

	
 
        dispatcher.connect(self.exprError,
 
                           "expr_error",
 
                           sender=self.ownerSubterm)
 
        keep.append(self.__dict__)
 

	
 
    def onFocus(self, *args):
 
        curveNames = self.curveset.curveNamesInOrder()
 
        currentExpr = self.entryBuffer.get_text()
 

	
 
        usedCurves = [n for n in curveNames if n in currentExpr]
 
        usedCurves.sort()
 
        usedCurves = sorted([n for n in curveNames if n in currentExpr])
 

	
 
        dispatcher.send("set_featured_curves", curveNames=usedCurves)
 

	
 
    def exprError(self, exc):
 
        self.error.set_text(str(exc))
 

	
 
    def set_expression_from_graph(self):
 
        e = str(self.graph.value(self.ownerSubterm, L9['expression']))
 
        print "from graph, set to %r" % e
 
        print("from graph, set to %r" % e)
 

	
 
        if e != self.entryBuffer.get_text():
 
            self.entryBuffer.set_text(e, len(e))
 

	
 
    def entry_changed(self, *args):
 
        log.info("want to patch to %r", self.entryBuffer.get_text())
 
        self.graph.patchObject(self.saveContext, self.ownerSubterm,
 
                               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
 

	
 
        self.label = Gtk.Label("sub")
 
        self.graph.addHandler(self.setName)
 

	
 
        self.label.drag_dest_set(flags=Gtk.DEST_DEFAULT_ALL,
 
                                 targets=[('text/uri-list', 0, 0)],
 
                                 actions=Gtk.gdk.ACTION_COPY)
 
        self.label.connect("drag-data-received", self.onDataReceivedOnLabel)
 

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

	
 
    def setName(self):
 
        # some of this could be pushed into Submaster
 
        sub = self.graph.value(self.subterm.uri, L9['sub'])
 
        if sub is None:
 
            tail = self.subterm.uri.rsplit('/', 1)[-1]
 
            self.label.set_text("no sub (%s)" % tail)
 
            return
 
        label = self.graph.label(sub)
 
        if label is None:
 
            self.label.set_text("sub %s has no label" % sub)
 
            return
light9/curvecalc/zoomcontrol.py
Show inline comments
 
from __future__ import division
 

	
 
from gi.repository import Gtk
 
from gi.repository import GObject
 
from gi.repository import GooCanvas
 
import louie as dispatcher
 
from light9.curvecalc import cursors
 
from lib.goocanvas_compat import Points, polyline_new_line
 
from twisted.internet import reactor
 

	
 

	
 
class ZoomControl(object):
 
    """
 
    please pack .widget
 
    """
 

	
 
    mintime = 0
 

	
 
    def maxtime():
 
        doc = "seconds at the right edge of the bar"
 

	
 
        def fget(self):
 
            return self._maxtime
 

	
 
        def fset(self, value):
 
            self._maxtime = value
 
            self.redrawzoom()
 

	
 
        return locals()
 

	
 
    maxtime = property(**maxtime())
 

	
 
    _end = _start = 0
 

	
 
    def start():
 

	
 
        def fget(self):
 
            return self._start
 

	
 
        def fset(self, v):
 
            v = max(self.mintime, v)
 
            # don't protect for start<end since zooming sometimes sets
 
            # start temporarily after end
 
            self._start = v
 

	
 
        return locals()
 

	
 
    start = property(**start())
 

	
 
    def end():
light9/dmxchanedit.py
Show inline comments
 
"""
 

	
 
widget to show all dmx channel levels and allow editing. levels might
 
not actually match what dmxserver is outputting.
 

	
 
proposal for new focus and edit system:
 
- rows can be selected
 
- the chan number or label can be used to select rows. dragging over rows brings all of them into or out of the current selection
 
- numbers drag up and down (like today)
 
- if you drag a number in a selected row, all the selected numbers change
 
- if you start dragging a number in an unselected row, your row becomes the new selection and then the edit works
 

	
 

	
 
proposal for new attribute system:
 
- we always want to plan some attributes for each light: where to center; what stage to cover; what color gel to apply; whether the light is burned out
 
- we have to stop packing these into the names. Names should be like 'b33' or 'blue3' or just '44'. maybe 'blacklight'.
 

	
 
"""
 
from __future__ import nested_scopes, division
 
import Tkinter as tk
 

	
 
import tkinter as tk
 
from rdflib import RDF, Literal
 
import math, logging
 
from decimal import Decimal
 
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
 

	
 
    source data is like this:
 
        ch:b11-c     a :Channel;
 
         :output dmx:c54;
 
         rdfs:label "b11-c" .
 

	
 
    and the level is like this:
 

	
 
       ?editor :currentSub ?sub .
 
       ?sub :lightLevel [:channel ?ch; :level ?level] .
 

	
 
    levels come in with self.setTo and go out by the onLevelChange
 
    callback. This object does not use the graph for level values,
 
    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
 
        self.onLevelChange = onLevelChange
 
        self.uri = channelUri
 
        self.currentLevel = 0  # the level we're displaying, 0..1
 

	
 
        # no statement yet
 
        self.channelnum = int(
 
            self.graph.value(self.uri, L9['output']).rsplit('/c')[-1])
 

	
 
        # 3 widgets, left-to-right:
 

	
 
        # channel number -- will turn yellow when being altered
 
        self.num_lab = tk.Label(self,
 
@@ -182,62 +182,62 @@ class Levelbox(tk.Frame):
 

	
 
    def updateChannels(self):
 
        """(re)make Onelevel boxes for the defined channels"""
 

	
 
        [ch.destroy() for ch in self.winfo_children()]
 
        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]))
 
        cols = 2
 
        rows = int(math.ceil(len(chans) / cols))
 

	
 
        def make_frame(parent):
 
            f = tk.Frame(parent, bd=0, bg='black')
 
            f.pack(side='left')
 
            return f
 

	
 
        columnFrames = [make_frame(self) for x in range(cols)]
 

	
 
        for i, channel in enumerate(chans):  # sort?
 
            # frame for this channel
 
            f = Onelevel(columnFrames[i // rows], self.graph, channel,
 
                         self.onLevelChange)
 

	
 
            self.levelFromUri[channel] = f
 
            f.pack(side='top')
 

	
 
    def updateLevelValues(self):
 
        """set UI level from graph"""
 
        submaster = self.currentSub()
 
        if submaster is None:
 
            return
 
        sub = submaster.uri
 
        if sub is None:
 
            raise ValueError("currentSub is %r" % submaster)
 

	
 
        remaining = set(self.levelFromUri.keys())
 
        for ll in self.graph.objects(sub, L9['lightLevel']):
 
            chan = self.graph.value(ll, L9['channel'])
 
            try:
 
                lev = self.graph.value(ll, L9['level']).toPython()
 
            except AttributeError as e:
 
                log.error('on lightlevel %r:', ll)
 
                log.exception(e)
 
                continue
 
            if isinstance(lev, Decimal):
 
                lev = float(lev)
 
            assert isinstance(lev, (int, long, float)), repr(lev)
 
            assert isinstance(lev, (int, float)), repr(lev)
 
            try:
 
                self.levelFromUri[chan].setTo(lev)
 
                remaining.remove(chan)
 
            except KeyError as e:
 
                log.exception(e)
 
        for channel in remaining:
 
            self.levelFromUri[channel].setTo(0)
 

	
 
    def onLevelChange(self, chan, newLevel):
 
        """UI received a change which we put in the graph"""
 
        if self.currentSub() is None:
 
            raise ValueError("no currentSub in Levelbox")
 
        self.currentSub().editLevel(chan, newLevel)
light9/dmxclient.py
Show inline comments
 
""" module for clients to use for easy talking to the dmx
 
server. sending levels is now a simple call to
 
dmxclient.outputlevels(..)
 

	
 
client id is formed from sys.argv[0] and the PID.  """
 

	
 
import xmlrpclib, os, sys, socket, time, logging
 
import xmlrpc.client, os, sys, socket, time, logging
 
from twisted.internet import defer
 
from txzmq import ZmqEndpoint, ZmqFactory, ZmqPushConnection
 
import json
 

	
 
from light9 import networking
 
_dmx = None
 
log = logging.getLogger('dmxclient')
 

	
 
procname = os.path.basename(sys.argv[0])
 
procname = procname.replace('.py', '')
 
_id = "%s-%s-%s" % (procname, socket.gethostname(), os.getpid())
 

	
 

	
 
class TwistedZmqClient(object):
 

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

	
 
        class Push(ZmqPushConnection):
 
            pass  # highWaterMark = 3000
 

	
 
        self.conn = Push(zf, e)
 

	
 
    def send(self, clientid, 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
 
    0..1. list can be any length- it will apply to the first len() dmx
 
    channels.
 

	
 
    if the server is not found, outputlevels will block for a
 
    second."""
 

	
 
    global _dmx, _id
 

	
 
    if _dmx is None:
 
        url = networking.dmxServer.url
 
        if not twisted:
 
            _dmx = xmlrpclib.Server(url)
 
            _dmx = xmlrpc.client.Server(url)
 
        else:
 
            _dmx = TwistedZmqClient(networking.dmxServerZmq)
 

	
 
    if not twisted:
 
        try:
 
            _dmx.outputlevels(clientid, levellist)
 
        except socket.error, e:
 
        except socket.error as e:
 
            log.error("dmx server error %s, waiting" % e)
 
            time.sleep(1)
 
        except xmlrpclib.Fault, e:
 
        except xmlrpc.client.Fault as e:
 
            log.error("outputlevels had xml fault: %s" % e)
 
            time.sleep(1)
 
    else:
 
        _dmx.send(clientid, levellist)
 
        return defer.succeed(None)
 

	
 

	
 
dummy = os.getenv('DMXDUMMY')
 
if dummy:
 
    print "dmxclient: DMX is in dummy mode."
 
    print("dmxclient: DMX is in dummy mode.")
 

	
 
    def outputlevels(*args, **kw):
 
        pass
light9/editchoice.py
Show inline comments
 
import Tkinter as tk
 
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
 

	
 
    widget for tying some UI to a shared resource for editing, or
 
    unlinking it (which means associating it with a local resource
 
    that's not named or shared). This object does not own the choice
 
    of resource; the caller does.
 

	
 
    UI actions:
 
    - drag a uri on here to make it the one we're editing
 

	
 
    - button to clear the currentSub (putting it back to
 
      sessionLocalSub, and also resetting sessionLocalSub to be empty
 
      again)
 

	
 
    - drag the sub uri off of here to send it to another receiver,
 
      but, if we're in local mode, the local sub should not be so
 
      easily addressable. Maybe you just can't drag it off.
 

	
 

	
 
    Todo:
 

	
 
    - filter by type so you can't drag a curve onto a subcomposer
 

	
 
    - 'save new' : make a new sub: transfers the current data (from a shared sub or
 
      from the local one) to the new sub. If you're on a local sub,
 
      the new sub is named automatically, ideally something brief,
 
      pretty distinct, readable, and based on the lights that are
 
      on. If you're on a named sub, the new one starts with a
 
      'namedsub 2' style name. The uri can also be with a '2' suffix,
 
      although maybe that will be stupid. If you change the name
 
      before anyone knows about this uri, we could update the current
 
      sub's uri to a slug of the new label.
 

	
 
    - rename this sub: not available if you're on a local sub. Sets
 
      the label of a named sub. Might update the uri of the named sub
 
      if it's new enough that no one else would have that uri. Not
 
@@ -65,54 +65,54 @@ class EditChoice(object):
 
        tk.Label(self.frame, text=label).pack(side='left')
 
        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.pack()
 

	
 
        self.resourceObservable = resourceObservable
 
        resourceObservable.subscribe(self.uriChanged)
 

	
 
        # when the value is local, this should stop being a drag source
 
        dragSourceRegister(self.subIcon, 'copy', 'text/uri-list',
 
                           self.resourceObservable)
 

	
 
        def onEv(ev):
 
            self.resourceObservable(URIRef(ev.data))
 
            return "link"
 

	
 
        self.onEv = onEv
 

	
 
        b = tk.Button(self.frame, text="Unlink", command=self.switchToLocalSub)
 
        b.pack(side='left')
 

	
 
        # it would be nice if I didn't receive my own drags here, and
 
        # if the hover display wasn't per widget
 
        for target in ([self.frame, self.currentLinkFrame] +
 
                       self.frame.winfo_children() +
 
                       self.currentLinkFrame.winfo_children()):
 
            dropTargetRegister(target,
 
                               typeList=["*"],
 
                               onDrop=onEv,
 
                               hoverStyle=dict(background="#555500"))
 

	
 
    def uriChanged(self, newUri):
 
        # if this resource had a type icon or a thumbnail, those would be
 
        # cool to show in here too
 
        if newUri is Local:
 
            self.subIcon.config(text="(local)")
 
        else:
 
            self.graph.addHandler(self.updateLabel)
 

	
 
    def updateLabel(self):
 
        uri = self.resourceObservable()
 
        print "get label", repr(uri)
 
        print("get label", repr(uri))
 
        label = self.graph.label(uri)
 
        self.subIcon.config(text=label or uri)
 

	
 
    def switchToLocalSub(self):
 
        self.resourceObservable(Local)
light9/effect/edit.py
Show inline comments
 
@@ -20,188 +20,188 @@ def getMusicStatus():
 
            (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:
 
        droppedTypes = list(g.objects(dropped, RDF.type))
 
        droppedLabel = g.label(dropped)
 
        droppedCodes = list(g.objects(dropped, L9['code']))
 

	
 
    quads = []
 
    fade = 2 if event == 'default' else 0
 

	
 
    if _songHasEffect(graph, song, dropped):
 
        # bump the existing curve
 
        pass
 
    else:
 
        effect, q = _newEffect(graph, song, ctx)
 
        quads.extend(q)
 

	
 
        curve = graph.sequentialUri(song + "/curve-")
 
        yield _newEnvelopeCurve(graph, ctx, curve, droppedLabel, fade)
 
        quads.extend([
 
            (song, L9['curve'], curve, ctx),
 
            (effect, RDFS.label, droppedLabel, ctx),
 
            (effect, L9['code'], Literal('env = %s' % curve.n3()), ctx),
 
        ])
 

	
 
        if L9['EffectClass'] in droppedTypes:
 
            quads.extend([
 
                (effect, RDF.type, dropped, ctx),
 
            ] + [(effect, L9['code'], c, ctx) for c in droppedCodes])
 
        elif L9['Submaster'] in droppedTypes:
 
            quads.extend([
 
                (effect, L9['code'], Literal('out = %s * env' % dropped.n3()),
 
                 ctx),
 
            ])
 
        else:
 
            raise NotImplementedError(
 
                "don't know how to add an effect from %r (types=%r)" %
 
                (dropped, droppedTypes))
 

	
 
        _maybeAddMusicLine(quads, effect, song, ctx)
 

	
 
    print "adding"
 
    print("adding")
 
    for qq in quads:
 
        print qq
 
        print(qq)
 
    returnValue(Patch(addQuads=quads))
 

	
 

	
 
@inlineCallbacks
 
def songNotePatch(graph, dropped, song, event, ctx, note=None):
 
    """
 
    drop into effectsequencer timeline
 

	
 
    ported from timeline.coffee makeNewNote
 
    """
 
    with graph.currentState(tripleFilter=(dropped, None, None)) as g:
 
        droppedTypes = list(g.objects(dropped, RDF.type))
 

	
 
    quads = []
 
    fade = 2 if event == 'default' else 0.1
 

	
 
    if note:
 
        musicStatus = yield getMusicStatus()
 
        songTime = musicStatus['t']
 
        _finishCurve(graph, note, quads, ctx, songTime)
 
    else:
 
        if L9['Effect'] in droppedTypes:
 
            musicStatus = yield getMusicStatus()
 
            songTime = musicStatus['t']
 
            note = _makeNote(graph, song, note, quads, ctx, dropped, songTime,
 
                             event, fade)
 
        else:
 
            raise NotImplementedError
 

	
 
    returnValue((note, Patch(addQuads=quads)))
 

	
 

	
 
def _point(ctx, uri, t, v):
 
    return [(uri, L9['time'], Literal(round(t, 3)), ctx),
 
            (uri, L9['value'], Literal(round(v, 3)), ctx)]
 

	
 

	
 
def _finishCurve(graph, note, quads, ctx, songTime):
 
    with graph.currentState() as g:
 
        origin = g.value(note, L9['originTime']).toPython()
 
        curve = g.value(note, L9['curve'])
 

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

	
 

	
 
def _makeNote(graph, song, note, quads, ctx, dropped, songTime, event, fade):
 
    note = graph.sequentialUri(song + '/n')
 
    curve = graph.sequentialUri(note + 'c')
 
    quads.extend([
 
        (song, L9['note'], note, ctx),
 
        (note, RDF.type, L9['Note'], ctx),
 
        (note, L9['curve'], curve, ctx),
 
        (note, L9['effectClass'], dropped, ctx),
 
        (note, L9['originTime'], Literal(songTime), ctx),
 
        (curve, RDF.type, L9['Curve'], ctx),
 
        (curve, L9['attr'], L9['strength'], ctx),
 
    ])
 
    if event == 'default':
 
        coords = [(0 - fade, 0), (0, 1), (20, 1), (20 + fade, 0)]
 
    elif event == 'start':
 
        coords = [
 
            (0 - fade, 0),
 
            (0, 1),
 
        ]
 
    elif event == 'end':  # probably unused- goes to _finishCurve instead
 
        coords = [(20, 1), (20 + fade, 0)]
 
    else:
 
        raise NotImplementedError(event)
 
    for t, v in coords:
 
        pt = graph.sequentialUri(curve + 'p')
 
        quads.extend([(curve, L9['point'], pt, ctx)] + _point(ctx, pt, t, v))
 
    return note
 

	
 

	
 
def _songHasEffect(graph, song, uri):
 
    """does this song have an effect of class uri or a sub curve for sub
 
    uri? this should be simpler to look up."""
 
    return False  # todo
 

	
 

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

	
 

	
 
def _newEffect(graph, song, ctx):
 
    effect = graph.sequentialUri(song + "/effect-")
 
    quads = [
 
        (song, L9['effect'], effect, ctx),
 
        (effect, RDF.type, L9['Effect'], ctx),
 
    ]
 
    print "_newEffect", effect, quads
 
    print("_newEffect", effect, quads)
 
    return effect, quads
 

	
 

	
 
@inlineCallbacks
 
def _newEnvelopeCurve(graph, ctx, uri, label, fade=2):
 
    """this does its own patch to the graph"""
 

	
 
    cr = CurveResource(graph, uri)
 
    cr.newCurve(ctx, label=Literal(label))
 
    yield _insertEnvelopePoints(cr.curve, fade)
 
    cr.saveCurve()
 

	
 

	
 
@inlineCallbacks
 
def _insertEnvelopePoints(curve, fade=2):
 
    # wrong: we might not be adding to the currently-playing song.
 
    musicStatus = yield getMusicStatus()
 
    songTime = musicStatus['t']
 
    songDuration = musicStatus['duration']
 

	
 
    t1 = clamp(songTime - fade, .1, songDuration - .1 * 2) + fade
 
    t2 = clamp(songTime + 20, t1 + .1, songDuration)
 

	
 
    curve.insert_pt((t1 - fade, 0))
 
    curve.insert_pt((t1, 1))
 
    curve.insert_pt((t2, 1))
 
    curve.insert_pt((t2 + fade, 0))
 

	
 

	
 
def _maybeAddMusicLine(quads, effect, song, ctx):
 
    """
 
    add a line getting the current music into 'music' if any code might
 
    be mentioning that var
 
    """
 

	
 
    for spoc in quads:
 
        if spoc[1] == L9['code'] and 'music' in spoc[2]:
 
            quads.extend([(effect, L9['code'],
 
                           Literal('music = %s' % musicCurveForSong(song).n3()),
 
                           ctx)])
 
            break
light9/effect/effecteval.py
Show inline comments
 
from __future__ import division
 

	
 
from rdflib import URIRef, Literal
 
from light9.namespaces import L9, RDF, DEV
 
from webcolors import rgb_to_hex, hex_to_rgb
 
from colorsys import hsv_to_rgb
 
from decimal import Decimal
 
import math
 
import traceback
 
from noise import pnoise1
 
import logging
 
import time
 
from light9.effect.settings import DeviceSettings
 
from light9.effect.scale import scale
 
import random
 
random.seed(0)
 
print "reload effecteval"
 
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)]))
 

	
 

	
 
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 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
 
        self.simpleOutputs = simpleOutputs
 

	
 
    def outputFromEffect(self, effectSettings, songTime, noteTime):
 
        """
 
        From effect attr settings, like strength=0.75, to output device
 
        settings like light1/bright=0.72;light2/bright=0.78. This runs
 
        the effect code.
 
        """
 
        # both callers need to apply note overrides
 
        effectSettings = dict(
 
            effectSettings
 
        )  # we should make everything into nice float and Color objects too
 

	
 
        strength = float(effectSettings[L9['strength']])
 
        if strength <= 0:
 
            return DeviceSettings(self.graph, []), {'zero': True}
 

	
 
        report = {}
 
        out = {}  # (dev, attr): value
 

	
 
        out.update(
 
            self.simpleOutputs.values(
 
                self.effect, strength,
 
                effectSettings.get(L9['colorScale'], None)))
 

	
 
        if self.effect.startswith(L9['effect/']):
 
            tail = 'effect_' + self.effect[len(L9['effect/']):]
 
            try:
 
                func = globals()[tail]
 
            except KeyError:
 
                report['error'] = 'effect code not found for %s' % self.effect
 
            else:
 
                out.update(func(effectSettings, strength, songTime, noteTime))
 

	
 
        outList = [(d, a, v) for (d, a), v in out.iteritems()]
 
        outList = [(d, a, v) for (d, a), v in out.items()]
 
        return DeviceSettings(self.graph, outList), report
 

	
 

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

	
 

	
 
def effect_animRainbow(effectSettings, strength, songTime, noteTime):
 
    out = {}
 
    tint = effectSettings.get(L9['tint'], '#ffffff')
 
    tintStrength = float(effectSettings.get(L9['tintStrength'], 0))
 
    tr, tg, tb = hex_to_rgb(tint)
 
    for n in range(1, 5 + 1):
 
        scl = strength * nsin(songTime + n * .3)**3
 
        col = literalColor(
 
            scl * lerp(nsin(songTime + n * .2), tr / 255, tintStrength),
 
            scl * lerp(nsin(songTime + n * .2 + .3), tg / 255, tintStrength),
 
            scl * lerp(nsin(songTime + n * .3 + .6), tb / 255, tintStrength))
 

	
 
        dev = L9['device/aura%s' % n]
 
        out.update({
 
            (dev, L9['color']): col,
 
            (dev, L9['zoom']): .9,
 
        })
 
        ang = songTime * 4
 
        out.update({
 
            (dev, L9['rx']):
 
            lerp(.27, .7, (n - 1) / 4) + .2 * math.sin(ang + n),
 
            (dev, L9['ry']):
 
            lerp(.46, .52, (n - 1) / 4) + .5 * math.cos(ang + n),
 
        })
 
    return out
 

	
 

	
 
def effect_auraSparkles(effectSettings, strength, songTime, noteTime):
 
    out = {}
 
    tint = effectSettings.get(L9['tint'], '#ffffff')
 
    tintStrength = float(effectSettings.get(L9['tintStrength'], 0))
 
    print effectSettings
 
    print(effectSettings)
 
    tr, tg, tb = hex_to_rgb(tint)
 
    for n in range(1, 5 + 1):
 
        scl = strength * ((int(songTime * 10) % n) < 1)
 
        col = literalColorHsv((songTime + (n / 5)) % 1, 1, scl)
 

	
 
        dev = L9['device/aura%s' % n]
 
        out.update({
 
            (dev, L9['color']): col,
 
            (dev, L9['zoom']): .95,
 
        })
 
        ang = songTime * 4
 
        out.update({
 
            (dev, L9['rx']):
 
            lerp(.27, .8, (n - 1) / 4) + .2 * math.sin(ang + n),
 
            (dev, L9['ry']):
 
            lerp(.46, .52, (n - 1) / 4) + .4 * math.cos(ang + n),
 
        })
 
    return out
 

	
 

	
 
def effect_qpan(effectSettings, strength, songTime, noteTime):
 
    dev = L9['device/q2']
 
    dur = 4
 
    col = scale(scale('#ffffff', strength),
 
                effectSettings.get(L9['colorScale']) or '#ffffff')
 
    return {
 
        (dev, L9['color']): col,
 
        (dev, L9['focus']): 0.589,
 
        (dev, L9['rx']): lerp(0.778, 0.291, clamp(0, 1, noteTime / dur)),
 
        (dev, L9['ry']): 0.5,
 
        (dev, L9['zoom']): 0.714,
 
    }
 

	
 

	
 
def effect_pulseRainbow(effectSettings, strength, songTime, noteTime):
 
    out = {}
 
    tint = effectSettings.get(L9['tint'], '#ffffff')
 
    tintStrength = float(effectSettings.get(L9['tintStrength'], 0))
 
    tr, tg, tb = hex_to_rgb(tint)
 
    for n in range(1, 5 + 1):
 
        scl = strength
 
        col = literalColor(
 
            scl * lerp(nsin(songTime + n * .2), tr / 255, tintStrength),
 
            scl * lerp(nsin(songTime + n * .2 + .3), tg / 255, tintStrength),
 
            scl * lerp(nsin(songTime + n * .3 + .6), tb / 255, tintStrength))
 

	
 
        dev = L9['device/aura%s' % n]
 
        out.update({
 
            (dev, L9['color']): col,
 
            (dev, L9['zoom']): .5,
 
        })
 
        out.update({
 
            (dev, L9['rx']): lerp(.27, .7, (n - 1) / 4),
 
            (dev, L9['ry']): lerp(.46, .52, (n - 1) / 4),
 
        })
 
    return out
 

	
 

	
 
def effect_aurawash(effectSettings, strength, songTime, noteTime):
 
    out = {}
 
    scl = strength
 
    period = float(effectSettings.get(L9['period'], 125 / 60 / 4))
 
    if period < .05:
 
        quantTime = songTime
 
    else:
 
        quantTime = int(songTime / period) * period
 
    noisePos = quantTime * 6.3456
 

	
 
    col = literalColorHsv(noise(noisePos), 1, scl)
 
    col = scale(col, effectSettings.get(L9['colorScale']) or '#ffffff')
 

	
 
    print songTime, quantTime, col
 
    print(songTime, quantTime, col)
 

	
 
    for n in range(1, 5 + 1):
 
        dev = L9['device/aura%s' % n]
 
        out.update({
 
            (dev, L9['color']): col,
 
            (dev, L9['zoom']): .5,
 
        })
 
        out.update({
 
            (dev, L9['rx']): lerp(.27, .7, (n - 1) / 4),
 
            (dev, L9['ry']): lerp(.46, .52, (n - 1) / 4),
 
        })
 
    return out
 

	
 

	
 
def effect_qsweep(effectSettings, strength, songTime, noteTime):
 
    out = {}
 
    period = float(effectSettings.get(L9['period'], 2))
 

	
 
    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({
 
            (dev, L9['color']): col,
 
            (dev, L9['zoom']): effectSettings.get(L9['zoom'], .5),
 
        })
 
        out.update({
 
            (dev, L9['rx']):
 
            lerp(.3, .8, nsin(songTime / period + n / 4)),
 
            (dev, L9['ry']):
 
            effectSettings.get(L9['ry'], .2),
 
        })
 
    return out
 

	
 

	
 
def effect_qsweepusa(effectSettings, strength, songTime, noteTime):
 
    out = {}
 
    period = float(effectSettings.get(L9['period'], 2))
 

	
 
    colmap = {
 
        1: '#ff0000',
 
        2: '#998888',
 
        3: '#0050ff',
 
    }
 

	
 
    for n in range(1, 3 + 1):
 
        dev = L9['device/q%s' % n]
light9/effect/scale.py
Show inline comments
 
from __future__ import division
 

	
 
from rdflib import Literal
 
from decimal import Decimal
 
from webcolors import rgb_to_hex, hex_to_rgb
 

	
 

	
 
def scale(value, strength):
 
    if isinstance(value, Literal):
 
        value = value.toPython()
 

	
 
    if isinstance(value, Decimal):
 
        value = float(value)
 

	
 
    if isinstance(value, basestring):
 
    if isinstance(value, str):
 
        if value[0] == '#':
 
            if strength == '#ffffff':
 
                return value
 
            r, g, b = hex_to_rgb(value)
 
            if isinstance(strength, Literal):
 
                strength = strength.toPython()
 
            if isinstance(strength, basestring):
 
            if isinstance(strength, str):
 
                sr, sg, sb = [v / 255 for v in hex_to_rgb(strength)]
 
            else:
 
                sr = sg = sb = strength
 
            return rgb_to_hex([int(r * sr), int(g * sg), int(b * sb)])
 
    elif isinstance(value, (int, float)):
 
        return value * strength
 

	
 
    raise NotImplementedError("%r,%r" % (value, strength))
light9/effect/sequencer.py
Show inline comments
 
'''
 
copies from effectloop.py, which this should replace
 
'''
 

	
 
from __future__ import division
 

	
 
from louie import dispatcher
 
from rdflib import URIRef
 
from twisted.internet import reactor
 
from twisted.internet import defer
 
from twisted.internet.inotify import INotify
 
from twisted.python.filepath import FilePath
 
import cyclone.sse
 
import logging, bisect, time
 
import traceback
 

	
 
from light9.namespaces import L9, RDF
 
from light9.vidref.musictime import MusicTime
 
from light9.effect import effecteval
 
from light9.effect.settings import DeviceSettings
 
from light9.effect.simple_outputs import SimpleOutputs
 

	
 
from greplin import scales
 
import imp
 

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

	
 

	
 
class Note(object):
 

	
 
    def __init__(self, graph, uri, effectevalModule, simpleOutputs):
 
        g = self.graph = graph
 
        self.uri = uri
 
        self.effectEval = effectevalModule.EffectEval(
 
            graph, g.value(uri, L9['effectClass']), simpleOutputs)
 
        self.baseEffectSettings = {}  # {effectAttr: value}
 
        for s in g.objects(uri, L9['setting']):
 
            settingValues = dict(g.predicate_objects(s))
 
            ea = settingValues[L9['effectAttr']]
 
            self.baseEffectSettings[ea] = settingValues[L9['value']]
 

	
 
        floatVal = lambda s, p: float(g.value(s, p).toPython())
 
        originTime = floatVal(uri, L9['originTime'])
 
        self.points = []
 
        for curve in g.objects(uri, L9['curve']):
 
            self.points.extend(
 
                self.getCurvePoints(curve, L9['strength'], originTime))
 
        self.points.sort()
 

	
 
    def getCurvePoints(self, curve, attr, originTime):
 
        points = []
 
        po = list(self.graph.predicate_objects(curve))
 
        if dict(po).get(L9['attr'], None) != attr:
 
            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']])))
 
        return points
 

	
 
    def activeAt(self, t):
 
        return self.points[0][0] <= t <= self.points[-1][0]
 

	
 
    def evalCurve(self, t):
 
        i = bisect.bisect_left(self.points, (t, None)) - 1
 

	
 
        if i == -1:
 
            return self.points[0][1]
 
        if self.points[i][0] > t:
 
            return self.points[i][1]
 
        if i >= len(self.points) - 1:
 
            return self.points[i][1]
 

	
 
        p1, p2 = self.points[i], self.points[i + 1]
 
        frac = (t - p1[0]) / (p2[0] - p1[0])
 
        y = p1[1] + (p2[1] - p1[1]) * frac
 
        return y
 

	
 
    def outputSettings(self, t):
 
        """
 
        list of (device, attr, value), and a report for web
 
        """
 
        report = {
 
            'note': str(self.uri),
 
            'effectClass': self.effectEval.effect,
 
        }
 
        effectSettings = self.baseEffectSettings.copy()
 
        effectSettings[L9['strength']] = self.evalCurve(t)
 
        report['effectSettings'] = dict(
 
            (str(k), str(v)) for k, v in sorted(effectSettings.items()))
 
        report['nonZero'] = effectSettings[L9['strength']] > 0
 
        out, evalReport = self.effectEval.outputFromEffect(
 
            effectSettings.items(),
 
            list(effectSettings.items()),
 
            songTime=t,
 
            # note: not using origin here since it's going away
 
            noteTime=t - self.points[0][0])
 
        report['devicesAffected'] = len(out.devices())
 
        return out, report
 

	
 

	
 
class CodeWatcher(object):
 

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

	
 
        self.notifier = INotify()
 
        self.notifier.startReading()
 
        self.notifier.watch(FilePath(effecteval.__file__.replace('.pyc',
 
                                                                 '.py')),
 
                            callbacks=[self.codeChange])
 

	
 
    def codeChange(self, watch, path, mask):
 

	
 
        def go():
 
            log.info("reload effecteval")
 
            reload(effecteval)
 
            imp.reload(effecteval)
 
            self.onChange()
 

	
 
        # in case we got an event at the start of the write
 
        reactor.callLater(.1, go)
 

	
 

	
 
class Sequencer(object):
 

	
 
    def __init__(self, graph, sendToCollector, fps=40):
 
        self.graph = graph
 
        self.fps = fps
 
        self.sendToCollector = sendToCollector
 
        self.music = MusicTime(period=.2, pollCurvecalc=False)
 

	
 
        self.recentUpdateTimes = []
 
        self.lastStatLog = 0
 
        self._compileGraphCall = None
 
        self.notes = {}  # song: [notes]
 
        self.simpleOutputs = SimpleOutputs(self.graph)
 
        self.graph.addHandler(self.compileGraph)
 
        self.updateLoop()
 

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

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

	
 
        for song in g.subjects(RDF.type, L9['Song']):
 
            self.graph.addHandler(lambda song=song: self.compileSong(song))
 
        log.info('compileGraph took %.2f ms', 1000 * (time.time() - t1))
 

	
 
    @stats.compileSong.time()
 
    def compileSong(self, song):
 
        t1 = time.time()
 

	
 
        self.notes[song] = []
 
        for note in self.graph.objects(song, L9['note']):
 
            self.notes[song].append(
 
                Note(self.graph, note, effecteval, self.simpleOutputs))
 
        log.info('  compile %s took %.2f ms', song, 1000 * (time.time() - t1))
 

	
 
    def updateLoop(self):
 
        # print "updateLoop"
 
        now = time.time()

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

0 comments (0 inline, 0 general)