Changeset - 17bee25a20cb
[Not reviewed]
default
0 11 0
Drew Perttula - 6 years ago 2019-05-28 06:46:08
drewp@bigasterisk.com
11 files changed with 31 insertions and 26 deletions:
0 comments (0 inline, 0 general)
bin/keyboardcomposer
Show inline comments
 
@@ -92,69 +92,69 @@ class SubmasterBox(tk.Frame):
 
        self.col, self.row = col, row
 
        bg = self.graph.value(sub, L9['color'], default='#000000')
 
        rgb = webcolors.hex_to_rgb(bg)
 
        hsv = colorsys.rgb_to_hsv(*[x / 255 for x in rgb])
 
        darkBg = webcolors.rgb_to_hex(
 
            tuple([
 
                int(x * 255) for x in colorsys.hsv_to_rgb(hsv[0], hsv[1], .2)
 
            ]))
 
        tk.Frame.__init__(self, master, bd=1, relief='raised', bg=bg)
 
        self.name = self.graph.label(sub)
 
        self._val = 0.0
 
        self.slider_var = tk.DoubleVar()
 
        self.pauseTrace = False
 
        self.scale = SubScale(self, variable=self.slider_var, width=20)
 

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

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

	
 
        for w in [self, self.namelabel, self.levellabel]:
 
            dragSourceRegister(w, 'copy', 'text/uri-list', sub)
 

	
 
        self._slider_var_trace = self.slider_var.trace('w', self.slider_changed)
 

	
 
        self.graph.addHandler(self.updateLevelFromGraph)
 

	
 
        # initial position
 
        # stil need? dispatcher.send("send_to_hw", sub=sub.uri, hwCol=col + 1)
 

	
 
    def getVal(self) -> float:
 
        return self._val
 
    
 

	
 
    def setVal(self, newVal: float) -> None:
 
        if self.scale is None:
 
            return
 
        try:
 
            self.scale.set(newVal)
 
            self.levellabel.config(text=str(newVal))
 
        except Exception:
 
            log.warn("disabling handlers on broken subbox")
 
            self.scale = None
 
        
 
    def cleanup(self):
 
        self.slider_var.trace_vdelete('w', self._slider_var_trace)
 

	
 
    def slider_changed(self, *args):
 
        self._val = self.scale.get()
 
        self.scale.draw_indicator_colors()
 

	
 
        if self.pauseTrace:
 
            return
 
        self.updateGraphWithLevel(self.sub, self.getVal())
 

	
 
        # needs fixing: plan is to use dispatcher or a method call to tell a hardware-mapping object who changed, and then it can make io if that's a current hw slider
 
        dispatcher.send("send_to_hw",
 
                        sub=self.sub,
 
@@ -168,53 +168,53 @@ class SubmasterBox(tk.Frame):
 
        """
 
        # move to syncedgraph patchMapping
 

	
 
        self.graph.patchMapping(context=self.session,
 
                                subject=self.session,
 
                                predicate=L9['subSetting'],
 
                                nodeClass=L9['SubSetting'],
 
                                keyPred=L9['sub'],
 
                                newKey=uri,
 
                                valuePred=L9['level'],
 
                                newValue=Literal(level))
 

	
 
    def updateLevelFromGraph(self):
 
        """read rdf level, write it to subbox.slider_var"""
 
        # move this to syncedgraph readMapping
 
        graph = self.graph
 

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

	
 
    def updateName(self):
 
        if self.scale is None:
 
            return
 
        
 

	
 
        def shortUri(u):
 
            return '.../' + u.split('/')[-1]
 

	
 
        try:
 
            self.namelabel.config(
 
                text=self.graph.label(self.sub) or shortUri(self.sub))
 
        except Exception:
 
            log.warn("disabling handlers on broken subbox")
 
            self.scale = None
 

	
 

	
 
class KeyboardComposer(tk.Frame, SubClient):
 

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

	
 
        self.subbox: Dict[URIRef, SubmasterBox] = {}  # sub uri : SubmasterBox
 
@@ -511,50 +511,51 @@ class KeyboardComposer(tk.Frame, SubClie
 

	
 
        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.getVal())
 
                     for uri, box in list(self.subbox.items())])
 
        return dict([
 
            (uri, box.getVal()) 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)
light9/FlyingFader.py
Show inline comments
 
from tkinter import tix
 
from time import time
 

	
 

	
 
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
light9/Submaster.py
Show inline comments
 
@@ -52,49 +52,49 @@ class Submaster(object):
 
    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 = 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()) # noqa
 
        return cmp(self.ident(), other.ident())  # noqa
 

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

	
light9/dmxclient.py
Show inline comments
 
@@ -51,26 +51,26 @@ def outputlevels(levellist, twisted=0, c
 
        url = networking.dmxServer.url
 
        if not twisted:
 
            _dmx = xmlrpc.client.Server(url)
 
        else:
 
            _dmx = TwistedZmqClient(networking.dmxServerZmq)
 

	
 
    if not twisted:
 
        try:
 
            _dmx.outputlevels(clientid, levellist)
 
        except socket.error as e:
 
            log.error("dmx server error %s, waiting" % e)
 
            time.sleep(1)
 
        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.")
 

	
 
    def outputlevels(*args, **kw): # noqa
 
    def outputlevels(*args, **kw):  # noqa
 
        pass
light9/effecteval/effectloop.py
Show inline comments
 
@@ -48,49 +48,50 @@ class EffectLoop(object):
 
        reactor.callLater(self.period, self.sendLevels)
 
        reactor.callLater(self.period, self.updateTimeFromMusic)
 

	
 
    def setEffects(self):
 
        self.currentEffects = []
 
        log.info('setEffects currentSong=%s', self.currentSong)
 
        if self.currentSong is None:
 
            return
 

	
 
        for effectUri in self.graph.objects(self.currentSong, L9['effect']):
 
            self.currentEffects.append(EffectNode(self.graph, effectUri))
 

	
 
        for sub in self.graph.subjects(RDF.type, L9['Submaster']):
 
            for effectUri in self.graph.objects(sub, L9['drivesEffect']):
 
                self.currentEffects.append(EffectNode(self.graph, effectUri))
 

	
 
        log.info('now we have %s effects', len(self.currentEffects))
 

	
 
    @inlineCallbacks
 
    def getSongTime(self):
 
        now = time.time()
 
        old = now - self.requestTime
 
        if old > self.coastSecs:
 
            try:
 
                r = yield treq.get(networking.musicPlayer.path('time'), timeout=.5)
 
                r = yield treq.get(networking.musicPlayer.path('time'),
 
                                   timeout=.5)
 
                response = yield r.json_content()
 
            except TimeoutError:
 
                log.warning("TimeoutError: using stale time from %.1f ago", old)
 
            else:
 
                self.requestTime = now
 
                self.currentPlaying = response['playing']
 
                self.songTimeFromRequest = response['t']
 
                returnValue((response['t'], (response['song'] and
 
                                             URIRef(response['song']))))
 

	
 
        estimated = self.songTimeFromRequest
 
        if self.currentSong is not None and self.currentPlaying:
 
            estimated += now - self.requestTime
 
        returnValue((estimated, self.currentSong))
 

	
 
    @inlineCallbacks
 
    def updateTimeFromMusic(self):
 
        t1 = time.time()
 
        with self.stats.getMusic.time():
 
            self.songTime, song = yield self.getSongTime()
 
            self.songTimeFetch = time.time()
 

	
 
        if song != self.currentSong:
 
            self.currentSong = song
light9/gtkpyconsole.py
Show inline comments
 
from lib.ipython_view import IPythonView
 
import gi # noqa
 
import gi  # noqa
 
from gi.repository import Gtk
 
from gi.repository import Pango
 

	
 

	
 
def togglePyConsole(self, item, user_ns):
 
    """
 
    toggles a toplevel window with an ipython console inside.
 

	
 
    self is an object we can stick the pythonWindow attribute on
 

	
 
    item is a checkedmenuitem
 

	
 
    user_ns is a dict you want to appear as locals in the console
 
    """
 
    if item.get_active():
 
        if not hasattr(self, 'pythonWindow'):
 
            self.pythonWindow = Gtk.Window()
 
            S = Gtk.ScrolledWindow()
 
            S.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
 
            V = IPythonView(user_ns=user_ns)
 
            V.modify_font(Pango.FontDescription("luxi mono 8"))
 
            V.set_wrap_mode(Gtk.WrapMode.CHAR)
 
            S.add(V)
 
            self.pythonWindow.add(S)
light9/showconfig.py
Show inline comments
 
@@ -37,51 +37,52 @@ def root() -> bytes:
 

	
 

	
 
_showUri = None
 

	
 

	
 
def showUri() -> URIRef:
 
    """Return the show URI associated with $LIGHT9_SHOW."""
 
    global _showUri
 
    if _showUri is None:
 
        _showUri = URIRef(open(path.join(root(), b'URI')).read().strip())
 
    return _showUri
 

	
 

	
 
def songOnDisk(song: URIRef) -> bytes:
 
    """given a song URI, where's the on-disk file that mpd would read?"""
 
    graph = getGraph()
 
    root = graph.value(showUri(), L9['musicRoot'])
 
    if not root:
 
        raise ValueError("%s has no :musicRoot" % showUri())
 

	
 
    name = graph.value(song, L9['songFilename'])
 
    if not name:
 
        raise ValueError("Song %r has no :songFilename" % song)
 

	
 
    return path.abspath(path.join(
 
        cast(Literal, root).toPython(),
 
        cast(Literal, name).toPython()))
 
    return path.abspath(
 
        path.join(
 
            cast(Literal, root).toPython(),
 
            cast(Literal, name).toPython()))
 

	
 

	
 
def songFilenameFromURI(uri: URIRef) -> bytes:
 
    """
 
    'http://light9.bigasterisk.com/show/dance2007/song8' -> 'song8'
 

	
 
    everything that uses this should be deprecated for real URIs
 
    everywhere"""
 
    assert isinstance(uri, URIRef)
 
    return str(uri).split('/')[-1].encode('ascii')
 

	
 

	
 
def getSongsFromShow(graph: Graph, show: URIRef) -> List[URIRef]:
 
    playList = graph.value(show, L9['playList'])
 
    if not playList:
 
        raise ValueError("%r has no l9:playList" % show)
 
    # The patch in https://github.com/RDFLib/rdflib/issues/305 fixed a
 
    # serious bug here.
 
    songs = list(graph.items(playList))
 

	
 
    return songs
 

	
 

	
 
def curvesDir():
light9/tkdnd.py
Show inline comments
 
from glob import glob
 
from os.path import join, basename
 
from typing import Dict, Any
 

	
 

	
 
class TkdndEvent(object):
 
    """
 
    see http://www.ellogon.org/petasis/tcltk-projects/tkdnd/tkdnd-man-page
 
    for details on the fields
 

	
 
    The longer attribute names (action instead of %A) were made up for
 
    this API.
 

	
 
    Not all attributes are visible yet, since I have not thought
 
    through what conversions they should receive and I don't want to
 
    unnecessarily change their types later.
 
    """
 
    substitutions = {
 
        "%A": "action",
 
        "%b": "button",
 
        "%D": "data",
 
        "%m": "modifiers",
 
        "%T": "type",
 
        "%W": "targetWindow",
 
        "%X": "mouseX",
 
        "%Y": "mouseY",
 
    }
 

	
 
    @classmethod
light9/uihelpers.py
Show inline comments
 
@@ -122,57 +122,57 @@ def eventtoparent(ev, sequence):
 

	
 
    wid_class = str(ev.widget.__class__)
 
    if wid_class == 'Tix.ComboBox' or wid_class == 'Tix.TixSubWidget':
 
        return
 

	
 
    evdict = {}
 
    for x in ['state', 'time', 'y', 'x', 'serial']:
 
        evdict[x] = getattr(ev, x)
 

	
 

	
 
#    evdict['button']=ev.num
 
    par = ev.widget.winfo_parent()
 
    if par != ".":
 
        ev.widget.nametowidget(par).event_generate(sequence, **evdict)
 
    #else the event made it all the way to the top, unhandled
 

	
 

	
 
def colorlabel(label):
 
    """color a label based on its own text"""
 
    txt = label['text'] or "0"
 
    lev = float(txt) / 100
 
    low = (80, 80, 180)
 
    high = (255, 55, 0o50)
 
    out = [int(l + lev * (h - l)) for h, l in zip(high, low)]
 
    col = "#%02X%02X%02X" % tuple(out) # type: ignore
 
    col = "#%02X%02X%02X" % tuple(out)  # type: ignore
 
    label.config(bg=col)
 

	
 

	
 
# TODO: get everyone to use this
 
def colorfade(low, high, percent):
 
    '''not foolproof.  make sure 0 < percent < 1'''
 
    out = [int(l + percent * (h - l)) for h, l in zip(high, low)]
 
    col = "#%02X%02X%02X" % tuple(out) # type: ignore
 
    col = "#%02X%02X%02X" % tuple(out)  # type: ignore
 
    return col
 

	
 

	
 
def colortotuple(anytkobj, colorname):
 
    'pass any tk object and a color name, like "yellow"'
 
    rgb = anytkobj.winfo_rgb(colorname)
 
    return [v / 256 for v in rgb]
 

	
 

	
 
class Togglebutton(Button):
 
    """works like a single radiobutton, but it's a button so the
 
    label's on the button face, not to the side. the optional command
 
    callback is called on button set, not on unset. takes a variable
 
    just like a checkbutton"""
 

	
 
    def __init__(self,
 
                 parent,
 
                 variable=None,
 
                 command=None,
 
                 downcolor='red',
 
                 **kw):
 

	
 
        self.oldcommand = command
 
        Button.__init__(self, parent, command=self.invoke, **kw)
light9/vidref/musictime.py
Show inline comments
 
@@ -105,31 +105,31 @@ class MusicTime(object):
 
        def cb(response):
 
            if response.code == 404:
 
                # not hovering
 
                self.lastHoverTime = None
 
                reactor.callLater(.2, self.pollCurvecalcTime)
 
                return
 
            if response.code != 200:
 
                raise ValueError("%s %s" % (response.code, response.body))
 
            self.lastHoverTime = json.loads(response.body)['hoverTime']
 

	
 
            reactor.callLater(self.hoverPeriod, self.pollCurvecalcTime)
 

	
 
        def eb(err):
 
            if self.lastHoverTime:
 
                log.warn("talking to curveCalc: %s", err.getErrorMessage())
 
            self.lastHoverTime = None
 
            reactor.callLater(2, self.pollCurvecalcTime)
 

	
 
        d = treq.get(networking.curveCalc.path("hoverTime"))
 
        d.addCallback(cb)
 
        d.addErrback(eb)  # note this includes errors in cb()
 

	
 
    def sendTime(self, t):
 
        """request that the player go to this time"""
 
        treq.post(networking.musicPlayer.path('time'),
 
                  data=json.dumps({
 
                      "t": time
 
                  }).encode('utf8'),
 
                  headers={b"content-type": [b"application/json"]},
 
        treq.post(
 
            networking.musicPlayer.path('time'),
 
            data=json.dumps({
 
                "t": time
 
            }).encode('utf8'),
 
            headers={b"content-type": [b"application/json"]},
 
        )
 
        
tasks.py
Show inline comments
 
@@ -26,32 +26,32 @@ bin_sources = [
 
        'bin/wavecurve',
 
    ]
 
def pkg_sources():
 
    return glob.glob('light9/**/*.py', recursive=True)
 

	
 
@task
 
def mypy(ctx):
 
    print('\n\n')
 
    def run(sources):
 
        ss = ' '.join(sources)
 
        ctx.run(f'MYPYPATH=stubs:/my/proj/rdfdb env/bin/mypy --check-untyped-defs {ss}',
 
                pty=True, warn=True)
 

	
 
    sources = ' '.join(bin_sources + pkg_sources())
 
    ctx.run(f'env/bin/flake8 --ignore=E115,E123,E124,E126,E225,E231,E261,E262,E265,E301,E302,E303,E305,E306,E401,E402,E501,E701,E731,W291,W293,W391,W504 {sources}', warn=True)
 

	
 
    sources = ' '.join(pkg_sources())
 
    run(['bin/rdfdb'])
 
    run(['bin/keyboardcomposer'])
 
    #for src in bin_sources:
 
    #    print(f"mypy {src}")
 
    #    run([src])# + pkg_sources())
 
@task
 
def reformat(ctx):
 
    ctx.run("env/bin/yapf --verbose --parallel --in-place --style google light9/**/*.py `file --no-pad  bin/* | grep 'Python script' | perl -lpe 's/:.*//'`")
 
    ctx.run("env/bin/yapf --verbose --parallel --in-place --style google light9/*.py light9/*/*.py `file --no-pad  bin/* | grep 'Python script' | perl -lpe 's/:.*//'`")
 
    
 
@task
 
def test(ctx):
 
    ctx.run('docker build -f Dockerfile.build -t light9_build:latest .')
 
    ctx.run('docker run --rm -it -v `pwd`:/opt light9_build:latest'
 
            ' nose2 -v light9.currentstategraphapi_test light9.graphfile_test',
 
            pty=True)
0 comments (0 inline, 0 general)