Changeset - 17bee25a20cb
[Not reviewed]
default
0 11 0
Drew Perttula - 6 years ago 2019-05-28 06:46:08
drewp@bigasterisk.com
7 files changed with 12 insertions and 7 deletions:
0 comments (0 inline, 0 general)
bin/keyboardcomposer
Show inline comments
 
@@ -487,98 +487,99 @@ class KeyboardComposer(tk.Frame, SubClie
 
        v = round(127 * level)
 
        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.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)
 
        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 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
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
 
        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]
 

	
 
        if abs(self.xgoal - self.x) < abs(self.v * 5 * dt):
 
            # apply the brakes on the last 5 steps
light9/effecteval/effectloop.py
Show inline comments
 
@@ -24,97 +24,98 @@ class EffectLoop(object):
 

	
 
    def __init__(self, graph, stats):
 
        self.graph, self.stats = graph, stats
 
        self.currentSong = None
 
        self.currentEffects = [
 
        ]  # EffectNodes for the current song plus the submaster ones
 
        self.lastLogTime = 0
 
        self.lastLogMsg = ""
 
        self.lastErrorLog = 0
 
        self.graph.addHandler(self.setEffects)
 
        self.period = 1 / 30
 
        self.coastSecs = .3  # main reason to keep this low is to notice play/pause
 
        self.songTimeFetch = 0
 
        self.songIsPlaying = False
 
        self.songTimeFromRequest = 0
 
        self.requestTime = 0  # unix sec for when we fetched songTime
 
        self.initOutput()
 

	
 
    def initOutput(self):
 
        pass
 

	
 
    def startLoop(self):
 
        log.info("startLoop")
 
        self.lastSendLevelsTime = 0
 
        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
 
            # this may be piling on the handlers
 
            self.graph.addHandler(self.setEffects)
 

	
 
        elapsed = time.time() - t1
 
        reactor.callLater(max(0, self.period - elapsed),
 
                          self.updateTimeFromMusic)
 

	
 
    def estimatedSongTime(self):
 
        now = time.time()
 
        t = self.songTime
 
        if self.currentPlaying:
 
            t += max(0, now - self.songTimeFetch)
 
        return t
 

	
 
    @inlineCallbacks
 
    def sendLevels(self):
 
        t1 = time.time()
 
        log.debug("time since last call: %.1f ms" %
 
                  (1000 * (t1 - self.lastSendLevelsTime)))
 
        self.lastSendLevelsTime = t1
 
        try:
 
            with self.stats.sendLevels.time():
 
                if self.currentSong is not None:
 
                    log.debug('allEffectOutputs')
light9/showconfig.py
Show inline comments
 
@@ -13,84 +13,85 @@ log = logging.getLogger('showconfig')
 
def getGraph() -> Graph:
 
    warnings.warn(
 
        "code that's using showconfig.getGraph should be "
 
        "converted to use the sync graph",
 
        stacklevel=2)
 
    global _config
 
    if _config is None:
 
        graph = Graph()
 
        # note that logging is probably not configured the first time
 
        # we're in here
 
        warnings.warn("reading n3 files around %r" % root())
 
        for f in FilePath(root()).globChildren("*.n3") + FilePath(
 
                root()).globChildren("build/*.n3"):
 
            graph.parse(location=f.path, format='n3')
 
        _config = graph
 
    return _config
 

	
 

	
 
def root() -> bytes:
 
    r = getenv("LIGHT9_SHOW")
 
    if r is None:
 
        raise OSError(
 
            "LIGHT9_SHOW env variable has not been set to the show root")
 
    return r.encode('ascii')
 

	
 

	
 
_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(
 
    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():
 
    return path.join(root(), b"curves")
 

	
 

	
 
def subFile(subname):
 
    return path.join(root(), b"subs", subname)
 

	
 

	
 
def subsDir():
 
    return path.join(root(), b'subs')
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
 
    def makeEvent(cls, *args):
 
        ev = cls()
 
        for (k, v), arg in zip(sorted(TkdndEvent.substitutions.items()), args):
 
            setattr(ev, v, arg)
 
        # it would be cool for this to decode text data according to the charset in the type
 
        for attr in ['button', 'mouseX', 'mouseY']:
 
            setattr(ev, attr, int(getattr(ev, attr)))
 
        return (ev,)
 

	
 
    tclSubstitutions = ' '.join(sorted(substitutions.keys()))
 

	
 
    def __repr__(self):
 
        return "<TkdndEvent %r>" % self.__dict__
 

	
 

	
 
class Hover(object):
 

	
 
    def __init__(self, widget, style):
 
        self.widget, self.style = widget, style
 
        self.oldStyle: Dict[Any, Any] = {}
 

	
 
    def set(self, ev):
 
        for k, v in list(self.style.items()):
 
            self.oldStyle[k] = self.widget.cget(k)
light9/vidref/musictime.py
Show inline comments
 
@@ -81,55 +81,55 @@ class MusicTime(object):
 

	
 
        def eb(err):
 
            log.warn("talking to ascoltami: %s", err.getErrorMessage())
 
            reactor.callLater(2, self.pollMusicTime)
 

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

	
 
    def pollCurvecalcTime(self):
 
        """
 
        poll the curvecalc position when music isn't playing, so replay
 
        can track it.
 

	
 
        This would be better done via rdfdb sync, where changes to the
 
        curvecalc position are written to the curvecalc session and we
 
        can pick them up in here
 
        """
 
        if self.position.get('playing'):
 
            # don't need this position during playback
 
            self.lastHoverTime = None
 
            reactor.callLater(.2, self.pollCurvecalcTime)
 
            return
 

	
 
        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'),
 
        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
 
@@ -2,56 +2,56 @@ from invoke import task
 
import glob
 

	
 
bin_sources = [
 
        'bin/ascoltami2',
 
        'bin/captureDevice',
 
        'bin/collector',
 
        'bin/collector_loadtest.py',
 
        'bin/effecteval',
 
        'bin/effectsequencer',
 
        'bin/homepageConfig',
 
        'bin/inputdemo',
 
        'bin/inputquneo',
 
        'bin/keyboardcomposer',
 
        'bin/listsongs',
 
        'bin/musicPad',
 
        'bin/musictime',
 
        'bin/paintserver',
 
        'bin/picamserve',
 
        'bin/rdfdb',
 
        'bin/run_local.py',
 
        'bin/subcomposer',
 
        'bin/subserver',
 
        'bin/vidref',
 
        'bin/vidrefsetup',
 
        '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)